1use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
2
3use crate::common::{db_to_gain, gain_to_db, input_sample, output_channels, prepare_channels};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct DynamicsEnvelope {
8 value: f32,
9 attack_coeff: f32,
10 release_coeff: f32,
11}
12
13impl DynamicsEnvelope {
14 pub fn new(sample_rate_hz: f32, attack_ms: f32, release_ms: f32) -> Self {
17 Self {
18 value: 0.0,
19 attack_coeff: time_coeff(sample_rate_hz, attack_ms),
20 release_coeff: time_coeff(sample_rate_hz, release_ms),
21 }
22 }
23
24 pub fn next(&mut self, input: f32) -> f32 {
26 let level = input.abs();
27 let coeff = if level > self.value {
28 self.attack_coeff
29 } else {
30 self.release_coeff
31 };
32 self.value = coeff * self.value + (1.0 - coeff) * level;
33 self.value
34 }
35
36 pub fn reset(&mut self) {
38 self.value = 0.0;
39 }
40}
41
42fn time_coeff(sample_rate_hz: f32, time_ms: f32) -> f32 {
43 let samples = (sample_rate_hz * time_ms.max(0.001)) / 1000.0;
44 (-1.0 / samples).exp()
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum Waveshape {
50 Tanh,
52 Cubic,
54 HardClip,
56}
57
58#[derive(Clone, Debug, PartialEq)]
61pub struct Waveshaper {
62 drive: f32,
63 output_gain: f32,
64 shape: Waveshape,
65}
66
67impl Waveshaper {
68 pub fn new(shape: Waveshape, drive: f32) -> Self {
70 Self {
71 drive: drive.max(0.0),
72 output_gain: 1.0,
73 shape,
74 }
75 }
76
77 pub fn with_output_gain(mut self, output_gain: f32) -> Self {
79 self.output_gain = output_gain;
80 self
81 }
82
83 pub fn process_sample(&self, input: f32) -> f32 {
85 let x = input * self.drive;
86 let shaped = match self.shape {
87 Waveshape::Tanh => x.tanh(),
88 Waveshape::Cubic => (x - x.powi(3) / 3.0).clamp(-1.0, 1.0),
89 Waveshape::HardClip => x.clamp(-1.0, 1.0),
90 };
91 shaped * self.output_gain
92 }
93}
94
95impl Processor for Waveshaper {
96 fn prepare(&mut self, _cfg: PrepareConfig) {}
97
98 fn reset(&mut self) {}
99
100 fn process(&mut self, block: &mut ProcessBlock<'_>) {
101 let frames = block.frames as usize;
102 for channel in 0..output_channels(block) {
103 for frame in 0..frames {
104 block.out_audio[channel][frame] =
105 self.process_sample(input_sample(block, channel, frame));
106 }
107 }
108 }
109}
110
111#[derive(Clone, Debug, PartialEq)]
113pub struct SoftClipper {
114 inner: Waveshaper,
115}
116
117impl SoftClipper {
118 pub fn new(drive: f32) -> Self {
120 Self {
121 inner: Waveshaper::new(Waveshape::Tanh, drive),
122 }
123 }
124}
125
126impl Processor for SoftClipper {
127 fn prepare(&mut self, cfg: PrepareConfig) {
128 self.inner.prepare(cfg);
129 }
130
131 fn reset(&mut self) {
132 self.inner.reset();
133 }
134
135 fn process(&mut self, block: &mut ProcessBlock<'_>) {
136 self.inner.process(block);
137 }
138}
139
140#[derive(Clone, Debug, PartialEq)]
142pub struct Compressor {
143 threshold_db: f32,
144 ratio: f32,
145 makeup_gain: f32,
146 sample_rate_hz: f32,
147 attack_ms: f32,
148 release_ms: f32,
149 envelopes: Vec<DynamicsEnvelope>,
150}
151
152impl Compressor {
153 pub fn new(threshold_db: f32, ratio: f32) -> Self {
156 Self {
157 threshold_db,
158 ratio: ratio.max(1.0),
159 makeup_gain: 1.0,
160 sample_rate_hz: 48_000.0,
161 attack_ms: 5.0,
162 release_ms: 80.0,
163 envelopes: Vec::new(),
164 }
165 }
166
167 pub fn with_timing(mut self, attack_ms: f32, release_ms: f32) -> Self {
169 self.attack_ms = attack_ms;
170 self.release_ms = release_ms;
171 self
172 }
173
174 pub fn with_makeup_gain_db(mut self, makeup_db: f32) -> Self {
176 self.makeup_gain = db_to_gain(makeup_db);
177 self
178 }
179
180 fn envelope(&self) -> DynamicsEnvelope {
181 DynamicsEnvelope::new(self.sample_rate_hz, self.attack_ms, self.release_ms)
182 }
183
184 fn gain_for_level(&self, level: f32) -> f32 {
185 let level_db = gain_to_db(level);
186 if level_db <= self.threshold_db {
187 return self.makeup_gain;
188 }
189 let compressed_db = self.threshold_db + (level_db - self.threshold_db) / self.ratio;
190 db_to_gain(compressed_db - level_db) * self.makeup_gain
191 }
192}
193
194impl Processor for Compressor {
195 fn prepare(&mut self, cfg: PrepareConfig) {
196 self.sample_rate_hz = cfg.sample_rate_hz as f32;
197 let envelope = self.envelope();
198 prepare_channels(&mut self.envelopes, cfg.out_channels as usize, envelope);
199 }
200
201 fn reset(&mut self) {
202 for envelope in &mut self.envelopes {
203 envelope.reset();
204 }
205 }
206
207 fn process(&mut self, block: &mut ProcessBlock<'_>) {
208 let prepared = self.envelopes.len();
211 debug_assert!(
212 output_channels(block) <= prepared,
213 "Compressor::process received more channels than prepare configured"
214 );
215 let channels = output_channels(block).min(prepared);
216 let frames = block.frames as usize;
217 for channel in 0..channels {
218 for frame in 0..frames {
219 let input = input_sample(block, channel, frame);
220 let level = self.envelopes[channel].next(input);
221 block.out_audio[channel][frame] = input * self.gain_for_level(level);
222 }
223 }
224 }
225}
226
227#[derive(Clone, Debug, PartialEq)]
229pub struct Limiter {
230 inner: Compressor,
231}
232
233impl Limiter {
234 pub fn new(threshold_db: f32) -> Self {
236 Self {
237 inner: Compressor::new(threshold_db, 20.0).with_timing(0.5, 30.0),
238 }
239 }
240}
241
242impl Processor for Limiter {
243 fn prepare(&mut self, cfg: PrepareConfig) {
244 self.inner.prepare(cfg);
245 }
246
247 fn reset(&mut self) {
248 self.inner.reset();
249 }
250
251 fn process(&mut self, block: &mut ProcessBlock<'_>) {
252 self.inner.process(block);
253 }
254}
255
256#[derive(Clone, Debug, PartialEq)]
258pub struct Gate {
259 threshold_db: f32,
260 closed_gain: f32,
261 sample_rate_hz: f32,
262 envelopes: Vec<DynamicsEnvelope>,
263}
264
265impl Gate {
266 pub fn new(threshold_db: f32, closed_gain_db: f32) -> Self {
268 Self {
269 threshold_db,
270 closed_gain: db_to_gain(closed_gain_db),
271 sample_rate_hz: 48_000.0,
272 envelopes: Vec::new(),
273 }
274 }
275
276 fn envelope(&self) -> DynamicsEnvelope {
277 DynamicsEnvelope::new(self.sample_rate_hz, 2.0, 40.0)
278 }
279}
280
281impl Processor for Gate {
282 fn prepare(&mut self, cfg: PrepareConfig) {
283 self.sample_rate_hz = cfg.sample_rate_hz as f32;
284 let envelope = self.envelope();
285 prepare_channels(&mut self.envelopes, cfg.out_channels as usize, envelope);
286 }
287
288 fn reset(&mut self) {
289 for envelope in &mut self.envelopes {
290 envelope.reset();
291 }
292 }
293
294 fn process(&mut self, block: &mut ProcessBlock<'_>) {
295 let prepared = self.envelopes.len();
298 debug_assert!(
299 output_channels(block) <= prepared,
300 "Gate::process received more channels than prepare configured"
301 );
302 let channels = output_channels(block).min(prepared);
303 let frames = block.frames as usize;
304 for channel in 0..channels {
305 for frame in 0..frames {
306 let input = input_sample(block, channel, frame);
307 let level_db = gain_to_db(self.envelopes[channel].next(input));
308 let gain = if level_db < self.threshold_db {
309 self.closed_gain
310 } else {
311 1.0
312 };
313 block.out_audio[channel][frame] = input * gain;
314 }
315 }
316 }
317}