1use std::f32::consts::{PI, TAU};
5use std::collections::VecDeque;
6
7pub trait AudioEffect: Send {
13 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32);
14 fn name(&self) -> &str;
16 fn reset(&mut self);
18}
19
20#[inline]
25fn db_to_linear(db: f32) -> f32 {
26 10.0_f32.powf(db / 20.0)
27}
28
29#[inline]
30fn linear_to_db(lin: f32) -> f32 {
31 if lin <= 1e-10 { -200.0 } else { 20.0 * lin.log10() }
32}
33
34struct OnePole {
36 alpha: f32,
37 state: f32,
38}
39impl OnePole {
40 fn new(cutoff_hz: f32, sample_rate: f32) -> Self {
41 let alpha = 1.0 - (-TAU * cutoff_hz / sample_rate).exp();
42 Self { alpha, state: 0.0 }
43 }
44 fn process(&mut self, x: f32) -> f32 {
45 self.state += self.alpha * (x - self.state);
46 self.state
47 }
48 fn reset(&mut self) { self.state = 0.0; }
49}
50
51pub struct Gain {
57 pub gain: f32,
58}
59impl Gain {
60 pub fn new(gain: f32) -> Self { Self { gain } }
61}
62impl AudioEffect for Gain {
63 fn process_block(&mut self, buffer: &mut [f32], _sample_rate: f32) {
64 for s in buffer.iter_mut() { *s *= self.gain; }
65 }
66 fn name(&self) -> &str { "Gain" }
67 fn reset(&mut self) {}
68}
69
70#[derive(Clone, Copy, Debug)]
76pub struct GainBreakpoint {
77 pub sample: u64,
79 pub gain: f32,
81 pub exponential: bool,
83}
84
85pub struct GainAutomation {
87 pub breakpoints: Vec<GainBreakpoint>,
88 current_sample: u64,
89 current_gain: f32,
90}
91impl GainAutomation {
92 pub fn new(breakpoints: Vec<GainBreakpoint>) -> Self {
93 let initial = breakpoints.first().map(|b| b.gain).unwrap_or(1.0);
94 Self { breakpoints, current_sample: 0, current_gain: initial }
95 }
96
97 fn gain_at_sample(&self, s: u64) -> f32 {
98 if self.breakpoints.is_empty() { return 1.0; }
99 if s <= self.breakpoints.first().unwrap().sample {
100 return self.breakpoints.first().unwrap().gain;
101 }
102 if s >= self.breakpoints.last().unwrap().sample {
103 return self.breakpoints.last().unwrap().gain;
104 }
105 let idx = self.breakpoints.partition_point(|b| b.sample <= s);
107 let a = &self.breakpoints[idx - 1];
108 let b = &self.breakpoints[idx];
109 let t = (s - a.sample) as f32 / (b.sample - a.sample) as f32;
110 if a.exponential && a.gain > 0.0 && b.gain > 0.0 {
111 a.gain * (b.gain / a.gain).powf(t)
112 } else {
113 a.gain + (b.gain - a.gain) * t
114 }
115 }
116}
117impl AudioEffect for GainAutomation {
118 fn process_block(&mut self, buffer: &mut [f32], _sample_rate: f32) {
119 for s in buffer.iter_mut() {
120 self.current_gain = self.gain_at_sample(self.current_sample);
121 *s *= self.current_gain;
122 self.current_sample += 1;
123 }
124 }
125 fn name(&self) -> &str { "GainAutomation" }
126 fn reset(&mut self) { self.current_sample = 0; }
127}
128
129pub struct Compressor {
135 pub threshold_db: f32,
137 pub ratio: f32,
139 pub attack_ms: f32,
141 pub release_ms: f32,
143 pub knee_db: f32,
145 pub makeup_db: f32,
147 pub use_rms: bool,
149 pub lookahead_samples: usize,
151 pub gain_reduction_db: f32,
153
154 envelope: f32,
155 rms_sum: f32,
156 rms_buf: Vec<f32>,
157 rms_pos: usize,
158 lookahead: VecDeque<f32>,
159}
160impl Compressor {
161 pub fn new(
162 threshold_db: f32,
163 ratio: f32,
164 attack_ms: f32,
165 release_ms: f32,
166 knee_db: f32,
167 makeup_db: f32,
168 use_rms: bool,
169 lookahead_samples: usize,
170 ) -> Self {
171 let rms_window = 256_usize.max(lookahead_samples);
172 Self {
173 threshold_db,
174 ratio,
175 attack_ms,
176 release_ms,
177 knee_db,
178 makeup_db,
179 use_rms,
180 lookahead_samples,
181 gain_reduction_db: 0.0,
182 envelope: 0.0,
183 rms_sum: 0.0,
184 rms_buf: vec![0.0; rms_window],
185 rms_pos: 0,
186 lookahead: VecDeque::from(vec![0.0; lookahead_samples]),
187 }
188 }
189
190 fn compute_gain_db(&self, level_db: f32) -> f32 {
191 let t = self.threshold_db;
192 let r = self.ratio;
193 let w = self.knee_db;
194 if w > 0.0 {
195 let half = w * 0.5;
196 if level_db < t - half {
197 0.0
198 } else if level_db <= t + half {
199 let x = level_db - t + half;
200 (1.0 / r - 1.0) * x * x / (2.0 * w)
201 } else {
202 (1.0 / r - 1.0) * (level_db - t)
203 }
204 } else if level_db > t {
205 (1.0 / r - 1.0) * (level_db - t)
206 } else {
207 0.0
208 }
209 }
210}
211impl AudioEffect for Compressor {
212 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
213 let attack_coef = (-1.0 / (self.attack_ms * 0.001 * sample_rate)).exp();
214 let release_coef = (-1.0 / (self.release_ms * 0.001 * sample_rate)).exp();
215 let makeup = db_to_linear(self.makeup_db);
216 let rms_len = self.rms_buf.len() as f32;
217
218 for s in buffer.iter_mut() {
219 let delayed = if self.lookahead_samples > 0 {
221 self.lookahead.push_back(*s);
222 self.lookahead.pop_front().unwrap_or(0.0)
223 } else {
224 *s
225 };
226
227 let level = if self.use_rms {
229 let old = self.rms_buf[self.rms_pos];
230 let new_sq = (*s) * (*s);
231 self.rms_sum = (self.rms_sum - old + new_sq).max(0.0);
232 self.rms_buf[self.rms_pos] = new_sq;
233 self.rms_pos = (self.rms_pos + 1) % self.rms_buf.len();
234 (self.rms_sum / rms_len).sqrt()
235 } else {
236 s.abs()
237 };
238
239 let level_db = linear_to_db(level);
240 let desired_gain_db = self.compute_gain_db(level_db);
241 let coef = if desired_gain_db < self.envelope {
243 attack_coef
244 } else {
245 release_coef
246 };
247 self.envelope = desired_gain_db + coef * (self.envelope - desired_gain_db);
248 self.gain_reduction_db = -self.envelope;
249 let gr = db_to_linear(self.envelope);
250 *s = delayed * gr * makeup;
251 }
252 }
253 fn name(&self) -> &str { "Compressor" }
254 fn reset(&mut self) {
255 self.envelope = 0.0;
256 self.rms_sum = 0.0;
257 for v in self.rms_buf.iter_mut() { *v = 0.0; }
258 self.rms_pos = 0;
259 let la = self.lookahead_samples;
260 self.lookahead = VecDeque::from(vec![0.0; la]);
261 self.gain_reduction_db = 0.0;
262 }
263}
264
265pub struct Limiter {
271 pub threshold_db: f32,
272 pub release_ms: f32,
273 pub lookahead_samples: usize,
274 pub gain_reduction_db: f32,
276
277 envelope: f32,
278 lookahead: VecDeque<f32>,
279}
280impl Limiter {
281 pub fn new(threshold_db: f32, release_ms: f32, lookahead_samples: usize) -> Self {
282 Self {
283 threshold_db,
284 release_ms,
285 lookahead_samples,
286 gain_reduction_db: 0.0,
287 envelope: 0.0,
288 lookahead: VecDeque::from(vec![0.0; lookahead_samples.max(1)]),
289 }
290 }
291
292 fn true_peak(x: f32, prev: f32) -> f32 {
294 let mut max = x.abs();
296 for k in 1..4usize {
297 let t = k as f32 / 4.0;
298 let interp = prev + (x - prev) * t;
299 max = max.max(interp.abs());
300 }
301 max
302 }
303}
304impl AudioEffect for Limiter {
305 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
306 let release_coef = (-1.0 / (self.release_ms * 0.001 * sample_rate)).exp();
307 let threshold = db_to_linear(self.threshold_db);
308 let mut prev = 0.0f32;
309
310 for s in buffer.iter_mut() {
311 let delayed = if self.lookahead_samples > 0 {
312 self.lookahead.push_back(*s);
313 self.lookahead.pop_front().unwrap_or(0.0)
314 } else {
315 *s
316 };
317
318 let tp = Self::true_peak(*s, prev);
319 prev = *s;
320 let gain_needed = if tp > threshold { threshold / tp.max(1e-10) } else { 1.0 };
321 let target_db = linear_to_db(gain_needed);
323 let current_db = if target_db < self.envelope {
324 target_db
325 } else {
326 target_db + release_coef * (self.envelope - target_db)
327 };
328 self.envelope = current_db;
329 self.gain_reduction_db = -(current_db.min(0.0));
330 let gr = db_to_linear(current_db);
331 *s = delayed * gr;
332 }
333 }
334 fn name(&self) -> &str { "Limiter" }
335 fn reset(&mut self) {
336 self.envelope = 0.0;
337 self.gain_reduction_db = 0.0;
338 let la = self.lookahead_samples.max(1);
339 self.lookahead = VecDeque::from(vec![0.0; la]);
340 }
341}
342
343#[derive(Clone, Copy, PartialEq, Eq, Debug)]
348pub enum GateState { Closed, Attacking, Open, Releasing, Holding }
349
350pub struct Gate {
352 pub open_threshold_db: f32,
353 pub close_threshold_db: f32,
354 pub range_db: f32,
356 pub attack_ms: f32,
357 pub hold_ms: f32,
358 pub release_ms: f32,
359 pub flip: bool,
361
362 state: GateState,
363 gain: f32,
364 hold_samples_remaining: usize,
365}
366impl Gate {
367 pub fn new(
368 open_threshold_db: f32,
369 close_threshold_db: f32,
370 range_db: f32,
371 attack_ms: f32,
372 hold_ms: f32,
373 release_ms: f32,
374 flip: bool,
375 ) -> Self {
376 Self {
377 open_threshold_db,
378 close_threshold_db,
379 range_db,
380 attack_ms,
381 hold_ms,
382 release_ms,
383 flip,
384 state: GateState::Closed,
385 gain: db_to_linear(range_db),
386 hold_samples_remaining: 0,
387 }
388 }
389}
390impl AudioEffect for Gate {
391 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
392 let open_lin = db_to_linear(self.open_threshold_db);
393 let close_lin = db_to_linear(self.close_threshold_db);
394 let floor = db_to_linear(self.range_db);
395 let attack_step = 1.0 / (self.attack_ms * 0.001 * sample_rate).max(1.0);
396 let release_step = (1.0 - floor) / (self.release_ms * 0.001 * sample_rate).max(1.0);
397 let hold_total = (self.hold_ms * 0.001 * sample_rate) as usize;
398
399 for s in buffer.iter_mut() {
400 let level = s.abs();
401 let above_open = if self.flip { level <= open_lin } else { level >= open_lin };
402 let below_close = if self.flip { level > close_lin } else { level < close_lin };
403
404 self.state = match self.state {
405 GateState::Closed => {
406 if above_open { GateState::Attacking } else { GateState::Closed }
407 }
408 GateState::Attacking => {
409 self.gain = (self.gain + attack_step).min(1.0);
410 if self.gain >= 1.0 { GateState::Open } else { GateState::Attacking }
411 }
412 GateState::Open => {
413 if below_close {
414 self.hold_samples_remaining = hold_total;
415 GateState::Holding
416 } else {
417 GateState::Open
418 }
419 }
420 GateState::Holding => {
421 if above_open {
422 GateState::Open
423 } else if self.hold_samples_remaining == 0 {
424 GateState::Releasing
425 } else {
426 self.hold_samples_remaining -= 1;
427 GateState::Holding
428 }
429 }
430 GateState::Releasing => {
431 self.gain = (self.gain - release_step).max(floor);
432 if above_open {
433 GateState::Attacking
434 } else if self.gain <= floor {
435 GateState::Closed
436 } else {
437 GateState::Releasing
438 }
439 }
440 };
441 *s *= self.gain;
442 }
443 }
444 fn name(&self) -> &str { "Gate" }
445 fn reset(&mut self) {
446 self.state = GateState::Closed;
447 self.gain = db_to_linear(self.range_db);
448 self.hold_samples_remaining = 0;
449 }
450}
451
452#[derive(Clone, Copy, Debug, PartialEq)]
457pub enum BiquadType {
458 LowPass,
459 HighPass,
460 BandPass,
461 Notch,
462 AllPass,
463 PeakEq,
464 LowShelf,
465 HighShelf,
466}
467
468#[derive(Clone, Debug)]
470pub struct BiquadBand {
471 pub filter_type: BiquadType,
472 pub frequency: f32,
473 pub q: f32,
474 pub gain_db: f32,
475 pub active: bool,
476 b0: f32, b1: f32, b2: f32,
478 a1: f32, a2: f32,
479 s1: f32, s2: f32,
481}
482impl BiquadBand {
483 pub fn new(filter_type: BiquadType, frequency: f32, q: f32, gain_db: f32) -> Self {
484 let mut b = Self {
485 filter_type, frequency, q, gain_db, active: true,
486 b0: 1.0, b1: 0.0, b2: 0.0, a1: 0.0, a2: 0.0,
487 s1: 0.0, s2: 0.0,
488 };
489 b.compute_coefficients(44100.0);
490 b
491 }
492
493 pub fn compute_coefficients(&mut self, sample_rate: f32) {
494 let f = self.frequency;
495 let q = self.q.max(0.001);
496 let a = db_to_linear(self.gain_db / 2.0); let w0 = TAU * f / sample_rate;
498 let cos_w0 = w0.cos();
499 let sin_w0 = w0.sin();
500 let alpha = sin_w0 / (2.0 * q);
501
502 let (b0, b1, b2, a0, a1, a2) = match self.filter_type {
503 BiquadType::LowPass => {
504 let b0 = (1.0 - cos_w0) / 2.0;
505 let b1 = 1.0 - cos_w0;
506 let b2 = (1.0 - cos_w0) / 2.0;
507 let a0 = 1.0 + alpha;
508 let a1 = -2.0 * cos_w0;
509 let a2 = 1.0 - alpha;
510 (b0, b1, b2, a0, a1, a2)
511 }
512 BiquadType::HighPass => {
513 let b0 = (1.0 + cos_w0) / 2.0;
514 let b1 = -(1.0 + cos_w0);
515 let b2 = (1.0 + cos_w0) / 2.0;
516 let a0 = 1.0 + alpha;
517 let a1 = -2.0 * cos_w0;
518 let a2 = 1.0 - alpha;
519 (b0, b1, b2, a0, a1, a2)
520 }
521 BiquadType::BandPass => {
522 let b0 = sin_w0 / 2.0;
523 let b1 = 0.0;
524 let b2 = -sin_w0 / 2.0;
525 let a0 = 1.0 + alpha;
526 let a1 = -2.0 * cos_w0;
527 let a2 = 1.0 - alpha;
528 (b0, b1, b2, a0, a1, a2)
529 }
530 BiquadType::Notch => {
531 let b0 = 1.0;
532 let b1 = -2.0 * cos_w0;
533 let b2 = 1.0;
534 let a0 = 1.0 + alpha;
535 let a1 = -2.0 * cos_w0;
536 let a2 = 1.0 - alpha;
537 (b0, b1, b2, a0, a1, a2)
538 }
539 BiquadType::AllPass => {
540 let b0 = 1.0 - alpha;
541 let b1 = -2.0 * cos_w0;
542 let b2 = 1.0 + alpha;
543 let a0 = 1.0 + alpha;
544 let a1 = -2.0 * cos_w0;
545 let a2 = 1.0 - alpha;
546 (b0, b1, b2, a0, a1, a2)
547 }
548 BiquadType::PeakEq => {
549 let b0 = 1.0 + alpha * a;
550 let b1 = -2.0 * cos_w0;
551 let b2 = 1.0 - alpha * a;
552 let a0 = 1.0 + alpha / a;
553 let a1 = -2.0 * cos_w0;
554 let a2 = 1.0 - alpha / a;
555 (b0, b1, b2, a0, a1, a2)
556 }
557 BiquadType::LowShelf => {
558 let sq = 2.0 * a.sqrt() * alpha;
559 let b0 = a * ((a + 1.0) - (a - 1.0) * cos_w0 + sq);
560 let b1 = 2.0 * a * ((a - 1.0) - (a + 1.0) * cos_w0);
561 let b2 = a * ((a + 1.0) - (a - 1.0) * cos_w0 - sq);
562 let a0 = (a + 1.0) + (a - 1.0) * cos_w0 + sq;
563 let a1 = -2.0 * ((a - 1.0) + (a + 1.0) * cos_w0);
564 let a2 = (a + 1.0) + (a - 1.0) * cos_w0 - sq;
565 (b0, b1, b2, a0, a1, a2)
566 }
567 BiquadType::HighShelf => {
568 let sq = 2.0 * a.sqrt() * alpha;
569 let b0 = a * ((a + 1.0) + (a - 1.0) * cos_w0 + sq);
570 let b1 = -2.0 * a * ((a - 1.0) + (a + 1.0) * cos_w0);
571 let b2 = a * ((a + 1.0) + (a - 1.0) * cos_w0 - sq);
572 let a0 = (a + 1.0) - (a - 1.0) * cos_w0 + sq;
573 let a1 = 2.0 * ((a - 1.0) - (a + 1.0) * cos_w0);
574 let a2 = (a + 1.0) - (a - 1.0) * cos_w0 - sq;
575 (b0, b1, b2, a0, a1, a2)
576 }
577 };
578 let a0_inv = 1.0 / a0;
579 self.b0 = b0 * a0_inv;
580 self.b1 = b1 * a0_inv;
581 self.b2 = b2 * a0_inv;
582 self.a1 = a1 * a0_inv;
583 self.a2 = a2 * a0_inv;
584 }
585
586 #[inline]
587 pub fn process_sample(&mut self, x: f32) -> f32 {
588 let y = self.b0 * x + self.s1;
589 self.s1 = self.b1 * x - self.a1 * y + self.s2;
590 self.s2 = self.b2 * x - self.a2 * y;
591 y
592 }
593
594 pub fn reset(&mut self) { self.s1 = 0.0; self.s2 = 0.0; }
595}
596
597pub struct Equalizer {
603 pub bands: Vec<BiquadBand>,
604 last_sample_rate: f32,
605}
606impl Equalizer {
607 pub fn new(bands: Vec<BiquadBand>) -> Self {
608 Self { bands, last_sample_rate: 0.0 }
609 }
610
611 pub fn add_band(&mut self, band: BiquadBand) {
612 if self.bands.len() < 16 { self.bands.push(band); }
613 }
614}
615impl AudioEffect for Equalizer {
616 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
617 if (sample_rate - self.last_sample_rate).abs() > 0.1 {
618 for b in self.bands.iter_mut() { b.compute_coefficients(sample_rate); }
619 self.last_sample_rate = sample_rate;
620 }
621 for s in buffer.iter_mut() {
622 let mut v = *s;
623 for band in self.bands.iter_mut() {
624 if band.active { v = band.process_sample(v); }
625 }
626 *s = v;
627 }
628 }
629 fn name(&self) -> &str { "Equalizer" }
630 fn reset(&mut self) {
631 for b in self.bands.iter_mut() { b.reset(); }
632 }
633}
634
635const COMB_TUNING: [usize; 8] = [1116,1188,1277,1356,1422,1491,1557,1617];
640const ALLPASS_TUNING: [usize; 4] = [556, 441, 341, 225];
641
642struct CombFilter {
643 buf: Vec<f32>,
644 pos: usize,
645 feedback: f32,
646 damp1: f32,
647 damp2: f32,
648 filterstore: f32,
649}
650impl CombFilter {
651 fn new(size: usize) -> Self {
652 Self { buf: vec![0.0; size], pos: 0, feedback: 0.5, damp1: 0.5, damp2: 0.5, filterstore: 0.0 }
653 }
654 fn set_damp(&mut self, damp: f32) { self.damp1 = damp; self.damp2 = 1.0 - damp; }
655 fn process(&mut self, input: f32) -> f32 {
656 let output = self.buf[self.pos];
657 self.filterstore = output * self.damp2 + self.filterstore * self.damp1;
658 self.buf[self.pos] = input + self.filterstore * self.feedback;
659 self.pos = (self.pos + 1) % self.buf.len();
660 output
661 }
662 fn reset(&mut self) { for v in self.buf.iter_mut() { *v = 0.0; } self.filterstore = 0.0; self.pos = 0; }
663}
664
665struct AllpassFilter {
666 buf: Vec<f32>,
667 pos: usize,
668 feedback: f32,
669}
670impl AllpassFilter {
671 fn new(size: usize) -> Self {
672 Self { buf: vec![0.0; size], pos: 0, feedback: 0.5 }
673 }
674 fn process(&mut self, input: f32) -> f32 {
675 let bufout = self.buf[self.pos];
676 let output = -input + bufout;
677 self.buf[self.pos] = input + bufout * self.feedback;
678 self.pos = (self.pos + 1) % self.buf.len();
679 output
680 }
681 fn reset(&mut self) { for v in self.buf.iter_mut() { *v = 0.0; } self.pos = 0; }
682}
683
684pub struct Reverb {
686 pub room_size: f32,
687 pub damping: f32,
688 pub wet: f32,
689 pub dry: f32,
690 pub pre_delay_ms: f32,
691 pub width: f32,
692
693 combs_l: Vec<CombFilter>,
694 combs_r: Vec<CombFilter>,
695 allpasses_l: Vec<AllpassFilter>,
696 allpasses_r: Vec<AllpassFilter>,
697 pre_delay_buf: VecDeque<f32>,
698 last_sample_rate: f32,
699}
700impl Reverb {
701 pub fn new(room_size: f32, damping: f32, wet: f32, dry: f32, pre_delay_ms: f32, width: f32) -> Self {
702 let stereo_spread = 23;
703 let combs_l: Vec<CombFilter> = COMB_TUNING.iter().map(|&s| CombFilter::new(s)).collect();
704 let combs_r: Vec<CombFilter> = COMB_TUNING.iter().map(|&s| CombFilter::new(s + stereo_spread)).collect();
705 let allpasses_l: Vec<AllpassFilter> = ALLPASS_TUNING.iter().map(|&s| AllpassFilter::new(s)).collect();
706 let allpasses_r: Vec<AllpassFilter> = ALLPASS_TUNING.iter().map(|&s| AllpassFilter::new(s + stereo_spread)).collect();
707 let pre_delay_samples = ((pre_delay_ms * 0.001) * 44100.0) as usize;
708 let pre_delay_buf = VecDeque::from(vec![0.0f32; pre_delay_samples.max(1)]);
709 let mut r = Self {
710 room_size, damping, wet, dry, pre_delay_ms, width,
711 combs_l, combs_r, allpasses_l, allpasses_r,
712 pre_delay_buf,
713 last_sample_rate: 44100.0,
714 };
715 r.update_coefficients();
716 r
717 }
718
719 fn update_coefficients(&mut self) {
720 let feedback = self.room_size * 0.28 + 0.7;
721 let damp = self.damping * 0.4;
722 for c in self.combs_l.iter_mut().chain(self.combs_r.iter_mut()) {
723 c.feedback = feedback;
724 c.set_damp(damp);
725 }
726 }
727}
728impl AudioEffect for Reverb {
729 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
730 if (sample_rate - self.last_sample_rate).abs() > 0.1 {
731 let pre_samples = ((self.pre_delay_ms * 0.001) * sample_rate) as usize;
732 self.pre_delay_buf = VecDeque::from(vec![0.0f32; pre_samples.max(1)]);
733 self.last_sample_rate = sample_rate;
734 }
735 self.update_coefficients();
736
737 for s in buffer.iter_mut() {
738 self.pre_delay_buf.push_back(*s);
740 let input = self.pre_delay_buf.pop_front().unwrap_or(0.0);
741
742 let input_mixed = input * 0.015;
744 let mut out_l = 0.0f32;
745 let mut out_r = 0.0f32;
746 for c in self.combs_l.iter_mut() { out_l += c.process(input_mixed); }
747 for c in self.combs_r.iter_mut() { out_r += c.process(input_mixed); }
748 for a in self.allpasses_l.iter_mut() { out_l = a.process(out_l); }
749 for a in self.allpasses_r.iter_mut() { out_r = a.process(out_r); }
750
751 let wet_l = out_l * (0.5 + self.width * 0.5) + out_r * (0.5 - self.width * 0.5);
752 *s = *s * self.dry + wet_l * self.wet;
753 }
754 }
755 fn name(&self) -> &str { "Reverb" }
756 fn reset(&mut self) {
757 for c in self.combs_l.iter_mut().chain(self.combs_r.iter_mut()) { c.reset(); }
758 for a in self.allpasses_l.iter_mut().chain(self.allpasses_r.iter_mut()) { a.reset(); }
759 let n = self.pre_delay_buf.len();
760 self.pre_delay_buf = VecDeque::from(vec![0.0f32; n]);
761 }
762}
763
764pub struct Delay {
770 pub delay_ms: f32,
771 pub feedback: f32,
772 pub highcut_hz: f32,
773 pub ping_pong: bool,
774 pub wet: f32,
775 pub dry: f32,
776 pub tempo_bpm: f32,
778 pub beat_division: f32,
780
781 buf_l: Vec<f32>,
782 buf_r: Vec<f32>,
783 pos: usize,
784 hpf_l: OnePole,
785 hpf_r: OnePole,
786 last_sample_rate: f32,
787}
788impl Delay {
789 pub fn new(delay_ms: f32, feedback: f32, highcut_hz: f32, ping_pong: bool, wet: f32, dry: f32) -> Self {
790 let max_samples = (4.0 * 48000.0) as usize; Self {
792 delay_ms, feedback, highcut_hz, ping_pong, wet, dry,
793 tempo_bpm: 0.0, beat_division: 1.0,
794 buf_l: vec![0.0; max_samples],
795 buf_r: vec![0.0; max_samples],
796 pos: 0,
797 hpf_l: OnePole::new(highcut_hz, 44100.0),
798 hpf_r: OnePole::new(highcut_hz, 44100.0),
799 last_sample_rate: 44100.0,
800 }
801 }
802
803 fn effective_delay_samples(&self, sample_rate: f32) -> usize {
804 let ms = if self.tempo_bpm > 0.0 {
805 60000.0 / self.tempo_bpm * self.beat_division
806 } else {
807 self.delay_ms
808 };
809 ((ms * 0.001 * sample_rate) as usize).clamp(1, self.buf_l.len() - 1)
810 }
811}
812impl AudioEffect for Delay {
813 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
814 if (sample_rate - self.last_sample_rate).abs() > 0.1 {
815 self.hpf_l = OnePole::new(self.highcut_hz, sample_rate);
816 self.hpf_r = OnePole::new(self.highcut_hz, sample_rate);
817 self.last_sample_rate = sample_rate;
818 }
819 let delay_samp = self.effective_delay_samples(sample_rate);
820 let len = self.buf_l.len();
821
822 for s in buffer.iter_mut() {
823 let read_pos = (self.pos + len - delay_samp) % len;
824 let del_l = self.buf_l[read_pos];
825 let del_r = self.buf_r[read_pos];
826
827 let fb_l = self.hpf_l.process(del_l) * self.feedback;
829 let fb_r = self.hpf_r.process(del_r) * self.feedback;
830
831 if self.ping_pong {
832 self.buf_l[self.pos] = *s + fb_r;
833 self.buf_r[self.pos] = fb_l;
834 } else {
835 self.buf_l[self.pos] = *s + fb_l;
836 self.buf_r[self.pos] = *s + fb_r;
837 }
838 self.pos = (self.pos + 1) % len;
839 *s = *s * self.dry + del_l * self.wet;
840 }
841 }
842 fn name(&self) -> &str { "Delay" }
843 fn reset(&mut self) {
844 for v in self.buf_l.iter_mut() { *v = 0.0; }
845 for v in self.buf_r.iter_mut() { *v = 0.0; }
846 self.pos = 0;
847 self.hpf_l.reset();
848 self.hpf_r.reset();
849 }
850}
851
852pub struct Chorus {
858 pub voices: usize,
859 pub rate_hz: f32,
860 pub depth_ms: f32,
861 pub spread: f32,
862 pub feedback: f32,
863 pub wet: f32,
864 pub dry: f32,
865
866 buf: Vec<Vec<f32>>,
867 pos: usize,
868 phases: Vec<f32>,
869}
870impl Chorus {
871 pub fn new(voices: usize, rate_hz: f32, depth_ms: f32, spread: f32, feedback: f32, wet: f32, dry: f32) -> Self {
872 let voices = voices.clamp(1, 8);
873 let max_samp = 4096usize;
874 let phases: Vec<f32> = (0..voices).map(|i| i as f32 / voices as f32).collect();
875 Self {
876 voices, rate_hz, depth_ms, spread, feedback, wet, dry,
877 buf: (0..voices).map(|_| vec![0.0f32; max_samp]).collect(),
878 pos: 0,
879 phases,
880 }
881 }
882}
883impl AudioEffect for Chorus {
884 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
885 let max_samp = self.buf[0].len();
886 let base_delay_samp = (0.5 * 0.001 * sample_rate) as f32; let depth_samp = (self.depth_ms * 0.001 * sample_rate).max(1.0);
888 let phase_inc = self.rate_hz / sample_rate;
889
890 for s in buffer.iter_mut() {
891 let mut out = 0.0f32;
892 for v in 0..self.voices {
893 let lfo = (self.phases[v] * TAU).sin();
894 let delay_samp = (base_delay_samp + lfo * depth_samp).max(1.0) as usize;
895 let read_pos = (self.pos + max_samp - delay_samp) % max_samp;
896 let delayed = self.buf[v][read_pos];
897 self.buf[v][self.pos] = *s + delayed * self.feedback;
898 out += delayed;
899 self.phases[v] = (self.phases[v] + phase_inc) % 1.0;
900 }
901 self.pos = (self.pos + 1) % max_samp;
902 out /= self.voices as f32;
903 *s = *s * self.dry + out * self.wet;
904 }
905 }
906 fn name(&self) -> &str { "Chorus" }
907 fn reset(&mut self) {
908 for buf in self.buf.iter_mut() { for v in buf.iter_mut() { *v = 0.0; } }
909 self.pos = 0;
910 }
911}
912
913pub struct Flanger {
919 pub rate_hz: f32,
920 pub depth_ms: f32,
921 pub feedback: f32,
922 pub invert: bool,
923 pub wet: f32,
924 pub dry: f32,
925
926 buf: Vec<f32>,
927 pos: usize,
928 phase: f32,
929}
930impl Flanger {
931 pub fn new(rate_hz: f32, depth_ms: f32, feedback: f32, invert: bool, wet: f32, dry: f32) -> Self {
932 Self {
933 rate_hz, depth_ms, feedback, invert, wet, dry,
934 buf: vec![0.0; 2048],
935 pos: 0,
936 phase: 0.0,
937 }
938 }
939}
940impl AudioEffect for Flanger {
941 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
942 let max_samp = self.buf.len();
943 let depth_samp = (self.depth_ms * 0.001 * sample_rate).max(1.0);
944 let phase_inc = self.rate_hz / sample_rate;
945 let fb_sign = if self.invert { -1.0 } else { 1.0 };
946
947 for s in buffer.iter_mut() {
948 let lfo = (self.phase * TAU).sin();
949 let delay_samp = (depth_samp * (1.0 + lfo) * 0.5 + 1.0) as usize;
950 let read_pos = (self.pos + max_samp - delay_samp.min(max_samp - 1)) % max_samp;
951 let delayed = self.buf[read_pos];
952 self.buf[self.pos] = *s + delayed * self.feedback * fb_sign;
953 self.pos = (self.pos + 1) % max_samp;
954 self.phase = (self.phase + phase_inc) % 1.0;
955 *s = *s * self.dry + delayed * self.wet;
956 }
957 }
958 fn name(&self) -> &str { "Flanger" }
959 fn reset(&mut self) {
960 for v in self.buf.iter_mut() { *v = 0.0; }
961 self.pos = 0;
962 }
963}
964
965pub struct Phaser {
971 pub stages: usize,
972 pub rate_hz: f32,
973 pub depth: f32,
974 pub feedback: f32,
975 pub base_freq: f32,
976 pub stereo: bool,
977 pub wet: f32,
978 pub dry: f32,
979
980 ap_state: Vec<[f32; 2]>,
982 phase: f32,
983 fb_l: f32,
984 fb_r: f32,
985}
986impl Phaser {
987 pub fn new(stages: usize, rate_hz: f32, depth: f32, feedback: f32, base_freq: f32, stereo: bool, wet: f32, dry: f32) -> Self {
988 let stages = [4usize, 8, 12].iter().copied().min_by_key(|&s| (s as i32 - stages as i32).abs()).unwrap_or(4);
989 Self {
990 stages, rate_hz, depth, feedback, base_freq, stereo, wet, dry,
991 ap_state: vec![[0.0f32; 2]; stages],
992 phase: 0.0,
993 fb_l: 0.0,
994 fb_r: 0.0,
995 }
996 }
997
998 fn allpass_stage(state: &mut f32, a: f32, x: f32) -> f32 {
999 let y = a * (x - *state) + *state;
1001 *state = x - a * y;
1002 y
1006 }
1007}
1008impl AudioEffect for Phaser {
1009 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1010 let phase_inc = self.rate_hz / sample_rate;
1011
1012 for s in buffer.iter_mut() {
1013 let lfo = (self.phase * TAU).sin();
1014 let lfo_r = ((self.phase + 0.25) * TAU).sin(); let freq_l = (self.base_freq * (1.0 + lfo * self.depth)).clamp(20.0, sample_rate * 0.49);
1017 let freq_r = (self.base_freq * (1.0 + lfo_r * self.depth)).clamp(20.0, sample_rate * 0.49);
1018 let a_l = ((PI * freq_l / sample_rate) - 1.0) / ((PI * freq_l / sample_rate) + 1.0);
1019 let a_r = ((PI * freq_r / sample_rate) - 1.0) / ((PI * freq_r / sample_rate) + 1.0);
1020
1021 let mut out_l = *s + self.fb_l * self.feedback;
1022 let mut out_r = *s + self.fb_r * self.feedback;
1023 for i in 0..self.stages {
1024 out_l = Self::allpass_stage(&mut self.ap_state[i][0], a_l, out_l);
1025 if self.stereo {
1026 out_r = Self::allpass_stage(&mut self.ap_state[i][1], a_r, out_r);
1027 }
1028 }
1029 self.fb_l = out_l;
1030 self.fb_r = out_r;
1031 self.phase = (self.phase + phase_inc) % 1.0;
1032
1033 let wet_out = if self.stereo { (out_l + out_r) * 0.5 } else { out_l };
1034 *s = *s * self.dry + wet_out * self.wet;
1035 }
1036 }
1037 fn name(&self) -> &str { "Phaser" }
1038 fn reset(&mut self) {
1039 for st in self.ap_state.iter_mut() { *st = [0.0, 0.0]; }
1040 self.fb_l = 0.0;
1041 self.fb_r = 0.0;
1042 }
1043}
1044
1045#[derive(Clone, Copy, Debug, PartialEq)]
1050pub enum DistortionMode {
1051 SoftClip,
1052 HardClip,
1053 Foldback,
1054 BitCrush { bits: u32, rate_reduction: u32 },
1055 Overdrive,
1056 TubeSaturation,
1057}
1058
1059pub struct Distortion {
1061 pub mode: DistortionMode,
1062 pub drive: f32,
1063 pub output_gain: f32,
1064 pub pre_filter: Option<BiquadBand>,
1065 pub post_filter: Option<BiquadBand>,
1066 held_sample: f32,
1068 rate_counter: u32,
1069}
1070impl Distortion {
1071 pub fn new(mode: DistortionMode, drive: f32, output_gain: f32) -> Self {
1072 Self {
1073 mode, drive, output_gain,
1074 pre_filter: None,
1075 post_filter: None,
1076 held_sample: 0.0,
1077 rate_counter: 0,
1078 }
1079 }
1080
1081 fn process_sample(&mut self, x: f32) -> f32 {
1082 let driven = x * self.drive;
1083 match self.mode {
1084 DistortionMode::SoftClip => {
1085 driven.tanh()
1086 }
1087 DistortionMode::HardClip => {
1088 driven.clamp(-1.0, 1.0)
1089 }
1090 DistortionMode::Foldback => {
1091 let mut v = driven;
1092 let threshold = 1.0f32;
1093 while v.abs() > threshold {
1094 if v > threshold { v = 2.0 * threshold - v; }
1095 else if v < -threshold { v = -2.0 * threshold - v; }
1096 }
1097 v
1098 }
1099 DistortionMode::BitCrush { bits, rate_reduction } => {
1100 let rate_red = rate_reduction.max(1);
1101 self.rate_counter += 1;
1102 if self.rate_counter >= rate_red {
1103 self.rate_counter = 0;
1104 let levels = (2.0f32).powi(bits.clamp(1, 24) as i32);
1105 self.held_sample = (driven * levels).round() / levels;
1106 }
1107 self.held_sample
1108 }
1109 DistortionMode::Overdrive => {
1110 if driven >= 0.0 {
1112 1.0 - (-driven).exp()
1113 } else {
1114 -1.0 + driven.exp()
1115 }
1116 }
1117 DistortionMode::TubeSaturation => {
1118 let y = driven + 0.2 * driven * driven - 0.1 * driven.powi(3);
1120 y.tanh()
1121 }
1122 }
1123 }
1124}
1125impl AudioEffect for Distortion {
1126 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1127 if let Some(ref mut pf) = self.pre_filter {
1128 pf.compute_coefficients(sample_rate);
1129 }
1130 if let Some(ref mut pf) = self.post_filter {
1131 pf.compute_coefficients(sample_rate);
1132 }
1133 for s in buffer.iter_mut() {
1134 let pre = if let Some(ref mut f) = self.pre_filter { f.process_sample(*s) } else { *s };
1135 let dist = self.process_sample(pre);
1136 let post = if let Some(ref mut f) = self.post_filter { f.process_sample(dist) } else { dist };
1137 *s = post * self.output_gain;
1138 }
1139 }
1140 fn name(&self) -> &str { "Distortion" }
1141 fn reset(&mut self) {
1142 self.held_sample = 0.0;
1143 self.rate_counter = 0;
1144 if let Some(ref mut f) = self.pre_filter { f.reset(); }
1145 if let Some(ref mut f) = self.post_filter { f.reset(); }
1146 }
1147}
1148
1149pub struct PitchShifter {
1155 pub pitch_ratio: f32,
1156 pub formant_preserve: bool,
1157
1158 in_buf: Vec<f32>,
1160 in_pos: usize,
1161 out_buf: Vec<f32>,
1163 out_pos: usize,
1164 grain_phase: f32,
1165 grain_size: usize,
1166}
1167impl PitchShifter {
1168 pub fn new(pitch_ratio: f32, formant_preserve: bool) -> Self {
1169 let grain_size = 2048usize;
1170 let buf_size = grain_size * 4;
1171 Self {
1172 pitch_ratio,
1173 formant_preserve,
1174 in_buf: vec![0.0; buf_size],
1175 in_pos: 0,
1176 out_buf: vec![0.0; buf_size],
1177 out_pos: 0,
1178 grain_phase: 0.0,
1179 grain_size,
1180 }
1181 }
1182
1183 fn hann_window(i: usize, n: usize) -> f32 {
1184 0.5 * (1.0 - (TAU * i as f32 / (n - 1) as f32).cos())
1185 }
1186}
1187impl AudioEffect for PitchShifter {
1188 fn process_block(&mut self, buffer: &mut [f32], _sample_rate: f32) {
1189 let gs = self.grain_size;
1190 let hop = gs / 4;
1191 let buf_len = self.in_buf.len();
1192 let out_len = self.out_buf.len();
1193
1194 for s in buffer.iter_mut() {
1195 self.in_buf[self.in_pos] = *s;
1197 self.in_pos = (self.in_pos + 1) % buf_len;
1198
1199 self.grain_phase += 1.0;
1201 if self.grain_phase as usize >= hop {
1202 self.grain_phase = 0.0;
1203 let read_offset = (gs as f32 / self.pitch_ratio.max(0.01)) as usize;
1205 for i in 0..gs {
1206 let in_idx = (self.in_pos + buf_len - read_offset + i) % buf_len;
1207 let w = Self::hann_window(i, gs);
1208 let out_idx = (self.out_pos + i) % out_len;
1209 self.out_buf[out_idx] += self.in_buf[in_idx] * w;
1210 }
1211 }
1212
1213 let out_val = self.out_buf[self.out_pos];
1215 self.out_buf[self.out_pos] = 0.0;
1216 self.out_pos = (self.out_pos + 1) % out_len;
1217 *s = out_val;
1218 }
1219 }
1220 fn name(&self) -> &str { "PitchShifter" }
1221 fn reset(&mut self) {
1222 for v in self.in_buf.iter_mut() { *v = 0.0; }
1223 for v in self.out_buf.iter_mut() { *v = 0.0; }
1224 self.in_pos = 0;
1225 self.out_pos = 0;
1226 self.grain_phase = 0.0;
1227 }
1228}
1229
1230pub struct AutoTune {
1236 pub speed: f32, pub concert_a: f32, yin_buf: Vec<f32>,
1240 yin_pos: usize,
1241 yin_size: usize,
1242 current_shift: f32,
1243 delay_line: Vec<f32>,
1244 delay_pos: usize,
1245}
1246impl AutoTune {
1247 pub fn new(speed: f32) -> Self {
1248 let yin_size = 2048usize;
1249 Self {
1250 speed: speed.clamp(0.0, 1.0),
1251 concert_a: 440.0,
1252 yin_buf: vec![0.0; yin_size],
1253 yin_pos: 0,
1254 yin_size,
1255 current_shift: 1.0,
1256 delay_line: vec![0.0; yin_size],
1257 delay_pos: 0,
1258 }
1259 }
1260
1261 fn yin_pitch(&self, sample_rate: f32) -> Option<f32> {
1263 let n = self.yin_size / 2;
1264 let mut d = vec![0.0f32; n];
1265 for tau in 1..n {
1267 let mut s = 0.0f32;
1268 for j in 0..n {
1269 let a = self.yin_buf[(self.yin_pos + j) % self.yin_size];
1270 let b = self.yin_buf[(self.yin_pos + j + tau) % self.yin_size];
1271 s += (a - b).powi(2);
1272 }
1273 d[tau] = s;
1274 }
1275 let mut cmnd = vec![0.0f32; n];
1277 cmnd[0] = 1.0;
1278 let mut running_sum = 0.0f32;
1279 for tau in 1..n {
1280 running_sum += d[tau];
1281 cmnd[tau] = d[tau] * tau as f32 / running_sum;
1282 }
1283 let threshold = 0.1f32;
1285 for tau in 2..n {
1286 if cmnd[tau] < threshold {
1287 let tau_f = if tau > 1 && tau < n - 1 {
1289 let x0 = cmnd[tau - 1];
1290 let x1 = cmnd[tau];
1291 let x2 = cmnd[tau + 1];
1292 let denom = 2.0 * (2.0 * x1 - x0 - x2);
1293 if denom.abs() < 1e-10 { tau as f32 }
1294 else { tau as f32 + (x0 - x2) / denom }
1295 } else { tau as f32 };
1296 return Some(sample_rate / tau_f);
1297 }
1298 }
1299 None
1300 }
1301
1302 fn note_to_freq(midi: i32, concert_a: f32) -> f32 {
1303 concert_a * 2.0f32.powf((midi - 69) as f32 / 12.0)
1304 }
1305
1306 fn freq_to_midi(freq: f32, concert_a: f32) -> f32 {
1307 if freq <= 0.0 { return 0.0; }
1308 69.0 + 12.0 * (freq / concert_a).log2()
1309 }
1310
1311 fn nearest_semitone_freq(freq: f32, concert_a: f32) -> f32 {
1312 let midi_f = Self::freq_to_midi(freq, concert_a);
1313 let midi_rounded = midi_f.round() as i32;
1314 Self::note_to_freq(midi_rounded, concert_a)
1315 }
1316}
1317impl AudioEffect for AutoTune {
1318 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1319 let smooth = 1.0 - self.speed;
1320 for s in buffer.iter_mut() {
1321 self.yin_buf[self.yin_pos] = *s;
1323 self.yin_pos = (self.yin_pos + 1) % self.yin_size;
1324
1325 self.delay_line[self.delay_pos] = *s;
1327
1328 if self.yin_pos % (self.yin_size / 4) == 0 {
1330 if let Some(detected_freq) = self.yin_pitch(sample_rate) {
1331 if detected_freq > 50.0 && detected_freq < 2000.0 {
1332 let target_freq = Self::nearest_semitone_freq(detected_freq, self.concert_a);
1333 let target_shift = target_freq / detected_freq;
1334 self.current_shift = self.current_shift * smooth + target_shift * (1.0 - smooth);
1335 }
1336 }
1337 }
1338
1339 let read_offset = (self.yin_size as f32 / 2.0 / self.current_shift.max(0.1)) as usize;
1341 let read_idx = (self.delay_pos + self.delay_line.len() - read_offset.min(self.delay_line.len() - 1)) % self.delay_line.len();
1342 *s = self.delay_line[read_idx];
1343
1344 self.delay_pos = (self.delay_pos + 1) % self.delay_line.len();
1345 }
1346 }
1347 fn name(&self) -> &str { "AutoTune" }
1348 fn reset(&mut self) {
1349 for v in self.yin_buf.iter_mut() { *v = 0.0; }
1350 for v in self.delay_line.iter_mut() { *v = 0.0; }
1351 self.yin_pos = 0;
1352 self.delay_pos = 0;
1353 self.current_shift = 1.0;
1354 }
1355}
1356
1357pub struct Tremolo {
1363 pub rate_hz: f32,
1364 pub depth: f32,
1365 phase: f32,
1366}
1367impl Tremolo {
1368 pub fn new(rate_hz: f32, depth: f32) -> Self {
1369 Self { rate_hz, depth, phase: 0.0 }
1370 }
1371}
1372impl AudioEffect for Tremolo {
1373 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1374 let phase_inc = self.rate_hz / sample_rate;
1375 for s in buffer.iter_mut() {
1376 let lfo = (self.phase * TAU).sin();
1377 let mod_gain = 1.0 - self.depth * (lfo * 0.5 + 0.5);
1378 *s *= mod_gain;
1379 self.phase = (self.phase + phase_inc) % 1.0;
1380 }
1381 }
1382 fn name(&self) -> &str { "Tremolo" }
1383 fn reset(&mut self) { self.phase = 0.0; }
1384}
1385
1386pub struct Vibrato {
1392 pub rate_hz: f32,
1393 pub depth_semitones: f32,
1394 phase: f32,
1395 delay_buf: Vec<f32>,
1396 delay_pos: usize,
1397}
1398impl Vibrato {
1399 pub fn new(rate_hz: f32, depth_semitones: f32) -> Self {
1400 Self {
1401 rate_hz,
1402 depth_semitones,
1403 phase: 0.0,
1404 delay_buf: vec![0.0; 2048],
1405 delay_pos: 0,
1406 }
1407 }
1408}
1409impl AudioEffect for Vibrato {
1410 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1411 let phase_inc = self.rate_hz / sample_rate;
1412 let max_delay_samp = (self.depth_semitones * 0.01 * sample_rate).max(1.0) as usize;
1413 let max_delay_samp = max_delay_samp.min(self.delay_buf.len() - 1);
1414 let buf_len = self.delay_buf.len();
1415
1416 for s in buffer.iter_mut() {
1417 self.delay_buf[self.delay_pos] = *s;
1418 let lfo = (self.phase * TAU).sin();
1419 let delay_samp = (max_delay_samp as f32 * (lfo * 0.5 + 0.5)).max(1.0) as usize;
1420 let read_pos = (self.delay_pos + buf_len - delay_samp) % buf_len;
1421 *s = self.delay_buf[read_pos];
1422 self.delay_pos = (self.delay_pos + 1) % buf_len;
1423 self.phase = (self.phase + phase_inc) % 1.0;
1424 }
1425 }
1426 fn name(&self) -> &str { "Vibrato" }
1427 fn reset(&mut self) {
1428 for v in self.delay_buf.iter_mut() { *v = 0.0; }
1429 self.delay_pos = 0;
1430 self.phase = 0.0;
1431 }
1432}
1433
1434#[derive(Clone, Copy, Debug, PartialEq)]
1439pub enum PanLaw {
1440 Linear,
1441 MinusThreeDb,
1442 MinusSixDb,
1443}
1444
1445pub struct Panner {
1447 pub pan: f32, pub law: PanLaw,
1449 pub haas_delay_ms: f32,
1450 pub ms_encode: bool,
1451
1452 haas_buf: Vec<f32>,
1453 haas_pos: usize,
1454}
1455impl Panner {
1456 pub fn new(pan: f32, law: PanLaw, haas_delay_ms: f32, ms_encode: bool) -> Self {
1457 Self {
1458 pan: pan.clamp(-1.0, 1.0),
1459 law,
1460 haas_delay_ms,
1461 ms_encode,
1462 haas_buf: vec![0.0; 4096],
1463 haas_pos: 0,
1464 }
1465 }
1466
1467 fn gains(&self) -> (f32, f32) {
1468 let p = (self.pan + 1.0) * 0.5; match self.law {
1470 PanLaw::Linear => (1.0 - p, p),
1471 PanLaw::MinusThreeDb => {
1472 let angle = p * PI * 0.5;
1473 (angle.cos(), angle.sin())
1474 }
1475 PanLaw::MinusSixDb => {
1476 ((1.0 - p).sqrt(), p.sqrt())
1478 }
1479 }
1480 }
1481}
1482impl AudioEffect for Panner {
1483 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1484 let haas_samp = (self.haas_delay_ms * 0.001 * sample_rate) as usize;
1485 let haas_samp = haas_samp.min(self.haas_buf.len() - 1);
1486 let buf_len = self.haas_buf.len();
1487 let (gl, gr) = self.gains();
1488
1489 for s in buffer.iter_mut() {
1490 self.haas_buf[self.haas_pos] = *s;
1491 let delayed = self.haas_buf[(self.haas_pos + buf_len - haas_samp) % buf_len];
1492 self.haas_pos = (self.haas_pos + 1) % buf_len;
1493
1494 if self.ms_encode {
1495 let mid = (*s + delayed) * 0.5;
1497 let _side = (*s - delayed) * 0.5;
1498 *s = mid;
1499 } else {
1500 *s = *s * gl + delayed * gr;
1502 }
1503 }
1504 }
1505 fn name(&self) -> &str { "Panner" }
1506 fn reset(&mut self) {
1507 for v in self.haas_buf.iter_mut() { *v = 0.0; }
1508 self.haas_pos = 0;
1509 }
1510}
1511
1512pub struct EffectSlot {
1518 pub effect: Box<dyn AudioEffect>,
1519 pub bypassed: bool,
1520 pub wet: f32,
1521 pub dry: f32,
1522}
1523
1524impl EffectSlot {
1525 pub fn new(effect: Box<dyn AudioEffect>) -> Self {
1526 Self { effect, bypassed: false, wet: 1.0, dry: 0.0 }
1527 }
1528 pub fn with_wet_dry(mut self, wet: f32, dry: f32) -> Self {
1529 self.wet = wet; self.dry = dry; self
1530 }
1531}
1532
1533pub struct EffectChain {
1535 pub slots: Vec<EffectSlot>,
1536}
1537impl EffectChain {
1538 pub fn new() -> Self { Self { slots: Vec::new() } }
1539
1540 pub fn add(&mut self, slot: EffectSlot) {
1541 self.slots.push(slot);
1542 }
1543
1544 pub fn add_effect(&mut self, effect: Box<dyn AudioEffect>) {
1545 self.slots.push(EffectSlot::new(effect));
1546 }
1547}
1548impl Default for EffectChain {
1549 fn default() -> Self { Self::new() }
1550}
1551impl AudioEffect for EffectChain {
1552 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1553 let n = buffer.len();
1554 let mut dry_buf: Vec<f32> = buffer.to_vec();
1555 let mut wet_buf: Vec<f32> = vec![0.0; n];
1556
1557 for slot in self.slots.iter_mut() {
1558 if slot.bypassed { continue; }
1559 let mut work: Vec<f32> = buffer.to_vec();
1561 slot.effect.process_block(&mut work, sample_rate);
1562
1563 let wet = slot.wet;
1564 let dry = slot.dry;
1565 if (wet - 1.0).abs() < 1e-6 && dry < 1e-6 {
1566 buffer.copy_from_slice(&work);
1568 } else {
1569 for i in 0..n {
1570 buffer[i] = dry_buf[i] * dry + work[i] * wet;
1571 }
1572 dry_buf.copy_from_slice(buffer);
1574 }
1575 let _ = wet_buf.as_mut_slice(); }
1577 }
1578 fn name(&self) -> &str { "EffectChain" }
1579 fn reset(&mut self) {
1580 for slot in self.slots.iter_mut() { slot.effect.reset(); }
1581 }
1582}
1583
1584#[cfg(test)]
1589mod tests {
1590 use super::*;
1591
1592 #[test]
1593 fn test_gain_unity() {
1594 let mut g = Gain::new(1.0);
1595 let mut buf = vec![0.5f32; 64];
1596 g.process_block(&mut buf, 44100.0);
1597 for s in &buf { assert!((*s - 0.5).abs() < 1e-6); }
1598 }
1599
1600 #[test]
1601 fn test_gain_silence() {
1602 let mut g = Gain::new(0.0);
1603 let mut buf = vec![1.0f32; 64];
1604 g.process_block(&mut buf, 44100.0);
1605 for s in &buf { assert!(s.abs() < 1e-6); }
1606 }
1607
1608 #[test]
1609 fn test_gain_automation_linear() {
1610 let bps = vec![
1611 GainBreakpoint { sample: 0, gain: 0.0, exponential: false },
1612 GainBreakpoint { sample: 100, gain: 1.0, exponential: false },
1613 ];
1614 let mut ga = GainAutomation::new(bps);
1615 let mut buf = vec![1.0f32; 100];
1616 ga.process_block(&mut buf, 44100.0);
1617 assert!(buf[0] < 0.05);
1619 assert!(buf[99] > 0.95);
1620 }
1621
1622 #[test]
1623 fn test_compressor_no_gain_reduction_below_threshold() {
1624 let mut c = Compressor::new(-10.0, 4.0, 1.0, 100.0, 0.0, 0.0, false, 0);
1625 let mut buf = vec![0.001f32; 256]; c.process_block(&mut buf, 44100.0);
1627 assert!(buf[200].abs() > 0.0009);
1629 }
1630
1631 #[test]
1632 fn test_limiter_brickwall() {
1633 let mut lim = Limiter::new(-6.0, 50.0, 0);
1634 let threshold = db_to_linear(-6.0);
1635 let mut buf = vec![1.0f32; 512]; lim.process_block(&mut buf, 44100.0);
1637 for s in &buf[100..] {
1638 assert!(*s <= threshold + 1e-4, "sample {} > threshold {}", s, threshold);
1639 }
1640 }
1641
1642 #[test]
1643 fn test_gate_opens_above_threshold() {
1644 let mut gate = Gate::new(-40.0, -50.0, -80.0, 1.0, 10.0, 50.0, false);
1645 let mut buf = vec![0.01f32; 512]; gate.process_block(&mut buf, 44100.0);
1647 let last = buf[400];
1649 assert!(last > 0.005, "gate should be open, got {}", last);
1650 }
1651
1652 #[test]
1653 fn test_biquad_lowpass_dc_passthrough() {
1654 let mut band = BiquadBand::new(BiquadType::LowPass, 1000.0, 0.707, 0.0);
1655 band.compute_coefficients(44100.0);
1656 let mut buf = vec![1.0f32; 512];
1658 for s in buf.iter_mut() { *s = band.process_sample(*s); }
1659 assert!(buf[400].abs() > 0.9, "DC should pass LPF, got {}", buf[400]);
1660 }
1661
1662 #[test]
1663 fn test_equalizer_processes() {
1664 let band = BiquadBand::new(BiquadType::PeakEq, 1000.0, 1.0, 6.0);
1665 let mut eq = Equalizer::new(vec![band]);
1666 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32 * 0.01).sin()).collect();
1667 let orig: Vec<f32> = buf.clone();
1668 eq.process_block(&mut buf, 44100.0);
1669 let changed = buf.iter().zip(orig.iter()).any(|(a, b)| (a - b).abs() > 1e-6);
1671 assert!(changed);
1672 }
1673
1674 #[test]
1675 fn test_reverb_adds_tail() {
1676 let mut rev = Reverb::new(0.8, 0.5, 0.5, 0.5, 0.0, 1.0);
1677 let mut buf = vec![0.0f32; 1024];
1678 buf[0] = 1.0; rev.process_block(&mut buf, 44100.0);
1680 let tail_energy: f32 = buf[100..].iter().map(|s| s * s).sum();
1682 assert!(tail_energy > 0.0, "reverb should produce a tail");
1683 }
1684
1685 #[test]
1686 fn test_delay_dry_signal() {
1687 let mut delay = Delay::new(100.0, 0.0, 10000.0, false, 0.0, 1.0);
1688 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32).sin()).collect();
1689 let orig = buf.clone();
1690 delay.process_block(&mut buf, 44100.0);
1691 for i in 0..256 {
1693 assert!((buf[i] - orig[i]).abs() < 1e-5, "dry signal mismatch at {}", i);
1694 }
1695 }
1696
1697 #[test]
1698 fn test_distortion_soft_clip_bounded() {
1699 let mut dist = Distortion::new(DistortionMode::SoftClip, 10.0, 1.0);
1700 let mut buf = vec![100.0f32; 256]; dist.process_block(&mut buf, 44100.0);
1702 for s in &buf { assert!(s.abs() <= 1.0 + 1e-5, "soft clip exceeded 1.0: {}", s); }
1703 }
1704
1705 #[test]
1706 fn test_distortion_hard_clip_bounded() {
1707 let mut dist = Distortion::new(DistortionMode::HardClip, 10.0, 1.0);
1708 let mut buf = vec![100.0f32; 256];
1709 dist.process_block(&mut buf, 44100.0);
1710 for s in &buf { assert!(s.abs() <= 10.0 + 1e-4); } }
1712
1713 #[test]
1714 fn test_tremolo_modulates_amplitude() {
1715 let mut trem = Tremolo::new(10.0, 1.0);
1716 let mut buf = vec![1.0f32; 1024];
1717 trem.process_block(&mut buf, 44100.0);
1718 let min = buf.iter().cloned().fold(f32::MAX, f32::min);
1719 let max = buf.iter().cloned().fold(f32::MIN, f32::max);
1720 assert!(max > 0.9, "should have near-full amplitude");
1721 assert!(min < 0.1, "should have near-zero amplitude with depth=1");
1722 }
1723
1724 #[test]
1725 fn test_effect_chain_bypass() {
1726 let mut chain = EffectChain::new();
1727 let mut slot = EffectSlot::new(Box::new(Gain::new(0.0)));
1728 slot.bypassed = true;
1729 chain.add(slot);
1730 let mut buf = vec![1.0f32; 64];
1731 chain.process_block(&mut buf, 44100.0);
1732 for s in &buf { assert!((*s - 1.0).abs() < 1e-6); }
1734 }
1735
1736 #[test]
1737 fn test_chorus_produces_output() {
1738 let mut chorus = Chorus::new(4, 1.5, 2.0, 0.5, 0.3, 0.5, 0.5);
1739 let mut buf: Vec<f32> = (0..512).map(|i| (i as f32 * 0.1).sin()).collect();
1740 chorus.process_block(&mut buf, 44100.0);
1741 let energy: f32 = buf.iter().map(|s| s * s).sum();
1742 assert!(energy > 0.0, "chorus should produce output");
1743 }
1744
1745 #[test]
1746 fn test_phaser_produces_output() {
1747 let mut phaser = Phaser::new(8, 0.5, 0.7, 0.5, 500.0, true, 0.7, 0.3);
1748 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32 * 0.05).sin()).collect();
1749 phaser.process_block(&mut buf, 44100.0);
1750 let energy: f32 = buf.iter().map(|s| s * s).sum();
1751 assert!(energy > 0.0);
1752 }
1753}