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