Skip to main content

proof_engine/audio/
effects.rs

1//! Audio effects and processing: gain, dynamics, EQ, reverb, delay, modulation,
2//! distortion, pitch, and effect chain management.
3
4use std::f32::consts::{PI, TAU};
5use std::collections::VecDeque;
6
7// ---------------------------------------------------------------------------
8// Core trait
9// ---------------------------------------------------------------------------
10
11/// Every audio effect implements this trait.
12pub trait AudioEffect: Send {
13    fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32);
14    /// Human-readable name for debugging/UI.
15    fn name(&self) -> &str;
16    /// Reset all internal state (e.g. delay lines, filters).
17    fn reset(&mut self);
18}
19
20// ---------------------------------------------------------------------------
21// Helpers
22// ---------------------------------------------------------------------------
23
24#[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
34/// One-pole low-pass: y[n] = alpha*x[n] + (1-alpha)*y[n-1]
35struct 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
51// ---------------------------------------------------------------------------
52// Gain
53// ---------------------------------------------------------------------------
54
55/// Linear gain applied to every sample.
56pub 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// ---------------------------------------------------------------------------
71// GainAutomation
72// ---------------------------------------------------------------------------
73
74/// A breakpoint for gain automation.
75#[derive(Clone, Copy, Debug)]
76pub struct GainBreakpoint {
77    /// Sample index.
78    pub sample: u64,
79    /// Target gain in linear scale.
80    pub gain: f32,
81    /// If true, interpolate exponentially; otherwise linearly.
82    pub exponential: bool,
83}
84
85/// Applies per-sample automated gain from a breakpoint list.
86pub 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        // Binary search for enclosing pair
106        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
129// ---------------------------------------------------------------------------
130// Compressor
131// ---------------------------------------------------------------------------
132
133/// Soft-knee / hard-knee compressor with optional RMS detection and sidechain.
134pub struct Compressor {
135    /// Threshold in dBFS.
136    pub threshold_db: f32,
137    /// Compression ratio (e.g. 4.0 = 4:1).
138    pub ratio: f32,
139    /// Attack time in milliseconds.
140    pub attack_ms: f32,
141    /// Release time in milliseconds.
142    pub release_ms: f32,
143    /// Soft-knee width in dB (0 = hard knee).
144    pub knee_db: f32,
145    /// Make-up gain in dB.
146    pub makeup_db: f32,
147    /// Use RMS detection (true) or peak (false).
148    pub use_rms: bool,
149    /// Lookahead delay in samples.
150    pub lookahead_samples: usize,
151    /// Current gain reduction in dB (read-only metering).
152    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            // Lookahead: push current into queue, pop delayed sample
220            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            // Level detection
228            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            // Envelope follower
242            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
265// ---------------------------------------------------------------------------
266// Limiter
267// ---------------------------------------------------------------------------
268
269/// Brickwall peak limiter with true-peak (4x oversampling) and lookahead.
270pub struct Limiter {
271    pub threshold_db: f32,
272    pub release_ms: f32,
273    pub lookahead_samples: usize,
274    /// Current gain reduction in dB.
275    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    /// 4× oversampled true-peak detection for a single sample.
293    fn true_peak(x: f32, prev: f32) -> f32 {
294        // Linear interpolation upsample 4×
295        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            // Attack is instant (brickwall), release follows envelope
322            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// ---------------------------------------------------------------------------
344// Gate
345// ---------------------------------------------------------------------------
346
347#[derive(Clone, Copy, PartialEq, Eq, Debug)]
348pub enum GateState { Closed, Attacking, Open, Releasing, Holding }
349
350/// Noise gate with hysteresis (separate open/close thresholds), hold, and flip (duck) mode.
351pub struct Gate {
352    pub open_threshold_db: f32,
353    pub close_threshold_db: f32,
354    /// Range in dB: how much to attenuate when gate is closed (negative value, e.g. -80).
355    pub range_db: f32,
356    pub attack_ms: f32,
357    pub hold_ms: f32,
358    pub release_ms: f32,
359    /// Flip: gate opens when signal is ABOVE threshold (ducking).
360    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// ---------------------------------------------------------------------------
453// Biquad filter
454// ---------------------------------------------------------------------------
455
456#[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/// Direct-form II transposed biquad filter.
469#[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    // Coefficients
477    b0: f32, b1: f32, b2: f32,
478    a1: f32, a2: f32,
479    // State
480    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); // amplitude for peak/shelf
497        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
597// ---------------------------------------------------------------------------
598// Equalizer
599// ---------------------------------------------------------------------------
600
601/// Parametric EQ with up to 16 bands.
602pub 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
635// ---------------------------------------------------------------------------
636// Reverb (Freeverb-style)
637// ---------------------------------------------------------------------------
638
639const 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
684/// Freeverb-inspired stereo reverb.
685pub 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            // Pre-delay
739            self.pre_delay_buf.push_back(*s);
740            let input = self.pre_delay_buf.pop_front().unwrap_or(0.0);
741
742            // Mono input → both channels
743            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
764// ---------------------------------------------------------------------------
765// Delay
766// ---------------------------------------------------------------------------
767
768/// Stereo-capable delay line with feedback, high-cut in feedback path, and ping-pong.
769pub 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    /// Tap tempo BPM (0 = use delay_ms directly).
777    pub tempo_bpm: f32,
778    /// Fraction of a beat (e.g. 0.5 = 8th note).
779    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; // 4 seconds at 48k
791        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            // High-cut on feedback path
828            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
852// ---------------------------------------------------------------------------
853// Chorus
854// ---------------------------------------------------------------------------
855
856/// Multi-voice chorus (up to 8 LFO-modulated delay lines).
857pub 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; // 0.5ms center
887        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
913// ---------------------------------------------------------------------------
914// Flanger
915// ---------------------------------------------------------------------------
916
917/// Short-delay flanger (0–15 ms) with LFO and feedback.
918pub 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
965// ---------------------------------------------------------------------------
966// Phaser
967// ---------------------------------------------------------------------------
968
969/// Multi-stage allpass phaser (4/8/12 stages), LFO, feedback, stereo.
970pub 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    // Allpass states: [stage][channel] — channel 0 = L, 1 = R
981    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        // First-order allpass: H(z) = (a + z^-1) / (1 + a*z^-1)
1000        let y = a * (x - *state) + *state;
1001        *state = x - a * y;
1002        // Actually use standard form:
1003        // y[n] = a1*x[n] + x[n-1] - a1*y[n-1]
1004        // simplified direct: let's use proper transposed form
1005        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(); // 90° for stereo
1015
1016            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// ---------------------------------------------------------------------------
1046// Distortion
1047// ---------------------------------------------------------------------------
1048
1049#[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
1059/// Multi-mode distortion with pre/post EQ bands.
1060pub 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    // bit crush state
1067    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                // Asymmetric waveshaping: positive side harder
1111                if driven >= 0.0 {
1112                    1.0 - (-driven).exp()
1113                } else {
1114                    -1.0 + driven.exp()
1115                }
1116            }
1117            DistortionMode::TubeSaturation => {
1118                // Even-harmonic bias: y = x + 0.2*x^2 - 0.1*x^3, then soft clip
1119                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
1149// ---------------------------------------------------------------------------
1150// Pitch Shifter (granular overlap-add)
1151// ---------------------------------------------------------------------------
1152
1153/// Granular overlap-add pitch shifter.
1154pub struct PitchShifter {
1155    pub pitch_ratio: f32,
1156    pub formant_preserve: bool,
1157
1158    // Input ring buffer
1159    in_buf: Vec<f32>,
1160    in_pos: usize,
1161    // Output accumulation buffer
1162    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            // Write into input ring buffer
1196            self.in_buf[self.in_pos] = *s;
1197            self.in_pos = (self.in_pos + 1) % buf_len;
1198
1199            // Grain processing: every hop samples spawn a new grain
1200            self.grain_phase += 1.0;
1201            if self.grain_phase as usize >= hop {
1202                self.grain_phase = 0.0;
1203                // Read grain from input at pitch-shifted position
1204                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            // Read from output buffer
1214            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
1230// ---------------------------------------------------------------------------
1231// AutoTune (YIN-based pitch detection + correction)
1232// ---------------------------------------------------------------------------
1233
1234/// YIN pitch detector and chromatic pitch corrector.
1235pub struct AutoTune {
1236    pub speed: f32,  // 0.0 = slow natural, 1.0 = hard snap
1237    pub concert_a: f32, // Hz for A4 (usually 440.0)
1238
1239    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    /// YIN algorithm: returns detected period in samples, or 0.0 if unclear.
1262    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        // Difference function
1266        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        // Cumulative mean normalized difference
1276        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        // Absolute threshold: find first tau where cmnd < 0.1
1284        let threshold = 0.1f32;
1285        for tau in 2..n {
1286            if cmnd[tau] < threshold {
1287                // Parabolic interpolation
1288                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            // Feed into YIN buffer
1322            self.yin_buf[self.yin_pos] = *s;
1323            self.yin_pos = (self.yin_pos + 1) % self.yin_size;
1324
1325            // Write delayed sample to output delay line
1326            self.delay_line[self.delay_pos] = *s;
1327
1328            // Periodic pitch detection (every yin_size/4 samples)
1329            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            // Apply pitch shift via simple resampling from delay line (crude but real)
1340            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
1357// ---------------------------------------------------------------------------
1358// Tremolo
1359// ---------------------------------------------------------------------------
1360
1361/// Amplitude modulation via LFO.
1362pub 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
1386// ---------------------------------------------------------------------------
1387// Vibrato
1388// ---------------------------------------------------------------------------
1389
1390/// Frequency modulation (pitch wobble) via LFO.
1391pub 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// ---------------------------------------------------------------------------
1435// Panner
1436// ---------------------------------------------------------------------------
1437
1438#[derive(Clone, Copy, Debug, PartialEq)]
1439pub enum PanLaw {
1440    Linear,
1441    MinusThreeDb,
1442    MinusSixDb,
1443}
1444
1445/// Stereo panner with selectable pan law, Haas effect, and mid-side encode/decode.
1446pub struct Panner {
1447    pub pan: f32, // -1.0 (left) to 1.0 (right)
1448    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; // 0..1
1469        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                // -6dB center: sqrt of linear
1477                ((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                // Mid-side: mid = (L+R)/2, side = (L-R)/2 — operating on mono, just pass through
1496                let mid = (*s + delayed) * 0.5;
1497                let _side = (*s - delayed) * 0.5;
1498                *s = mid;
1499            } else {
1500                // Apply panning to mono: output is mono × gain (L-channel perspective)
1501                *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
1512// ---------------------------------------------------------------------------
1513// EffectChain
1514// ---------------------------------------------------------------------------
1515
1516/// A slot in the effect chain with bypass and wet/dry mix.
1517pub 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
1533/// Ordered chain of audio effects. Supports per-slot bypass and wet/dry.
1534pub 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            // Start from current buffer state
1560            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                // Pure wet — just copy
1567                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                // Update dry_buf to current buffer for next stage
1573                dry_buf.copy_from_slice(buffer);
1574            }
1575            let _ = wet_buf.as_mut_slice(); // suppress unused warning
1576        }
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// ---------------------------------------------------------------------------
1585// Tests
1586// ---------------------------------------------------------------------------
1587
1588#[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        // First sample ~0, last sample ~1
1618        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]; // well below -10 dBFS
1626        c.process_block(&mut buf, 44100.0);
1627        // Should pass nearly unchanged
1628        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]; // 0 dBFS, above -6 dBFS threshold
1636        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]; // above -40 dBFS
1646        gate.process_block(&mut buf, 44100.0);
1647        // After attack, signal should be close to input
1648        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        // DC (constant 1.0) should pass through a low-pass
1657        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        // EQ should have changed at least some samples
1670        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; // impulse
1679        rev.process_block(&mut buf, 44100.0);
1680        // After the impulse there should be some reverb tail
1681        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        // Dry=1, wet=0: output should equal input
1692        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]; // extreme drive
1701        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); } // drive * 1.0 clamped
1711    }
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        // Bypassed gain=0 should leave signal unchanged
1733        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}