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 channels = output_channels(block);
209 if self.envelopes.len() < channels {
210 self.envelopes.resize(channels, self.envelope());
211 }
212 let frames = block.frames as usize;
213 for channel in 0..channels {
214 for frame in 0..frames {
215 let input = input_sample(block, channel, frame);
216 let level = self.envelopes[channel].next(input);
217 block.out_audio[channel][frame] = input * self.gain_for_level(level);
218 }
219 }
220 }
221}
222
223#[derive(Clone, Debug, PartialEq)]
225pub struct Limiter {
226 inner: Compressor,
227}
228
229impl Limiter {
230 pub fn new(threshold_db: f32) -> Self {
232 Self {
233 inner: Compressor::new(threshold_db, 20.0).with_timing(0.5, 30.0),
234 }
235 }
236}
237
238impl Processor for Limiter {
239 fn prepare(&mut self, cfg: PrepareConfig) {
240 self.inner.prepare(cfg);
241 }
242
243 fn reset(&mut self) {
244 self.inner.reset();
245 }
246
247 fn process(&mut self, block: &mut ProcessBlock<'_>) {
248 self.inner.process(block);
249 }
250}
251
252#[derive(Clone, Debug, PartialEq)]
254pub struct Gate {
255 threshold_db: f32,
256 closed_gain: f32,
257 sample_rate_hz: f32,
258 envelopes: Vec<DynamicsEnvelope>,
259}
260
261impl Gate {
262 pub fn new(threshold_db: f32, closed_gain_db: f32) -> Self {
264 Self {
265 threshold_db,
266 closed_gain: db_to_gain(closed_gain_db),
267 sample_rate_hz: 48_000.0,
268 envelopes: Vec::new(),
269 }
270 }
271
272 fn envelope(&self) -> DynamicsEnvelope {
273 DynamicsEnvelope::new(self.sample_rate_hz, 2.0, 40.0)
274 }
275}
276
277impl Processor for Gate {
278 fn prepare(&mut self, cfg: PrepareConfig) {
279 self.sample_rate_hz = cfg.sample_rate_hz as f32;
280 let envelope = self.envelope();
281 prepare_channels(&mut self.envelopes, cfg.out_channels as usize, envelope);
282 }
283
284 fn reset(&mut self) {
285 for envelope in &mut self.envelopes {
286 envelope.reset();
287 }
288 }
289
290 fn process(&mut self, block: &mut ProcessBlock<'_>) {
291 let channels = output_channels(block);
292 if self.envelopes.len() < channels {
293 self.envelopes.resize(channels, self.envelope());
294 }
295 let frames = block.frames as usize;
296 for channel in 0..channels {
297 for frame in 0..frames {
298 let input = input_sample(block, channel, frame);
299 let level_db = gain_to_db(self.envelopes[channel].next(input));
300 let gain = if level_db < self.threshold_db {
301 self.closed_gain
302 } else {
303 1.0
304 };
305 block.out_audio[channel][frame] = input * gain;
306 }
307 }
308 }
309}