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] = [116, 118, 127, 135, 142, 149, 155, 161];
640const ALLPASS_TUNING: [usize; 4] = [55, 44, 34, 22];
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 = *state + a * x;
1002 *state = x - a * y;
1003 y
1004 }
1005}
1006impl AudioEffect for Phaser {
1007 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1008 let phase_inc = self.rate_hz / sample_rate;
1009
1010 for s in buffer.iter_mut() {
1011 let lfo = (self.phase * TAU).sin();
1012 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);
1015 let freq_r = (self.base_freq * (1.0 + lfo_r * self.depth)).clamp(20.0, sample_rate * 0.49);
1016 let a_l = ((PI * freq_l / sample_rate) - 1.0) / ((PI * freq_l / sample_rate) + 1.0);
1017 let a_r = ((PI * freq_r / sample_rate) - 1.0) / ((PI * freq_r / sample_rate) + 1.0);
1018
1019 let mut out_l = *s + self.fb_l * self.feedback;
1020 let mut out_r = *s + self.fb_r * self.feedback;
1021 for i in 0..self.stages {
1022 out_l = Self::allpass_stage(&mut self.ap_state[i][0], a_l, out_l);
1023 if self.stereo {
1024 out_r = Self::allpass_stage(&mut self.ap_state[i][1], a_r, out_r);
1025 }
1026 }
1027 self.fb_l = out_l;
1028 self.fb_r = out_r;
1029 self.phase = (self.phase + phase_inc) % 1.0;
1030
1031 let wet_out = if self.stereo { (out_l + out_r) * 0.5 } else { out_l };
1032 *s = *s * self.dry + wet_out * self.wet;
1033 }
1034 }
1035 fn name(&self) -> &str { "Phaser" }
1036 fn reset(&mut self) {
1037 for st in self.ap_state.iter_mut() { *st = [0.0, 0.0]; }
1038 self.fb_l = 0.0;
1039 self.fb_r = 0.0;
1040 }
1041}
1042
1043#[derive(Clone, Copy, Debug, PartialEq)]
1048pub enum DistortionMode {
1049 SoftClip,
1050 HardClip,
1051 Foldback,
1052 BitCrush { bits: u32, rate_reduction: u32 },
1053 Overdrive,
1054 TubeSaturation,
1055}
1056
1057pub struct Distortion {
1059 pub mode: DistortionMode,
1060 pub drive: f32,
1061 pub output_gain: f32,
1062 pub pre_filter: Option<BiquadBand>,
1063 pub post_filter: Option<BiquadBand>,
1064 held_sample: f32,
1066 rate_counter: u32,
1067}
1068impl Distortion {
1069 pub fn new(mode: DistortionMode, drive: f32, output_gain: f32) -> Self {
1070 Self {
1071 mode, drive, output_gain,
1072 pre_filter: None,
1073 post_filter: None,
1074 held_sample: 0.0,
1075 rate_counter: 0,
1076 }
1077 }
1078
1079 fn process_sample(&mut self, x: f32) -> f32 {
1080 let driven = x * self.drive;
1081 match self.mode {
1082 DistortionMode::SoftClip => {
1083 driven.tanh()
1084 }
1085 DistortionMode::HardClip => {
1086 driven.clamp(-1.0, 1.0)
1087 }
1088 DistortionMode::Foldback => {
1089 let mut v = driven;
1090 let threshold = 1.0f32;
1091 while v.abs() > threshold {
1092 if v > threshold { v = 2.0 * threshold - v; }
1093 else if v < -threshold { v = -2.0 * threshold - v; }
1094 }
1095 v
1096 }
1097 DistortionMode::BitCrush { bits, rate_reduction } => {
1098 let rate_red = rate_reduction.max(1);
1099 self.rate_counter += 1;
1100 if self.rate_counter >= rate_red {
1101 self.rate_counter = 0;
1102 let levels = (2.0f32).powi(bits.clamp(1, 24) as i32);
1103 self.held_sample = (driven * levels).round() / levels;
1104 }
1105 self.held_sample
1106 }
1107 DistortionMode::Overdrive => {
1108 if driven >= 0.0 {
1110 1.0 - (-driven).exp()
1111 } else {
1112 -1.0 + driven.exp()
1113 }
1114 }
1115 DistortionMode::TubeSaturation => {
1116 let y = driven + 0.2 * driven * driven - 0.1 * driven.powi(3);
1118 y.tanh()
1119 }
1120 }
1121 }
1122}
1123impl AudioEffect for Distortion {
1124 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1125 if let Some(ref mut pf) = self.pre_filter {
1126 pf.compute_coefficients(sample_rate);
1127 }
1128 if let Some(ref mut pf) = self.post_filter {
1129 pf.compute_coefficients(sample_rate);
1130 }
1131 for s in buffer.iter_mut() {
1132 let pre = if let Some(ref mut f) = self.pre_filter { f.process_sample(*s) } else { *s };
1133 let dist = self.process_sample(pre);
1134 let post = if let Some(ref mut f) = self.post_filter { f.process_sample(dist) } else { dist };
1135 *s = post * self.output_gain;
1136 }
1137 }
1138 fn name(&self) -> &str { "Distortion" }
1139 fn reset(&mut self) {
1140 self.held_sample = 0.0;
1141 self.rate_counter = 0;
1142 if let Some(ref mut f) = self.pre_filter { f.reset(); }
1143 if let Some(ref mut f) = self.post_filter { f.reset(); }
1144 }
1145}
1146
1147pub struct PitchShifter {
1153 pub pitch_ratio: f32,
1154 pub formant_preserve: bool,
1155
1156 in_buf: Vec<f32>,
1158 in_pos: usize,
1159 out_buf: Vec<f32>,
1161 out_pos: usize,
1162 grain_phase: f32,
1163 grain_size: usize,
1164}
1165impl PitchShifter {
1166 pub fn new(pitch_ratio: f32, formant_preserve: bool) -> Self {
1167 let grain_size = 2048usize;
1168 let buf_size = grain_size * 4;
1169 Self {
1170 pitch_ratio,
1171 formant_preserve,
1172 in_buf: vec![0.0; buf_size],
1173 in_pos: 0,
1174 out_buf: vec![0.0; buf_size],
1175 out_pos: 0,
1176 grain_phase: 0.0,
1177 grain_size,
1178 }
1179 }
1180
1181 fn hann_window(i: usize, n: usize) -> f32 {
1182 0.5 * (1.0 - (TAU * i as f32 / (n - 1) as f32).cos())
1183 }
1184}
1185impl AudioEffect for PitchShifter {
1186 fn process_block(&mut self, buffer: &mut [f32], _sample_rate: f32) {
1187 let gs = self.grain_size;
1188 let hop = gs / 4;
1189 let buf_len = self.in_buf.len();
1190 let out_len = self.out_buf.len();
1191
1192 for s in buffer.iter_mut() {
1193 self.in_buf[self.in_pos] = *s;
1195 self.in_pos = (self.in_pos + 1) % buf_len;
1196
1197 self.grain_phase += 1.0;
1199 if self.grain_phase as usize >= hop {
1200 self.grain_phase = 0.0;
1201 let read_offset = (gs as f32 / self.pitch_ratio.max(0.01)) as usize;
1203 for i in 0..gs {
1204 let in_idx = (self.in_pos + buf_len - read_offset + i) % buf_len;
1205 let w = Self::hann_window(i, gs);
1206 let out_idx = (self.out_pos + i) % out_len;
1207 self.out_buf[out_idx] += self.in_buf[in_idx] * w;
1208 }
1209 }
1210
1211 let out_val = self.out_buf[self.out_pos];
1213 self.out_buf[self.out_pos] = 0.0;
1214 self.out_pos = (self.out_pos + 1) % out_len;
1215 *s = out_val;
1216 }
1217 }
1218 fn name(&self) -> &str { "PitchShifter" }
1219 fn reset(&mut self) {
1220 for v in self.in_buf.iter_mut() { *v = 0.0; }
1221 for v in self.out_buf.iter_mut() { *v = 0.0; }
1222 self.in_pos = 0;
1223 self.out_pos = 0;
1224 self.grain_phase = 0.0;
1225 }
1226}
1227
1228pub struct AutoTune {
1234 pub speed: f32, pub concert_a: f32, yin_buf: Vec<f32>,
1238 yin_pos: usize,
1239 yin_size: usize,
1240 current_shift: f32,
1241 delay_line: Vec<f32>,
1242 delay_pos: usize,
1243}
1244impl AutoTune {
1245 pub fn new(speed: f32) -> Self {
1246 let yin_size = 2048usize;
1247 Self {
1248 speed: speed.clamp(0.0, 1.0),
1249 concert_a: 440.0,
1250 yin_buf: vec![0.0; yin_size],
1251 yin_pos: 0,
1252 yin_size,
1253 current_shift: 1.0,
1254 delay_line: vec![0.0; yin_size],
1255 delay_pos: 0,
1256 }
1257 }
1258
1259 fn yin_pitch(&self, sample_rate: f32) -> Option<f32> {
1261 let n = self.yin_size / 2;
1262 let mut d = vec![0.0f32; n];
1263 for tau in 1..n {
1265 let mut s = 0.0f32;
1266 for j in 0..n {
1267 let a = self.yin_buf[(self.yin_pos + j) % self.yin_size];
1268 let b = self.yin_buf[(self.yin_pos + j + tau) % self.yin_size];
1269 s += (a - b).powi(2);
1270 }
1271 d[tau] = s;
1272 }
1273 let mut cmnd = vec![0.0f32; n];
1275 cmnd[0] = 1.0;
1276 let mut running_sum = 0.0f32;
1277 for tau in 1..n {
1278 running_sum += d[tau];
1279 cmnd[tau] = d[tau] * tau as f32 / running_sum;
1280 }
1281 let threshold = 0.1f32;
1283 for tau in 2..n {
1284 if cmnd[tau] < threshold {
1285 let tau_f = if tau > 1 && tau < n - 1 {
1287 let x0 = cmnd[tau - 1];
1288 let x1 = cmnd[tau];
1289 let x2 = cmnd[tau + 1];
1290 let denom = 2.0 * (2.0 * x1 - x0 - x2);
1291 if denom.abs() < 1e-10 { tau as f32 }
1292 else { tau as f32 + (x0 - x2) / denom }
1293 } else { tau as f32 };
1294 return Some(sample_rate / tau_f);
1295 }
1296 }
1297 None
1298 }
1299
1300 fn note_to_freq(midi: i32, concert_a: f32) -> f32 {
1301 concert_a * 2.0f32.powf((midi - 69) as f32 / 12.0)
1302 }
1303
1304 fn freq_to_midi(freq: f32, concert_a: f32) -> f32 {
1305 if freq <= 0.0 { return 0.0; }
1306 69.0 + 12.0 * (freq / concert_a).log2()
1307 }
1308
1309 fn nearest_semitone_freq(freq: f32, concert_a: f32) -> f32 {
1310 let midi_f = Self::freq_to_midi(freq, concert_a);
1311 let midi_rounded = midi_f.round() as i32;
1312 Self::note_to_freq(midi_rounded, concert_a)
1313 }
1314}
1315impl AudioEffect for AutoTune {
1316 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1317 let smooth = 1.0 - self.speed;
1318 for s in buffer.iter_mut() {
1319 self.yin_buf[self.yin_pos] = *s;
1321 self.yin_pos = (self.yin_pos + 1) % self.yin_size;
1322
1323 self.delay_line[self.delay_pos] = *s;
1325
1326 if self.yin_pos % (self.yin_size / 4) == 0 {
1328 if let Some(detected_freq) = self.yin_pitch(sample_rate) {
1329 if detected_freq > 50.0 && detected_freq < 2000.0 {
1330 let target_freq = Self::nearest_semitone_freq(detected_freq, self.concert_a);
1331 let target_shift = target_freq / detected_freq;
1332 self.current_shift = self.current_shift * smooth + target_shift * (1.0 - smooth);
1333 }
1334 }
1335 }
1336
1337 let read_offset = (self.yin_size as f32 / 2.0 / self.current_shift.max(0.1)) as usize;
1339 let read_idx = (self.delay_pos + self.delay_line.len() - read_offset.min(self.delay_line.len() - 1)) % self.delay_line.len();
1340 *s = self.delay_line[read_idx];
1341
1342 self.delay_pos = (self.delay_pos + 1) % self.delay_line.len();
1343 }
1344 }
1345 fn name(&self) -> &str { "AutoTune" }
1346 fn reset(&mut self) {
1347 for v in self.yin_buf.iter_mut() { *v = 0.0; }
1348 for v in self.delay_line.iter_mut() { *v = 0.0; }
1349 self.yin_pos = 0;
1350 self.delay_pos = 0;
1351 self.current_shift = 1.0;
1352 }
1353}
1354
1355pub struct Tremolo {
1361 pub rate_hz: f32,
1362 pub depth: f32,
1363 phase: f32,
1364}
1365impl Tremolo {
1366 pub fn new(rate_hz: f32, depth: f32) -> Self {
1367 Self { rate_hz, depth, phase: 0.0 }
1368 }
1369}
1370impl AudioEffect for Tremolo {
1371 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1372 let phase_inc = self.rate_hz / sample_rate;
1373 for s in buffer.iter_mut() {
1374 let lfo = (self.phase * TAU).sin();
1375 let mod_gain = 1.0 - self.depth * lfo;
1376 *s *= mod_gain;
1377 self.phase = (self.phase + phase_inc) % 1.0;
1378 }
1379 }
1380 fn name(&self) -> &str { "Tremolo" }
1381 fn reset(&mut self) { self.phase = 0.0; }
1382}
1383
1384pub struct Vibrato {
1390 pub rate_hz: f32,
1391 pub depth_semitones: f32,
1392 phase: f32,
1393 delay_buf: Vec<f32>,
1394 delay_pos: usize,
1395}
1396impl Vibrato {
1397 pub fn new(rate_hz: f32, depth_semitones: f32) -> Self {
1398 Self {
1399 rate_hz,
1400 depth_semitones,
1401 phase: 0.0,
1402 delay_buf: vec![0.0; 2048],
1403 delay_pos: 0,
1404 }
1405 }
1406}
1407impl AudioEffect for Vibrato {
1408 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1409 let phase_inc = self.rate_hz / sample_rate;
1410 let max_delay_samp = (self.depth_semitones * 0.01 * sample_rate).max(1.0) as usize;
1411 let max_delay_samp = max_delay_samp.min(self.delay_buf.len() - 1);
1412 let buf_len = self.delay_buf.len();
1413
1414 for s in buffer.iter_mut() {
1415 self.delay_buf[self.delay_pos] = *s;
1416 let lfo = (self.phase * TAU).sin();
1417 let delay_samp = (max_delay_samp as f32 * (lfo * 0.5 + 0.5)).max(1.0) as usize;
1418 let read_pos = (self.delay_pos + buf_len - delay_samp) % buf_len;
1419 *s = self.delay_buf[read_pos];
1420 self.delay_pos = (self.delay_pos + 1) % buf_len;
1421 self.phase = (self.phase + phase_inc) % 1.0;
1422 }
1423 }
1424 fn name(&self) -> &str { "Vibrato" }
1425 fn reset(&mut self) {
1426 for v in self.delay_buf.iter_mut() { *v = 0.0; }
1427 self.delay_pos = 0;
1428 self.phase = 0.0;
1429 }
1430}
1431
1432#[derive(Clone, Copy, Debug, PartialEq)]
1437pub enum PanLaw {
1438 Linear,
1439 MinusThreeDb,
1440 MinusSixDb,
1441}
1442
1443pub struct Panner {
1445 pub pan: f32, pub law: PanLaw,
1447 pub haas_delay_ms: f32,
1448 pub ms_encode: bool,
1449
1450 haas_buf: Vec<f32>,
1451 haas_pos: usize,
1452}
1453impl Panner {
1454 pub fn new(pan: f32, law: PanLaw, haas_delay_ms: f32, ms_encode: bool) -> Self {
1455 Self {
1456 pan: pan.clamp(-1.0, 1.0),
1457 law,
1458 haas_delay_ms,
1459 ms_encode,
1460 haas_buf: vec![0.0; 4096],
1461 haas_pos: 0,
1462 }
1463 }
1464
1465 fn gains(&self) -> (f32, f32) {
1466 let p = (self.pan + 1.0) * 0.5; match self.law {
1468 PanLaw::Linear => (1.0 - p, p),
1469 PanLaw::MinusThreeDb => {
1470 let angle = p * PI * 0.5;
1471 (angle.cos(), angle.sin())
1472 }
1473 PanLaw::MinusSixDb => {
1474 ((1.0 - p).sqrt(), p.sqrt())
1476 }
1477 }
1478 }
1479}
1480impl AudioEffect for Panner {
1481 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1482 let haas_samp = (self.haas_delay_ms * 0.001 * sample_rate) as usize;
1483 let haas_samp = haas_samp.min(self.haas_buf.len() - 1);
1484 let buf_len = self.haas_buf.len();
1485 let (gl, gr) = self.gains();
1486
1487 for s in buffer.iter_mut() {
1488 self.haas_buf[self.haas_pos] = *s;
1489 let delayed = self.haas_buf[(self.haas_pos + buf_len - haas_samp) % buf_len];
1490 self.haas_pos = (self.haas_pos + 1) % buf_len;
1491
1492 if self.ms_encode {
1493 let mid = (*s + delayed) * 0.5;
1495 let _side = (*s - delayed) * 0.5;
1496 *s = mid;
1497 } else {
1498 *s = *s * gl + delayed * gr;
1500 }
1501 }
1502 }
1503 fn name(&self) -> &str { "Panner" }
1504 fn reset(&mut self) {
1505 for v in self.haas_buf.iter_mut() { *v = 0.0; }
1506 self.haas_pos = 0;
1507 }
1508}
1509
1510pub struct EffectSlot {
1516 pub effect: Box<dyn AudioEffect>,
1517 pub bypassed: bool,
1518 pub wet: f32,
1519 pub dry: f32,
1520}
1521
1522impl EffectSlot {
1523 pub fn new(effect: Box<dyn AudioEffect>) -> Self {
1524 Self { effect, bypassed: false, wet: 1.0, dry: 0.0 }
1525 }
1526 pub fn with_wet_dry(mut self, wet: f32, dry: f32) -> Self {
1527 self.wet = wet; self.dry = dry; self
1528 }
1529}
1530
1531pub struct EffectChain {
1533 pub slots: Vec<EffectSlot>,
1534}
1535impl EffectChain {
1536 pub fn new() -> Self { Self { slots: Vec::new() } }
1537
1538 pub fn add(&mut self, slot: EffectSlot) {
1539 self.slots.push(slot);
1540 }
1541
1542 pub fn add_effect(&mut self, effect: Box<dyn AudioEffect>) {
1543 self.slots.push(EffectSlot::new(effect));
1544 }
1545}
1546impl Default for EffectChain {
1547 fn default() -> Self { Self::new() }
1548}
1549impl AudioEffect for EffectChain {
1550 fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32) {
1551 let n = buffer.len();
1552 let mut dry_buf: Vec<f32> = buffer.to_vec();
1553 let mut wet_buf: Vec<f32> = vec![0.0; n];
1554
1555 for slot in self.slots.iter_mut() {
1556 if slot.bypassed { continue; }
1557 let mut work: Vec<f32> = buffer.to_vec();
1559 slot.effect.process_block(&mut work, sample_rate);
1560
1561 let wet = slot.wet;
1562 let dry = slot.dry;
1563 if (wet - 1.0).abs() < 1e-6 && dry < 1e-6 {
1564 buffer.copy_from_slice(&work);
1566 } else {
1567 for i in 0..n {
1568 buffer[i] = dry_buf[i] * dry + work[i] * wet;
1569 }
1570 dry_buf.copy_from_slice(buffer);
1572 }
1573 let _ = wet_buf.as_mut_slice(); }
1575 }
1576 fn name(&self) -> &str { "EffectChain" }
1577 fn reset(&mut self) {
1578 for slot in self.slots.iter_mut() { slot.effect.reset(); }
1579 }
1580}
1581
1582#[cfg(test)]
1587mod tests {
1588 use super::*;
1589
1590 #[test]
1591 fn test_gain_unity() {
1592 let mut g = Gain::new(1.0);
1593 let mut buf = vec![0.5f32; 64];
1594 g.process_block(&mut buf, 44100.0);
1595 for s in &buf { assert!((*s - 0.5).abs() < 1e-6); }
1596 }
1597
1598 #[test]
1599 fn test_gain_silence() {
1600 let mut g = Gain::new(0.0);
1601 let mut buf = vec![1.0f32; 64];
1602 g.process_block(&mut buf, 44100.0);
1603 for s in &buf { assert!(s.abs() < 1e-6); }
1604 }
1605
1606 #[test]
1607 fn test_gain_automation_linear() {
1608 let bps = vec![
1609 GainBreakpoint { sample: 0, gain: 0.0, exponential: false },
1610 GainBreakpoint { sample: 100, gain: 1.0, exponential: false },
1611 ];
1612 let mut ga = GainAutomation::new(bps);
1613 let mut buf = vec![1.0f32; 100];
1614 ga.process_block(&mut buf, 44100.0);
1615 assert!(buf[0] < 0.05);
1617 assert!(buf[99] > 0.95);
1618 }
1619
1620 #[test]
1621 fn test_compressor_no_gain_reduction_below_threshold() {
1622 let mut c = Compressor::new(-10.0, 4.0, 1.0, 100.0, 0.0, 0.0, false, 0);
1623 let mut buf = vec![0.001f32; 256]; c.process_block(&mut buf, 44100.0);
1625 assert!(buf[200].abs() > 0.0009);
1627 }
1628
1629 #[test]
1630 fn test_limiter_brickwall() {
1631 let mut lim = Limiter::new(-6.0, 50.0, 0);
1632 let threshold = db_to_linear(-6.0);
1633 let mut buf = vec![1.0f32; 512]; lim.process_block(&mut buf, 44100.0);
1635 for s in &buf[100..] {
1636 assert!(*s <= threshold + 1e-4, "sample {} > threshold {}", s, threshold);
1637 }
1638 }
1639
1640 #[test]
1641 fn test_gate_opens_above_threshold() {
1642 let mut gate = Gate::new(-40.0, -50.0, -80.0, 1.0, 10.0, 50.0, false);
1643 let mut buf = vec![0.01f32; 512]; gate.process_block(&mut buf, 44100.0);
1645 let last = buf[400];
1647 assert!(last > 0.005, "gate should be open, got {}", last);
1648 }
1649
1650 #[test]
1651 fn test_biquad_lowpass_dc_passthrough() {
1652 let mut band = BiquadBand::new(BiquadType::LowPass, 1000.0, 0.707, 0.0);
1653 band.compute_coefficients(44100.0);
1654 let mut buf = vec![1.0f32; 512];
1656 for s in buf.iter_mut() { *s = band.process_sample(*s); }
1657 assert!(buf[400].abs() > 0.9, "DC should pass LPF, got {}", buf[400]);
1658 }
1659
1660 #[test]
1661 fn test_equalizer_processes() {
1662 let band = BiquadBand::new(BiquadType::PeakEq, 1000.0, 1.0, 6.0);
1663 let mut eq = Equalizer::new(vec![band]);
1664 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32 * 0.01).sin()).collect();
1665 let orig: Vec<f32> = buf.clone();
1666 eq.process_block(&mut buf, 44100.0);
1667 let changed = buf.iter().zip(orig.iter()).any(|(a, b)| (a - b).abs() > 1e-6);
1669 assert!(changed);
1670 }
1671
1672 #[test]
1673 fn test_reverb_adds_tail() {
1674 let mut rev = Reverb::new(0.8, 0.5, 0.5, 0.5, 0.0, 1.0);
1675 let mut buf = vec![0.0f32; 1024];
1676 buf[0] = 1.0; rev.process_block(&mut buf, 44100.0);
1678 let tail_energy: f32 = buf[100..].iter().map(|s| s * s).sum();
1680 assert!(tail_energy > 0.0, "reverb should produce a tail");
1681 }
1682
1683 #[test]
1684 fn test_delay_dry_signal() {
1685 let mut delay = Delay::new(100.0, 0.0, 10000.0, false, 0.0, 1.0);
1686 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32).sin()).collect();
1687 let orig = buf.clone();
1688 delay.process_block(&mut buf, 44100.0);
1689 for i in 0..256 {
1691 assert!((buf[i] - orig[i]).abs() < 1e-5, "dry signal mismatch at {}", i);
1692 }
1693 }
1694
1695 #[test]
1696 fn test_distortion_soft_clip_bounded() {
1697 let mut dist = Distortion::new(DistortionMode::SoftClip, 10.0, 1.0);
1698 let mut buf = vec![100.0f32; 256]; dist.process_block(&mut buf, 44100.0);
1700 for s in &buf { assert!(s.abs() <= 1.0 + 1e-5, "soft clip exceeded 1.0: {}", s); }
1701 }
1702
1703 #[test]
1704 fn test_distortion_hard_clip_bounded() {
1705 let mut dist = Distortion::new(DistortionMode::HardClip, 10.0, 1.0);
1706 let mut buf = vec![100.0f32; 256];
1707 dist.process_block(&mut buf, 44100.0);
1708 for s in &buf { assert!(s.abs() <= 10.0 + 1e-4); } }
1710
1711 #[test]
1712 fn test_tremolo_modulates_amplitude() {
1713 let mut trem = Tremolo::new(10.0, 1.0);
1714 let mut buf = vec![1.0f32; 1024];
1715 trem.process_block(&mut buf, 44100.0);
1716 let min = buf.iter().cloned().fold(f32::MAX, f32::min);
1717 let max = buf.iter().cloned().fold(f32::MIN, f32::max);
1718 assert!(max > 0.9, "should have near-full amplitude");
1719 assert!(min < 0.1, "should have near-zero amplitude with depth=1");
1720 }
1721
1722 #[test]
1723 fn test_effect_chain_bypass() {
1724 let mut chain = EffectChain::new();
1725 let mut slot = EffectSlot::new(Box::new(Gain::new(0.0)));
1726 slot.bypassed = true;
1727 chain.add(slot);
1728 let mut buf = vec![1.0f32; 64];
1729 chain.process_block(&mut buf, 44100.0);
1730 for s in &buf { assert!((*s - 1.0).abs() < 1e-6); }
1732 }
1733
1734 #[test]
1735 fn test_chorus_produces_output() {
1736 let mut chorus = Chorus::new(4, 1.5, 2.0, 0.5, 0.3, 0.5, 0.5);
1737 let mut buf: Vec<f32> = (0..512).map(|i| (i as f32 * 0.1).sin()).collect();
1738 chorus.process_block(&mut buf, 44100.0);
1739 let energy: f32 = buf.iter().map(|s| s * s).sum();
1740 assert!(energy > 0.0, "chorus should produce output");
1741 }
1742
1743 #[test]
1744 fn test_phaser_produces_output() {
1745 let mut phaser = Phaser::new(8, 0.5, 0.7, 0.5, 500.0, true, 0.7, 0.3);
1746 let mut buf: Vec<f32> = (0..256).map(|i| (i as f32 * 0.05).sin()).collect();
1747 phaser.process_block(&mut buf, 44100.0);
1748 let energy: f32 = buf.iter().map(|s| s * s).sum();
1749 assert!(energy > 0.0);
1750 }
1751}