Skip to main content

proof_engine/editor/
audio_mixer_editor.rs

1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6// ============================================================
7// CONSTANTS
8// ============================================================
9
10const SAMPLE_RATE: f32 = 44100.0;
11const TWO_PI: f32 = std::f32::consts::TAU;
12const SQRT2: f32 = std::f32::consts::SQRT_2;
13const LN10_OVER_20: f32 = 0.11512925464970228;  // ln(10)/20
14const SPEED_OF_SOUND: f32 = 343.0;              // m/s
15const MAX_VOICES: usize = 256;
16const MAX_BUS_COUNT: usize = 64;
17const MAX_EFFECT_CHAIN_LENGTH: usize = 16;
18const SPECTRUM_FFT_SIZE: usize = 1024;
19const SPECTRUM_BINS: usize = SPECTRUM_FFT_SIZE / 2;
20const RMS_WINDOW_SAMPLES: usize = 4410; // 100ms at 44100 Hz
21const PEAK_HOLD_FRAMES: u32 = 120;
22const LUFS_BLOCK_DURATION_S: f32 = 0.4;
23const LUFS_BLOCK_SAMPLES: usize = (LUFS_BLOCK_DURATION_S * SAMPLE_RATE) as usize;
24const SCHROEDER_COMB_COUNT: usize = 4;
25const SCHROEDER_ALLPASS_COUNT: usize = 2;
26const PHASER_STAGES: usize = 6;
27const HRTF_FILTER_LENGTH: usize = 128;
28const SNAPSHOT_INTERP_MAX: usize = 16;
29
30// ============================================================
31// DECIBEL MATH
32// ============================================================
33
34pub fn db_to_linear(db: f32) -> f32 {
35    (db * LN10_OVER_20).exp()
36}
37
38pub fn linear_to_db(linear: f32) -> f32 {
39    if linear <= 1e-9 { return -180.0; }
40    linear.ln() / LN10_OVER_20
41}
42
43pub fn db_clamp(db: f32, min_db: f32, max_db: f32) -> f32 {
44    db.clamp(min_db, max_db)
45}
46
47// ============================================================
48// ENUMS
49// ============================================================
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum BusType {
53    Master,
54    Music,
55    Sfx,
56    Voice,
57    Ambient,
58    Ui,
59    Reverb,
60    Custom,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub enum EffectType {
65    Equalizer,
66    Compressor,
67    Reverb,
68    Delay,
69    Chorus,
70    Limiter,
71    Gate,
72    Distortion,
73    Phaser,
74    Flanger,
75    BitCrusher,
76    Spatializer,
77    Convolution,
78    Expander,
79    Transient,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum EqFilterType {
84    LowPass,
85    HighPass,
86    BandPass,
87    Notch,
88    LowShelf,
89    HighShelf,
90    PeakingEq,
91    AllPass,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum CompressionMode {
96    Rms,
97    Peak,
98    TruePeak,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum DistortionMode {
103    SoftClip,
104    HardClip,
105    Tanh,
106    Polynomial,
107    Foldback,
108    BitCrush,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum LfoShape {
113    Sine,
114    Triangle,
115    Sawtooth,
116    ReverseSawtooth,
117    Square,
118    RandomSampleHold,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum AttenuationModel {
123    InverseSquare,
124    Linear,
125    Logarithmic,
126    Custom,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum MusicTransitionType {
131    OnBar,
132    OnBeat,
133    Immediate,
134    CrossFade,
135    StitchPoint,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum AudioLodLevel {
140    Full,
141    Reduced,
142    Minimal,
143    Virtual,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum SnapshotTransitionCurve {
148    Linear,
149    EaseIn,
150    EaseOut,
151    EaseInOut,
152    Immediate,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub enum SoundCategory {
157    Music,
158    Sfx,
159    Voice,
160    Ambient,
161    Ui,
162    Footstep,
163    Weapon,
164    Explosion,
165    Environment,
166}
167
168// ============================================================
169// BIQUAD FILTER
170// ============================================================
171// H(z) = (b0 + b1*z^-1 + b2*z^-2) / (a0 + a1*z^-1 + a2*z^-2)
172
173#[derive(Debug, Clone)]
174pub struct BiquadCoefficients {
175    pub b0: f32,
176    pub b1: f32,
177    pub b2: f32,
178    pub a1: f32, // normalized (divided by a0)
179    pub a2: f32, // normalized
180}
181
182impl BiquadCoefficients {
183    pub fn identity() -> Self {
184        Self { b0: 1.0, b1: 0.0, b2: 0.0, a1: 0.0, a2: 0.0 }
185    }
186
187    /// Low-pass filter coefficients
188    pub fn low_pass(freq_hz: f32, q: f32, sample_rate: f32) -> Self {
189        let w0 = TWO_PI * freq_hz / sample_rate;
190        let cos_w0 = w0.cos();
191        let sin_w0 = w0.sin();
192        let alpha = sin_w0 / (2.0 * q);
193        let a0 = 1.0 + alpha;
194        Self {
195            b0: ((1.0 - cos_w0) / 2.0) / a0,
196            b1: (1.0 - cos_w0) / a0,
197            b2: ((1.0 - cos_w0) / 2.0) / a0,
198            a1: (-2.0 * cos_w0) / a0,
199            a2: (1.0 - alpha) / a0,
200        }
201    }
202
203    /// High-pass filter coefficients
204    pub fn high_pass(freq_hz: f32, q: f32, sample_rate: f32) -> Self {
205        let w0 = TWO_PI * freq_hz / sample_rate;
206        let cos_w0 = w0.cos();
207        let sin_w0 = w0.sin();
208        let alpha = sin_w0 / (2.0 * q);
209        let a0 = 1.0 + alpha;
210        Self {
211            b0: ((1.0 + cos_w0) / 2.0) / a0,
212            b1: (-(1.0 + cos_w0)) / a0,
213            b2: ((1.0 + cos_w0) / 2.0) / a0,
214            a1: (-2.0 * cos_w0) / a0,
215            a2: (1.0 - alpha) / a0,
216        }
217    }
218
219    /// Band-pass filter (constant skirt gain, peak gain = Q)
220    pub fn band_pass(freq_hz: f32, q: f32, sample_rate: f32) -> Self {
221        let w0 = TWO_PI * freq_hz / sample_rate;
222        let cos_w0 = w0.cos();
223        let sin_w0 = w0.sin();
224        let alpha = sin_w0 / (2.0 * q);
225        let a0 = 1.0 + alpha;
226        Self {
227            b0: (sin_w0 / 2.0) / a0,
228            b1: 0.0,
229            b2: -(sin_w0 / 2.0) / a0,
230            a1: (-2.0 * cos_w0) / a0,
231            a2: (1.0 - alpha) / a0,
232        }
233    }
234
235    /// Notch filter
236    pub fn notch(freq_hz: f32, q: f32, sample_rate: f32) -> Self {
237        let w0 = TWO_PI * freq_hz / sample_rate;
238        let cos_w0 = w0.cos();
239        let sin_w0 = w0.sin();
240        let alpha = sin_w0 / (2.0 * q);
241        let a0 = 1.0 + alpha;
242        Self {
243            b0: 1.0 / a0,
244            b1: (-2.0 * cos_w0) / a0,
245            b2: 1.0 / a0,
246            a1: (-2.0 * cos_w0) / a0,
247            a2: (1.0 - alpha) / a0,
248        }
249    }
250
251    /// Peaking EQ filter
252    pub fn peaking_eq(freq_hz: f32, q: f32, gain_db: f32, sample_rate: f32) -> Self {
253        let w0 = TWO_PI * freq_hz / sample_rate;
254        let cos_w0 = w0.cos();
255        let sin_w0 = w0.sin();
256        let a_lin = db_to_linear(gain_db / 2.0); // sqrt(10^(dBgain/20))
257        let alpha = sin_w0 / (2.0 * q);
258        let a0 = 1.0 + alpha / a_lin;
259        Self {
260            b0: (1.0 + alpha * a_lin) / a0,
261            b1: (-2.0 * cos_w0) / a0,
262            b2: (1.0 - alpha * a_lin) / a0,
263            a1: (-2.0 * cos_w0) / a0,
264            a2: (1.0 - alpha / a_lin) / a0,
265        }
266    }
267
268    /// Low shelf filter
269    pub fn low_shelf(freq_hz: f32, slope: f32, gain_db: f32, sample_rate: f32) -> Self {
270        let w0 = TWO_PI * freq_hz / sample_rate;
271        let cos_w0 = w0.cos();
272        let sin_w0 = w0.sin();
273        let a_lin = db_to_linear(gain_db / 2.0);
274        let alpha = sin_w0 / 2.0 * ((a_lin + 1.0 / a_lin) * (1.0 / slope - 1.0) + 2.0).sqrt();
275        let two_sqrt_a_alpha = 2.0 * a_lin.sqrt() * alpha;
276        let a0 = (a_lin + 1.0) + (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha;
277        Self {
278            b0: a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha) / a0,
279            b1: 2.0 * a_lin * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w0) / a0,
280            b2: a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha) / a0,
281            a1: -2.0 * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w0) / a0,
282            a2: ((a_lin + 1.0) + (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha) / a0,
283        }
284    }
285
286    /// High shelf filter
287    pub fn high_shelf(freq_hz: f32, slope: f32, gain_db: f32, sample_rate: f32) -> Self {
288        let w0 = TWO_PI * freq_hz / sample_rate;
289        let cos_w0 = w0.cos();
290        let sin_w0 = w0.sin();
291        let a_lin = db_to_linear(gain_db / 2.0);
292        let alpha = sin_w0 / 2.0 * ((a_lin + 1.0 / a_lin) * (1.0 / slope - 1.0) + 2.0).sqrt();
293        let two_sqrt_a_alpha = 2.0 * a_lin.sqrt() * alpha;
294        let a0 = (a_lin + 1.0) - (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha;
295        Self {
296            b0: a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha) / a0,
297            b1: -2.0 * a_lin * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w0) / a0,
298            b2: a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha) / a0,
299            a1: 2.0 * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w0) / a0,
300            a2: ((a_lin + 1.0) - (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha) / a0,
301        }
302    }
303
304    /// All-pass filter
305    pub fn all_pass(freq_hz: f32, q: f32, sample_rate: f32) -> Self {
306        let w0 = TWO_PI * freq_hz / sample_rate;
307        let cos_w0 = w0.cos();
308        let sin_w0 = w0.sin();
309        let alpha = sin_w0 / (2.0 * q);
310        let a0 = 1.0 + alpha;
311        Self {
312            b0: (1.0 - alpha) / a0,
313            b1: (-2.0 * cos_w0) / a0,
314            b2: 1.0,
315            a1: (-2.0 * cos_w0) / a0,
316            a2: (1.0 - alpha) / a0,
317        }
318    }
319}
320
321#[derive(Debug, Clone)]
322pub struct BiquadState {
323    pub x1: f32, // z^-1 input
324    pub x2: f32, // z^-2 input
325    pub y1: f32, // z^-1 output
326    pub y2: f32, // z^-2 output
327}
328
329impl BiquadState {
330    pub fn new() -> Self {
331        Self { x1: 0.0, x2: 0.0, y1: 0.0, y2: 0.0 }
332    }
333
334    /// Process one sample through biquad filter (Direct Form I)
335    pub fn process(&mut self, x: f32, coeff: &BiquadCoefficients) -> f32 {
336        let y = coeff.b0 * x
337               + coeff.b1 * self.x1
338               + coeff.b2 * self.x2
339               - coeff.a1 * self.y1
340               - coeff.a2 * self.y2;
341        self.x2 = self.x1;
342        self.x1 = x;
343        self.y2 = self.y1;
344        self.y1 = y;
345        y
346    }
347
348    /// Process a buffer in-place
349    pub fn process_buffer(&mut self, buffer: &mut [f32], coeff: &BiquadCoefficients) {
350        for sample in buffer.iter_mut() {
351            *sample = self.process(*sample, coeff);
352        }
353    }
354
355    pub fn reset(&mut self) {
356        self.x1 = 0.0; self.x2 = 0.0; self.y1 = 0.0; self.y2 = 0.0;
357    }
358}
359
360// ============================================================
361// PARAMETRIC EQ
362// ============================================================
363
364#[derive(Debug, Clone)]
365pub struct ParametricEqualizer {
366    pub bands: Vec<EqBand>,
367    pub output_gain_db: f32,
368    pub is_enabled: bool,
369}
370
371impl ParametricEqualizer {
372    pub fn new() -> Self {
373        let mut bands = Vec::new();
374        // Default 8-band EQ
375        bands.push(EqBand::new(EqBandType::LowCut, 80.0, 0.0, 0.707));
376        bands.push(EqBand::new(EqBandType::LowShelf, 200.0, 0.0, 0.707));
377        bands.push(EqBand::new(EqBandType::Peak, 500.0, 0.0, 1.0));
378        bands.push(EqBand::new(EqBandType::Peak, 1000.0, 0.0, 1.0));
379        bands.push(EqBand::new(EqBandType::Peak, 2500.0, 0.0, 1.0));
380        bands.push(EqBand::new(EqBandType::Peak, 5000.0, 0.0, 1.0));
381        bands.push(EqBand::new(EqBandType::HighShelf, 10000.0, 0.0, 0.707));
382        bands.push(EqBand::new(EqBandType::HighCut, 20000.0, 0.0, 0.707));
383        Self {
384            bands,
385            output_gain_db: 0.0,
386            is_enabled: true,
387        }
388    }
389
390    pub fn process_stereo(&mut self, mut left: f32, mut right: f32) -> (f32, f32) {
391        if !self.is_enabled { return (left, right); }
392        for band in &mut self.bands {
393            let (l, r) = band.process_sample(left, right);
394            left = l;
395            right = r;
396        }
397        let gain = db_to_linear(self.output_gain_db);
398        (left * gain, right * gain)
399    }
400
401    pub fn process_buffer_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
402        if !self.is_enabled { return; }
403        let len = left.len().min(right.len());
404        for i in 0..len {
405            let (l, r) = self.process_stereo(left[i], right[i]);
406            left[i] = l;
407            right[i] = r;
408        }
409    }
410
411    pub fn add_band(&mut self, band: EqBand) {
412        if self.bands.len() < 32 {
413            self.bands.push(band);
414        }
415    }
416
417    pub fn remove_band(&mut self, index: usize) {
418        if index < self.bands.len() {
419            self.bands.remove(index);
420        }
421    }
422
423    pub fn set_band_gain(&mut self, index: usize, gain_db: f32) {
424        if let Some(band) = self.bands.get_mut(index) {
425            band.gain_db = gain_db;
426            band.update_parameters(band.frequency_hz, band.gain_db, band.q);
427        }
428    }
429
430    pub fn frequency_response_db(&self, freq_hz: f32) -> f32 {
431        if !self.is_enabled { return 0.0; }
432        let mut total_linear = 1.0f32;
433        for band in &self.bands {
434            if band.enabled {
435                total_linear *= band.frequency_response_at(freq_hz);
436            }
437        }
438        linear_to_db(total_linear) + self.output_gain_db
439    }
440}
441
442// ============================================================
443// COMPRESSOR
444// ============================================================
445
446#[derive(Debug, Clone)]
447pub struct CompressorParams {
448    pub threshold_db: f32,
449    pub ratio: f32,           // e.g. 4.0 means 4:1
450    pub knee_db: f32,         // soft knee width in dB
451    pub attack_ms: f32,
452    pub release_ms: f32,
453    pub makeup_gain_db: f32,
454    pub mode: CompressionMode,
455    pub lookahead_ms: f32,
456    pub auto_makeup: bool,
457}
458
459impl Default for CompressorParams {
460    fn default() -> Self {
461        Self {
462            threshold_db: -18.0,
463            ratio: 4.0,
464            knee_db: 6.0,
465            attack_ms: 10.0,
466            release_ms: 100.0,
467            makeup_gain_db: 0.0,
468            mode: CompressionMode::Rms,
469            lookahead_ms: 0.0,
470            auto_makeup: false,
471        }
472    }
473}
474
475#[derive(Debug, Clone)]
476pub struct CompressorState {
477    pub envelope: f32,
478    pub gain_db: f32,
479    pub rms_buffer: VecDeque<f32>,
480    pub rms_sum: f32,
481    pub level_db: f32,
482    pub gr_db: f32, // gain reduction in dB (negative)
483}
484
485impl CompressorState {
486    pub fn new() -> Self {
487        Self {
488            envelope: 0.0,
489            gain_db: 0.0,
490            rms_buffer: VecDeque::with_capacity(RMS_WINDOW_SAMPLES),
491            rms_sum: 0.0,
492            level_db: -120.0,
493            gr_db: 0.0,
494        }
495    }
496}
497
498#[derive(Debug, Clone)]
499pub struct Compressor {
500    pub params: CompressorParams,
501    pub state: CompressorState,
502    pub is_enabled: bool,
503}
504
505impl Compressor {
506    pub fn new(params: CompressorParams) -> Self {
507        Self {
508            params,
509            state: CompressorState::new(),
510            is_enabled: true,
511        }
512    }
513
514    pub fn compute_gain_db(&self, level_db: f32) -> f32 {
515        let t = self.params.threshold_db;
516        let r = self.params.ratio;
517        let k = self.params.knee_db;
518        let overshoot = level_db - t;
519
520        if k > 0.0 && overshoot > -k / 2.0 && overshoot < k / 2.0 {
521            // Soft knee interpolation
522            let knee_factor = (overshoot + k / 2.0) / k;
523            let compressed = overshoot * (1.0 - 1.0 / r) * knee_factor * knee_factor * 0.5;
524            -compressed
525        } else if overshoot > k / 2.0 {
526            // Above knee: apply full ratio
527            let gain_reduction = overshoot * (1.0 - 1.0 / r);
528            -gain_reduction
529        } else {
530            // Below threshold (or below knee)
531            0.0
532        }
533    }
534
535    /// Attack/release envelope follower
536    pub fn update_envelope(&mut self, input_abs: f32, sample_rate: f32) -> f32 {
537        let attack_coeff = (-1.0 / (self.params.attack_ms * 0.001 * sample_rate)).exp();
538        let release_coeff = (-1.0 / (self.params.release_ms * 0.001 * sample_rate)).exp();
539        if input_abs > self.state.envelope {
540            self.state.envelope = attack_coeff * self.state.envelope + (1.0 - attack_coeff) * input_abs;
541        } else {
542            self.state.envelope = release_coeff * self.state.envelope;
543        }
544        self.state.envelope
545    }
546
547    /// Update RMS envelope
548    pub fn update_rms(&mut self, sample: f32) -> f32 {
549        let sq = sample * sample;
550        let n = RMS_WINDOW_SAMPLES;
551        if self.state.rms_buffer.len() >= n {
552            if let Some(old) = self.state.rms_buffer.pop_front() {
553                self.state.rms_sum -= old * old;
554            }
555        }
556        self.state.rms_buffer.push_back(sample);
557        self.state.rms_sum += sq;
558        self.state.rms_sum = self.state.rms_sum.max(0.0);
559        (self.state.rms_sum / n as f32).sqrt()
560    }
561
562    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
563        if !self.is_enabled { return (left, right); }
564
565        // Level detection
566        let mono = (left.abs() + right.abs()) * 0.5;
567        let level = match self.params.mode {
568            CompressionMode::Rms => self.update_rms(mono),
569            CompressionMode::Peak | CompressionMode::TruePeak => {
570                self.update_envelope(mono, SAMPLE_RATE)
571            }
572        };
573
574        let level_db = linear_to_db(level.max(1e-9));
575        self.state.level_db = level_db;
576
577        let target_gr = self.compute_gain_db(level_db);
578        // Smooth gain reduction via attack/release
579        let gr_coeff = if target_gr < self.state.gr_db {
580            (-1.0 / (self.params.attack_ms * 0.001 * SAMPLE_RATE)).exp()
581        } else {
582            (-1.0 / (self.params.release_ms * 0.001 * SAMPLE_RATE)).exp()
583        };
584        self.state.gr_db = gr_coeff * self.state.gr_db + (1.0 - gr_coeff) * target_gr;
585
586        let makeup = if self.params.auto_makeup {
587            -(self.params.threshold_db * (1.0 - 1.0 / self.params.ratio)) * 0.5
588        } else {
589            self.params.makeup_gain_db
590        };
591
592        let total_gain = db_to_linear(self.state.gr_db + makeup);
593        (left * total_gain, right * total_gain)
594    }
595
596    pub fn process_buffer_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
597        let len = left.len().min(right.len());
598        for i in 0..len {
599            let (l, r) = self.process_sample(left[i], right[i]);
600            left[i] = l;
601            right[i] = r;
602        }
603    }
604
605    pub fn gain_reduction_db(&self) -> f32 {
606        self.state.gr_db
607    }
608}
609
610// ============================================================
611// SCHROEDER REVERB (COMB + ALLPASS)
612// ============================================================
613
614#[derive(Debug, Clone)]
615pub struct CombFilter {
616    pub buffer: Vec<f32>,
617    pub buffer_size: usize,
618    pub write_pos: usize,
619    pub feedback: f32,
620    pub damp_coeff: f32,
621    pub damp_state: f32,
622}
623
624impl CombFilter {
625    pub fn new(delay_samples: usize, feedback: f32, damp: f32) -> Self {
626        let size = delay_samples.max(1);
627        Self {
628            buffer: vec![0.0; size],
629            buffer_size: size,
630            write_pos: 0,
631            feedback,
632            damp_coeff: damp,
633            damp_state: 0.0,
634        }
635    }
636
637    pub fn process(&mut self, input: f32) -> f32 {
638        let out = self.buffer[self.write_pos];
639        // One-pole lowpass on feedback (Schroeder damping)
640        self.damp_state = out * (1.0 - self.damp_coeff) + self.damp_state * self.damp_coeff;
641        self.buffer[self.write_pos] = input + self.damp_state * self.feedback;
642        self.write_pos = (self.write_pos + 1) % self.buffer_size;
643        out
644    }
645
646    pub fn set_feedback(&mut self, fb: f32) { self.feedback = fb.clamp(-0.99, 0.99); }
647
648    pub fn resize(&mut self, delay_samples: usize) {
649        let size = delay_samples.max(1);
650        self.buffer = vec![0.0; size];
651        self.buffer_size = size;
652        self.write_pos = 0;
653    }
654}
655
656#[derive(Debug, Clone)]
657pub struct AllPassFilter {
658    pub buffer: Vec<f32>,
659    pub buffer_size: usize,
660    pub write_pos: usize,
661    pub feedback: f32,
662}
663
664impl AllPassFilter {
665    pub fn new(delay_samples: usize, feedback: f32) -> Self {
666        let size = delay_samples.max(1);
667        Self {
668            buffer: vec![0.0; size],
669            buffer_size: size,
670            write_pos: 0,
671            feedback,
672        }
673    }
674
675    pub fn process(&mut self, input: f32) -> f32 {
676        let buf_out = self.buffer[self.write_pos];
677        let v = input + buf_out * self.feedback;
678        self.buffer[self.write_pos] = v;
679        self.write_pos = (self.write_pos + 1) % self.buffer_size;
680        buf_out - input * self.feedback
681    }
682}
683
684#[derive(Debug, Clone)]
685pub struct SchroederReverb {
686    pub room_size: f32,    // 0..1
687    pub damping: f32,      // 0..1
688    pub wet_mix: f32,
689    pub dry_mix: f32,
690    pub width: f32,        // stereo width
691    pub pre_delay_ms: f32,
692    pub combs_l: Vec<CombFilter>,
693    pub combs_r: Vec<CombFilter>,
694    pub allpasses_l: Vec<AllPassFilter>,
695    pub allpasses_r: Vec<AllPassFilter>,
696    pub pre_delay_buf: VecDeque<f32>,
697    pub is_enabled: bool,
698}
699
700impl SchroederReverb {
701    // Schroeder comb delay times in samples at 44100 Hz
702    const COMB_DELAYS: [usize; 4] = [1557, 1617, 1491, 1422];
703    const ALLPASS_DELAYS: [usize; 2] = [225, 341];
704
705    pub fn new() -> Self {
706        let feedback = 0.84;
707        let damp = 0.5;
708        let combs_l: Vec<CombFilter> = Self::COMB_DELAYS.iter()
709            .map(|&d| CombFilter::new(d, feedback, damp))
710            .collect();
711        let combs_r: Vec<CombFilter> = Self::COMB_DELAYS.iter()
712            .map(|&d| CombFilter::new(d + 23, feedback, damp))
713            .collect();
714        let allpasses_l: Vec<AllPassFilter> = Self::ALLPASS_DELAYS.iter()
715            .map(|&d| AllPassFilter::new(d, 0.5))
716            .collect();
717        let allpasses_r: Vec<AllPassFilter> = Self::ALLPASS_DELAYS.iter()
718            .map(|&d| AllPassFilter::new(d + 7, 0.5))
719            .collect();
720
721        Self {
722            room_size: 0.5,
723            damping: 0.5,
724            wet_mix: 0.3,
725            dry_mix: 0.7,
726            width: 1.0,
727            pre_delay_ms: 10.0,
728            combs_l,
729            combs_r,
730            allpasses_l,
731            allpasses_r,
732            pre_delay_buf: VecDeque::with_capacity(4800),
733            is_enabled: true,
734        }
735    }
736
737    pub fn set_room_size(&mut self, size: f32) {
738        self.room_size = size.clamp(0.0, 1.0);
739        let feedback = 0.7 + self.room_size * 0.28;
740        for c in &mut self.combs_l { c.set_feedback(feedback); }
741        for c in &mut self.combs_r { c.set_feedback(feedback); }
742    }
743
744    pub fn set_damping(&mut self, damp: f32) {
745        self.damping = damp.clamp(0.0, 1.0);
746        for c in &mut self.combs_l { c.damp_coeff = self.damping; }
747        for c in &mut self.combs_r { c.damp_coeff = self.damping; }
748    }
749
750    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
751        if !self.is_enabled {
752            return (in_l * self.dry_mix, in_r * self.dry_mix);
753        }
754
755        // Pre-delay
756        let pre_delay_samples = (self.pre_delay_ms * 0.001 * SAMPLE_RATE) as usize;
757        let mono = (in_l + in_r) * 0.5;
758        self.pre_delay_buf.push_back(mono);
759        let delayed = if self.pre_delay_buf.len() > pre_delay_samples {
760            self.pre_delay_buf.pop_front().unwrap_or(0.0)
761        } else { mono };
762
763        // Parallel comb filters
764        let mut rev_l = 0.0f32;
765        let mut rev_r = 0.0f32;
766        for c in &mut self.combs_l { rev_l += c.process(delayed); }
767        for c in &mut self.combs_r { rev_r += c.process(delayed); }
768
769        // Series all-pass filters
770        for ap in &mut self.allpasses_l { rev_l = ap.process(rev_l); }
771        for ap in &mut self.allpasses_r { rev_r = ap.process(rev_r); }
772
773        // Stereo width
774        let w = self.width * 0.5;
775        let wet_l = rev_l * (0.5 + w) + rev_r * (0.5 - w);
776        let wet_r = rev_r * (0.5 + w) + rev_l * (0.5 - w);
777
778        let out_l = in_l * self.dry_mix + wet_l * self.wet_mix;
779        let out_r = in_r * self.dry_mix + wet_r * self.wet_mix;
780        (out_l, out_r)
781    }
782
783    pub fn process_buffer_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
784        let len = left.len().min(right.len());
785        for i in 0..len {
786            let (l, r) = self.process_sample(left[i], right[i]);
787            left[i] = l;
788            right[i] = r;
789        }
790    }
791}
792
793// ============================================================
794// DELAY EFFECT
795// ============================================================
796
797#[derive(Debug, Clone)]
798pub struct DelayEffect {
799    pub delay_l_ms: f32,
800    pub delay_r_ms: f32,
801    pub feedback: f32,
802    pub wet_mix: f32,
803    pub dry_mix: f32,
804    pub ping_pong: bool,
805    pub tempo_sync: bool,
806    pub tempo_bpm: f32,
807    pub tempo_division: f32, // e.g. 0.5 = eighth note
808    pub buf_l: Vec<f32>,
809    pub buf_r: Vec<f32>,
810    pub write_l: usize,
811    pub write_r: usize,
812    pub buf_size: usize,
813    pub is_enabled: bool,
814}
815
816impl DelayEffect {
817    pub fn new(delay_ms: f32) -> Self {
818        let max_samples = (SAMPLE_RATE * 2.0) as usize; // 2 second max delay
819        Self {
820            delay_l_ms: delay_ms,
821            delay_r_ms: delay_ms,
822            feedback: 0.4,
823            wet_mix: 0.3,
824            dry_mix: 1.0,
825            ping_pong: false,
826            tempo_sync: false,
827            tempo_bpm: 120.0,
828            tempo_division: 0.5,
829            buf_l: vec![0.0; max_samples],
830            buf_r: vec![0.0; max_samples],
831            write_l: 0,
832            write_r: 0,
833            buf_size: max_samples,
834            is_enabled: true,
835        }
836    }
837
838    fn delay_samples(delay_ms: f32) -> usize {
839        ((delay_ms * 0.001 * SAMPLE_RATE) as usize).clamp(1, (SAMPLE_RATE * 2.0) as usize - 1)
840    }
841
842    fn tempo_delay_ms(bpm: f32, division: f32) -> f32 {
843        // division=1 = quarter note, 0.5 = eighth note
844        60000.0 / bpm * division * 4.0
845    }
846
847    pub fn effective_delay_ms(&self) -> (f32, f32) {
848        if self.tempo_sync {
849            let ms = Self::tempo_delay_ms(self.tempo_bpm, self.tempo_division);
850            (ms, ms)
851        } else {
852            (self.delay_l_ms, self.delay_r_ms)
853        }
854    }
855
856    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
857        if !self.is_enabled {
858            return (in_l * self.dry_mix, in_r * self.dry_mix);
859        }
860        let (dl, dr) = self.effective_delay_ms();
861        let dly_l = Self::delay_samples(dl);
862        let dly_r = Self::delay_samples(dr);
863        let read_l = (self.write_l + self.buf_size - dly_l) % self.buf_size;
864        let read_r = (self.write_r + self.buf_size - dly_r) % self.buf_size;
865
866        let wet_l = self.buf_l[read_l];
867        let wet_r = self.buf_r[read_r];
868
869        if self.ping_pong {
870            self.buf_l[self.write_l] = in_l + wet_r * self.feedback;
871            self.buf_r[self.write_r] = in_r + wet_l * self.feedback;
872        } else {
873            self.buf_l[self.write_l] = in_l + wet_l * self.feedback;
874            self.buf_r[self.write_r] = in_r + wet_r * self.feedback;
875        }
876
877        self.write_l = (self.write_l + 1) % self.buf_size;
878        self.write_r = (self.write_r + 1) % self.buf_size;
879
880        let out_l = in_l * self.dry_mix + wet_l * self.wet_mix;
881        let out_r = in_r * self.dry_mix + wet_r * self.wet_mix;
882        (out_l, out_r)
883    }
884
885    pub fn process_buffer_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
886        let len = left.len().min(right.len());
887        for i in 0..len {
888            let (l, r) = self.process_sample(left[i], right[i]);
889            left[i] = l;
890            right[i] = r;
891        }
892    }
893}
894
895// ============================================================
896// LFO
897// ============================================================
898
899#[derive(Debug, Clone)]
900pub struct Lfo {
901    pub shape: LfoShape,
902    pub rate_hz: f32,
903    pub depth: f32,
904    pub phase: f32,
905    pub phase_offset: f32,
906    pub random_state: f32,
907    pub random_target: f32,
908    pub samples_since_update: usize,
909    pub random_hold_samples: usize,
910}
911
912impl Lfo {
913    pub fn new(shape: LfoShape, rate_hz: f32, depth: f32) -> Self {
914        Self {
915            shape,
916            rate_hz,
917            depth,
918            phase: 0.0,
919            phase_offset: 0.0,
920            random_state: 0.0,
921            random_target: 0.0,
922            samples_since_update: 0,
923            random_hold_samples: (SAMPLE_RATE / rate_hz.max(0.001)) as usize,
924        }
925    }
926
927    fn lcg_rand(state: f32) -> f32 {
928        // Simple deterministic "random" from float state
929        let bits = (state * 1000000.0) as u32;
930        let next = bits.wrapping_mul(1664525).wrapping_add(1013904223);
931        (next as f32 / u32::MAX as f32) * 2.0 - 1.0
932    }
933
934    pub fn tick(&mut self) -> f32 {
935        let phase = (self.phase + self.phase_offset).fract();
936        let value = match self.shape {
937            LfoShape::Sine => (TWO_PI * phase).sin(),
938            LfoShape::Triangle => {
939                if phase < 0.5 { 4.0 * phase - 1.0 }
940                else { 3.0 - 4.0 * phase }
941            }
942            LfoShape::Sawtooth => phase * 2.0 - 1.0,
943            LfoShape::ReverseSawtooth => 1.0 - phase * 2.0,
944            LfoShape::Square => if phase < 0.5 { 1.0 } else { -1.0 },
945            LfoShape::RandomSampleHold => {
946                self.samples_since_update += 1;
947                let hold = (SAMPLE_RATE / self.rate_hz.max(0.001)) as usize;
948                if self.samples_since_update >= hold {
949                    self.samples_since_update = 0;
950                    self.random_state = Self::lcg_rand(self.random_state);
951                    self.random_target = self.random_state;
952                }
953                self.random_target
954            }
955        };
956        self.phase += self.rate_hz / SAMPLE_RATE;
957        if self.phase >= 1.0 { self.phase -= 1.0; }
958        value * self.depth
959    }
960
961    pub fn tick_n(&mut self, n: usize) -> Vec<f32> {
962        (0..n).map(|_| self.tick()).collect()
963    }
964}
965
966// ============================================================
967// CHORUS
968// ============================================================
969
970#[derive(Debug, Clone)]
971pub struct ChorusEffect {
972    pub rate_hz: f32,
973    pub depth_ms: f32,
974    pub delay_ms: f32,
975    pub wet_mix: f32,
976    pub dry_mix: f32,
977    pub voices: usize,
978    pub buf_l: Vec<f32>,
979    pub buf_r: Vec<f32>,
980    pub write_pos: usize,
981    pub buf_size: usize,
982    pub lfo_l: Lfo,
983    pub lfo_r: Lfo,
984    pub is_enabled: bool,
985}
986
987impl ChorusEffect {
988    pub fn new() -> Self {
989        let buf_size = 4096;
990        let mut lfo_r = Lfo::new(LfoShape::Sine, 0.5, 1.0);
991        lfo_r.phase_offset = 0.25; // 90 degree phase offset for stereo
992        Self {
993            rate_hz: 0.5,
994            depth_ms: 2.0,
995            delay_ms: 20.0,
996            wet_mix: 0.5,
997            dry_mix: 0.5,
998            voices: 2,
999            buf_l: vec![0.0; buf_size],
1000            buf_r: vec![0.0; buf_size],
1001            write_pos: 0,
1002            buf_size,
1003            lfo_l: Lfo::new(LfoShape::Sine, 0.5, 1.0),
1004            lfo_r,
1005            is_enabled: true,
1006        }
1007    }
1008
1009    fn read_interpolated(buf: &[f32], pos: f32, size: usize) -> f32 {
1010        let i0 = (pos as usize) % size;
1011        let i1 = (i0 + 1) % size;
1012        let frac = pos - pos.floor();
1013        buf[i0] * (1.0 - frac) + buf[i1] * frac
1014    }
1015
1016    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1017        if !self.is_enabled {
1018            return (in_l * self.dry_mix, in_r * self.dry_mix);
1019        }
1020
1021        self.buf_l[self.write_pos] = in_l;
1022        self.buf_r[self.write_pos] = in_r;
1023
1024        let lfo_l_val = self.lfo_l.tick();
1025        let lfo_r_val = self.lfo_r.tick();
1026
1027        let base_delay = self.delay_ms * 0.001 * SAMPLE_RATE;
1028        let mod_l = base_delay + lfo_l_val * self.depth_ms * 0.001 * SAMPLE_RATE;
1029        let mod_r = base_delay + lfo_r_val * self.depth_ms * 0.001 * SAMPLE_RATE;
1030
1031        let read_l = (self.write_pos as f32 + self.buf_size as f32 - mod_l.clamp(1.0, self.buf_size as f32 - 1.0)) % self.buf_size as f32;
1032        let read_r = (self.write_pos as f32 + self.buf_size as f32 - mod_r.clamp(1.0, self.buf_size as f32 - 1.0)) % self.buf_size as f32;
1033
1034        let wet_l = Self::read_interpolated(&self.buf_l, read_l, self.buf_size);
1035        let wet_r = Self::read_interpolated(&self.buf_r, read_r, self.buf_size);
1036
1037        self.write_pos = (self.write_pos + 1) % self.buf_size;
1038
1039        let out_l = in_l * self.dry_mix + wet_l * self.wet_mix;
1040        let out_r = in_r * self.dry_mix + wet_r * self.wet_mix;
1041        (out_l, out_r)
1042    }
1043
1044    pub fn process_buffer_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
1045        let len = left.len().min(right.len());
1046        for i in 0..len {
1047            let (l, r) = self.process_sample(left[i], right[i]);
1048            left[i] = l;
1049            right[i] = r;
1050        }
1051    }
1052}
1053
1054// ============================================================
1055// LIMITER
1056// ============================================================
1057
1058#[derive(Debug, Clone)]
1059pub struct Limiter {
1060    pub ceiling_db: f32,
1061    pub release_ms: f32,
1062    pub lookahead_ms: f32,
1063    pub envelope_l: f32,
1064    pub envelope_r: f32,
1065    pub lookahead_buf_l: VecDeque<f32>,
1066    pub lookahead_buf_r: VecDeque<f32>,
1067    pub is_enabled: bool,
1068}
1069
1070impl Limiter {
1071    pub fn new(ceiling_db: f32) -> Self {
1072        let lookahead_samples = 256;
1073        Self {
1074            ceiling_db,
1075            release_ms: 50.0,
1076            lookahead_ms: 5.0,
1077            envelope_l: 0.0,
1078            envelope_r: 0.0,
1079            lookahead_buf_l: VecDeque::with_capacity(lookahead_samples),
1080            lookahead_buf_r: VecDeque::with_capacity(lookahead_samples),
1081            is_enabled: true,
1082        }
1083    }
1084
1085    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1086        if !self.is_enabled { return (in_l, in_r); }
1087        let ceiling_lin = db_to_linear(self.ceiling_db);
1088        let release_coeff = (-1.0 / (self.release_ms * 0.001 * SAMPLE_RATE)).exp();
1089
1090        let lookahead_samples = (self.lookahead_ms * 0.001 * SAMPLE_RATE) as usize;
1091        self.lookahead_buf_l.push_back(in_l);
1092        self.lookahead_buf_r.push_back(in_r);
1093
1094        let delayed_l = if self.lookahead_buf_l.len() > lookahead_samples {
1095            self.lookahead_buf_l.pop_front().unwrap_or(0.0)
1096        } else { 0.0 };
1097        let delayed_r = if self.lookahead_buf_r.len() > lookahead_samples {
1098            self.lookahead_buf_r.pop_front().unwrap_or(0.0)
1099        } else { 0.0 };
1100
1101        // Peak envelope
1102        let peak = in_l.abs().max(in_r.abs());
1103        if peak > self.envelope_l {
1104            self.envelope_l = peak;
1105        } else {
1106            self.envelope_l = release_coeff * self.envelope_l;
1107        }
1108
1109        let gain = if self.envelope_l > ceiling_lin {
1110            ceiling_lin / self.envelope_l
1111        } else { 1.0 };
1112
1113        (delayed_l * gain, delayed_r * gain)
1114    }
1115}
1116
1117// ============================================================
1118// NOISE GATE
1119// ============================================================
1120// ============================================================
1121// DISTORTION
1122// ============================================================
1123
1124#[derive(Debug, Clone)]
1125pub struct DistortionEffect {
1126    pub mode: DistortionMode,
1127    pub drive: f32,        // pre-gain (linear)
1128    pub output_gain: f32,  // post-gain (linear)
1129    pub mix: f32,
1130    pub tone: f32,         // 0..1 lowpass cutoff
1131    pub tone_filter_l: BiquadState,
1132    pub tone_filter_r: BiquadState,
1133    pub tone_coeff: BiquadCoefficients,
1134    pub is_enabled: bool,
1135    // Polynomial coefficients for polynomial mode
1136    pub poly_coeffs: [f32; 4],
1137    pub bit_depth: f32,
1138    pub sample_rate_factor: f32,
1139    pub srr_counter: usize,
1140    pub srr_held_l: f32,
1141    pub srr_held_r: f32,
1142}
1143
1144impl DistortionEffect {
1145    pub fn new(mode: DistortionMode, drive: f32) -> Self {
1146        let tone_freq = 5000.0;
1147        let tone_coeff = BiquadCoefficients::low_pass(tone_freq, 0.707, SAMPLE_RATE);
1148        Self {
1149            mode,
1150            drive,
1151            output_gain: 1.0 / drive.max(1.0),
1152            mix: 1.0,
1153            tone: 1.0,
1154            tone_filter_l: BiquadState::new(),
1155            tone_filter_r: BiquadState::new(),
1156            tone_coeff,
1157            is_enabled: true,
1158            poly_coeffs: [1.0, -0.333, 0.2, -0.1],
1159            bit_depth: 8.0,
1160            sample_rate_factor: 1.0,
1161            srr_counter: 0,
1162            srr_held_l: 0.0,
1163            srr_held_r: 0.0,
1164        }
1165    }
1166
1167    fn soft_clip(x: f32) -> f32 {
1168        x.clamp(-1.5, 1.5) * (1.0 - (x.clamp(-1.5, 1.5).powi(2)) / 3.0)
1169    }
1170
1171    fn hard_clip(x: f32, threshold: f32) -> f32 {
1172        x.clamp(-threshold, threshold)
1173    }
1174
1175    fn tanh_clip(x: f32) -> f32 {
1176        x.tanh()
1177    }
1178
1179    fn polynomial_clip(x: f32, coeffs: &[f32; 4]) -> f32 {
1180        let x2 = x * x;
1181        let x3 = x2 * x;
1182        coeffs[0] * x + coeffs[1] * x3 + coeffs[2] * x2 * x3 + coeffs[3] * x2 * x2 * x
1183    }
1184
1185    fn foldback(x: f32, threshold: f32) -> f32 {
1186        let mut v = x;
1187        while v.abs() > threshold {
1188            if v > threshold { v = 2.0 * threshold - v; }
1189            if v < -threshold { v = -2.0 * threshold - v; }
1190        }
1191        v
1192    }
1193
1194    fn bitcrush(x: f32, bits: f32) -> f32 {
1195        let levels = 2.0f32.powf(bits.clamp(1.0, 32.0));
1196        (x * levels).round() / levels
1197    }
1198
1199    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1200        if !self.is_enabled { return (in_l, in_r); }
1201
1202        // Sample rate reduction
1203        let srr_period = (1.0 / self.sample_rate_factor).max(1.0) as usize;
1204        let (dl, dr) = if srr_period > 1 {
1205            self.srr_counter += 1;
1206            if self.srr_counter >= srr_period {
1207                self.srr_counter = 0;
1208                self.srr_held_l = in_l * self.drive;
1209                self.srr_held_r = in_r * self.drive;
1210            }
1211            (self.srr_held_l, self.srr_held_r)
1212        } else {
1213            (in_l * self.drive, in_r * self.drive)
1214        };
1215
1216        let (out_l, out_r) = match self.mode {
1217            DistortionMode::SoftClip => (Self::soft_clip(dl), Self::soft_clip(dr)),
1218            DistortionMode::HardClip => (Self::hard_clip(dl, 1.0), Self::hard_clip(dr, 1.0)),
1219            DistortionMode::Tanh => (Self::tanh_clip(dl), Self::tanh_clip(dr)),
1220            DistortionMode::Polynomial => (
1221                Self::polynomial_clip(dl.clamp(-2.0, 2.0), &self.poly_coeffs),
1222                Self::polynomial_clip(dr.clamp(-2.0, 2.0), &self.poly_coeffs),
1223            ),
1224            DistortionMode::Foldback => (Self::foldback(dl, 1.0), Self::foldback(dr, 1.0)),
1225            DistortionMode::BitCrush => (Self::bitcrush(dl.tanh(), self.bit_depth), Self::bitcrush(dr.tanh(), self.bit_depth)),
1226        };
1227
1228        // Apply tone filter
1229        let tone_l = self.tone_filter_l.process(out_l, &self.tone_coeff);
1230        let tone_r = self.tone_filter_r.process(out_r, &self.tone_coeff);
1231
1232        // Blend dry/wet, apply output gain
1233        let final_l = (tone_l * self.mix + in_l * (1.0 - self.mix)) * self.output_gain;
1234        let final_r = (tone_r * self.mix + in_r * (1.0 - self.mix)) * self.output_gain;
1235        (final_l, final_r)
1236    }
1237}
1238
1239// ============================================================
1240// PHASER
1241// ============================================================
1242
1243#[derive(Debug, Clone)]
1244pub struct PhaserEffect {
1245    pub rate_hz: f32,
1246    pub depth: f32,
1247    pub center_hz: f32,
1248    pub feedback: f32,
1249    pub wet_mix: f32,
1250    pub dry_mix: f32,
1251    pub stages: usize,
1252    pub lfo: Lfo,
1253    pub filters_l: Vec<BiquadState>,
1254    pub filters_r: Vec<BiquadState>,
1255    pub last_out_l: f32,
1256    pub last_out_r: f32,
1257    pub is_enabled: bool,
1258}
1259
1260impl PhaserEffect {
1261    pub fn new() -> Self {
1262        let mut lfo_r = Lfo::new(LfoShape::Sine, 0.5, 1.0);
1263        lfo_r.phase_offset = 0.5;
1264        Self {
1265            rate_hz: 0.5,
1266            depth: 0.8,
1267            center_hz: 1000.0,
1268            feedback: 0.5,
1269            wet_mix: 0.5,
1270            dry_mix: 0.5,
1271            stages: PHASER_STAGES,
1272            lfo: Lfo::new(LfoShape::Sine, 0.5, 1.0),
1273            filters_l: vec![BiquadState::new(); PHASER_STAGES],
1274            filters_r: vec![BiquadState::new(); PHASER_STAGES],
1275            last_out_l: 0.0,
1276            last_out_r: 0.0,
1277            is_enabled: true,
1278        }
1279    }
1280
1281    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1282        if !self.is_enabled { return (in_l * self.dry_mix, in_r * self.dry_mix); }
1283
1284        let mod_val = self.lfo.tick();
1285        let freq = (self.center_hz * (1.0 + mod_val * self.depth)).clamp(20.0, 20000.0);
1286        let coeff = BiquadCoefficients::all_pass(freq, 0.707, SAMPLE_RATE);
1287
1288        let feed_l = in_l + self.last_out_l * self.feedback;
1289        let feed_r = in_r + self.last_out_r * self.feedback;
1290
1291        let mut sig_l = feed_l;
1292        let mut sig_r = feed_r;
1293        for i in 0..self.stages.min(self.filters_l.len()) {
1294            sig_l = self.filters_l[i].process(sig_l, &coeff);
1295            sig_r = self.filters_r[i].process(sig_r, &coeff);
1296        }
1297
1298        self.last_out_l = sig_l;
1299        self.last_out_r = sig_r;
1300
1301        let out_l = in_l * self.dry_mix + sig_l * self.wet_mix;
1302        let out_r = in_r * self.dry_mix + sig_r * self.wet_mix;
1303        (out_l, out_r)
1304    }
1305}
1306
1307// ============================================================
1308// FLANGER
1309// ============================================================
1310
1311#[derive(Debug, Clone)]
1312pub struct FlangerEffect {
1313    pub rate_hz: f32,
1314    pub depth_ms: f32,
1315    pub delay_center_ms: f32,
1316    pub feedback: f32,
1317    pub wet_mix: f32,
1318    pub dry_mix: f32,
1319    pub buf_l: Vec<f32>,
1320    pub buf_r: Vec<f32>,
1321    pub write_pos: usize,
1322    pub buf_size: usize,
1323    pub lfo: Lfo,
1324    pub is_enabled: bool,
1325}
1326
1327impl FlangerEffect {
1328    pub fn new() -> Self {
1329        let buf_size = 4096;
1330        Self {
1331            rate_hz: 0.3,
1332            depth_ms: 3.0,
1333            delay_center_ms: 5.0,
1334            feedback: 0.5,
1335            wet_mix: 0.5,
1336            dry_mix: 0.5,
1337            buf_l: vec![0.0; buf_size],
1338            buf_r: vec![0.0; buf_size],
1339            write_pos: 0,
1340            buf_size,
1341            lfo: Lfo::new(LfoShape::Sine, 0.3, 1.0),
1342            is_enabled: true,
1343        }
1344    }
1345
1346    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1347        if !self.is_enabled { return (in_l * self.dry_mix, in_r * self.dry_mix); }
1348
1349        self.buf_l[self.write_pos] = in_l;
1350        self.buf_r[self.write_pos] = in_r;
1351
1352        let mod_val = self.lfo.tick();
1353        let delay_samples = ((self.delay_center_ms + mod_val * self.depth_ms) * 0.001 * SAMPLE_RATE)
1354            .clamp(1.0, self.buf_size as f32 - 1.0);
1355
1356        let read_pos = (self.write_pos as f32 + self.buf_size as f32 - delay_samples) % self.buf_size as f32;
1357        let i0 = read_pos as usize % self.buf_size;
1358        let i1 = (i0 + 1) % self.buf_size;
1359        let frac = read_pos - read_pos.floor();
1360
1361        let wet_l = self.buf_l[i0] * (1.0 - frac) + self.buf_l[i1] * frac;
1362        let wet_r = self.buf_r[i0] * (1.0 - frac) + self.buf_r[i1] * frac;
1363
1364        self.write_pos = (self.write_pos + 1) % self.buf_size;
1365
1366        let out_l = in_l * self.dry_mix + wet_l * self.wet_mix + wet_l * self.feedback;
1367        let out_r = in_r * self.dry_mix + wet_r * self.wet_mix + wet_r * self.feedback;
1368        (out_l, out_r)
1369    }
1370}
1371
1372// ============================================================
1373// BIT CRUSHER (Standalone)
1374// ============================================================
1375
1376#[derive(Debug, Clone)]
1377pub struct BitCrusherEffect {
1378    pub bit_depth: f32,
1379    pub sample_rate_divider: u32,
1380    pub mix: f32,
1381    pub counter: u32,
1382    pub held_l: f32,
1383    pub held_r: f32,
1384    pub is_enabled: bool,
1385}
1386
1387impl BitCrusherEffect {
1388    pub fn new(bit_depth: f32, rate_divider: u32) -> Self {
1389        Self {
1390            bit_depth,
1391            sample_rate_divider: rate_divider.max(1),
1392            mix: 1.0,
1393            counter: 0,
1394            held_l: 0.0,
1395            held_r: 0.0,
1396            is_enabled: true,
1397        }
1398    }
1399
1400    fn quantize(x: f32, bits: f32) -> f32 {
1401        let levels = 2.0f32.powf(bits.clamp(1.0, 32.0));
1402        ((x * levels).floor() + 0.5) / levels
1403    }
1404
1405    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1406        if !self.is_enabled { return (in_l, in_r); }
1407        self.counter += 1;
1408        if self.counter >= self.sample_rate_divider {
1409            self.counter = 0;
1410            self.held_l = Self::quantize(in_l, self.bit_depth);
1411            self.held_r = Self::quantize(in_r, self.bit_depth);
1412        }
1413        let out_l = self.held_l * self.mix + in_l * (1.0 - self.mix);
1414        let out_r = self.held_r * self.mix + in_r * (1.0 - self.mix);
1415        (out_l, out_r)
1416    }
1417}
1418
1419// ============================================================
1420// SPATIALIZER / HRTF
1421// ============================================================
1422
1423#[derive(Debug, Clone)]
1424pub struct HrtfFilter {
1425    pub impulse_l: Vec<f32>,
1426    pub impulse_r: Vec<f32>,
1427    pub history_l: VecDeque<f32>,
1428    pub history_r: VecDeque<f32>,
1429}
1430
1431impl HrtfFilter {
1432    pub fn new(elevation_rad: f32, azimuth_rad: f32) -> Self {
1433        // Approximate HRTF with minimum-phase FIR filters
1434        let len = HRTF_FILTER_LENGTH;
1435        let mut impulse_l = vec![0.0f32; len];
1436        let mut impulse_r = vec![0.0f32; len];
1437
1438        // ITD (inter-aural time delay) based on azimuth
1439        // Woodworth formula: ITD = (r/c) * (sin(azimuth) + azimuth)
1440        let r = 0.0875; // head radius in meters
1441        let itd_s = (r / SPEED_OF_SOUND) * (azimuth_rad.sin() + azimuth_rad);
1442        let itd_samples = (itd_s * SAMPLE_RATE).round() as i32;
1443
1444        // ILD (inter-aural level difference)
1445        let ild_db = 10.0 * azimuth_rad.sin().abs();
1446        let ild_linear = db_to_linear(-ild_db * 0.5);
1447
1448        // Build FIR with delay and simple spectral shaping
1449        let hann = |n: usize| -> f32 {
1450            0.5 * (1.0 - (TWO_PI * n as f32 / (len as f32 - 1.0)).cos())
1451        };
1452
1453        // Sinc-based low-pass at ~3kHz for contralateral ear
1454        let fc = 3000.0 / SAMPLE_RATE;
1455        for n in 0..len {
1456            let idx = n as f32 - len as f32 / 2.0;
1457            let sinc = if idx.abs() < 1e-6 {
1458                2.0 * fc
1459            } else {
1460                (2.0 * std::f32::consts::PI * fc * idx).sin() / (std::f32::consts::PI * idx)
1461            };
1462            let win = hann(n);
1463
1464            if azimuth_rad >= 0.0 {
1465                // Source on right: left ear is contralateral, right is ipsilateral
1466                let delay_n = (n as i32 - itd_samples.min(len as i32 - 1)).clamp(0, len as i32 - 1) as usize;
1467                impulse_l[delay_n] += sinc * win * ild_linear;
1468                impulse_r[n] += sinc * win;
1469            } else {
1470                impulse_l[n] += sinc * win;
1471                let delay_n = (n as i32 + itd_samples.min(len as i32 - 1)).clamp(0, len as i32 - 1) as usize;
1472                impulse_r[delay_n] += sinc * win * ild_linear;
1473            }
1474        }
1475
1476        Self {
1477            impulse_l,
1478            impulse_r,
1479            history_l: VecDeque::with_capacity(len),
1480            history_r: VecDeque::with_capacity(len),
1481        }
1482    }
1483
1484    pub fn process_mono(&mut self, mono_in: f32) -> (f32, f32) {
1485        let len = self.impulse_l.len();
1486
1487        if self.history_l.len() >= len { self.history_l.pop_back(); }
1488        if self.history_r.len() >= len { self.history_r.pop_back(); }
1489        self.history_l.push_front(mono_in);
1490        self.history_r.push_front(mono_in);
1491
1492        // Convolution
1493        let out_l: f32 = self.history_l.iter()
1494            .zip(self.impulse_l.iter())
1495            .map(|(h, imp)| h * imp)
1496            .sum();
1497        let out_r: f32 = self.history_r.iter()
1498            .zip(self.impulse_r.iter())
1499            .map(|(h, imp)| h * imp)
1500            .sum();
1501
1502        (out_l, out_r)
1503    }
1504}
1505
1506// ============================================================
1507// SPATIALIZER
1508// ============================================================
1509
1510#[derive(Debug, Clone)]
1511pub struct Spatializer3D {
1512    pub position: Vec3,
1513    pub listener_pos: Vec3,
1514    pub listener_forward: Vec3,
1515    pub listener_up: Vec3,
1516    pub attenuation_model: AttenuationModel,
1517    pub min_distance: f32,
1518    pub max_distance: f32,
1519    pub rolloff_factor: f32,
1520    pub doppler_factor: f32,
1521    pub occlusion_db: f32,
1522    pub obstruction_db: f32,
1523    pub use_hrtf: bool,
1524    pub hrtf_filter: Option<HrtfFilter>,
1525    pub reverb_send_db: f32,
1526    pub current_distance: f32,
1527    pub current_azimuth: f32,
1528    pub current_elevation: f32,
1529    pub pan_l: f32,
1530    pub pan_r: f32,
1531    pub distance_gain: f32,
1532    pub doppler_pitch: f32,
1533    pub source_velocity: Vec3,
1534    pub listener_velocity: Vec3,
1535    pub is_enabled: bool,
1536}
1537
1538impl Spatializer3D {
1539    pub fn new() -> Self {
1540        Self {
1541            position: Vec3::ZERO,
1542            listener_pos: Vec3::ZERO,
1543            listener_forward: Vec3::NEG_Z,
1544            listener_up: Vec3::Y,
1545            attenuation_model: AttenuationModel::InverseSquare,
1546            min_distance: 1.0,
1547            max_distance: 100.0,
1548            rolloff_factor: 1.0,
1549            doppler_factor: 1.0,
1550            occlusion_db: 0.0,
1551            obstruction_db: 0.0,
1552            use_hrtf: false,
1553            hrtf_filter: None,
1554            reverb_send_db: -6.0,
1555            current_distance: 0.0,
1556            current_azimuth: 0.0,
1557            current_elevation: 0.0,
1558            pan_l: SQRT2 / 2.0,
1559            pan_r: SQRT2 / 2.0,
1560            distance_gain: 1.0,
1561            doppler_pitch: 1.0,
1562            source_velocity: Vec3::ZERO,
1563            listener_velocity: Vec3::ZERO,
1564            is_enabled: true,
1565        }
1566    }
1567
1568    pub fn update(&mut self) {
1569        let to_source = self.position - self.listener_pos;
1570        self.current_distance = to_source.length();
1571
1572        if self.current_distance < 1e-6 {
1573            self.pan_l = SQRT2 / 2.0;
1574            self.pan_r = SQRT2 / 2.0;
1575            self.distance_gain = 1.0;
1576            self.doppler_pitch = 1.0;
1577            return;
1578        }
1579
1580        let dir = to_source / self.current_distance;
1581
1582        // Azimuth and elevation relative to listener
1583        let right = self.listener_forward.cross(self.listener_up).normalize_or_zero();
1584        let up = self.listener_up;
1585        let fwd = self.listener_forward;
1586
1587        self.current_azimuth = dir.dot(right).atan2(dir.dot(fwd));
1588        self.current_elevation = dir.dot(up).asin().clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);
1589
1590        // Constant-power panning from azimuth
1591        let pan_angle = self.current_azimuth.clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);
1592        let pan_norm = (pan_angle / std::f32::consts::FRAC_PI_2 + 1.0) * 0.5; // 0..1, 0=left 1=right
1593        self.pan_l = ((1.0 - pan_norm) * std::f32::consts::FRAC_PI_2).cos();
1594        self.pan_r = (pan_norm * std::f32::consts::FRAC_PI_2).cos();
1595
1596        // Distance attenuation
1597        let dist = self.current_distance.clamp(self.min_distance, self.max_distance);
1598        self.distance_gain = match self.attenuation_model {
1599            AttenuationModel::InverseSquare => {
1600                let d = dist / self.min_distance;
1601                1.0 / (d * d).max(1.0)
1602            }
1603            AttenuationModel::Linear => {
1604                1.0 - self.rolloff_factor * (dist - self.min_distance) / (self.max_distance - self.min_distance)
1605            }
1606            AttenuationModel::Logarithmic => {
1607                1.0 - self.rolloff_factor * (dist / self.min_distance).ln() / (self.max_distance / self.min_distance).ln().max(1.0)
1608            }
1609            AttenuationModel::Custom => 1.0,
1610        };
1611        self.distance_gain = self.distance_gain.clamp(0.0, 1.0);
1612
1613        // Apply occlusion/obstruction
1614        let occ_gain = db_to_linear(self.occlusion_db + self.obstruction_db);
1615        self.distance_gain *= occ_gain;
1616
1617        // Doppler shift: f' = f * (v_sound + v_listener) / (v_sound + v_source)
1618        // Project velocities onto the source-listener direction
1619        let v_listener_proj = self.listener_velocity.dot(dir);
1620        let v_source_proj = self.source_velocity.dot(dir);
1621        let numerator = SPEED_OF_SOUND + v_listener_proj;
1622        let denominator = SPEED_OF_SOUND + v_source_proj;
1623        self.doppler_pitch = if denominator.abs() > 1.0 {
1624            (numerator / denominator).clamp(0.5, 2.0) * self.doppler_factor + (1.0 - self.doppler_factor)
1625        } else { 1.0 };
1626
1627        // Update HRTF if enabled
1628        if self.use_hrtf {
1629            self.hrtf_filter = Some(HrtfFilter::new(self.current_elevation, self.current_azimuth));
1630        }
1631    }
1632
1633    pub fn process_mono(&mut self, mono: f32) -> (f32, f32) {
1634        if !self.is_enabled { return (mono, mono); }
1635        let gained = mono * self.distance_gain;
1636        if self.use_hrtf {
1637            if let Some(hrtf) = &mut self.hrtf_filter {
1638                return hrtf.process_mono(gained);
1639            }
1640        }
1641        (gained * self.pan_l, gained * self.pan_r)
1642    }
1643
1644    pub fn reverb_send_gain(&self) -> f32 {
1645        db_to_linear(self.reverb_send_db) * self.distance_gain.powf(0.5)
1646    }
1647}
1648
1649// ============================================================
1650// CONVOLUTION (parameters only — no full IR processing)
1651// ============================================================
1652
1653#[derive(Debug, Clone)]
1654pub struct ConvolutionParams {
1655    pub ir_asset_id: u64,
1656    pub wet_mix: f32,
1657    pub dry_mix: f32,
1658    pub pre_delay_ms: f32,
1659    pub ir_length_ms: f32,
1660    pub is_enabled: bool,
1661    // Simulation: apply a simple exponential decay approximation
1662    pub decay_coeff: f32,
1663    pub sim_state_l: f32,
1664    pub sim_state_r: f32,
1665}
1666
1667impl ConvolutionParams {
1668    pub fn new(ir_asset_id: u64) -> Self {
1669        Self {
1670            ir_asset_id,
1671            wet_mix: 0.3,
1672            dry_mix: 0.7,
1673            pre_delay_ms: 5.0,
1674            ir_length_ms: 1000.0,
1675            is_enabled: true,
1676            decay_coeff: (-1.0 / (1000.0 * 0.001 * SAMPLE_RATE)).exp(),
1677            sim_state_l: 0.0,
1678            sim_state_r: 0.0,
1679        }
1680    }
1681
1682    pub fn process_sample_approx(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
1683        if !self.is_enabled { return (in_l * self.dry_mix, in_r * self.dry_mix); }
1684        // Exponential decay approximation (not true convolution)
1685        self.sim_state_l = self.sim_state_l * self.decay_coeff + in_l;
1686        self.sim_state_r = self.sim_state_r * self.decay_coeff + in_r;
1687        let out_l = in_l * self.dry_mix + self.sim_state_l * self.wet_mix;
1688        let out_r = in_r * self.dry_mix + self.sim_state_r * self.wet_mix;
1689        (out_l, out_r)
1690    }
1691}
1692
1693// ============================================================
1694// EFFECT CHAIN
1695// ============================================================
1696
1697#[derive(Debug, Clone)]
1698pub enum AudioEffect {
1699    Eq(ParametricEqualizer),
1700    Compressor(Compressor),
1701    Reverb(SchroederReverb),
1702    Delay(DelayEffect),
1703    Chorus(ChorusEffect),
1704    Limiter(Limiter),
1705    Gate(NoiseGate),
1706    Distortion(DistortionEffect),
1707    Phaser(PhaserEffect),
1708    Flanger(FlangerEffect),
1709    BitCrusher(BitCrusherEffect),
1710    Convolution(ConvolutionParams),
1711}
1712
1713impl AudioEffect {
1714    pub fn effect_type(&self) -> EffectType {
1715        match self {
1716            AudioEffect::Eq(_) => EffectType::Equalizer,
1717            AudioEffect::Compressor(_) => EffectType::Compressor,
1718            AudioEffect::Reverb(_) => EffectType::Reverb,
1719            AudioEffect::Delay(_) => EffectType::Delay,
1720            AudioEffect::Chorus(_) => EffectType::Chorus,
1721            AudioEffect::Limiter(_) => EffectType::Limiter,
1722            AudioEffect::Gate(_) => EffectType::Gate,
1723            AudioEffect::Distortion(_) => EffectType::Distortion,
1724            AudioEffect::Phaser(_) => EffectType::Phaser,
1725            AudioEffect::Flanger(_) => EffectType::Flanger,
1726            AudioEffect::BitCrusher(_) => EffectType::BitCrusher,
1727            AudioEffect::Convolution(_) => EffectType::Convolution,
1728        }
1729    }
1730
1731    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
1732        match self {
1733            AudioEffect::Eq(eq) => eq.process_stereo(left, right),
1734            AudioEffect::Compressor(c) => c.process_sample(left, right),
1735            AudioEffect::Reverb(r) => r.process_sample(left, right),
1736            AudioEffect::Delay(d) => d.process_sample(left, right),
1737            AudioEffect::Chorus(c) => c.process_sample(left, right),
1738            AudioEffect::Limiter(l) => l.process_sample(left, right),
1739            AudioEffect::Gate(g) => g.process_sample(left, right),
1740            AudioEffect::Distortion(d) => d.process_sample(left, right),
1741            AudioEffect::Phaser(p) => p.process_sample(left, right),
1742            AudioEffect::Flanger(f) => f.process_sample(left, right),
1743            AudioEffect::BitCrusher(b) => b.process_sample(left, right),
1744            AudioEffect::Convolution(c) => c.process_sample_approx(left, right),
1745        }
1746    }
1747
1748    pub fn is_enabled(&self) -> bool {
1749        match self {
1750            AudioEffect::Eq(e) => e.is_enabled,
1751            AudioEffect::Compressor(c) => c.is_enabled,
1752            AudioEffect::Reverb(r) => r.is_enabled,
1753            AudioEffect::Delay(d) => d.is_enabled,
1754            AudioEffect::Chorus(c) => c.is_enabled,
1755            AudioEffect::Limiter(l) => l.is_enabled,
1756            AudioEffect::Gate(g) => !g.bypass,
1757            AudioEffect::Distortion(d) => d.is_enabled,
1758            AudioEffect::Phaser(p) => p.is_enabled,
1759            AudioEffect::Flanger(f) => f.is_enabled,
1760            AudioEffect::BitCrusher(b) => b.is_enabled,
1761            AudioEffect::Convolution(c) => c.is_enabled,
1762        }
1763    }
1764
1765    pub fn cpu_cost_estimate(&self) -> f32 {
1766        match self {
1767            AudioEffect::Eq(e) => 0.02 * e.bands.len() as f32,
1768            AudioEffect::Compressor(_) => 0.05,
1769            AudioEffect::Reverb(_) => 0.15,
1770            AudioEffect::Delay(_) => 0.03,
1771            AudioEffect::Chorus(_) => 0.04,
1772            AudioEffect::Limiter(_) => 0.02,
1773            AudioEffect::Gate(_) => 0.02,
1774            AudioEffect::Distortion(_) => 0.03,
1775            AudioEffect::Phaser(_) => 0.06,
1776            AudioEffect::Flanger(_) => 0.04,
1777            AudioEffect::BitCrusher(_) => 0.01,
1778            AudioEffect::Convolution(_) => 0.3,
1779        }
1780    }
1781}
1782
1783#[derive(Debug, Clone)]
1784pub struct EffectChain {
1785    pub effects: Vec<AudioEffect>,
1786    pub bypass: bool,
1787}
1788
1789impl EffectChain {
1790    pub fn new() -> Self {
1791        Self { effects: Vec::new(), bypass: false }
1792    }
1793
1794    pub fn add(&mut self, effect: AudioEffect) {
1795        if self.effects.len() < MAX_EFFECT_CHAIN_LENGTH {
1796            self.effects.push(effect);
1797        }
1798    }
1799
1800    pub fn remove(&mut self, index: usize) {
1801        if index < self.effects.len() {
1802            self.effects.remove(index);
1803        }
1804    }
1805
1806    pub fn move_effect(&mut self, from: usize, to: usize) {
1807        if from < self.effects.len() && to < self.effects.len() && from != to {
1808            let effect = self.effects.remove(from);
1809            self.effects.insert(to, effect);
1810        }
1811    }
1812
1813    pub fn process_sample(&mut self, mut left: f32, mut right: f32) -> (f32, f32) {
1814        if self.bypass { return (left, right); }
1815        for effect in &mut self.effects {
1816            if effect.is_enabled() {
1817                let (l, r) = effect.process_sample(left, right);
1818                left = l;
1819                right = r;
1820            }
1821        }
1822        (left, right)
1823    }
1824
1825    pub fn total_cpu_cost(&self) -> f32 {
1826        self.effects.iter()
1827            .filter(|e| e.is_enabled())
1828            .map(|e| e.cpu_cost_estimate())
1829            .sum()
1830    }
1831
1832    pub fn clear(&mut self) { self.effects.clear(); }
1833    pub fn effect_count(&self) -> usize { self.effects.len() }
1834}
1835
1836// ============================================================
1837// AUDIO BUS
1838// ============================================================
1839
1840#[derive(Debug, Clone)]
1841pub struct AudioBus {
1842    pub id: u64,
1843    pub name: String,
1844    pub bus_type: BusType,
1845    pub gain_db: f32,
1846    pub pan: f32,           // -1.0 (L) .. 0.0 .. 1.0 (R)
1847    pub mute: bool,
1848    pub solo: bool,
1849    pub effect_chain: EffectChain,
1850    pub send_levels: HashMap<u64, f32>, // bus_id -> send level (linear)
1851    pub sidechain_source: Option<u64>,  // bus_id for sidechain
1852    pub sidechain_gain: f32,
1853    pub parent_bus_id: Option<u64>,
1854    pub children_bus_ids: Vec<u64>,
1855    pub input_level_l: f32,
1856    pub input_level_r: f32,
1857    pub output_level_l: f32,
1858    pub output_level_r: f32,
1859    pub peak_hold_l: f32,
1860    pub peak_hold_r: f32,
1861    pub peak_hold_timer: u32,
1862    pub channel_count: usize,
1863    pub accumulated_l: f32,
1864    pub accumulated_r: f32,
1865    pub is_enabled: bool,
1866}
1867
1868impl AudioBus {
1869    pub fn new(id: u64, name: String, bus_type: BusType) -> Self {
1870        Self {
1871            id,
1872            name,
1873            bus_type,
1874            gain_db: 0.0,
1875            pan: 0.0,
1876            mute: false,
1877            solo: false,
1878            effect_chain: EffectChain::new(),
1879            send_levels: HashMap::new(),
1880            sidechain_source: None,
1881            sidechain_gain: 1.0,
1882            parent_bus_id: None,
1883            children_bus_ids: Vec::new(),
1884            input_level_l: 0.0,
1885            input_level_r: 0.0,
1886            output_level_l: 0.0,
1887            output_level_r: 0.0,
1888            peak_hold_l: 0.0,
1889            peak_hold_r: 0.0,
1890            peak_hold_timer: 0,
1891            channel_count: 2,
1892            accumulated_l: 0.0,
1893            accumulated_r: 0.0,
1894            is_enabled: true,
1895        }
1896    }
1897
1898    /// Process a single frame (one stereo pair)
1899    pub fn process_sample(&mut self, mut left: f32, mut right: f32) -> (f32, f32) {
1900        if !self.is_enabled || self.mute { return (0.0, 0.0); }
1901
1902        self.input_level_l = left.abs().max(self.input_level_l * 0.999);
1903        self.input_level_r = right.abs().max(self.input_level_r * 0.999);
1904
1905        // Apply gain
1906        let gain = db_to_linear(self.gain_db);
1907        left *= gain;
1908        right *= gain;
1909
1910        // Pan (constant-power)
1911        let pan_r = (self.pan * 0.5 + 0.5).clamp(0.0, 1.0);
1912        let pan_l = 1.0 - pan_r;
1913        let pan_gain_l = (pan_l * std::f32::consts::FRAC_PI_2).cos() * SQRT2;
1914        let pan_gain_r = (pan_r * std::f32::consts::FRAC_PI_2).cos() * SQRT2;
1915        left *= pan_gain_l;
1916        right *= pan_gain_r;
1917
1918        // Insert chain
1919        let (l, r) = self.effect_chain.process_sample(left, right);
1920        left = l;
1921        right = r;
1922
1923        self.output_level_l = left.abs().max(self.output_level_l * 0.999);
1924        self.output_level_r = right.abs().max(self.output_level_r * 0.999);
1925
1926        // Peak hold
1927        if left.abs() > self.peak_hold_l {
1928            self.peak_hold_l = left.abs();
1929            self.peak_hold_timer = PEAK_HOLD_FRAMES;
1930        } else if self.peak_hold_timer > 0 {
1931            self.peak_hold_timer -= 1;
1932        } else {
1933            self.peak_hold_l *= 0.9995;
1934        }
1935        if right.abs() > self.peak_hold_r {
1936            self.peak_hold_r = right.abs();
1937        } else {
1938            self.peak_hold_r *= 0.9995;
1939        }
1940
1941        (left, right)
1942    }
1943
1944    pub fn add_child(&mut self, child_id: u64) {
1945        if !self.children_bus_ids.contains(&child_id) {
1946            self.children_bus_ids.push(child_id);
1947        }
1948    }
1949
1950    pub fn remove_child(&mut self, child_id: u64) {
1951        self.children_bus_ids.retain(|&id| id != child_id);
1952    }
1953
1954    pub fn set_send(&mut self, target_bus_id: u64, level: f32) {
1955        self.send_levels.insert(target_bus_id, level.clamp(0.0, 4.0));
1956    }
1957
1958    pub fn peak_l_db(&self) -> f32 { linear_to_db(self.peak_hold_l.max(1e-9)) }
1959    pub fn peak_r_db(&self) -> f32 { linear_to_db(self.peak_hold_r.max(1e-9)) }
1960    pub fn output_l_db(&self) -> f32 { linear_to_db(self.output_level_l.max(1e-9)) }
1961    pub fn output_r_db(&self) -> f32 { linear_to_db(self.output_level_r.max(1e-9)) }
1962}
1963
1964// ============================================================
1965// SIGNAL FLOW GRAPH
1966// ============================================================
1967
1968#[derive(Debug, Clone)]
1969pub struct SignalFlowEdge {
1970    pub from_bus_id: u64,
1971    pub to_bus_id: u64,
1972    pub send_level: f32,
1973    pub is_sidechain: bool,
1974}
1975
1976#[derive(Debug)]
1977pub struct SignalFlowGraph {
1978    pub buses: HashMap<u64, AudioBus>,
1979    pub edges: Vec<SignalFlowEdge>,
1980    pub next_bus_id: u64,
1981    pub master_bus_id: u64,
1982    pub topology_order: Vec<u64>, // buses in processing order
1983}
1984
1985impl SignalFlowGraph {
1986    pub fn new() -> Self {
1987        let mut graph = Self {
1988            buses: HashMap::new(),
1989            edges: Vec::new(),
1990            next_bus_id: 1,
1991            master_bus_id: 1,
1992            topology_order: Vec::new(),
1993        };
1994        // Create default bus hierarchy
1995        let master = graph.create_bus("Master".into(), BusType::Master);
1996        let music  = graph.create_bus("Music".into(),  BusType::Music);
1997        let sfx    = graph.create_bus("SFX".into(),    BusType::Sfx);
1998        let voice  = graph.create_bus("Voice".into(),  BusType::Voice);
1999        let ambient = graph.create_bus("Ambient".into(), BusType::Ambient);
2000        let ui     = graph.create_bus("UI".into(),     BusType::Ui);
2001
2002        graph.connect(music, master, 1.0);
2003        graph.connect(sfx, master, 1.0);
2004        graph.connect(voice, master, 1.0);
2005        graph.connect(ambient, master, 1.0);
2006        graph.connect(ui, master, 1.0);
2007
2008        graph.master_bus_id = master;
2009        graph.rebuild_topology();
2010        graph
2011    }
2012
2013    pub fn create_bus(&mut self, name: String, bus_type: BusType) -> u64 {
2014        let id = self.next_bus_id;
2015        self.next_bus_id += 1;
2016        self.buses.insert(id, AudioBus::new(id, name, bus_type));
2017        id
2018    }
2019
2020    pub fn connect(&mut self, from: u64, to: u64, level: f32) {
2021        // Avoid duplicate edges
2022        self.edges.retain(|e| !(e.from_bus_id == from && e.to_bus_id == to));
2023        self.edges.push(SignalFlowEdge {
2024            from_bus_id: from,
2025            to_bus_id: to,
2026            send_level: level,
2027            is_sidechain: false,
2028        });
2029        if let Some(bus) = self.buses.get_mut(&from) {
2030            bus.set_send(to, level);
2031            if let Some(parent) = bus.parent_bus_id {
2032                // Already has parent
2033            } else {
2034                bus.parent_bus_id = Some(to);
2035            }
2036        }
2037        if let Some(bus) = self.buses.get_mut(&to) {
2038            bus.add_child(from);
2039        }
2040        self.rebuild_topology();
2041    }
2042
2043    pub fn disconnect(&mut self, from: u64, to: u64) {
2044        self.edges.retain(|e| !(e.from_bus_id == from && e.to_bus_id == to));
2045        if let Some(bus) = self.buses.get_mut(&from) {
2046            bus.send_levels.remove(&to);
2047        }
2048        if let Some(bus) = self.buses.get_mut(&to) {
2049            bus.remove_child(from);
2050        }
2051        self.rebuild_topology();
2052    }
2053
2054    /// Kahn's algorithm topological sort for processing order (children before parents)
2055    pub fn rebuild_topology(&mut self) {
2056        let mut in_degree: HashMap<u64, usize> = HashMap::new();
2057        for &id in self.buses.keys() { in_degree.insert(id, 0); }
2058
2059        for edge in &self.edges {
2060            *in_degree.entry(edge.to_bus_id).or_insert(0) += 1;
2061        }
2062
2063        let mut queue: VecDeque<u64> = in_degree.iter()
2064            .filter(|(_, &d)| d == 0)
2065            .map(|(&id, _)| id)
2066            .collect();
2067        let mut order = Vec::new();
2068
2069        while let Some(id) = queue.pop_front() {
2070            order.push(id);
2071            for edge in &self.edges {
2072                if edge.from_bus_id == id {
2073                    if let Some(d) = in_degree.get_mut(&edge.to_bus_id) {
2074                        *d = d.saturating_sub(1);
2075                        if *d == 0 { queue.push_back(edge.to_bus_id); }
2076                    }
2077                }
2078            }
2079        }
2080        self.topology_order = order;
2081    }
2082
2083    pub fn get_bus(&self, id: u64) -> Option<&AudioBus> { self.buses.get(&id) }
2084    pub fn get_bus_mut(&mut self, id: u64) -> Option<&mut AudioBus> { self.buses.get_mut(&id) }
2085}
2086
2087// ============================================================
2088// ADSR ENVELOPE
2089// ============================================================
2090
2091#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2092pub enum EnvelopeStage {
2093    Idle,
2094    Attack,
2095    Decay,
2096    Sustain,
2097    Release,
2098}
2099
2100#[derive(Debug, Clone)]
2101pub struct AdsrEnvelope {
2102    pub attack_s: f32,
2103    pub decay_s: f32,
2104    pub sustain_level: f32,
2105    pub release_s: f32,
2106    pub attack_curve: f32,   // 1.0 = linear, <1 = convex, >1 = concave
2107    pub decay_curve: f32,
2108    pub release_curve: f32,
2109    pub stage: EnvelopeStage,
2110    pub value: f32,
2111    pub release_start_value: f32,
2112    pub time_in_stage: f32,
2113}
2114
2115impl AdsrEnvelope {
2116    pub fn new(attack_s: f32, decay_s: f32, sustain: f32, release_s: f32) -> Self {
2117        Self {
2118            attack_s,
2119            decay_s,
2120            sustain_level: sustain.clamp(0.0, 1.0),
2121            release_s,
2122            attack_curve: 1.0,
2123            decay_curve: 2.0,
2124            release_curve: 2.0,
2125            stage: EnvelopeStage::Idle,
2126            value: 0.0,
2127            release_start_value: 0.0,
2128            time_in_stage: 0.0,
2129        }
2130    }
2131
2132    pub fn trigger_attack(&mut self) {
2133        self.stage = EnvelopeStage::Attack;
2134        self.time_in_stage = 0.0;
2135    }
2136
2137    pub fn trigger_release(&mut self) {
2138        if self.stage != EnvelopeStage::Idle {
2139            self.release_start_value = self.value;
2140            self.stage = EnvelopeStage::Release;
2141            self.time_in_stage = 0.0;
2142        }
2143    }
2144
2145    fn apply_curve(t: f32, curve: f32) -> f32 {
2146        t.powf(curve)
2147    }
2148
2149    pub fn tick(&mut self, dt_s: f32) -> f32 {
2150        self.time_in_stage += dt_s;
2151        match self.stage {
2152            EnvelopeStage::Idle => { self.value = 0.0; }
2153            EnvelopeStage::Attack => {
2154                let t = if self.attack_s > 0.0 {
2155                    (self.time_in_stage / self.attack_s).clamp(0.0, 1.0)
2156                } else { 1.0 };
2157                self.value = Self::apply_curve(t, self.attack_curve);
2158                if t >= 1.0 {
2159                    self.stage = EnvelopeStage::Decay;
2160                    self.time_in_stage = 0.0;
2161                }
2162            }
2163            EnvelopeStage::Decay => {
2164                let t = if self.decay_s > 0.0 {
2165                    (self.time_in_stage / self.decay_s).clamp(0.0, 1.0)
2166                } else { 1.0 };
2167                let ct = Self::apply_curve(t, self.decay_curve);
2168                self.value = 1.0 - ct * (1.0 - self.sustain_level);
2169                if t >= 1.0 {
2170                    self.stage = EnvelopeStage::Sustain;
2171                    self.value = self.sustain_level;
2172                }
2173            }
2174            EnvelopeStage::Sustain => { self.value = self.sustain_level; }
2175            EnvelopeStage::Release => {
2176                let t = if self.release_s > 0.0 {
2177                    (self.time_in_stage / self.release_s).clamp(0.0, 1.0)
2178                } else { 1.0 };
2179                let ct = Self::apply_curve(t, self.release_curve);
2180                self.value = self.release_start_value * (1.0 - ct);
2181                if t >= 1.0 {
2182                    self.stage = EnvelopeStage::Idle;
2183                    self.value = 0.0;
2184                }
2185            }
2186        }
2187        self.value
2188    }
2189
2190    pub fn is_active(&self) -> bool { self.stage != EnvelopeStage::Idle }
2191    pub fn is_released(&self) -> bool { self.stage == EnvelopeStage::Release }
2192}
2193
2194// ============================================================
2195// SOUND DESIGN PARAMETERS
2196// ============================================================
2197
2198#[derive(Debug, Clone)]
2199pub struct SoundDesignParams {
2200    pub volume_adsr: AdsrEnvelope,
2201    pub pitch_lfo: Lfo,
2202    pub amplitude_lfo: Lfo,
2203    pub pitch_random_range_semitones: f32,
2204    pub volume_random_range_db: f32,
2205    pub start_offset_random_s: f32,
2206    pub pitch_semitones: f32,        // base pitch offset
2207    pub fine_tune_cents: f32,
2208    pub looping: bool,
2209    pub loop_start_s: f32,
2210    pub loop_end_s: f32,
2211    pub fade_in_s: f32,
2212    pub fade_out_s: f32,
2213}
2214
2215impl SoundDesignParams {
2216    pub fn new() -> Self {
2217        Self {
2218            volume_adsr: AdsrEnvelope::new(0.005, 0.1, 1.0, 0.3),
2219            pitch_lfo: Lfo::new(LfoShape::Sine, 5.0, 0.0),
2220            amplitude_lfo: Lfo::new(LfoShape::Sine, 4.0, 0.0),
2221            pitch_random_range_semitones: 0.0,
2222            volume_random_range_db: 0.0,
2223            start_offset_random_s: 0.0,
2224            pitch_semitones: 0.0,
2225            fine_tune_cents: 0.0,
2226            looping: false,
2227            loop_start_s: 0.0,
2228            loop_end_s: 0.0,
2229            fade_in_s: 0.0,
2230            fade_out_s: 0.0,
2231        }
2232    }
2233
2234    pub fn pitch_ratio(&self, random_seed: f32) -> f32 {
2235        let base = self.pitch_semitones + self.fine_tune_cents * 0.01;
2236        let rand_offset = random_seed * self.pitch_random_range_semitones;
2237        semitones_to_ratio(base + rand_offset)
2238    }
2239
2240    pub fn volume_linear(&self, random_seed: f32) -> f32 {
2241        let rand_db = (random_seed * 2.0 - 1.0) * self.volume_random_range_db;
2242        db_to_linear(rand_db)
2243    }
2244}
2245
2246pub fn semitones_to_ratio(semitones: f32) -> f32 {
2247    2.0f32.powf(semitones / 12.0)
2248}
2249
2250// ============================================================
2251// SPATIAL REVERB ZONES
2252// ============================================================
2253
2254#[derive(Debug, Clone)]
2255pub struct ReverbZone {
2256    pub id: u64,
2257    pub name: String,
2258    pub center: Vec3,
2259    pub radius: f32,
2260    pub blend_radius: f32, // transition zone outside radius
2261    pub reverb_params: SchroederReverb,
2262    pub priority: u32,
2263    pub is_enabled: bool,
2264}
2265
2266impl ReverbZone {
2267    pub fn new(id: u64, name: String, center: Vec3, radius: f32, blend_radius: f32) -> Self {
2268        Self {
2269            id,
2270            name,
2271            center,
2272            radius,
2273            blend_radius,
2274            reverb_params: SchroederReverb::new(),
2275            priority: 0,
2276            is_enabled: true,
2277        }
2278    }
2279
2280    pub fn blend_factor(&self, listener_pos: Vec3) -> f32 {
2281        let dist = (listener_pos - self.center).length();
2282        if dist <= self.radius { return 1.0; }
2283        let outer = self.radius + self.blend_radius;
2284        if dist >= outer { return 0.0; }
2285        1.0 - (dist - self.radius) / self.blend_radius.max(0.001)
2286    }
2287
2288    pub fn is_active(&self, listener_pos: Vec3) -> bool {
2289        self.is_enabled && self.blend_factor(listener_pos) > 0.0
2290    }
2291}
2292
2293#[derive(Debug)]
2294pub struct ReverbZoneManager {
2295    pub zones: HashMap<u64, ReverbZone>,
2296    pub active_blend: HashMap<u64, f32>,
2297    pub next_id: u64,
2298}
2299
2300impl ReverbZoneManager {
2301    pub fn new() -> Self {
2302        Self {
2303            zones: HashMap::new(),
2304            active_blend: HashMap::new(),
2305            next_id: 1,
2306        }
2307    }
2308
2309    pub fn add_zone(&mut self, name: String, center: Vec3, radius: f32, blend: f32) -> u64 {
2310        let id = self.next_id;
2311        self.next_id += 1;
2312        self.zones.insert(id, ReverbZone::new(id, name, center, radius, blend));
2313        id
2314    }
2315
2316    pub fn update(&mut self, listener_pos: Vec3) {
2317        self.active_blend.clear();
2318        for (id, zone) in &self.zones {
2319            let blend = zone.blend_factor(listener_pos);
2320            if blend > 0.0 {
2321                self.active_blend.insert(*id, blend);
2322            }
2323        }
2324    }
2325
2326    pub fn highest_priority_zone(&self) -> Option<u64> {
2327        self.active_blend.keys()
2328            .max_by_key(|&&id| {
2329                self.zones.get(&id).map(|z| z.priority).unwrap_or(0)
2330            })
2331            .copied()
2332    }
2333
2334    pub fn blended_room_size(&self) -> f32 {
2335        let total_blend: f32 = self.active_blend.values().sum();
2336        if total_blend < 1e-6 { return 0.3; }
2337        self.active_blend.iter()
2338            .filter_map(|(id, &blend)| self.zones.get(id).map(|z| blend * z.reverb_params.room_size))
2339            .sum::<f32>() / total_blend
2340    }
2341}
2342
2343// ============================================================
2344// ADAPTIVE MUSIC SYSTEM
2345// ============================================================
2346
2347#[derive(Debug, Clone)]
2348pub struct MusicStem {
2349    pub id: u64,
2350    pub name: String,
2351    pub volume_db: f32,
2352    pub is_active: bool,
2353    pub fade_in_s: f32,
2354    pub fade_out_s: f32,
2355    pub fade_value: f32,
2356    pub category: String,
2357    pub beat_length: u32, // in beats
2358}
2359
2360impl MusicStem {
2361    pub fn new(id: u64, name: String) -> Self {
2362        Self {
2363            id,
2364            name,
2365            volume_db: 0.0,
2366            is_active: false,
2367            fade_in_s: 0.5,
2368            fade_out_s: 1.0,
2369            fade_value: 0.0,
2370            category: "melody".into(),
2371            beat_length: 16,
2372        }
2373    }
2374
2375    pub fn update_fade(&mut self, dt_s: f32) {
2376        let target = if self.is_active { 1.0f32 } else { 0.0f32 };
2377        let speed = if self.is_active { 1.0 / self.fade_in_s.max(0.001) }
2378                    else { 1.0 / self.fade_out_s.max(0.001) };
2379        if (self.fade_value - target).abs() < speed * dt_s {
2380            self.fade_value = target;
2381        } else if self.fade_value < target {
2382            self.fade_value += speed * dt_s;
2383        } else {
2384            self.fade_value -= speed * dt_s;
2385        }
2386        self.fade_value = self.fade_value.clamp(0.0, 1.0);
2387    }
2388
2389    pub fn effective_volume(&self) -> f32 {
2390        db_to_linear(self.volume_db) * self.fade_value
2391    }
2392}
2393
2394#[derive(Debug, Clone)]
2395pub struct MusicTransitionRule {
2396    pub from_state: String,
2397    pub to_state: String,
2398    pub transition_type: MusicTransitionType,
2399    pub crossfade_s: f32,
2400    pub condition: String, // tag/condition name
2401    pub priority: u32,
2402}
2403
2404#[derive(Debug, Clone)]
2405pub struct BeatTracker {
2406    pub bpm: f32,
2407    pub time_signature_num: u32,   // beats per bar
2408    pub time_signature_den: u32,   // note value
2409    pub current_beat: f32,
2410    pub current_bar: u32,
2411    pub beat_elapsed_s: f32,
2412    pub bar_elapsed_s: f32,
2413    pub is_running: bool,
2414}
2415
2416impl BeatTracker {
2417    pub fn new(bpm: f32) -> Self {
2418        Self {
2419            bpm,
2420            time_signature_num: 4,
2421            time_signature_den: 4,
2422            current_beat: 0.0,
2423            current_bar: 0,
2424            beat_elapsed_s: 0.0,
2425            bar_elapsed_s: 0.0,
2426            is_running: false,
2427        }
2428    }
2429
2430    pub fn tick(&mut self, dt_s: f32) {
2431        if !self.is_running { return; }
2432        self.beat_elapsed_s += dt_s;
2433        self.bar_elapsed_s += dt_s;
2434        let seconds_per_beat = 60.0 / self.bpm.max(1.0);
2435        self.current_beat = self.beat_elapsed_s / seconds_per_beat;
2436        let seconds_per_bar = seconds_per_beat * self.time_signature_num as f32;
2437        self.current_bar = (self.bar_elapsed_s / seconds_per_bar) as u32;
2438    }
2439
2440    pub fn beat_within_bar(&self) -> f32 {
2441        self.current_beat % self.time_signature_num as f32
2442    }
2443
2444    pub fn is_on_beat(&self, tolerance_s: f32) -> bool {
2445        let seconds_per_beat = 60.0 / self.bpm.max(1.0);
2446        let beat_phase = (self.beat_elapsed_s % seconds_per_beat) / seconds_per_beat;
2447        beat_phase < (tolerance_s / seconds_per_beat) || beat_phase > (1.0 - tolerance_s / seconds_per_beat)
2448    }
2449
2450    pub fn is_on_bar(&self, tolerance_s: f32) -> bool {
2451        let seconds_per_beat = 60.0 / self.bpm.max(1.0);
2452        let seconds_per_bar = seconds_per_beat * self.time_signature_num as f32;
2453        let bar_phase = (self.bar_elapsed_s % seconds_per_bar) / seconds_per_bar;
2454        bar_phase < (tolerance_s / seconds_per_bar) || bar_phase > (1.0 - tolerance_s / seconds_per_bar)
2455    }
2456
2457    pub fn time_to_next_beat(&self) -> f32 {
2458        let seconds_per_beat = 60.0 / self.bpm.max(1.0);
2459        let elapsed_in_beat = self.beat_elapsed_s % seconds_per_beat;
2460        seconds_per_beat - elapsed_in_beat
2461    }
2462
2463    pub fn time_to_next_bar(&self) -> f32 {
2464        let seconds_per_beat = 60.0 / self.bpm.max(1.0);
2465        let seconds_per_bar = seconds_per_beat * self.time_signature_num as f32;
2466        let elapsed_in_bar = self.bar_elapsed_s % seconds_per_bar;
2467        seconds_per_bar - elapsed_in_bar
2468    }
2469
2470    pub fn current_beat_integer(&self) -> u32 { self.current_beat as u32 }
2471}
2472
2473#[derive(Debug)]
2474pub struct AdaptiveMusicSystem {
2475    pub stems: HashMap<u64, MusicStem>,
2476    pub transition_rules: Vec<MusicTransitionRule>,
2477    pub beat_tracker: BeatTracker,
2478    pub current_state: String,
2479    pub pending_state: Option<String>,
2480    pub pending_transition: Option<MusicTransitionType>,
2481    pub next_stem_id: u64,
2482    pub vertical_layers: HashMap<String, Vec<u64>>, // layer_name -> [stem_ids]
2483    pub intensity: f32, // 0..1 controls layer mixing
2484}
2485
2486impl AdaptiveMusicSystem {
2487    pub fn new(bpm: f32) -> Self {
2488        Self {
2489            stems: HashMap::new(),
2490            transition_rules: Vec::new(),
2491            beat_tracker: BeatTracker::new(bpm),
2492            current_state: "silence".into(),
2493            pending_state: None,
2494            pending_transition: None,
2495            next_stem_id: 1,
2496            vertical_layers: HashMap::new(),
2497            intensity: 0.0,
2498        }
2499    }
2500
2501    pub fn add_stem(&mut self, name: String, category: String) -> u64 {
2502        let id = self.next_stem_id;
2503        self.next_stem_id += 1;
2504        let mut stem = MusicStem::new(id, name);
2505        stem.category = category.clone();
2506        self.stems.insert(id, stem);
2507        self.vertical_layers.entry(category).or_default().push(id);
2508        id
2509    }
2510
2511    pub fn set_state(&mut self, new_state: String, transition: MusicTransitionType) {
2512        self.pending_state = Some(new_state);
2513        self.pending_transition = Some(transition);
2514    }
2515
2516    pub fn update(&mut self, dt_s: f32) {
2517        self.beat_tracker.tick(dt_s);
2518
2519        // Check pending state transitions
2520        if let Some(ref state) = self.pending_state.clone() {
2521            let can_transition = match self.pending_transition.unwrap_or(MusicTransitionType::Immediate) {
2522                MusicTransitionType::Immediate => true,
2523                MusicTransitionType::OnBeat => self.beat_tracker.is_on_beat(0.05),
2524                MusicTransitionType::OnBar => self.beat_tracker.is_on_bar(0.05),
2525                MusicTransitionType::CrossFade => true,
2526                MusicTransitionType::StitchPoint => self.beat_tracker.is_on_beat(0.02),
2527            };
2528            if can_transition {
2529                self.current_state = state.clone();
2530                self.pending_state = None;
2531                self.pending_transition = None;
2532                self.apply_state_to_stems(&self.current_state.clone());
2533            }
2534        }
2535
2536        // Update intensity-based vertical re-orchestration
2537        self.update_intensity_layers();
2538
2539        // Update stem fades
2540        let stem_ids: Vec<u64> = self.stems.keys().copied().collect();
2541        for id in stem_ids {
2542            if let Some(stem) = self.stems.get_mut(&id) {
2543                stem.update_fade(dt_s);
2544            }
2545        }
2546    }
2547
2548    fn apply_state_to_stems(&mut self, state: &str) {
2549        for stem in self.stems.values_mut() {
2550            // Simple state -> stem activation rule
2551            stem.is_active = stem.category == state || state == "all";
2552        }
2553    }
2554
2555    fn update_intensity_layers(&mut self) {
2556        // Add stems layer by layer based on intensity
2557        let layer_names: Vec<String> = self.vertical_layers.keys().cloned().collect();
2558        let layer_count = layer_names.len().max(1);
2559        for (i, layer) in layer_names.iter().enumerate() {
2560            let threshold = i as f32 / layer_count as f32;
2561            let active = self.intensity >= threshold;
2562            if let Some(stem_ids) = self.vertical_layers.get(layer) {
2563                for &sid in stem_ids {
2564                    if let Some(stem) = self.stems.get_mut(&sid) {
2565                        // Don't override state-based activation
2566                        if self.current_state != "silence" {
2567                            stem.is_active = active;
2568                        }
2569                    }
2570                }
2571            }
2572        }
2573    }
2574
2575    pub fn set_intensity(&mut self, intensity: f32) {
2576        self.intensity = intensity.clamp(0.0, 1.0);
2577    }
2578
2579    pub fn active_stem_count(&self) -> usize {
2580        self.stems.values().filter(|s| s.is_active && s.fade_value > 0.001).count()
2581    }
2582
2583    pub fn mixed_volume_for_stem(&self, stem_id: u64) -> f32 {
2584        self.stems.get(&stem_id).map(|s| s.effective_volume()).unwrap_or(0.0)
2585    }
2586}
2587
2588// ============================================================
2589// AUDIO LOD / VOICE MANAGEMENT
2590// ============================================================
2591
2592#[derive(Debug, Clone)]
2593pub struct AudioVoice {
2594    pub id: u64,
2595    pub sound_id: u64,
2596    pub category: SoundCategory,
2597    pub position: Vec3,
2598    pub volume_db: f32,
2599    pub priority_score: f32,
2600    pub distance: f32,
2601    pub lod_level: AudioLodLevel,
2602    pub is_active: bool,
2603    pub is_virtual: bool,
2604    pub start_time_s: f64,
2605    pub age_s: f32,
2606    pub spatializer: Spatializer3D,
2607    pub design_params: SoundDesignParams,
2608    pub bus_id: u64,
2609    pub importance: f32,
2610}
2611
2612impl AudioVoice {
2613    pub fn new(id: u64, sound_id: u64, category: SoundCategory, position: Vec3) -> Self {
2614        Self {
2615            id,
2616            sound_id,
2617            category,
2618            position,
2619            volume_db: 0.0,
2620            priority_score: 0.0,
2621            distance: 0.0,
2622            lod_level: AudioLodLevel::Full,
2623            is_active: true,
2624            is_virtual: false,
2625            start_time_s: 0.0,
2626            age_s: 0.0,
2627            spatializer: Spatializer3D::new(),
2628            design_params: SoundDesignParams::new(),
2629            bus_id: 0,
2630            importance: 1.0,
2631        }
2632    }
2633
2634    pub fn compute_priority(&mut self, listener_pos: Vec3) -> f32 {
2635        self.distance = (self.position - listener_pos).length();
2636        let dist_factor = 1.0 / (1.0 + self.distance * 0.01).powf(2.0);
2637        let vol_factor = db_to_linear(self.volume_db.clamp(-60.0, 0.0));
2638        let cat_factor = match self.category {
2639            SoundCategory::Voice => 1.5,
2640            SoundCategory::Music => 1.2,
2641            SoundCategory::Weapon | SoundCategory::Explosion => 1.1,
2642            _ => 1.0,
2643        };
2644        self.priority_score = dist_factor * vol_factor * cat_factor * self.importance;
2645        self.priority_score
2646    }
2647
2648    pub fn determine_lod(&mut self, listener_pos: Vec3, voice_budget_fraction: f32) -> AudioLodLevel {
2649        let dist = (self.position - listener_pos).length();
2650        self.lod_level = if self.is_virtual {
2651            AudioLodLevel::Virtual
2652        } else if dist > 100.0 || voice_budget_fraction < 0.1 {
2653            AudioLodLevel::Minimal
2654        } else if dist > 50.0 || voice_budget_fraction < 0.5 {
2655            AudioLodLevel::Reduced
2656        } else {
2657            AudioLodLevel::Full
2658        };
2659        self.lod_level
2660    }
2661}
2662
2663#[derive(Debug)]
2664pub struct VoiceManager {
2665    pub voices: HashMap<u64, AudioVoice>,
2666    pub next_voice_id: u64,
2667    pub max_voices: usize,
2668    pub category_limits: HashMap<SoundCategory, usize>,
2669    pub category_counts: HashMap<SoundCategory, usize>,
2670    pub virtual_voices: HashSet<u64>,
2671    pub total_active: usize,
2672    pub total_virtual: usize,
2673}
2674
2675impl VoiceManager {
2676    pub fn new(max_voices: usize) -> Self {
2677        let mut limits = HashMap::new();
2678        limits.insert(SoundCategory::Music, 8);
2679        limits.insert(SoundCategory::Sfx, 64);
2680        limits.insert(SoundCategory::Voice, 16);
2681        limits.insert(SoundCategory::Ambient, 16);
2682        limits.insert(SoundCategory::Ui, 8);
2683        limits.insert(SoundCategory::Footstep, 8);
2684        limits.insert(SoundCategory::Weapon, 16);
2685        limits.insert(SoundCategory::Explosion, 8);
2686        limits.insert(SoundCategory::Environment, 16);
2687        Self {
2688            voices: HashMap::new(),
2689            next_voice_id: 1,
2690            max_voices,
2691            category_limits: limits,
2692            category_counts: HashMap::new(),
2693            virtual_voices: HashSet::new(),
2694            total_active: 0,
2695            total_virtual: 0,
2696        }
2697    }
2698
2699    pub fn spawn_voice(&mut self, sound_id: u64, category: SoundCategory, pos: Vec3) -> Option<u64> {
2700        let cat_limit = *self.category_limits.get(&category).unwrap_or(&32);
2701        let cat_count = *self.category_counts.get(&category).unwrap_or(&0);
2702
2703        if self.voices.len() >= self.max_voices || cat_count >= cat_limit {
2704            // Try to steal lowest priority voice of same category
2705            return None;
2706        }
2707
2708        let id = self.next_voice_id;
2709        self.next_voice_id += 1;
2710        let voice = AudioVoice::new(id, sound_id, category, pos);
2711        self.voices.insert(id, voice);
2712        *self.category_counts.entry(category).or_insert(0) += 1;
2713        Some(id)
2714    }
2715
2716    pub fn retire_voice(&mut self, voice_id: u64) {
2717        if let Some(voice) = self.voices.remove(&voice_id) {
2718            let cat = voice.category;
2719            if let Some(count) = self.category_counts.get_mut(&cat) {
2720                *count = count.saturating_sub(1);
2721            }
2722            self.virtual_voices.remove(&voice_id);
2723        }
2724    }
2725
2726    pub fn update_priorities(&mut self, listener_pos: Vec3) {
2727        let ids: Vec<u64> = self.voices.keys().copied().collect();
2728        for id in ids {
2729            if let Some(voice) = self.voices.get_mut(&id) {
2730                voice.compute_priority(listener_pos);
2731            }
2732        }
2733    }
2734
2735    pub fn cull_excess_voices(&mut self, listener_pos: Vec3) {
2736        if self.voices.len() <= self.max_voices { return; }
2737
2738        let mut sorted: Vec<(u64, f32)> = self.voices.iter()
2739            .map(|(&id, v)| (id, v.priority_score))
2740            .collect();
2741        sorted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
2742
2743        let excess = self.voices.len() - self.max_voices;
2744        for i in 0..excess {
2745            self.retire_voice(sorted[i].0);
2746        }
2747    }
2748
2749    pub fn virtualize_distant_voices(&mut self, listener_pos: Vec3, virtual_threshold_m: f32) {
2750        for (id, voice) in &mut self.voices {
2751            let dist = (voice.position - listener_pos).length();
2752            voice.is_virtual = dist > virtual_threshold_m;
2753            if voice.is_virtual {
2754                self.virtual_voices.insert(*id);
2755            } else {
2756                self.virtual_voices.remove(id);
2757            }
2758        }
2759        self.total_virtual = self.virtual_voices.len();
2760        self.total_active = self.voices.values().filter(|v| !v.is_virtual).count();
2761    }
2762
2763    pub fn voices_by_category(&self, category: SoundCategory) -> Vec<&AudioVoice> {
2764        self.voices.values().filter(|v| v.category == category).collect()
2765    }
2766
2767    pub fn steal_voice(&mut self, category: SoundCategory) -> Option<u64> {
2768        // Steal lowest-priority active voice of given category
2769        self.voices.iter()
2770            .filter(|(_, v)| v.category == category && !v.is_virtual)
2771            .min_by(|(_, a), (_, b)| a.priority_score.partial_cmp(&b.priority_score)
2772                .unwrap_or(std::cmp::Ordering::Equal))
2773            .map(|(&id, _)| id)
2774    }
2775}
2776
2777// ============================================================
2778// WAVEFORM ANALYSIS (RMS, PEAK, SPECTRUM)
2779// ============================================================
2780
2781#[derive(Debug)]
2782pub struct LevelMeter {
2783    pub rms_window: VecDeque<f32>, // squared samples
2784    pub rms_sum: f64,
2785    pub window_size: usize,
2786    pub peak_l: f32,
2787    pub peak_r: f32,
2788    pub peak_hold_l: f32,
2789    pub peak_hold_r: f32,
2790    pub peak_hold_timer_l: u32,
2791    pub peak_hold_timer_r: u32,
2792    pub rms_l: f32,
2793    pub rms_r: f32,
2794    pub clip_count: u64,
2795}
2796
2797impl LevelMeter {
2798    pub fn new(window_size: usize) -> Self {
2799        Self {
2800            rms_window: VecDeque::with_capacity(window_size),
2801            rms_sum: 0.0,
2802            window_size,
2803            peak_l: 0.0,
2804            peak_r: 0.0,
2805            peak_hold_l: 0.0,
2806            peak_hold_r: 0.0,
2807            peak_hold_timer_l: 0,
2808            peak_hold_timer_r: 0,
2809            rms_l: 0.0,
2810            rms_r: 0.0,
2811            clip_count: 0,
2812        }
2813    }
2814
2815    pub fn process_sample(&mut self, left: f32, right: f32) {
2816        let mono_sq = (left * left + right * right) * 0.5;
2817        if self.rms_window.len() >= self.window_size {
2818            if let Some(old) = self.rms_window.pop_front() {
2819                self.rms_sum -= old as f64;
2820            }
2821        }
2822        self.rms_window.push_back(mono_sq);
2823        self.rms_sum = (self.rms_sum + mono_sq as f64).max(0.0);
2824        let rms = (self.rms_sum / self.window_size as f64).sqrt() as f32;
2825        self.rms_l = rms;
2826        self.rms_r = rms;
2827
2828        let abs_l = left.abs();
2829        let abs_r = right.abs();
2830        self.peak_l = abs_l;
2831        self.peak_r = abs_r;
2832
2833        if abs_l > self.peak_hold_l {
2834            self.peak_hold_l = abs_l;
2835            self.peak_hold_timer_l = PEAK_HOLD_FRAMES;
2836        } else if self.peak_hold_timer_l > 0 {
2837            self.peak_hold_timer_l -= 1;
2838        } else {
2839            self.peak_hold_l *= 0.999;
2840        }
2841
2842        if abs_r > self.peak_hold_r {
2843            self.peak_hold_r = abs_r;
2844            self.peak_hold_timer_r = PEAK_HOLD_FRAMES;
2845        } else if self.peak_hold_timer_r > 0 {
2846            self.peak_hold_timer_r -= 1;
2847        } else {
2848            self.peak_hold_r *= 0.999;
2849        }
2850
2851        if abs_l > 1.0 || abs_r > 1.0 { self.clip_count += 1; }
2852    }
2853
2854    pub fn rms_db(&self) -> f32 { linear_to_db(self.rms_l.max(1e-9)) }
2855    pub fn peak_hold_l_db(&self) -> f32 { linear_to_db(self.peak_hold_l.max(1e-9)) }
2856    pub fn peak_hold_r_db(&self) -> f32 { linear_to_db(self.peak_hold_r.max(1e-9)) }
2857    pub fn is_clipping(&self) -> bool { self.clip_count > 0 }
2858}
2859
2860// ============================================================
2861// DFT SPECTRUM ANALYZER
2862// ============================================================
2863// ============================================================
2864// LUFS LOUDNESS METER
2865// ============================================================
2866
2867#[derive(Debug)]
2868pub struct LufsMeter {
2869    // K-weighting filter chain (two biquad stages per channel)
2870    pub pre_filter_l: BiquadState,
2871    pub pre_filter_r: BiquadState,
2872    pub rlb_filter_l: BiquadState,
2873    pub rlb_filter_r: BiquadState,
2874    pub pre_coeff: BiquadCoefficients,
2875    pub rlb_coeff: BiquadCoefficients,
2876    // Short-term block buffer (400ms = ~17640 samples)
2877    pub block_buffer: VecDeque<f32>,
2878    pub block_size: usize,
2879    // Integrated loudness
2880    pub blocks: Vec<f32>,        // mean square values per block
2881    pub integrated_lufs: f32,
2882    pub short_term_lufs: f32,
2883    pub momentary_lufs: f32,
2884    pub lra_high: f32,
2885    pub lra_low: f32,
2886    // Momentary (100ms)
2887    pub momentary_buffer: VecDeque<f32>,
2888    pub momentary_size: usize,
2889}
2890
2891impl LufsMeter {
2892    pub fn new() -> Self {
2893        // K-weighting pre-filter: high-shelf +4dB at 1681 Hz
2894        let pre_coeff = BiquadCoefficients::high_shelf(1681.0, 1.0, 4.0, SAMPLE_RATE);
2895        // RLB filter: high-pass at 38 Hz (Q=0.5)
2896        let rlb_coeff = BiquadCoefficients::high_pass(38.0, 0.5, SAMPLE_RATE);
2897        let block_size = LUFS_BLOCK_SAMPLES;
2898        let momentary_size = (0.1 * SAMPLE_RATE) as usize; // 100ms
2899        Self {
2900            pre_filter_l: BiquadState::new(),
2901            pre_filter_r: BiquadState::new(),
2902            rlb_filter_l: BiquadState::new(),
2903            rlb_filter_r: BiquadState::new(),
2904            pre_coeff,
2905            rlb_coeff,
2906            block_buffer: VecDeque::with_capacity(block_size),
2907            block_size,
2908            blocks: Vec::new(),
2909            integrated_lufs: f32::NEG_INFINITY,
2910            short_term_lufs: f32::NEG_INFINITY,
2911            momentary_lufs: f32::NEG_INFINITY,
2912            lra_high: 0.0,
2913            lra_low: 0.0,
2914            momentary_buffer: VecDeque::with_capacity(momentary_size),
2915            momentary_size,
2916        }
2917    }
2918
2919    pub fn process_sample(&mut self, left: f32, right: f32) {
2920        // Apply K-weighting to each channel
2921        let wl = {
2922            let pre = self.pre_filter_l.process(left, &self.pre_coeff);
2923            self.rlb_filter_l.process(pre, &self.rlb_coeff)
2924        };
2925        let wr = {
2926            let pre = self.pre_filter_r.process(right, &self.pre_coeff);
2927            self.rlb_filter_r.process(pre, &self.rlb_coeff)
2928        };
2929        let mean_sq = wl * wl + wr * wr; // sum of mean squares (2 channels)
2930
2931        // Short-term block accumulation
2932        if self.block_buffer.len() >= self.block_size {
2933            let block_sum: f32 = self.block_buffer.iter().sum();
2934            let block_mean = block_sum / self.block_size as f32;
2935            self.blocks.push(block_mean);
2936            self.block_buffer.pop_front();
2937
2938            // Compute short-term LUFS (3s sliding window = ~7.5 blocks)
2939            let st_blocks = self.blocks.len().min(8);
2940            if st_blocks > 0 {
2941                let st_sum: f32 = self.blocks.iter().rev().take(st_blocks).sum();
2942                let st_mean = st_sum / st_blocks as f32;
2943                self.short_term_lufs = -0.691 + 10.0 * st_mean.max(1e-10).log10();
2944            }
2945        }
2946        self.block_buffer.push_back(mean_sq);
2947
2948        // Momentary (100ms)
2949        if self.momentary_buffer.len() >= self.momentary_size {
2950            self.momentary_buffer.pop_front();
2951        }
2952        self.momentary_buffer.push_back(mean_sq);
2953        let mom_sum: f32 = self.momentary_buffer.iter().sum();
2954        let mom_mean = mom_sum / self.momentary_buffer.len() as f32;
2955        self.momentary_lufs = -0.691 + 10.0 * mom_mean.max(1e-10).log10();
2956
2957        // Integrated LUFS using absolute gating at -70 LUFS and relative gating at -10
2958        self.compute_integrated_lufs();
2959    }
2960
2961    fn compute_integrated_lufs(&mut self) {
2962        if self.blocks.is_empty() { return; }
2963        // Absolute gating: discard blocks below -70 LUFS
2964        let abs_gate_linear = db_to_linear((-70.691) * LN10_OVER_20 * 20.0);
2965        let gated: Vec<f32> = self.blocks.iter()
2966            .copied()
2967            .filter(|&b| b >= 1e-7) // rough -70 LUFS threshold
2968            .collect();
2969        if gated.is_empty() {
2970            self.integrated_lufs = f32::NEG_INFINITY;
2971            return;
2972        }
2973        let mean_gated: f32 = gated.iter().sum::<f32>() / gated.len() as f32;
2974        // Relative gating: discard blocks 10 dB below mean
2975        let relative_threshold = mean_gated * db_to_linear(-10.0);
2976        let rel_gated: Vec<f32> = gated.into_iter()
2977            .filter(|&b| b >= relative_threshold)
2978            .collect();
2979        if rel_gated.is_empty() {
2980            self.integrated_lufs = f32::NEG_INFINITY;
2981            return;
2982        }
2983        let final_mean = rel_gated.iter().sum::<f32>() / rel_gated.len() as f32;
2984        self.integrated_lufs = -0.691 + 10.0 * final_mean.max(1e-10).log10();
2985    }
2986
2987    pub fn loudness_range(&mut self) {
2988        // LRA = difference between 10th and 95th percentile of gated short-term loudness
2989        let mut lufs_values: Vec<f32> = self.blocks.iter()
2990            .filter(|&&b| b >= 1e-7)
2991            .map(|&b| -0.691 + 10.0 * b.max(1e-10).log10())
2992            .collect();
2993        if lufs_values.len() < 2 {
2994            self.lra_high = 0.0;
2995            self.lra_low = 0.0;
2996            return;
2997        }
2998        lufs_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2999        let n = lufs_values.len();
3000        let low_idx = (n as f32 * 0.1) as usize;
3001        let high_idx = (n as f32 * 0.95) as usize;
3002        self.lra_low = lufs_values[low_idx.min(n - 1)];
3003        self.lra_high = lufs_values[high_idx.min(n - 1)];
3004    }
3005
3006    pub fn lra_db(&self) -> f32 { (self.lra_high - self.lra_low).max(0.0) }
3007    pub fn integrated_lufs(&self) -> f32 { self.integrated_lufs }
3008    pub fn short_term_lufs(&self) -> f32 { self.short_term_lufs }
3009    pub fn momentary_lufs(&self) -> f32 { self.momentary_lufs }
3010}
3011
3012// ============================================================
3013// MIXER SNAPSHOT / PRESET SYSTEM
3014// ============================================================
3015
3016#[derive(Debug, Clone)]
3017pub struct BusSnapshot {
3018    pub bus_id: u64,
3019    pub gain_db: f32,
3020    pub pan: f32,
3021    pub mute: bool,
3022    pub effect_bypasses: Vec<bool>, // one per effect in chain
3023}
3024
3025#[derive(Debug, Clone)]
3026pub struct MixerSnapshot {
3027    pub id: u64,
3028    pub name: String,
3029    pub bus_states: HashMap<u64, BusSnapshot>,
3030    pub created_at: f64,
3031    pub tags: Vec<String>,
3032}
3033
3034impl MixerSnapshot {
3035    pub fn new(id: u64, name: String) -> Self {
3036        Self {
3037            id,
3038            name,
3039            bus_states: HashMap::new(),
3040            created_at: 0.0,
3041            tags: Vec::new(),
3042        }
3043    }
3044
3045    pub fn capture_bus(&mut self, bus: &AudioBus) {
3046        let bypasses: Vec<bool> = bus.effect_chain.effects.iter().map(|e| !e.is_enabled()).collect();
3047        self.bus_states.insert(bus.id, BusSnapshot {
3048            bus_id: bus.id,
3049            gain_db: bus.gain_db,
3050            pan: bus.pan,
3051            mute: bus.mute,
3052            effect_bypasses: bypasses,
3053        });
3054    }
3055
3056    pub fn blend_with(&self, other: &MixerSnapshot, t: f32) -> HashMap<u64, (f32, f32)> {
3057        let mut result = HashMap::new();
3058        for (id, a_state) in &self.bus_states {
3059            if let Some(b_state) = other.bus_states.get(id) {
3060                let gain = a_state.gain_db + (b_state.gain_db - a_state.gain_db) * t;
3061                let pan = a_state.pan + (b_state.pan - a_state.pan) * t;
3062                result.insert(*id, (gain, pan));
3063            }
3064        }
3065        result
3066    }
3067}
3068
3069#[derive(Debug)]
3070pub struct SnapshotSystem {
3071    pub snapshots: HashMap<u64, MixerSnapshot>,
3072    pub active_snapshot_id: Option<u64>,
3073    pub target_snapshot_id: Option<u64>,
3074    pub transition_progress: f32,
3075    pub transition_duration_s: f32,
3076    pub transition_curve: SnapshotTransitionCurve,
3077    pub next_id: u64,
3078}
3079
3080impl SnapshotSystem {
3081    pub fn new() -> Self {
3082        Self {
3083            snapshots: HashMap::new(),
3084            active_snapshot_id: None,
3085            target_snapshot_id: None,
3086            transition_progress: 1.0,
3087            transition_duration_s: 1.0,
3088            transition_curve: SnapshotTransitionCurve::EaseInOut,
3089            next_id: 1,
3090        }
3091    }
3092
3093    pub fn create_snapshot(&mut self, name: String, timestamp: f64) -> u64 {
3094        let id = self.next_id;
3095        self.next_id += 1;
3096        let mut snap = MixerSnapshot::new(id, name);
3097        snap.created_at = timestamp;
3098        self.snapshots.insert(id, snap);
3099        id
3100    }
3101
3102    pub fn delete_snapshot(&mut self, id: u64) -> bool {
3103        if Some(id) == self.active_snapshot_id {
3104            return false; // Can't delete active
3105        }
3106        self.snapshots.remove(&id).is_some()
3107    }
3108
3109    pub fn begin_transition(&mut self, target_id: u64, duration_s: f32, curve: SnapshotTransitionCurve) {
3110        if !self.snapshots.contains_key(&target_id) { return; }
3111        self.target_snapshot_id = Some(target_id);
3112        self.transition_duration_s = duration_s;
3113        self.transition_curve = curve;
3114        self.transition_progress = if curve == SnapshotTransitionCurve::Immediate { 1.0 } else { 0.0 };
3115    }
3116
3117    pub fn update(&mut self, dt_s: f32) -> Option<HashMap<u64, (f32, f32)>> {
3118        if self.target_snapshot_id.is_none() { return None; }
3119        if self.transition_progress >= 1.0 {
3120            self.active_snapshot_id = self.target_snapshot_id.take();
3121            return None;
3122        }
3123
3124        self.transition_progress += dt_s / self.transition_duration_s.max(0.001);
3125        self.transition_progress = self.transition_progress.min(1.0);
3126
3127        let t = apply_curve(self.transition_progress, self.transition_curve);
3128
3129        let from_id = self.active_snapshot_id?;
3130        let to_id = self.target_snapshot_id?;
3131        let from = self.snapshots.get(&from_id)?;
3132        let to = self.snapshots.get(&to_id)?;
3133        Some(from.blend_with(to, t))
3134    }
3135
3136    pub fn is_transitioning(&self) -> bool {
3137        self.target_snapshot_id.is_some() && self.transition_progress < 1.0
3138    }
3139}
3140
3141pub fn apply_curve(t: f32, curve: SnapshotTransitionCurve) -> f32 {
3142    match curve {
3143        SnapshotTransitionCurve::Linear => t,
3144        SnapshotTransitionCurve::EaseIn => t * t,
3145        SnapshotTransitionCurve::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
3146        SnapshotTransitionCurve::EaseInOut => t * t * (3.0 - 2.0 * t),
3147        SnapshotTransitionCurve::Immediate => 1.0,
3148    }
3149}
3150
3151// ============================================================
3152// AUDIO PROFILER
3153// ============================================================
3154
3155#[derive(Debug, Clone)]
3156pub struct AudioProfilerFrame {
3157    pub timestamp_s: f64,
3158    pub voice_count: usize,
3159    pub virtual_voice_count: usize,
3160    pub active_buses: usize,
3161    pub cpu_percent: f32,
3162    pub memory_bytes: u64,
3163    pub dsp_chain_cost: f32,
3164    pub streaming_kb_s: f32,
3165}
3166
3167#[derive(Debug)]
3168pub struct AudioProfiler {
3169    pub frames: VecDeque<AudioProfilerFrame>,
3170    pub frame_capacity: usize,
3171    pub sound_bank_memory: HashMap<String, u64>, // bank_name -> bytes
3172    pub effect_cpu_breakdown: HashMap<EffectType, f32>,
3173    pub total_samples_processed: u64,
3174    pub dropouts: u64,
3175    pub peak_voice_count: usize,
3176    pub average_voice_count: f32,
3177}
3178
3179impl AudioProfiler {
3180    pub fn new(capacity: usize) -> Self {
3181        Self {
3182            frames: VecDeque::with_capacity(capacity),
3183            frame_capacity: capacity,
3184            sound_bank_memory: HashMap::new(),
3185            effect_cpu_breakdown: HashMap::new(),
3186            total_samples_processed: 0,
3187            dropouts: 0,
3188            peak_voice_count: 0,
3189            average_voice_count: 0.0,
3190        }
3191    }
3192
3193    pub fn record_frame(&mut self, frame: AudioProfilerFrame) {
3194        if self.frames.len() >= self.frame_capacity { self.frames.pop_front(); }
3195        self.peak_voice_count = self.peak_voice_count.max(frame.voice_count);
3196        // Update rolling average
3197        let n = self.frames.len().max(1) as f32;
3198        self.average_voice_count = self.average_voice_count * (n - 1.0) / n + frame.voice_count as f32 / n;
3199        self.frames.push_back(frame);
3200    }
3201
3202    pub fn update_dsp_costs(&mut self, graph: &SignalFlowGraph) {
3203        self.effect_cpu_breakdown.clear();
3204        for bus in graph.buses.values() {
3205            for effect in &bus.effect_chain.effects {
3206                *self.effect_cpu_breakdown.entry(effect.effect_type()).or_insert(0.0)
3207                    += effect.cpu_cost_estimate();
3208            }
3209        }
3210    }
3211
3212    pub fn register_sound_bank(&mut self, name: String, size_bytes: u64) {
3213        self.sound_bank_memory.insert(name, size_bytes);
3214    }
3215
3216    pub fn total_sound_bank_memory_mb(&self) -> f32 {
3217        self.sound_bank_memory.values().sum::<u64>() as f32 / (1024.0 * 1024.0)
3218    }
3219
3220    pub fn average_cpu_percent(&self) -> f32 {
3221        if self.frames.is_empty() { return 0.0; }
3222        self.frames.iter().map(|f| f.cpu_percent).sum::<f32>() / self.frames.len() as f32
3223    }
3224
3225    pub fn peak_cpu_percent(&self) -> f32 {
3226        self.frames.iter().map(|f| f.cpu_percent).fold(0.0f32, f32::max)
3227    }
3228
3229    pub fn record_dropout(&mut self) { self.dropouts += 1; }
3230
3231    pub fn most_expensive_effect(&self) -> Option<(EffectType, f32)> {
3232        self.effect_cpu_breakdown.iter()
3233            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
3234            .map(|(&t, &c)| (t, c))
3235    }
3236}
3237
3238// ============================================================
3239// AUDIO MIXER EDITOR UI STATE
3240// ============================================================
3241
3242#[derive(Debug, Clone, PartialEq, Eq)]
3243pub enum MixerEditorPanel {
3244    SignalFlow,
3245    EffectChain,
3246    BusRouting,
3247    Spectrum,
3248    Loudness,
3249    SpatialAudio,
3250    MusicSystem,
3251    Snapshots,
3252    Profiler,
3253    VoiceManager,
3254}
3255
3256#[derive(Debug)]
3257pub struct MixerEditorUiState {
3258    pub active_panel: MixerEditorPanel,
3259    pub selected_bus_id: Option<u64>,
3260    pub selected_effect_index: Option<usize>,
3261    pub show_spectrum: bool,
3262    pub show_rta: bool,         // real-time analyzer
3263    pub show_lissajous: bool,
3264    pub spectrum_log_scale: bool,
3265    pub show_automation: bool,
3266    pub parameter_link_mode: bool,
3267    pub solo_mode: bool,        // solo-in-place vs solo-exclusive
3268    pub snap_to_grid: bool,
3269    pub grid_size_db: f32,
3270    pub drag_source_bus: Option<u64>,
3271    pub drag_target_bus: Option<u64>,
3272    pub effect_drag_index: Option<usize>,
3273    pub timeline_zoom: f32,
3274    pub timeline_scroll: f32,
3275}
3276
3277impl MixerEditorUiState {
3278    pub fn new() -> Self {
3279        Self {
3280            active_panel: MixerEditorPanel::SignalFlow,
3281            selected_bus_id: None,
3282            selected_effect_index: None,
3283            show_spectrum: true,
3284            show_rta: false,
3285            show_lissajous: false,
3286            spectrum_log_scale: true,
3287            show_automation: false,
3288            parameter_link_mode: false,
3289            solo_mode: false,
3290            snap_to_grid: false,
3291            grid_size_db: 3.0,
3292            drag_source_bus: None,
3293            drag_target_bus: None,
3294            effect_drag_index: None,
3295            timeline_zoom: 1.0,
3296            timeline_scroll: 0.0,
3297        }
3298    }
3299
3300    pub fn select_bus(&mut self, id: u64) {
3301        self.selected_bus_id = Some(id);
3302        self.selected_effect_index = None;
3303    }
3304
3305    pub fn select_effect(&mut self, bus_id: u64, effect_index: usize) {
3306        self.selected_bus_id = Some(bus_id);
3307        self.selected_effect_index = Some(effect_index);
3308    }
3309
3310    pub fn snap_gain_db(&self, gain_db: f32) -> f32 {
3311        if !self.snap_to_grid { return gain_db; }
3312        (gain_db / self.grid_size_db).round() * self.grid_size_db
3313    }
3314
3315    pub fn begin_drag(&mut self, source_bus: u64) {
3316        self.drag_source_bus = Some(source_bus);
3317        self.drag_target_bus = None;
3318    }
3319
3320    pub fn complete_drag(&mut self) -> Option<(u64, u64)> {
3321        match (self.drag_source_bus.take(), self.drag_target_bus.take()) {
3322            (Some(s), Some(t)) => Some((s, t)),
3323            _ => None,
3324        }
3325    }
3326}
3327
3328// ============================================================
3329// DOPPLER CALCULATOR
3330// ============================================================
3331
3332pub struct DopplerCalculator;
3333
3334impl DopplerCalculator {
3335    /// Compute observed frequency using exact Doppler formula
3336    /// f' = f * (v_sound + v_listener) / (v_sound + v_source)
3337    /// where velocities are projected onto source→listener direction
3338    pub fn compute_pitch_ratio(
3339        source_pos: Vec3,
3340        listener_pos: Vec3,
3341        source_vel: Vec3,
3342        listener_vel: Vec3,
3343        doppler_factor: f32,
3344    ) -> f32 {
3345        let dir = listener_pos - source_pos;
3346        let dist = dir.length();
3347        if dist < 1e-4 { return 1.0; }
3348        let unit = dir / dist;
3349
3350        // Positive means moving away from listener
3351        let v_source = source_vel.dot(-unit);
3352        // Positive means moving toward source
3353        let v_listener = listener_vel.dot(unit);
3354
3355        let denom = SPEED_OF_SOUND + v_source;
3356        if denom.abs() < 1.0 { return 1.0; }
3357        let ratio = (SPEED_OF_SOUND + v_listener) / denom;
3358        let clamped = ratio.clamp(0.5, 2.0);
3359        // Blend between 1.0 and clamped by doppler_factor
3360        1.0 + (clamped - 1.0) * doppler_factor.clamp(0.0, 1.0)
3361    }
3362
3363    pub fn pitch_to_playback_rate(pitch_ratio: f32) -> f32 { pitch_ratio }
3364
3365    pub fn playback_rate_to_cents(rate: f32) -> f32 {
3366        1200.0 * rate.log2()
3367    }
3368}
3369
3370// ============================================================
3371// OCCLUSION MODEL
3372// ============================================================
3373
3374#[derive(Debug, Clone)]
3375pub struct AcousticMaterial {
3376    pub name: String,
3377    pub transmission_loss_db: f32,    // how much attenuated when sound passes through
3378    pub absorption_coefficients: [f32; 6], // at 125, 250, 500, 1k, 2k, 4k Hz
3379}
3380
3381impl AcousticMaterial {
3382    pub fn new(name: &str, transmission_loss_db: f32, absorptions: [f32; 6]) -> Self {
3383        Self {
3384            name: name.to_string(),
3385            transmission_loss_db,
3386            absorption_coefficients: absorptions,
3387        }
3388    }
3389
3390    pub fn concrete() -> Self {
3391        Self::new("Concrete", 45.0, [0.01, 0.01, 0.02, 0.02, 0.03, 0.04])
3392    }
3393    pub fn wood() -> Self {
3394        Self::new("Wood", 25.0, [0.15, 0.12, 0.10, 0.08, 0.08, 0.07])
3395    }
3396    pub fn glass() -> Self {
3397        Self::new("Glass", 20.0, [0.35, 0.25, 0.20, 0.10, 0.07, 0.04])
3398    }
3399    pub fn fabric() -> Self {
3400        Self::new("Fabric", 5.0, [0.35, 0.53, 0.75, 0.70, 0.60, 0.55])
3401    }
3402
3403    pub fn absorption_at_freq(&self, freq_hz: f32) -> f32 {
3404        // Interpolate over octave bands: 125, 250, 500, 1000, 2000, 4000
3405        let bands = [125.0f32, 250.0, 500.0, 1000.0, 2000.0, 4000.0];
3406        let n = bands.len();
3407        if freq_hz <= bands[0] { return self.absorption_coefficients[0]; }
3408        if freq_hz >= bands[n - 1] { return self.absorption_coefficients[n - 1]; }
3409        for i in 0..(n - 1) {
3410            if freq_hz >= bands[i] && freq_hz <= bands[i + 1] {
3411                let t = (freq_hz - bands[i]) / (bands[i + 1] - bands[i]);
3412                return self.absorption_coefficients[i] * (1.0 - t) + self.absorption_coefficients[i + 1] * t;
3413            }
3414        }
3415        self.absorption_coefficients[n / 2]
3416    }
3417}
3418
3419#[derive(Debug, Clone)]
3420pub struct OcclusionQuery {
3421    pub sound_id: u64,
3422    pub source_pos: Vec3,
3423    pub listener_pos: Vec3,
3424    pub occlusion_factor: f32,     // 0=open, 1=fully occluded
3425    pub obstruction_factor: f32,   // partially blocked
3426    pub materials: Vec<AcousticMaterial>,
3427    pub total_transmission_loss_db: f32,
3428    pub wet_occlusion_db: f32,     // reverb path (often unoccluded)
3429}
3430
3431impl OcclusionQuery {
3432    pub fn new(sound_id: u64, source: Vec3, listener: Vec3) -> Self {
3433        Self {
3434            sound_id,
3435            source_pos: source,
3436            listener_pos: listener,
3437            occlusion_factor: 0.0,
3438            obstruction_factor: 0.0,
3439            materials: Vec::new(),
3440            total_transmission_loss_db: 0.0,
3441            wet_occlusion_db: 0.0,
3442        }
3443    }
3444
3445    pub fn add_material(&mut self, mat: AcousticMaterial) {
3446        self.total_transmission_loss_db += mat.transmission_loss_db;
3447        self.materials.push(mat);
3448    }
3449
3450    pub fn direct_path_gain_db(&self) -> f32 {
3451        -self.total_transmission_loss_db * self.occlusion_factor
3452    }
3453
3454    pub fn apply_to_spatializer(&self, spatializer: &mut Spatializer3D) {
3455        spatializer.occlusion_db = self.direct_path_gain_db();
3456        spatializer.obstruction_db = -self.obstruction_factor * 6.0;
3457    }
3458}
3459
3460// ============================================================
3461// BUS AUTOMATION
3462// ============================================================
3463
3464#[derive(Debug, Clone)]
3465pub struct AutomationKeyframe {
3466    pub time_s: f32,
3467    pub value: f32,
3468    pub curve: f32, // 0=linear, <0=ease-in, >0=ease-out
3469}
3470
3471impl AutomationKeyframe {
3472    pub fn new(time_s: f32, value: f32) -> Self {
3473        Self { time_s, value, curve: 0.0 }
3474    }
3475}
3476
3477#[derive(Debug, Clone)]
3478pub struct AutomationLane {
3479    pub parameter_name: String,
3480    pub bus_id: u64,
3481    pub keyframes: Vec<AutomationKeyframe>,
3482    pub is_enabled: bool,
3483    pub looping: bool,
3484    pub loop_duration_s: f32,
3485}
3486
3487impl AutomationLane {
3488    pub fn new(parameter_name: String, bus_id: u64) -> Self {
3489        Self {
3490            parameter_name,
3491            bus_id,
3492            keyframes: Vec::new(),
3493            is_enabled: true,
3494            looping: false,
3495            loop_duration_s: 1.0,
3496        }
3497    }
3498
3499    pub fn add_keyframe(&mut self, kf: AutomationKeyframe) {
3500        self.keyframes.push(kf);
3501        self.keyframes.sort_by(|a, b| a.time_s.partial_cmp(&b.time_s).unwrap_or(std::cmp::Ordering::Equal));
3502    }
3503
3504    pub fn evaluate_at(&self, time_s: f32) -> f32 {
3505        let t = if self.looping && self.loop_duration_s > 0.0 {
3506            time_s % self.loop_duration_s
3507        } else { time_s };
3508
3509        let kfs = &self.keyframes;
3510        if kfs.is_empty() { return 0.0; }
3511        if t <= kfs[0].time_s { return kfs[0].value; }
3512        if t >= kfs[kfs.len() - 1].time_s { return kfs[kfs.len() - 1].value; }
3513
3514        for i in 0..(kfs.len() - 1) {
3515            if t >= kfs[i].time_s && t <= kfs[i + 1].time_s {
3516                let dt = kfs[i + 1].time_s - kfs[i].time_s;
3517                let local_t = if dt > 0.0 { (t - kfs[i].time_s) / dt } else { 1.0 };
3518                let c = kfs[i].curve;
3519                let curved_t = if c.abs() < 1e-4 {
3520                    local_t
3521                } else if c > 0.0 {
3522                    local_t.powf(1.0 + c)
3523                } else {
3524                    1.0 - (1.0 - local_t).powf(1.0 - c)
3525                };
3526                return kfs[i].value + (kfs[i + 1].value - kfs[i].value) * curved_t;
3527            }
3528        }
3529        kfs[kfs.len() - 1].value
3530    }
3531
3532    pub fn duration_s(&self) -> f32 {
3533        self.keyframes.last().map(|k| k.time_s).unwrap_or(0.0)
3534    }
3535}
3536
3537// ============================================================
3538// AUDIO MIXER EDITOR MAIN STRUCT
3539// ============================================================
3540
3541#[derive(Debug)]
3542pub struct AudioMixerEditor {
3543    // Signal flow
3544    pub signal_flow: SignalFlowGraph,
3545
3546    // Voice management
3547    pub voice_manager: VoiceManager,
3548
3549    // Adaptive music
3550    pub music_system: AdaptiveMusicSystem,
3551
3552    // Analysis
3553    pub level_meters: HashMap<u64, LevelMeter>,  // bus_id -> meter
3554    pub master_spectrum: SpectrumAnalyzer,
3555    pub lufs_meter: LufsMeter,
3556
3557    // Spatial
3558    pub reverb_zones: ReverbZoneManager,
3559    pub occlusion_queries: HashMap<u64, OcclusionQuery>,
3560
3561    // Snapshots
3562    pub snapshot_system: SnapshotSystem,
3563
3564    // Automation
3565    pub automation_lanes: Vec<AutomationLane>,
3566
3567    // Profiler
3568    pub profiler: AudioProfiler,
3569
3570    // UI state
3571    pub ui_state: MixerEditorUiState,
3572
3573    // Current time
3574    pub time_s: f64,
3575    pub sample_rate: f32,
3576
3577    // Master settings
3578    pub master_volume_db: f32,
3579    pub master_mute: bool,
3580
3581    // Stats
3582    pub stats: AudioMixerStats,
3583}
3584
3585#[derive(Debug, Default, Clone)]
3586pub struct AudioMixerStats {
3587    pub total_voices: usize,
3588    pub active_voices: usize,
3589    pub virtual_voices: usize,
3590    pub total_buses: usize,
3591    pub estimated_cpu_percent: f32,
3592    pub total_sound_bank_mb: f32,
3593    pub master_rms_db: f32,
3594    pub master_peak_db: f32,
3595    pub integrated_lufs: f32,
3596    pub is_clipping: bool,
3597}
3598
3599impl AudioMixerEditor {
3600    pub fn new() -> Self {
3601        let mut editor = Self {
3602            signal_flow: SignalFlowGraph::new(),
3603            voice_manager: VoiceManager::new(MAX_VOICES),
3604            music_system: AdaptiveMusicSystem::new(120.0),
3605            level_meters: HashMap::new(),
3606            master_spectrum: SpectrumAnalyzer::new(SPECTRUM_FFT_SIZE),
3607            lufs_meter: LufsMeter::new(),
3608            reverb_zones: ReverbZoneManager::new(),
3609            occlusion_queries: HashMap::new(),
3610            snapshot_system: SnapshotSystem::new(),
3611            automation_lanes: Vec::new(),
3612            profiler: AudioProfiler::new(512),
3613            ui_state: MixerEditorUiState::new(),
3614            time_s: 0.0,
3615            sample_rate: SAMPLE_RATE,
3616            master_volume_db: 0.0,
3617            master_mute: false,
3618            stats: AudioMixerStats::default(),
3619        };
3620
3621        // Initialize level meters for default buses
3622        for &id in editor.signal_flow.buses.keys() {
3623            editor.level_meters.insert(id, LevelMeter::new(RMS_WINDOW_SAMPLES));
3624        }
3625
3626        editor
3627    }
3628
3629    pub fn tick(&mut self, dt_s: f32) {
3630        self.time_s += dt_s as f64;
3631        self.music_system.update(dt_s);
3632        if let Some(blended) = self.snapshot_system.update(dt_s) {
3633            self.apply_snapshot_blend(blended);
3634        }
3635        self.update_automation();
3636        self.collect_stats();
3637    }
3638
3639    fn apply_snapshot_blend(&mut self, blended: HashMap<u64, (f32, f32)>) {
3640        for (bus_id, (gain_db, pan)) in blended {
3641            if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3642                bus.gain_db = gain_db;
3643                bus.pan = pan;
3644            }
3645        }
3646    }
3647
3648    fn update_automation(&mut self) {
3649        let t = self.time_s as f32;
3650        for lane in &self.automation_lanes {
3651            if !lane.is_enabled { continue; }
3652            let value = lane.evaluate_at(t);
3653            if let Some(bus) = self.signal_flow.buses.get_mut(&lane.bus_id) {
3654                match lane.parameter_name.as_str() {
3655                    "gain_db" => bus.gain_db = value,
3656                    "pan" => bus.pan = value.clamp(-1.0, 1.0),
3657                    _ => {}
3658                }
3659            }
3660        }
3661    }
3662
3663    fn collect_stats(&mut self) {
3664        self.stats.total_voices = self.voice_manager.voices.len();
3665        self.stats.active_voices = self.voice_manager.total_active;
3666        self.stats.virtual_voices = self.voice_manager.total_virtual;
3667        self.stats.total_buses = self.signal_flow.buses.len();
3668        self.stats.estimated_cpu_percent = self.profiler.average_cpu_percent();
3669        self.stats.total_sound_bank_mb = self.profiler.total_sound_bank_memory_mb();
3670        self.stats.integrated_lufs = self.lufs_meter.integrated_lufs();
3671
3672        if let Some(master_meter) = self.level_meters.get(&self.signal_flow.master_bus_id) {
3673            self.stats.master_rms_db = master_meter.rms_db();
3674            self.stats.master_peak_db = master_meter.peak_hold_l_db();
3675            self.stats.is_clipping = master_meter.is_clipping();
3676        }
3677    }
3678
3679    pub fn process_audio_frame(&mut self, input_l: f32, input_r: f32) -> (f32, f32) {
3680        if self.master_mute { return (0.0, 0.0); }
3681
3682        // Process through signal flow in topology order
3683        let mut bus_outputs: HashMap<u64, (f32, f32)> = HashMap::new();
3684
3685        for &bus_id in &self.signal_flow.topology_order.clone() {
3686            // Sum inputs from child buses
3687            let mut sum_l = 0.0f32;
3688            let mut sum_r = 0.0f32;
3689
3690            for edge in &self.signal_flow.edges {
3691                if edge.to_bus_id == bus_id && !edge.is_sidechain {
3692                    if let Some(&(out_l, out_r)) = bus_outputs.get(&edge.from_bus_id) {
3693                        sum_l += out_l * edge.send_level;
3694                        sum_r += out_r * edge.send_level;
3695                    }
3696                }
3697            }
3698
3699            // If this is an input bus, add external audio input
3700            if self.signal_flow.topology_order.first().copied() == Some(bus_id) {
3701                sum_l += input_l;
3702                sum_r += input_r;
3703            }
3704
3705            if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3706                let (out_l, out_r) = bus.process_sample(sum_l, sum_r);
3707                if let Some(meter) = self.level_meters.get_mut(&bus_id) {
3708                    meter.process_sample(out_l, out_r);
3709                }
3710                bus_outputs.insert(bus_id, (out_l, out_r));
3711            }
3712        }
3713
3714        let (master_l, master_r) = bus_outputs.get(&self.signal_flow.master_bus_id).copied().unwrap_or((0.0, 0.0));
3715        let master_gain = db_to_linear(self.master_volume_db);
3716        let out = (master_l * master_gain, master_r * master_gain);
3717
3718        // Feed master to LUFS and spectrum
3719        self.lufs_meter.process_sample(out.0, out.1);
3720        self.master_spectrum.push_samples((out.0 + out.1) * 0.5, (out.0 + out.1) * 0.5);
3721
3722        out
3723    }
3724
3725    pub fn add_effect_to_bus(&mut self, bus_id: u64, effect: AudioEffect) -> bool {
3726        if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3727            bus.effect_chain.add(effect);
3728            true
3729        } else { false }
3730    }
3731
3732    pub fn remove_effect_from_bus(&mut self, bus_id: u64, index: usize) -> bool {
3733        if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3734            bus.effect_chain.remove(index);
3735            true
3736        } else { false }
3737    }
3738
3739    pub fn route_bus(&mut self, from_id: u64, to_id: u64, level: f32) {
3740        self.signal_flow.connect(from_id, to_id, level);
3741        self.level_meters.entry(from_id).or_insert_with(|| LevelMeter::new(RMS_WINDOW_SAMPLES));
3742        self.level_meters.entry(to_id).or_insert_with(|| LevelMeter::new(RMS_WINDOW_SAMPLES));
3743    }
3744
3745    pub fn unroute_bus(&mut self, from_id: u64, to_id: u64) {
3746        self.signal_flow.disconnect(from_id, to_id);
3747    }
3748
3749    pub fn set_bus_gain(&mut self, bus_id: u64, gain_db: f32) {
3750        if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3751            bus.gain_db = gain_db.clamp(-120.0, 24.0);
3752        }
3753    }
3754
3755    pub fn set_bus_mute(&mut self, bus_id: u64, mute: bool) {
3756        if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3757            bus.mute = mute;
3758        }
3759    }
3760
3761    pub fn set_bus_solo(&mut self, bus_id: u64, solo: bool) {
3762        if let Some(bus) = self.signal_flow.buses.get_mut(&bus_id) {
3763            bus.solo = solo;
3764        }
3765        // Mute all other non-soloed buses if any bus is soloed
3766        let any_solo = self.signal_flow.buses.values().any(|b| b.solo);
3767        if any_solo {
3768            let bus_ids: Vec<u64> = self.signal_flow.buses.keys().copied().collect();
3769            for id in bus_ids {
3770                if let Some(b) = self.signal_flow.buses.get_mut(&id) {
3771                    if !b.solo && id != self.signal_flow.master_bus_id {
3772                        b.mute = true;
3773                    }
3774                }
3775            }
3776        }
3777    }
3778
3779    pub fn save_snapshot(&mut self, name: String) -> u64 {
3780        let id = self.snapshot_system.create_snapshot(name, self.time_s);
3781        // Capture all buses
3782        let bus_ids: Vec<u64> = self.signal_flow.buses.keys().copied().collect();
3783        for bus_id in bus_ids {
3784            if let Some(snap) = self.snapshot_system.snapshots.get_mut(&id) {
3785                if let Some(bus) = self.signal_flow.buses.get(&bus_id) {
3786                    snap.capture_bus(bus);
3787                }
3788            }
3789        }
3790        id
3791    }
3792
3793    pub fn load_snapshot(&mut self, snapshot_id: u64, transition_s: f32) {
3794        self.snapshot_system.begin_transition(
3795            snapshot_id,
3796            transition_s,
3797            SnapshotTransitionCurve::EaseInOut,
3798        );
3799    }
3800
3801    pub fn compute_spectrum(&mut self) {
3802        // spectrum is updated on push_samples
3803    }
3804
3805    pub fn get_bus_by_type(&self, bus_type: BusType) -> Option<&AudioBus> {
3806        self.signal_flow.buses.values().find(|b| b.bus_type == bus_type)
3807    }
3808
3809    pub fn get_bus_by_name(&self, name: &str) -> Option<&AudioBus> {
3810        self.signal_flow.buses.values().find(|b| b.name == name)
3811    }
3812
3813    pub fn create_custom_bus(&mut self, name: String) -> u64 {
3814        let id = self.signal_flow.create_bus(name, BusType::Custom);
3815        self.level_meters.insert(id, LevelMeter::new(RMS_WINDOW_SAMPLES));
3816        id
3817    }
3818
3819    pub fn add_automation_lane(&mut self, bus_id: u64, param: String) -> usize {
3820        let idx = self.automation_lanes.len();
3821        self.automation_lanes.push(AutomationLane::new(param, bus_id));
3822        idx
3823    }
3824
3825    pub fn add_keyframe_to_lane(&mut self, lane_idx: usize, time_s: f32, value: f32) {
3826        if let Some(lane) = self.automation_lanes.get_mut(lane_idx) {
3827            lane.add_keyframe(AutomationKeyframe::new(time_s, value));
3828        }
3829    }
3830
3831    pub fn spawn_voice(&mut self, sound_id: u64, category: SoundCategory, pos: Vec3) -> Option<u64> {
3832        self.voice_manager.spawn_voice(sound_id, category, pos)
3833    }
3834
3835    pub fn update_voices(&mut self, listener_pos: Vec3) {
3836        self.voice_manager.update_priorities(listener_pos);
3837        self.voice_manager.cull_excess_voices(listener_pos);
3838        self.voice_manager.virtualize_distant_voices(listener_pos, 100.0);
3839        for voice in self.voice_manager.voices.values_mut() {
3840            voice.spatializer.listener_pos = listener_pos;
3841            voice.spatializer.update();
3842        }
3843    }
3844
3845    pub fn set_music_intensity(&mut self, intensity: f32) {
3846        self.music_system.set_intensity(intensity);
3847    }
3848
3849    pub fn set_music_state(&mut self, state: String, transition: MusicTransitionType) {
3850        self.music_system.set_state(state, transition);
3851    }
3852
3853    pub fn add_reverb_zone(&mut self, name: String, center: Vec3, radius: f32, blend: f32) -> u64 {
3854        self.reverb_zones.add_zone(name, center, radius, blend)
3855    }
3856
3857    pub fn update_reverb_zones(&mut self, listener_pos: Vec3) {
3858        self.reverb_zones.update(listener_pos);
3859    }
3860
3861    pub fn register_sound_bank(&mut self, name: String, size_bytes: u64) {
3862        self.profiler.register_sound_bank(name, size_bytes);
3863    }
3864
3865    pub fn generate_mixing_report(&self) -> MixingReport {
3866        MixingReport {
3867            active_voice_count: self.stats.active_voices,
3868            virtual_voice_count: self.stats.virtual_voices,
3869            bus_count: self.stats.total_buses,
3870            cpu_estimate: self.stats.estimated_cpu_percent,
3871            sound_bank_mb: self.stats.total_sound_bank_mb,
3872            master_rms_db: self.stats.master_rms_db,
3873            master_peak_db: self.stats.master_peak_db,
3874            integrated_lufs: self.stats.integrated_lufs,
3875            short_term_lufs: self.lufs_meter.short_term_lufs(),
3876            is_clipping: self.stats.is_clipping,
3877            most_expensive_effect: self.profiler.most_expensive_effect(),
3878            active_reverb_zones: self.reverb_zones.active_blend.len(),
3879        }
3880    }
3881
3882    pub fn get_signal_flow_edges(&self) -> &[SignalFlowEdge] {
3883        &self.signal_flow.edges
3884    }
3885
3886    pub fn get_bus_level_db(&self, bus_id: u64) -> (f32, f32) {
3887        if let Some(meter) = self.level_meters.get(&bus_id) {
3888            (meter.peak_hold_l_db(), meter.peak_hold_r_db())
3889        } else { (-120.0, -120.0) }
3890    }
3891
3892    pub fn get_spectrum_data(&self) -> &[f32] {
3893        &self.master_spectrum.magnitude_l
3894    }
3895}
3896
3897#[derive(Debug, Clone)]
3898pub struct MixingReport {
3899    pub active_voice_count: usize,
3900    pub virtual_voice_count: usize,
3901    pub bus_count: usize,
3902    pub cpu_estimate: f32,
3903    pub sound_bank_mb: f32,
3904    pub master_rms_db: f32,
3905    pub master_peak_db: f32,
3906    pub integrated_lufs: f32,
3907    pub short_term_lufs: f32,
3908    pub is_clipping: bool,
3909    pub most_expensive_effect: Option<(EffectType, f32)>,
3910    pub active_reverb_zones: usize,
3911}
3912
3913// ============================================================
3914// ADVANCED EFFECTS: TRANSIENT SHAPER
3915// ============================================================
3916// ============================================================
3917// MULTI-BAND COMPRESSOR
3918// ============================================================
3919
3920#[derive(Debug, Clone)]
3921pub struct MultiBandCompressor {
3922    pub band_count: usize,
3923    pub crossover_freqs: Vec<f32>,
3924    pub compressors: Vec<Compressor>,
3925    pub crossover_filters_l: Vec<[BiquadState; 2]>,
3926    pub crossover_filters_r: Vec<[BiquadState; 2]>,
3927    pub crossover_coeffs: Vec<[BiquadCoefficients; 2]>,
3928    pub band_gains_db: Vec<f32>,
3929    pub is_enabled: bool,
3930}
3931
3932impl MultiBandCompressor {
3933    pub fn new_three_band(low_mid_hz: f32, mid_high_hz: f32) -> Self {
3934        let crossover_freqs = vec![low_mid_hz, mid_high_hz];
3935        let band_count = 3;
3936        let mut compressors = Vec::new();
3937        for _ in 0..band_count {
3938            compressors.push(Compressor::new(CompressorParams::default()));
3939        }
3940        let mut crossover_filters_l = Vec::new();
3941        let mut crossover_filters_r = Vec::new();
3942        let mut crossover_coeffs = Vec::new();
3943
3944        for &freq in &crossover_freqs {
3945            crossover_filters_l.push([BiquadState::new(), BiquadState::new()]);
3946            crossover_filters_r.push([BiquadState::new(), BiquadState::new()]);
3947            crossover_coeffs.push([
3948                BiquadCoefficients::low_pass(freq, 0.707, SAMPLE_RATE),
3949                BiquadCoefficients::high_pass(freq, 0.707, SAMPLE_RATE),
3950            ]);
3951        }
3952
3953        Self {
3954            band_count,
3955            crossover_freqs,
3956            compressors,
3957            crossover_filters_l,
3958            crossover_filters_r,
3959            crossover_coeffs,
3960            band_gains_db: vec![0.0; band_count],
3961            is_enabled: true,
3962        }
3963    }
3964
3965    pub fn process_sample(&mut self, in_l: f32, in_r: f32) -> (f32, f32) {
3966        if !self.is_enabled { return (in_l, in_r); }
3967
3968        // Split into bands via crossover filters
3969        let mut bands_l = vec![in_l; self.band_count];
3970        let mut bands_r = vec![in_r; self.band_count];
3971
3972        if self.crossover_freqs.len() >= 1 {
3973            let (coeff_l, coeff_h) = (&self.crossover_coeffs[0][0], &self.crossover_coeffs[0][1]);
3974            bands_l[0] = self.crossover_filters_l[0][0].process(in_l, coeff_l);
3975            bands_r[0] = self.crossover_filters_r[0][0].process(in_r, coeff_l);
3976            let high_l = self.crossover_filters_l[0][1].process(in_l, coeff_h);
3977            let high_r = self.crossover_filters_r[0][1].process(in_r, coeff_h);
3978            if self.crossover_freqs.len() >= 2 {
3979                let (coeff_l2, coeff_h2) = (&self.crossover_coeffs[1][0], &self.crossover_coeffs[1][1]);
3980                bands_l[1] = self.crossover_filters_l[1][0].process(high_l, coeff_l2);
3981                bands_r[1] = self.crossover_filters_r[1][0].process(high_r, coeff_l2);
3982                bands_l[2] = self.crossover_filters_l[1][1].process(high_l, coeff_h2);
3983                bands_r[2] = self.crossover_filters_r[1][1].process(high_r, coeff_h2);
3984            }
3985        }
3986
3987        // Compress and recombine
3988        let mut out_l = 0.0f32;
3989        let mut out_r = 0.0f32;
3990        for i in 0..self.band_count {
3991            let (bl, br) = self.compressors[i].process_sample(bands_l[i], bands_r[i]);
3992            let band_gain = db_to_linear(self.band_gains_db[i]);
3993            out_l += bl * band_gain;
3994            out_r += br * band_gain;
3995        }
3996        (out_l, out_r)
3997    }
3998}
3999
4000// ============================================================
4001// SEND/RETURN SYSTEM
4002// ============================================================
4003// ============================================================
4004// EFFECT PRESET LIBRARY
4005// ============================================================
4006
4007#[derive(Debug, Clone)]
4008pub struct EffectPreset {
4009    pub id: u64,
4010    pub name: String,
4011    pub effect_type: EffectType,
4012    pub parameters: HashMap<String, f32>,
4013    pub tags: Vec<String>,
4014}
4015
4016impl EffectPreset {
4017    pub fn new(id: u64, name: String, effect_type: EffectType) -> Self {
4018        Self {
4019            id,
4020            name,
4021            effect_type,
4022            parameters: HashMap::new(),
4023            tags: Vec::new(),
4024        }
4025    }
4026
4027    pub fn set_param(&mut self, name: &str, value: f32) {
4028        self.parameters.insert(name.to_string(), value);
4029    }
4030
4031    pub fn get_param(&self, name: &str, default: f32) -> f32 {
4032        self.parameters.get(name).copied().unwrap_or(default)
4033    }
4034}
4035
4036#[derive(Debug)]
4037pub struct EffectPresetLibrary {
4038    pub presets: HashMap<u64, EffectPreset>,
4039    pub next_id: u64,
4040}
4041
4042impl EffectPresetLibrary {
4043    pub fn new() -> Self {
4044        let mut lib = Self { presets: HashMap::new(), next_id: 1 };
4045        lib.add_defaults();
4046        lib
4047    }
4048
4049    fn add_defaults(&mut self) {
4050        // Default reverb preset
4051        let mut reverb = EffectPreset::new(self.next_id, "Large Hall".into(), EffectType::Reverb);
4052        reverb.set_param("room_size", 0.85);
4053        reverb.set_param("damping", 0.3);
4054        reverb.set_param("wet_mix", 0.4);
4055        reverb.set_param("pre_delay_ms", 20.0);
4056        self.presets.insert(self.next_id, reverb);
4057        self.next_id += 1;
4058
4059        // Default compressor
4060        let mut comp = EffectPreset::new(self.next_id, "Gentle Glue".into(), EffectType::Compressor);
4061        comp.set_param("threshold_db", -18.0);
4062        comp.set_param("ratio", 2.0);
4063        comp.set_param("attack_ms", 20.0);
4064        comp.set_param("release_ms", 200.0);
4065        comp.set_param("makeup_db", 3.0);
4066        self.presets.insert(self.next_id, comp);
4067        self.next_id += 1;
4068
4069        // Broadcast limiter
4070        let mut lim = EffectPreset::new(self.next_id, "Broadcast Limiter".into(), EffectType::Limiter);
4071        lim.set_param("ceiling_db", -1.0);
4072        lim.set_param("release_ms", 50.0);
4073        self.presets.insert(self.next_id, lim);
4074        self.next_id += 1;
4075    }
4076
4077    pub fn presets_by_type(&self, effect_type: EffectType) -> Vec<&EffectPreset> {
4078        self.presets.values().filter(|p| p.effect_type == effect_type).collect()
4079    }
4080
4081    pub fn create_effect_from_preset(&self, preset_id: u64) -> Option<AudioEffect> {
4082        let preset = self.presets.get(&preset_id)?;
4083        match preset.effect_type {
4084            EffectType::Reverb => {
4085                let mut r = SchroederReverb::new();
4086                r.room_size = preset.get_param("room_size", 0.5);
4087                r.damping = preset.get_param("damping", 0.5);
4088                r.wet_mix = preset.get_param("wet_mix", 0.3);
4089                r.pre_delay_ms = preset.get_param("pre_delay_ms", 10.0);
4090                Some(AudioEffect::Reverb(r))
4091            }
4092            EffectType::Compressor => {
4093                let params = CompressorParams {
4094                    threshold_db: preset.get_param("threshold_db", -18.0),
4095                    ratio: preset.get_param("ratio", 4.0),
4096                    attack_ms: preset.get_param("attack_ms", 10.0),
4097                    release_ms: preset.get_param("release_ms", 100.0),
4098                    makeup_gain_db: preset.get_param("makeup_db", 0.0),
4099                    ..Default::default()
4100                };
4101                Some(AudioEffect::Compressor(Compressor::new(params)))
4102            }
4103            EffectType::Limiter => {
4104                let ceiling = preset.get_param("ceiling_db", -1.0);
4105                let mut lim = Limiter::new(ceiling);
4106                lim.release_ms = preset.get_param("release_ms", 50.0);
4107                Some(AudioEffect::Limiter(lim))
4108            }
4109            _ => None,
4110        }
4111    }
4112
4113    pub fn add_preset(&mut self, preset: EffectPreset) -> u64 {
4114        let id = self.next_id;
4115        self.next_id += 1;
4116        self.presets.insert(id, preset);
4117        id
4118    }
4119}
4120
4121// ============================================================
4122// REAL-TIME PARAMETER PREVIEW
4123// ============================================================
4124
4125#[derive(Debug, Clone)]
4126pub struct ParameterChange {
4127    pub bus_id: u64,
4128    pub effect_index: Option<usize>,
4129    pub parameter_name: String,
4130    pub old_value: f32,
4131    pub new_value: f32,
4132    pub timestamp_s: f64,
4133}
4134
4135#[derive(Debug)]
4136pub struct RealTimeParameterPreview {
4137    pub active_changes: Vec<ParameterChange>,
4138    pub change_history: VecDeque<ParameterChange>,
4139    pub history_capacity: usize,
4140    pub preview_buffer_l: Vec<f32>,
4141    pub preview_buffer_r: Vec<f32>,
4142    pub buffer_size: usize,
4143}
4144
4145impl RealTimeParameterPreview {
4146    pub fn new(buffer_size: usize) -> Self {
4147        Self {
4148            active_changes: Vec::new(),
4149            change_history: VecDeque::with_capacity(256),
4150            history_capacity: 256,
4151            preview_buffer_l: vec![0.0; buffer_size],
4152            preview_buffer_r: vec![0.0; buffer_size],
4153            buffer_size,
4154        }
4155    }
4156
4157    pub fn record_change(&mut self, change: ParameterChange) {
4158        if self.change_history.len() >= self.history_capacity {
4159            self.change_history.pop_front();
4160        }
4161        self.change_history.push_back(change.clone());
4162        self.active_changes.retain(|c| !(c.bus_id == change.bus_id && c.parameter_name == change.parameter_name));
4163        self.active_changes.push(change);
4164    }
4165
4166    pub fn fill_preview_with_sine(&mut self, freq_hz: f32, amplitude: f32) {
4167        for (i, (l, r)) in self.preview_buffer_l.iter_mut().zip(self.preview_buffer_r.iter_mut()).enumerate() {
4168            let t = i as f32 / SAMPLE_RATE;
4169            let sample = (TWO_PI * freq_hz * t).sin() * amplitude;
4170            *l = sample;
4171            *r = sample;
4172        }
4173    }
4174
4175    pub fn process_preview_through_effect(&mut self, effect: &mut AudioEffect) {
4176        for i in 0..self.buffer_size {
4177            let (l, r) = effect.process_sample(self.preview_buffer_l[i], self.preview_buffer_r[i]);
4178            self.preview_buffer_l[i] = l;
4179            self.preview_buffer_r[i] = r;
4180        }
4181    }
4182
4183    pub fn preview_rms_db(&self) -> f32 {
4184        let sum_sq: f32 = self.preview_buffer_l.iter()
4185            .zip(self.preview_buffer_r.iter())
4186            .map(|(l, r)| l * l + r * r)
4187            .sum();
4188        let rms = (sum_sq / (2.0 * self.buffer_size as f32)).sqrt();
4189        linear_to_db(rms.max(1e-9))
4190    }
4191}
4192
4193// ============================================================
4194// AUDIO MIXER COMMAND SYSTEM
4195// ============================================================
4196
4197#[derive(Debug, Clone)]
4198pub enum AudioMixerCommand {
4199    SetBusGain { bus_id: u64, old_db: f32, new_db: f32 },
4200    SetBusPan { bus_id: u64, old_pan: f32, new_pan: f32 },
4201    SetBusMute { bus_id: u64, old: bool, new: bool },
4202    AddEffect { bus_id: u64, effect_type: EffectType },
4203    RemoveEffect { bus_id: u64, index: usize },
4204    MoveEffect { bus_id: u64, from: usize, to: usize },
4205    AddBusRoute { from: u64, to: u64, level: f32 },
4206    RemoveBusRoute { from: u64, to: u64 },
4207    AddAutomationKeyframe { lane_idx: usize, time_s: f32, value: f32 },
4208}
4209
4210#[derive(Debug)]
4211pub struct AudioMixerCommandHistory {
4212    pub undo_stack: Vec<AudioMixerCommand>,
4213    pub redo_stack: Vec<AudioMixerCommand>,
4214    pub max_history: usize,
4215}
4216
4217impl AudioMixerCommandHistory {
4218    pub fn new(max_history: usize) -> Self {
4219        Self { undo_stack: Vec::new(), redo_stack: Vec::new(), max_history }
4220    }
4221
4222    pub fn push(&mut self, cmd: AudioMixerCommand) {
4223        self.redo_stack.clear();
4224        if self.undo_stack.len() >= self.max_history { self.undo_stack.remove(0); }
4225        self.undo_stack.push(cmd);
4226    }
4227
4228    pub fn undo(&mut self) -> Option<AudioMixerCommand> {
4229        let cmd = self.undo_stack.pop()?;
4230        self.redo_stack.push(cmd.clone());
4231        Some(cmd)
4232    }
4233
4234    pub fn redo(&mut self) -> Option<AudioMixerCommand> {
4235        let cmd = self.redo_stack.pop()?;
4236        self.undo_stack.push(cmd.clone());
4237        Some(cmd)
4238    }
4239
4240    pub fn can_undo(&self) -> bool { !self.undo_stack.is_empty() }
4241    pub fn can_redo(&self) -> bool { !self.redo_stack.is_empty() }
4242}
4243
4244pub fn apply_audio_command(editor: &mut AudioMixerEditor, cmd: &AudioMixerCommand) {
4245    match cmd {
4246        AudioMixerCommand::SetBusGain { bus_id, new_db, .. } => {
4247            editor.set_bus_gain(*bus_id, *new_db);
4248        }
4249        AudioMixerCommand::SetBusPan { bus_id, new_pan, .. } => {
4250            if let Some(bus) = editor.signal_flow.buses.get_mut(bus_id) {
4251                bus.pan = *new_pan;
4252            }
4253        }
4254        AudioMixerCommand::SetBusMute { bus_id, new, .. } => {
4255            editor.set_bus_mute(*bus_id, *new);
4256        }
4257        AudioMixerCommand::AddBusRoute { from, to, level } => {
4258            editor.route_bus(*from, *to, *level);
4259        }
4260        AudioMixerCommand::RemoveBusRoute { from, to } => {
4261            editor.unroute_bus(*from, *to);
4262        }
4263        AudioMixerCommand::MoveEffect { bus_id, from, to } => {
4264            if let Some(bus) = editor.signal_flow.buses.get_mut(bus_id) {
4265                bus.effect_chain.move_effect(*from, *to);
4266            }
4267        }
4268        AudioMixerCommand::RemoveEffect { bus_id, index } => {
4269            editor.remove_effect_from_bus(*bus_id, *index);
4270        }
4271        _ => {}
4272    }
4273}
4274
4275pub fn undo_audio_command(editor: &mut AudioMixerEditor, cmd: &AudioMixerCommand) {
4276    match cmd {
4277        AudioMixerCommand::SetBusGain { bus_id, old_db, .. } => {
4278            editor.set_bus_gain(*bus_id, *old_db);
4279        }
4280        AudioMixerCommand::SetBusPan { bus_id, old_pan, .. } => {
4281            if let Some(bus) = editor.signal_flow.buses.get_mut(bus_id) {
4282                bus.pan = *old_pan;
4283            }
4284        }
4285        AudioMixerCommand::SetBusMute { bus_id, old, .. } => {
4286            editor.set_bus_mute(*bus_id, *old);
4287        }
4288        AudioMixerCommand::AddBusRoute { from, to, .. } => {
4289            editor.unroute_bus(*from, *to);
4290        }
4291        AudioMixerCommand::RemoveBusRoute { from, to } => {
4292            editor.route_bus(*from, *to, 1.0);
4293        }
4294        _ => {}
4295    }
4296}
4297
4298// ============================================================
4299// TESTS
4300// ============================================================
4301
4302#[cfg(test)]
4303mod tests {
4304    use super::*;
4305
4306    #[test]
4307    fn test_db_conversions() {
4308        assert!((db_to_linear(0.0) - 1.0).abs() < 1e-5);
4309        assert!((db_to_linear(6.0) - 1.9953).abs() < 0.001);
4310        assert!((linear_to_db(1.0) - 0.0).abs() < 1e-4);
4311        assert!((linear_to_db(2.0) - 6.0206).abs() < 0.01);
4312    }
4313
4314    #[test]
4315    fn test_biquad_identity() {
4316        let coeff = BiquadCoefficients::identity();
4317        let mut state = BiquadState::new();
4318        assert!((state.process(0.5, &coeff) - 0.5).abs() < 1e-6);
4319        assert!((state.process(1.0, &coeff) - 1.0).abs() < 1e-6);
4320    }
4321
4322    #[test]
4323    fn test_low_pass_attenuates_above_cutoff() {
4324        let coeff = BiquadCoefficients::low_pass(100.0, 0.707, SAMPLE_RATE);
4325        let mut state = BiquadState::new();
4326        // Feed a high-frequency signal (10kHz) - should be heavily attenuated
4327        let mut output_sum = 0.0f32;
4328        for i in 0..1000 {
4329            let sample = (TWO_PI * 10000.0 * i as f32 / SAMPLE_RATE).sin();
4330            let out = state.process(sample, &coeff);
4331            output_sum += out.abs();
4332        }
4333        // Should be near zero for 10kHz with 100Hz cutoff
4334        assert!(output_sum / 1000.0 < 0.01);
4335    }
4336
4337    #[test]
4338    fn test_compressor_no_compression_below_threshold() {
4339        let params = CompressorParams {
4340            threshold_db: 0.0,
4341            ..Default::default()
4342        };
4343        let mut comp = Compressor::new(params);
4344        // Signal well below threshold (silence)
4345        let (l, r) = comp.process_sample(0.001, 0.001);
4346        // Should pass through almost unchanged
4347        assert!(l.abs() < 0.01);
4348    }
4349
4350    #[test]
4351    fn test_adsr_envelope() {
4352        let mut env = AdsrEnvelope::new(0.01, 0.1, 0.7, 0.2);
4353        env.trigger_attack();
4354        let dt = 1.0 / SAMPLE_RATE;
4355        // Process through attack
4356        for _ in 0..((0.01 * SAMPLE_RATE) as usize + 10) {
4357            env.tick(dt);
4358        }
4359        assert!(env.stage == EnvelopeStage::Decay || env.stage == EnvelopeStage::Sustain);
4360        // Process to sustain
4361        for _ in 0..(SAMPLE_RATE as usize) {
4362            env.tick(dt);
4363        }
4364        assert!((env.value - 0.7).abs() < 0.01);
4365        env.trigger_release();
4366        for _ in 0..(SAMPLE_RATE as usize) {
4367            env.tick(dt);
4368        }
4369        assert!(env.value < 0.01);
4370    }
4371
4372    #[test]
4373    fn test_lfo_sine() {
4374        let mut lfo = Lfo::new(LfoShape::Sine, 1.0, 1.0);
4375        let samples: Vec<f32> = (0..SAMPLE_RATE as usize).map(|_| lfo.tick()).collect();
4376        let max = samples.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
4377        let min = samples.iter().cloned().fold(f32::INFINITY, f32::min);
4378        assert!((max - 1.0).abs() < 0.01);
4379        assert!((min + 1.0).abs() < 0.01);
4380    }
4381
4382    #[test]
4383    fn test_schroeder_reverb_wet() {
4384        let mut reverb = SchroederReverb::new();
4385        reverb.wet_mix = 1.0;
4386        reverb.dry_mix = 0.0;
4387        // Feed a pulse
4388        let (l, r) = reverb.process_sample(1.0, 1.0);
4389        // Should produce something (reverb tail)
4390        // After the impulse, feed silence
4391        let mut has_tail = false;
4392        for _ in 0..4000 {
4393            let (l2, r2) = reverb.process_sample(0.0, 0.0);
4394            if l2.abs() > 0.001 || r2.abs() > 0.001 { has_tail = true; }
4395        }
4396        assert!(has_tail, "Reverb should produce a tail");
4397    }
4398
4399    #[test]
4400    fn test_delay_ping_pong() {
4401        let mut delay = DelayEffect::new(100.0);
4402        delay.ping_pong = true;
4403        delay.feedback = 0.5;
4404        delay.wet_mix = 1.0;
4405        delay.dry_mix = 0.0;
4406        // Process a bunch of samples; should not blow up
4407        let mut max_out = 0.0f32;
4408        for i in 0..10000 {
4409            let input = if i == 0 { 1.0 } else { 0.0 };
4410            let (l, r) = delay.process_sample(input, 0.0);
4411            max_out = max_out.max(l.abs()).max(r.abs());
4412        }
4413        assert!(max_out < 2.0, "Ping-pong delay should not blow up");
4414    }
4415
4416    #[test]
4417    fn test_spatializer_doppler() {
4418        let ratio = DopplerCalculator::compute_pitch_ratio(
4419            Vec3::new(100.0, 0.0, 0.0), // source far right
4420            Vec3::ZERO,                   // listener at origin
4421            Vec3::new(-10.0, 0.0, 0.0), // source moving toward listener
4422            Vec3::ZERO,
4423            1.0,
4424        );
4425        // Moving source toward listener should increase pitch
4426        assert!(ratio > 1.0, "Source approaching = higher pitch, got {}", ratio);
4427    }
4428
4429    #[test]
4430    fn test_bit_crusher() {
4431        let mut bc = BitCrusherEffect::new(8.0, 1);
4432        let mut max_error = 0.0f32;
4433        for i in 0..100 {
4434            let x = (i as f32 / 100.0) * 2.0 - 1.0;
4435            let (out, _) = bc.process_sample(x, 0.0);
4436            // 8-bit quantization: step size = 1/128 = 0.0078125
4437            let error = (out - x).abs();
4438            max_error = max_error.max(error);
4439        }
4440        // Max error should be about half a step: 0.0039
4441        assert!(max_error < 0.01, "8-bit quantization error too large: {}", max_error);
4442    }
4443
4444    #[test]
4445    fn test_fft_magnitude_impulse() {
4446        let mut analyzer = SpectrumAnalyzer::new(32);
4447        // An impulse in the middle of the window, where the Hann window is
4448        // not zero, then silence; the spectrum updates every hop.
4449        for i in 0..SPECTRUM_FFT_SIZE {
4450            let s = if i == SPECTRUM_FFT_SIZE / 2 { 1.0 } else { 0.0 };
4451            analyzer.push_samples(s, s);
4452        }
4453        // Impulse has a flat spectrum: every band is above the silence floor.
4454        // Magnitudes are in dB, with -120 meaning nothing at all.
4455        let floor = -119.0;
4456        assert!(
4457            analyzer.magnitude_l.iter().all(|m| *m > floor),
4458            "FFT of impulse should have energy in every band: {:?}",
4459            analyzer.magnitude_l
4460        );
4461    }
4462
4463    #[test]
4464    fn test_beat_tracker() {
4465        let mut tracker = BeatTracker::new(120.0);
4466        tracker.is_running = true;
4467        let seconds_per_beat = 0.5; // 120 BPM
4468        let dt = 1.0 / 60.0; // 60 fps
4469        let total_steps = (seconds_per_beat * 4.0 / dt) as usize;
4470        for _ in 0..total_steps {
4471            tracker.tick(dt);
4472        }
4473        assert!(tracker.current_beat >= 3.9, "Expected ~4 beats, got {}", tracker.current_beat);
4474    }
4475
4476    #[test]
4477    fn test_snapshot_blend() {
4478        let mut sys = SnapshotSystem::new();
4479        let id_a = sys.create_snapshot("A".into(), 0.0);
4480        let id_b = sys.create_snapshot("B".into(), 1.0);
4481        {
4482            let snap_a = sys.snapshots.get_mut(&id_a).unwrap();
4483            snap_a.bus_states.insert(1, crate::editor::audio_mixer_editor::BusSnapshot {
4484                bus_id: 1,
4485                gain_db: 0.0,
4486                pan: 0.0,
4487                mute: false,
4488                effect_bypasses: vec![],
4489            });
4490        }
4491        {
4492            let snap_b = sys.snapshots.get_mut(&id_b).unwrap();
4493            snap_b.bus_states.insert(1, crate::editor::audio_mixer_editor::BusSnapshot {
4494                bus_id: 1,
4495                gain_db: -6.0,
4496                pan: 0.5,
4497                mute: false,
4498                effect_bypasses: vec![],
4499            });
4500        }
4501        let blended = sys.snapshots[&id_a].blend_with(&sys.snapshots[&id_b], 0.5);
4502        if let Some(&(gain, pan)) = blended.get(&1) {
4503            assert!((gain - (-3.0)).abs() < 0.01, "Blended gain should be -3.0, got {}", gain);
4504            assert!((pan - 0.25).abs() < 0.01, "Blended pan should be 0.25, got {}", pan);
4505        } else {
4506            panic!("Expected blended bus 1");
4507        }
4508    }
4509
4510    #[test]
4511    fn test_lufs_silence() {
4512        let mut meter = LufsMeter::new();
4513        for _ in 0..10000 {
4514            meter.process_sample(0.0, 0.0);
4515        }
4516        assert!(meter.integrated_lufs == f32::NEG_INFINITY || meter.integrated_lufs < -60.0);
4517    }
4518
4519    #[test]
4520    fn test_voice_manager_spawn_and_cull() {
4521        let mut vm = VoiceManager::new(4);
4522        for i in 0..6 {
4523            vm.spawn_voice(i, SoundCategory::Sfx, Vec3::ZERO);
4524        }
4525        // Max 4 voices total
4526        assert!(vm.voices.len() <= 4);
4527    }
4528
4529    #[test]
4530    fn test_compressor_gain_reduction() {
4531        let params = CompressorParams {
4532            threshold_db: -20.0,
4533            ratio: 4.0,
4534            knee_db: 0.0,
4535            ..Default::default()
4536        };
4537        let comp = Compressor::new(params);
4538        // Signal at 0 dB (full scale): 20 dB above threshold
4539        // Gain reduction should be 20*(1-1/4) = 15 dB
4540        let gr = comp.compute_gain_db(0.0);
4541        assert!((gr + 15.0).abs() < 0.1, "Expected -15dB GR, got {}", gr);
4542    }
4543
4544    #[test]
4545    fn test_eq_frequency_response() {
4546        let eq = ParametricEqualizer::new();
4547        // At 1kHz with all bands at 0dB gain, response should be ~0dB
4548        let response = eq.frequency_response_db(1000.0);
4549        assert!(response.abs() < 2.0, "Flat EQ response at 1kHz should be near 0dB, got {}", response);
4550    }
4551
4552    #[test]
4553    fn test_signal_flow_graph_topology() {
4554        let graph = SignalFlowGraph::new();
4555        // Default graph has 6 buses; topology should be populated
4556        assert!(!graph.topology_order.is_empty());
4557        // Master should be last in topology (all routes lead to it)
4558        assert_eq!(*graph.topology_order.last().unwrap(), graph.master_bus_id);
4559    }
4560
4561    #[test]
4562    fn test_automation_lane_interpolation() {
4563        let mut lane = AutomationLane::new("gain_db".into(), 1);
4564        lane.add_keyframe(AutomationKeyframe::new(0.0, -20.0));
4565        lane.add_keyframe(AutomationKeyframe::new(1.0, 0.0));
4566        let mid = lane.evaluate_at(0.5);
4567        assert!((mid - (-10.0)).abs() < 0.1, "Expected -10dB at t=0.5, got {}", mid);
4568    }
4569
4570    #[test]
4571    fn test_hrtf_stereo_separation() {
4572        let mut hrtf = HrtfFilter::new(0.0, std::f32::consts::FRAC_PI_2); // 90 degrees right
4573        let (l, r) = hrtf.process_mono(1.0);
4574        // Right-panned source should have different L/R levels
4575        // (just verify it produces output)
4576        assert!(l.is_finite() && r.is_finite());
4577    }
4578
4579    #[test]
4580    fn test_reverb_zone_blend() {
4581        let mut mgr = ReverbZoneManager::new();
4582        let id = mgr.add_zone("Test".into(), Vec3::ZERO, 10.0, 5.0);
4583        mgr.update(Vec3::new(5.0, 0.0, 0.0)); // inside zone
4584        assert!(mgr.active_blend.contains_key(&id));
4585        let blend = mgr.active_blend[&id];
4586        assert!(blend > 0.0 && blend <= 1.0);
4587    }
4588
4589    #[test]
4590    fn test_distortion_soft_clip_bounded() {
4591        let mut dist = DistortionEffect::new(DistortionMode::SoftClip, 10.0);
4592        for i in 0..1000 {
4593            let x = (i as f32 / 500.0) - 1.0;
4594            let (l, _) = dist.process_sample(x, 0.0);
4595            assert!(l.is_finite(), "Distortion output must be finite");
4596            assert!(l.abs() < 2.0, "Soft clip should bound output");
4597        }
4598    }
4599
4600    #[test]
4601    fn test_multiband_compressor_passthrough() {
4602        let mut mbc = MultiBandCompressor::new_three_band(300.0, 3000.0);
4603        // With default (low threshold relative to silence), should mostly pass through
4604        let (l, r) = mbc.process_sample(0.5, -0.5);
4605        assert!(l.is_finite() && r.is_finite());
4606    }
4607
4608    #[test]
4609    fn test_semitones_to_ratio() {
4610        // One octave = 12 semitones = ratio 2.0
4611        assert!((semitones_to_ratio(12.0) - 2.0).abs() < 1e-5);
4612        // Unison
4613        assert!((semitones_to_ratio(0.0) - 1.0).abs() < 1e-5);
4614        // Perfect fifth (7 semitones) ≈ 1.498
4615        assert!((semitones_to_ratio(7.0) - 1.498).abs() < 0.01);
4616    }
4617}
4618
4619// ============================================================
4620// SECTION: Parametric EQ Strip
4621// ============================================================
4622
4623#[derive(Clone, Debug)]
4624pub struct EqBand {
4625    pub band_type: EqBandType,
4626    pub frequency_hz: f32,
4627    pub gain_db: f32,
4628    pub q: f32,
4629    pub enabled: bool,
4630    coefficients: BiquadCoefficients,
4631    state_l: BiquadState,
4632    state_r: BiquadState,
4633}
4634
4635#[derive(Clone, Debug, PartialEq)]
4636pub enum EqBandType {
4637    LowCut,
4638    LowShelf,
4639    Peak,
4640    Notch,
4641    HighShelf,
4642    HighCut,
4643    AllPass,
4644}
4645
4646impl EqBand {
4647    pub fn new(band_type: EqBandType, frequency_hz: f32, gain_db: f32, q: f32) -> Self {
4648        let coefficients = Self::compute_coefficients(&band_type, frequency_hz, gain_db, q);
4649        Self {
4650            band_type,
4651            frequency_hz,
4652            gain_db,
4653            q,
4654            enabled: true,
4655            coefficients,
4656            state_l: BiquadState::new(),
4657            state_r: BiquadState::new(),
4658        }
4659    }
4660
4661    fn compute_coefficients(band_type: &EqBandType, freq: f32, gain_db: f32, q: f32) -> BiquadCoefficients {
4662        match band_type {
4663            EqBandType::LowCut   => BiquadCoefficients::high_pass(freq, q, SAMPLE_RATE),
4664            EqBandType::HighCut  => BiquadCoefficients::low_pass(freq, q, SAMPLE_RATE),
4665            EqBandType::LowShelf => BiquadCoefficients::low_shelf(freq, q, gain_db, SAMPLE_RATE),
4666            EqBandType::HighShelf=> BiquadCoefficients::high_shelf(freq, q, gain_db, SAMPLE_RATE),
4667            EqBandType::Peak     => BiquadCoefficients::peaking_eq(freq, q, gain_db, SAMPLE_RATE),
4668            EqBandType::Notch    => BiquadCoefficients::band_pass(freq, q, SAMPLE_RATE),
4669            EqBandType::AllPass  => BiquadCoefficients::all_pass(freq, q, SAMPLE_RATE),
4670        }
4671    }
4672
4673    pub fn update_parameters(&mut self, frequency_hz: f32, gain_db: f32, q: f32) {
4674        self.frequency_hz = frequency_hz;
4675        self.gain_db = gain_db;
4676        self.q = q;
4677        self.coefficients = Self::compute_coefficients(&self.band_type, frequency_hz, gain_db, q);
4678    }
4679
4680    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
4681        if !self.enabled {
4682            return (left, right);
4683        }
4684        let l = self.state_l.process(left, &self.coefficients);
4685        let r = self.state_r.process(right, &self.coefficients);
4686        (l, r)
4687    }
4688
4689    pub fn frequency_response_at(&self, freq_hz: f32) -> f32 {
4690        // Evaluate |H(e^{jw})| at a specific frequency
4691        // w = 2*pi*freq_hz/sample_rate
4692        let w = TWO_PI * freq_hz / SAMPLE_RATE;
4693        let z_re = w.cos();
4694        let z_im = -w.sin();
4695        // numerator: b0 + b1*z^-1 + b2*z^-2
4696        let num_re = self.coefficients.b0 + self.coefficients.b1 * z_re
4697            + self.coefficients.b2 * (z_re * z_re - z_im * z_im);
4698        let num_im = self.coefficients.b1 * z_im
4699            + self.coefficients.b2 * 2.0 * z_re * z_im;
4700        // denominator: 1 + a1*z^-1 + a2*z^-2
4701        let den_re = 1.0 + self.coefficients.a1 * z_re
4702            + self.coefficients.a2 * (z_re * z_re - z_im * z_im);
4703        let den_im = self.coefficients.a1 * z_im
4704            + self.coefficients.a2 * 2.0 * z_re * z_im;
4705        let num_mag_sq = num_re * num_re + num_im * num_im;
4706        let den_mag_sq = den_re * den_re + den_im * den_im;
4707        if den_mag_sq < 1e-30 { return 0.0; }
4708        (num_mag_sq / den_mag_sq).sqrt()
4709    }
4710}
4711
4712#[derive(Clone, Debug)]
4713pub struct ParametricEqStrip {
4714    pub bands: Vec<EqBand>,
4715    pub name: String,
4716    pub bypass: bool,
4717}
4718
4719impl ParametricEqStrip {
4720    pub fn new(name: &str) -> Self {
4721        Self {
4722            name: name.to_string(),
4723            bands: Vec::new(),
4724            bypass: false,
4725        }
4726    }
4727
4728    pub fn add_band(&mut self, band: EqBand) -> usize {
4729        let idx = self.bands.len();
4730        self.bands.push(band);
4731        idx
4732    }
4733
4734    pub fn remove_band(&mut self, index: usize) {
4735        if index < self.bands.len() {
4736            self.bands.remove(index);
4737        }
4738    }
4739
4740    pub fn process_sample(&mut self, mut left: f32, mut right: f32) -> (f32, f32) {
4741        if self.bypass { return (left, right); }
4742        for band in &mut self.bands {
4743            let (l, r) = band.process_sample(left, right);
4744            left = l;
4745            right = r;
4746        }
4747        (left, right)
4748    }
4749
4750    pub fn frequency_response_at(&self, freq_hz: f32) -> f32 {
4751        if self.bypass { return 1.0; }
4752        let mut mag = 1.0f32;
4753        for band in &self.bands {
4754            if band.enabled {
4755                mag *= band.frequency_response_at(freq_hz);
4756            }
4757        }
4758        mag
4759    }
4760
4761    pub fn compute_response_curve(&self, num_points: usize) -> Vec<(f32, f32)> {
4762        let min_freq = 20.0f32;
4763        let max_freq = 20000.0f32;
4764        (0..num_points).map(|i| {
4765            let t = i as f32 / (num_points - 1) as f32;
4766            let freq = min_freq * (max_freq / min_freq).powf(t);
4767            let mag = self.frequency_response_at(freq);
4768            let db = if mag > 1e-10 { 20.0 * mag.log10() } else { -120.0 };
4769            (freq, db)
4770        }).collect()
4771    }
4772
4773    pub fn reset_states(&mut self) {
4774        for band in &mut self.bands {
4775            band.state_l = BiquadState::new();
4776            band.state_r = BiquadState::new();
4777        }
4778    }
4779}
4780
4781// ============================================================
4782// SECTION: Stereo Width / Mid-Side Processing
4783// ============================================================
4784
4785#[derive(Clone, Debug)]
4786pub struct StereoWidthProcessor {
4787    pub width: f32,   // 0.0=mono, 1.0=normal, 2.0=extra wide
4788    pub balance: f32, // -1.0=left, 0.0=center, 1.0=right
4789    pub bypass: bool,
4790    // Haas effect: slight delay on one channel
4791    haas_delay_samples: usize,
4792    haas_buffer: VecDeque<f32>,
4793    pub haas_delay_ms: f32,
4794    pub haas_enabled: bool,
4795}
4796
4797impl StereoWidthProcessor {
4798    pub fn new() -> Self {
4799        Self {
4800            width: 1.0,
4801            balance: 0.0,
4802            bypass: false,
4803            haas_delay_samples: 0,
4804            haas_buffer: VecDeque::new(),
4805            haas_delay_ms: 0.0,
4806            haas_enabled: false,
4807        }
4808    }
4809
4810    pub fn set_haas_delay(&mut self, ms: f32) {
4811        self.haas_delay_ms = ms;
4812        self.haas_delay_samples = (ms * 0.001 * SAMPLE_RATE) as usize;
4813        // Resize buffer
4814        while self.haas_buffer.len() < self.haas_delay_samples + 1 {
4815            self.haas_buffer.push_back(0.0);
4816        }
4817    }
4818
4819    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
4820        if self.bypass { return (left, right); }
4821        // Mid-Side encode
4822        let mid = (left + right) * 0.5;
4823        let side = (left - right) * 0.5;
4824        // Apply width to side channel
4825        let side_scaled = side * self.width;
4826        // Decode back to L/R
4827        let mut l_out = mid + side_scaled;
4828        let mut r_out = mid - side_scaled;
4829        // Apply balance (constant power)
4830        let bal_rad = (self.balance + 1.0) * 0.5 * std::f32::consts::FRAC_PI_2;
4831        let l_gain = bal_rad.cos();
4832        let r_gain = bal_rad.sin();
4833        l_out *= l_gain * std::f32::consts::SQRT_2;
4834        r_out *= r_gain * std::f32::consts::SQRT_2;
4835        // Haas effect
4836        if self.haas_enabled && self.haas_delay_samples > 0 {
4837            self.haas_buffer.push_back(l_out);
4838            if self.haas_buffer.len() > self.haas_delay_samples {
4839                let delayed = self.haas_buffer.pop_front().unwrap_or(0.0);
4840                r_out = r_out * 0.7 + delayed * 0.3;
4841            }
4842        }
4843        (l_out, r_out)
4844    }
4845
4846    pub fn encode_mid_side(left: f32, right: f32) -> (f32, f32) {
4847        let mid = (left + right) * 0.5;
4848        let side = (left - right) * 0.5;
4849        (mid, side)
4850    }
4851
4852    pub fn decode_mid_side(mid: f32, side: f32) -> (f32, f32) {
4853        (mid + side, mid - side)
4854    }
4855}
4856
4857// ============================================================
4858// SECTION: Noise Gate
4859// ============================================================
4860
4861#[derive(Clone, Debug)]
4862pub struct NoiseGate {
4863    pub threshold_db: f32,
4864    pub attack_ms: f32,
4865    pub hold_ms: f32,
4866    pub release_ms: f32,
4867    pub range_db: f32,
4868    pub hysteresis_db: f32,
4869    pub bypass: bool,
4870    // Internal state
4871    envelope: f32,
4872    gain: f32,
4873    hold_counter: f32,
4874    state: GateState,
4875    attack_coeff: f32,
4876    release_coeff: f32,
4877}
4878
4879#[derive(Clone, Debug, PartialEq)]
4880pub enum GateState {
4881    Closed,
4882    Opening,
4883    Open,
4884    Holding,
4885    Closing,
4886}
4887
4888impl NoiseGate {
4889    pub fn new() -> Self {
4890        let mut gate = Self {
4891            threshold_db: -60.0,
4892            attack_ms: 1.0,
4893            hold_ms: 50.0,
4894            release_ms: 100.0,
4895            range_db: -80.0,
4896            hysteresis_db: 3.0,
4897            bypass: false,
4898            envelope: 0.0,
4899            gain: 0.0,
4900            hold_counter: 0.0,
4901            state: GateState::Closed,
4902            attack_coeff: 0.0,
4903            release_coeff: 0.0,
4904        };
4905        gate.update_coefficients();
4906        gate
4907    }
4908
4909    fn update_coefficients(&mut self) {
4910        self.attack_coeff = if self.attack_ms > 0.0 {
4911            (-1.0f32 / (self.attack_ms * 0.001 * SAMPLE_RATE)).exp()
4912        } else { 0.0 };
4913        self.release_coeff = if self.release_ms > 0.0 {
4914            (-1.0f32 / (self.release_ms * 0.001 * SAMPLE_RATE)).exp()
4915        } else { 0.0 };
4916    }
4917
4918    pub fn set_attack_ms(&mut self, ms: f32) {
4919        self.attack_ms = ms;
4920        self.update_coefficients();
4921    }
4922
4923    pub fn set_release_ms(&mut self, ms: f32) {
4924        self.release_ms = ms;
4925        self.update_coefficients();
4926    }
4927
4928    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
4929        if self.bypass { return (left, right); }
4930        // Level detection (peak)
4931        let level = left.abs().max(right.abs());
4932        let level_db = if level > 1e-10 { 20.0 * level.log10() } else { -120.0 };
4933        let threshold = db_to_linear(self.threshold_db);
4934        let open_threshold = threshold;
4935        let close_threshold = db_to_linear(self.threshold_db - self.hysteresis_db);
4936        // Envelope follower
4937        if level > self.envelope {
4938            self.envelope = level + self.attack_coeff * (self.envelope - level);
4939        } else {
4940            self.envelope = level + self.release_coeff * (self.envelope - level);
4941        }
4942        // State machine
4943        match &self.state {
4944            GateState::Closed => {
4945                if self.envelope > open_threshold {
4946                    self.state = GateState::Opening;
4947                }
4948            }
4949            GateState::Opening => {
4950                self.gain = (self.gain + (1.0 - self.gain) * (1.0 - self.attack_coeff)).min(1.0);
4951                if self.gain >= 0.999 {
4952                    self.state = GateState::Open;
4953                    self.hold_counter = self.hold_ms * 0.001 * SAMPLE_RATE;
4954                }
4955            }
4956            GateState::Open => {
4957                self.hold_counter -= 1.0;
4958                if self.envelope < close_threshold {
4959                    if self.hold_counter <= 0.0 {
4960                        self.state = GateState::Holding;
4961                        self.hold_counter = self.hold_ms * 0.001 * SAMPLE_RATE;
4962                    }
4963                } else {
4964                    self.hold_counter = self.hold_ms * 0.001 * SAMPLE_RATE;
4965                }
4966            }
4967            GateState::Holding => {
4968                self.hold_counter -= 1.0;
4969                if self.hold_counter <= 0.0 {
4970                    self.state = GateState::Closing;
4971                }
4972                if self.envelope > open_threshold {
4973                    self.state = GateState::Open;
4974                    self.hold_counter = self.hold_ms * 0.001 * SAMPLE_RATE;
4975                }
4976            }
4977            GateState::Closing => {
4978                let min_gain = db_to_linear(self.range_db);
4979                self.gain = (self.gain - (self.gain - min_gain) * (1.0 - self.release_coeff)).max(min_gain);
4980                if self.gain <= min_gain + 0.001 {
4981                    self.state = GateState::Closed;
4982                }
4983                if self.envelope > open_threshold {
4984                    self.state = GateState::Opening;
4985                }
4986            }
4987        }
4988        let _ = level_db; // used only for clarity
4989        (left * self.gain, right * self.gain)
4990    }
4991
4992    pub fn is_open(&self) -> bool {
4993        matches!(self.state, GateState::Open | GateState::Opening | GateState::Holding)
4994    }
4995}
4996
4997// ============================================================
4998// SECTION: Harmonic Exciter / Saturation
4999// ============================================================
5000
5001#[derive(Clone, Debug)]
5002pub struct HarmonicExciter {
5003    pub drive: f32,         // 0.0–1.0
5004    pub mix: f32,           // 0.0–1.0 wet
5005    pub harmonic_order: u32, // 2=even, 3=odd harmonics
5006    pub bypass: bool,
5007    hp_filter: BiquadCoefficients,
5008    hp_state_l: BiquadState,
5009    hp_state_r: BiquadState,
5010    lp_filter: BiquadCoefficients,
5011    lp_state_l: BiquadState,
5012    lp_state_r: BiquadState,
5013}
5014
5015impl HarmonicExciter {
5016    pub fn new() -> Self {
5017        Self {
5018            drive: 0.5,
5019            mix: 0.3,
5020            harmonic_order: 2,
5021            bypass: false,
5022            hp_filter: BiquadCoefficients::high_pass(3000.0, 0.707, SAMPLE_RATE),
5023            hp_state_l: BiquadState::new(),
5024            hp_state_r: BiquadState::new(),
5025            lp_filter: BiquadCoefficients::low_pass(8000.0, 0.707, SAMPLE_RATE),
5026            lp_state_l: BiquadState::new(),
5027            lp_state_r: BiquadState::new(),
5028        }
5029    }
5030
5031    fn generate_harmonics(&self, x: f32) -> f32 {
5032        let driven = x * (1.0 + self.drive * 5.0);
5033        match self.harmonic_order {
5034            2 => {
5035                // Even harmonics: asymmetric waveshaping
5036                let shaped = driven - driven * driven * driven.signum() * 0.333;
5037                shaped.tanh()
5038            }
5039            3 => {
5040                // Odd harmonics: symmetric waveshaping
5041                driven.tanh()
5042            }
5043            _ => {
5044                // Full harmonic stack: Chebyshev polynomial
5045                let t = 1.0 + driven;
5046                t.tanh() - 0.5 * (2.0 * t).tanh()
5047            }
5048        }
5049    }
5050
5051    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5052        if self.bypass { return (left, right); }
5053        // Split high frequencies only for excitation (presence range)
5054        let l_hi = self.hp_state_l.process(left, &self.hp_filter);
5055        let r_hi = self.hp_state_r.process(right, &self.hp_filter);
5056        // Generate harmonics from the high-frequency content
5057        let l_harm = self.generate_harmonics(l_hi);
5058        let r_harm = self.generate_harmonics(r_hi);
5059        // Low-pass the harmonics to avoid aliasing artifacts
5060        let l_harm_lp = self.lp_state_l.process(l_harm, &self.lp_filter);
5061        let r_harm_lp = self.lp_state_r.process(r_harm, &self.lp_filter);
5062        // Mix back
5063        let l_out = left + l_harm_lp * self.mix;
5064        let r_out = right + r_harm_lp * self.mix;
5065        (l_out, r_out)
5066    }
5067}
5068
5069// ============================================================
5070// SECTION: Transient Shaper
5071// ============================================================
5072
5073#[derive(Clone, Debug)]
5074pub struct TransientShaper {
5075    pub attack_gain_db: f32,   // boost/cut transients
5076    pub sustain_gain_db: f32,  // boost/cut sustain
5077    pub attack_speed: f32,     // 0.0–1.0
5078    pub release_speed: f32,    // 0.0–1.0
5079    pub bypass: bool,
5080    fast_env: f32,
5081    slow_env: f32,
5082    fast_attack_coeff: f32,
5083    fast_release_coeff: f32,
5084    slow_attack_coeff: f32,
5085    slow_release_coeff: f32,
5086}
5087
5088impl TransientShaper {
5089    pub fn new() -> Self {
5090        let mut ts = Self {
5091            attack_gain_db: 6.0,
5092            sustain_gain_db: -3.0,
5093            attack_speed: 0.5,
5094            release_speed: 0.5,
5095            bypass: false,
5096            fast_env: 0.0,
5097            slow_env: 0.0,
5098            fast_attack_coeff: 0.0,
5099            fast_release_coeff: 0.0,
5100            slow_attack_coeff: 0.0,
5101            slow_release_coeff: 0.0,
5102        };
5103        ts.update_coefficients();
5104        ts
5105    }
5106
5107    fn update_coefficients(&mut self) {
5108        // Fast envelope: responds quickly to transients
5109        let fast_attack_ms = 0.5 + (1.0 - self.attack_speed) * 5.0;
5110        let fast_release_ms = 5.0 + (1.0 - self.release_speed) * 20.0;
5111        // Slow envelope: follows sustained content
5112        let slow_attack_ms = fast_attack_ms * 10.0;
5113        let slow_release_ms = fast_release_ms * 10.0;
5114        self.fast_attack_coeff  = (-1.0f32 / (fast_attack_ms * 0.001 * SAMPLE_RATE)).exp();
5115        self.fast_release_coeff = (-1.0f32 / (fast_release_ms * 0.001 * SAMPLE_RATE)).exp();
5116        self.slow_attack_coeff  = (-1.0f32 / (slow_attack_ms * 0.001 * SAMPLE_RATE)).exp();
5117        self.slow_release_coeff = (-1.0f32 / (slow_release_ms * 0.001 * SAMPLE_RATE)).exp();
5118    }
5119
5120    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5121        if self.bypass { return (left, right); }
5122        let level = left.abs().max(right.abs());
5123        // Update fast envelope
5124        let fast_coeff = if level > self.fast_env { self.fast_attack_coeff } else { self.fast_release_coeff };
5125        self.fast_env = level + fast_coeff * (self.fast_env - level);
5126        // Update slow envelope
5127        let slow_coeff = if level > self.slow_env { self.slow_attack_coeff } else { self.slow_release_coeff };
5128        self.slow_env = level + slow_coeff * (self.slow_env - level);
5129        // Transient signal = fast - slow (positive = transient)
5130        let transient = (self.fast_env - self.slow_env).max(0.0);
5131        let sustain = self.slow_env;
5132        // Compute gain adjustment
5133        let attack_gain = db_to_linear(self.attack_gain_db);
5134        let sustain_gain = db_to_linear(self.sustain_gain_db);
5135        let total_env = self.fast_env.max(1e-30);
5136        let gain = 1.0 + (attack_gain - 1.0) * (transient / total_env)
5137                       + (sustain_gain - 1.0) * (sustain / total_env);
5138        let _ = gain;
5139        let clipped_gain = gain.max(0.0).min(4.0);
5140        (left * clipped_gain, right * clipped_gain)
5141    }
5142}
5143
5144// ============================================================
5145// SECTION: Convolution Reverb (FIR approximation)
5146// ============================================================
5147
5148#[derive(Clone, Debug)]
5149pub struct ConvolutionReverb {
5150    pub wet_dry: f32,
5151    pub pre_delay_ms: f32,
5152    pub bypass: bool,
5153    ir_left: Vec<f32>,
5154    ir_right: Vec<f32>,
5155    buffer_l: VecDeque<f32>,
5156    buffer_r: VecDeque<f32>,
5157    pre_delay_buf_l: VecDeque<f32>,
5158    pre_delay_buf_r: VecDeque<f32>,
5159    pre_delay_samples: usize,
5160}
5161
5162impl ConvolutionReverb {
5163    pub fn new_with_ir(ir: Vec<f32>) -> Self {
5164        let len = ir.len();
5165        let mut cr = Self {
5166            wet_dry: 0.3,
5167            pre_delay_ms: 0.0,
5168            bypass: false,
5169            ir_left: ir.clone(),
5170            ir_right: ir,
5171            buffer_l: VecDeque::from(vec![0.0f32; len]),
5172            buffer_r: VecDeque::from(vec![0.0f32; len]),
5173            pre_delay_buf_l: VecDeque::new(),
5174            pre_delay_buf_r: VecDeque::new(),
5175            pre_delay_samples: 0,
5176        };
5177        cr.set_pre_delay(0.0);
5178        cr
5179    }
5180
5181    pub fn new_synthetic_room(size: f32) -> Self {
5182        // Generate a synthetic IR from exponential noise decay
5183        let len = (SAMPLE_RATE * size.clamp(0.1, 5.0)) as usize;
5184        let mut ir = Vec::with_capacity(len);
5185        let decay = (-6.9 / len as f32).exp(); // -60dB decay
5186        let mut env = 1.0f32;
5187        // Simple deterministic pseudo-random using linear congruential
5188        let mut seed = 12345u32;
5189        for _ in 0..len {
5190            seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
5191            let noise = (seed as f32 / u32::MAX as f32) * 2.0 - 1.0;
5192            ir.push(noise * env);
5193            env *= decay;
5194        }
5195        // Normalize
5196        let peak = ir.iter().map(|x| x.abs()).fold(0.0f32, f32::max);
5197        if peak > 1e-10 {
5198            for x in &mut ir { *x /= peak; }
5199        }
5200        Self::new_with_ir(ir)
5201    }
5202
5203    pub fn set_pre_delay(&mut self, ms: f32) {
5204        self.pre_delay_ms = ms;
5205        self.pre_delay_samples = (ms * 0.001 * SAMPLE_RATE) as usize;
5206        self.pre_delay_buf_l = VecDeque::from(vec![0.0f32; self.pre_delay_samples + 1]);
5207        self.pre_delay_buf_r = VecDeque::from(vec![0.0f32; self.pre_delay_samples + 1]);
5208    }
5209
5210    pub fn load_ir(&mut self, ir_left: Vec<f32>, ir_right: Vec<f32>) {
5211        let max_len = ir_left.len().max(ir_right.len());
5212        self.ir_left = ir_left;
5213        self.ir_right = ir_right;
5214        self.buffer_l = VecDeque::from(vec![0.0f32; max_len]);
5215        self.buffer_r = VecDeque::from(vec![0.0f32; max_len]);
5216    }
5217
5218    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5219        if self.bypass { return (left, right); }
5220        // Pre-delay
5221        self.pre_delay_buf_l.push_back(left);
5222        self.pre_delay_buf_r.push_back(right);
5223        let l_delayed = if self.pre_delay_samples > 0 {
5224            self.pre_delay_buf_l.pop_front().unwrap_or(left)
5225        } else { left };
5226        let r_delayed = if self.pre_delay_samples > 0 {
5227            self.pre_delay_buf_r.pop_front().unwrap_or(right)
5228        } else { right };
5229        // Add to input buffer
5230        self.buffer_l.push_front(l_delayed);
5231        self.buffer_r.push_front(r_delayed);
5232        if self.buffer_l.len() > self.ir_left.len() { self.buffer_l.pop_back(); }
5233        if self.buffer_r.len() > self.ir_right.len() { self.buffer_r.pop_back(); }
5234        // Convolve (direct form, expensive for long IRs — suitable for short IRs)
5235        let wet_l: f32 = self.buffer_l.iter()
5236            .zip(self.ir_left.iter())
5237            .map(|(s, h)| s * h)
5238            .sum();
5239        let wet_r: f32 = self.buffer_r.iter()
5240            .zip(self.ir_right.iter())
5241            .map(|(s, h)| s * h)
5242            .sum();
5243        let l_out = left * (1.0 - self.wet_dry) + wet_l * self.wet_dry;
5244        let r_out = right * (1.0 - self.wet_dry) + wet_r * self.wet_dry;
5245        (l_out, r_out)
5246    }
5247
5248    pub fn tail_length_samples(&self) -> usize {
5249        self.ir_left.len().max(self.ir_right.len())
5250    }
5251
5252    pub fn energy_rt60_estimate(&self) -> f32 {
5253        // Estimate RT60 from IR energy decay
5254        let energy: Vec<f32> = {
5255            let mut e = Vec::with_capacity(self.ir_left.len());
5256            let mut running = 0.0f32;
5257            for (i, &s) in self.ir_left.iter().enumerate().rev() {
5258                running += s * s;
5259                e.push((i, running));
5260            }
5261            e.reverse();
5262            e.into_iter().map(|(_, v)| v).collect()
5263        };
5264        if energy.is_empty() { return 0.0; }
5265        let peak = energy[0];
5266        if peak < 1e-30 { return 0.0; }
5267        // Find -60dB point
5268        let target = peak * db_to_linear(-60.0);
5269        let idx_60 = energy.iter().position(|&e| e < target).unwrap_or(energy.len() - 1);
5270        idx_60 as f32 / SAMPLE_RATE
5271    }
5272}
5273
5274// ============================================================
5275// SECTION: Stereo Chorus / Flanger
5276// ============================================================
5277
5278#[derive(Clone, Debug)]
5279pub struct StereoChorus {
5280    pub rate_hz: f32,
5281    pub depth_ms: f32,
5282    pub feedback: f32,
5283    pub wet_dry: f32,
5284    pub stereo_spread: f32,
5285    pub bypass: bool,
5286    pub mode: ChorusMode,
5287    buffer_l: Vec<f32>,
5288    buffer_r: Vec<f32>,
5289    write_pos: usize,
5290    lfo_phase_l: f32,
5291    lfo_phase_r: f32,
5292    max_delay_samples: usize,
5293    feedback_sample_l: f32,
5294    feedback_sample_r: f32,
5295}
5296
5297#[derive(Clone, Debug, PartialEq)]
5298pub enum ChorusMode {
5299    Chorus,
5300    Flanger,
5301    Vibrato,
5302}
5303
5304impl StereoChorus {
5305    pub fn new(mode: ChorusMode) -> Self {
5306        let max_delay_ms = match mode { ChorusMode::Flanger => 15.0, _ => 30.0 };
5307        let max_delay_samples = (max_delay_ms * 0.001 * SAMPLE_RATE) as usize + 2;
5308        Self {
5309            rate_hz: match mode { ChorusMode::Flanger => 0.5, _ => 1.0 },
5310            depth_ms: match mode { ChorusMode::Flanger => 3.0, _ => 10.0 },
5311            feedback: match mode { ChorusMode::Flanger => 0.7, _ => 0.0 },
5312            wet_dry: 0.5,
5313            stereo_spread: 0.5,
5314            bypass: false,
5315            mode,
5316            buffer_l: vec![0.0; max_delay_samples],
5317            buffer_r: vec![0.0; max_delay_samples],
5318            write_pos: 0,
5319            lfo_phase_l: 0.0,
5320            lfo_phase_r: std::f32::consts::FRAC_PI_2,
5321            max_delay_samples,
5322            feedback_sample_l: 0.0,
5323            feedback_sample_r: 0.0,
5324        }
5325    }
5326
5327    fn read_interpolated(buf: &[f32], write_pos: usize, delay_samples: f32) -> f32 {
5328        let len = buf.len();
5329        let read_float = write_pos as f32 - delay_samples;
5330        let read_int = read_float.floor() as isize;
5331        let frac = read_float - read_int as f32;
5332        let idx0 = read_int.rem_euclid(len as isize) as usize;
5333        let idx1 = (read_int + 1).rem_euclid(len as isize) as usize;
5334        buf[idx0] * (1.0 - frac) + buf[idx1] * frac
5335    }
5336
5337    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5338        if self.bypass { return (left, right); }
5339        // Compute LFO values (sinusoidal)
5340        let lfo_l = self.lfo_phase_l.sin();
5341        let lfo_r = self.lfo_phase_r.sin();
5342        self.lfo_phase_l += TWO_PI * self.rate_hz / SAMPLE_RATE;
5343        self.lfo_phase_r += TWO_PI * self.rate_hz / SAMPLE_RATE;
5344        if self.lfo_phase_l > TWO_PI { self.lfo_phase_l -= TWO_PI; }
5345        if self.lfo_phase_r > TWO_PI { self.lfo_phase_r -= TWO_PI; }
5346        let center_samples = match self.mode {
5347            ChorusMode::Flanger => 0.5 * 0.001 * SAMPLE_RATE,
5348            _ => self.depth_ms * 0.001 * SAMPLE_RATE,
5349        };
5350        let depth_samples = self.depth_ms * 0.001 * SAMPLE_RATE * 0.5;
5351        let delay_l = center_samples + lfo_l * depth_samples;
5352        let delay_r = center_samples + lfo_r * depth_samples;
5353        // Write to buffer with feedback
5354        self.buffer_l[self.write_pos] = left + self.feedback_sample_l * self.feedback;
5355        self.buffer_r[self.write_pos] = right + self.feedback_sample_r * self.feedback;
5356        let wet_l = Self::read_interpolated(&self.buffer_l, self.write_pos, delay_l);
5357        let wet_r = Self::read_interpolated(&self.buffer_r, self.write_pos, delay_r);
5358        self.feedback_sample_l = wet_l;
5359        self.feedback_sample_r = wet_r;
5360        self.write_pos = (self.write_pos + 1) % self.max_delay_samples;
5361        match self.mode {
5362            ChorusMode::Vibrato => (wet_l, wet_r),
5363            _ => {
5364                let l_out = left * (1.0 - self.wet_dry) + wet_l * self.wet_dry;
5365                let r_out = right * (1.0 - self.wet_dry) + wet_r * self.wet_dry;
5366                (l_out, r_out)
5367            }
5368        }
5369    }
5370}
5371
5372// ============================================================
5373// SECTION: Spectrum Analyzer (Real-time)
5374// ============================================================
5375
5376
5377#[derive(Clone, Debug)]
5378pub struct SpectrumAnalyzer {
5379    pub num_bands: usize,
5380    pub smoothing: f32,       // 0.0–1.0
5381    pub peak_hold_frames: usize,
5382    input_buffer_l: Vec<f32>,
5383    input_buffer_r: Vec<f32>,
5384    buffer_pos: usize,
5385    window: Vec<f32>,
5386    pub magnitude_l: Vec<f32>,
5387    pub magnitude_r: Vec<f32>,
5388    peak_l: Vec<f32>,
5389    peak_r: Vec<f32>,
5390    peak_hold_counter: Vec<usize>,
5391    pub sample_count: usize,
5392    fft_scratch: Vec<(f32, f32)>,
5393}
5394
5395impl SpectrumAnalyzer {
5396    pub fn new(num_bands: usize) -> Self {
5397        let n = SPECTRUM_FFT_SIZE;
5398        // Hann window
5399        let window: Vec<f32> = (0..n).map(|i| {
5400            0.5 * (1.0 - (TWO_PI * i as f32 / (n - 1) as f32).cos())
5401        }).collect();
5402        Self {
5403            num_bands,
5404            smoothing: 0.8,
5405            peak_hold_frames: 60,
5406            input_buffer_l: vec![0.0; n],
5407            input_buffer_r: vec![0.0; n],
5408            buffer_pos: 0,
5409            window,
5410            magnitude_l: vec![0.0; num_bands],
5411            magnitude_r: vec![0.0; num_bands],
5412            peak_l: vec![0.0; num_bands],
5413            peak_r: vec![0.0; num_bands],
5414            peak_hold_counter: vec![0; num_bands],
5415            sample_count: 0,
5416            fft_scratch: vec![(0.0, 0.0); n],
5417        }
5418    }
5419
5420    fn fft_inplace(data: &mut Vec<(f32, f32)>) {
5421        let n = data.len();
5422        // Bit-reversal permutation
5423        let mut j = 0usize;
5424        for i in 1..n {
5425            let mut bit = n >> 1;
5426            while j & bit != 0 { j ^= bit; bit >>= 1; }
5427            j ^= bit;
5428            if i < j { data.swap(i, j); }
5429        }
5430        // Cooley-Tukey butterfly
5431        let mut len = 2usize;
5432        while len <= n {
5433            let ang = -TWO_PI / len as f32;
5434            let wlen = (ang.cos(), ang.sin());
5435            let mut i = 0;
5436            while i < n {
5437                let mut w = (1.0f32, 0.0f32);
5438                for jj in 0..(len / 2) {
5439                    let u = data[i + jj];
5440                    let v_re = data[i + jj + len / 2].0 * w.0 - data[i + jj + len / 2].1 * w.1;
5441                    let v_im = data[i + jj + len / 2].0 * w.1 + data[i + jj + len / 2].1 * w.0;
5442                    data[i + jj] = (u.0 + v_re, u.1 + v_im);
5443                    data[i + jj + len / 2] = (u.0 - v_re, u.1 - v_im);
5444                    let new_w = (w.0 * wlen.0 - w.1 * wlen.1, w.0 * wlen.1 + w.1 * wlen.0);
5445                    w = new_w;
5446                }
5447                i += len;
5448            }
5449            len <<= 1;
5450        }
5451    }
5452
5453    pub fn push_samples(&mut self, left: f32, right: f32) {
5454        let n = SPECTRUM_FFT_SIZE;
5455        self.input_buffer_l[self.buffer_pos] = left;
5456        self.input_buffer_r[self.buffer_pos] = right;
5457        self.buffer_pos = (self.buffer_pos + 1) % n;
5458        self.sample_count += 1;
5459        // Update spectrum every hop
5460        if self.sample_count % (n / 4) == 0 {
5461            self.compute_spectrum();
5462        }
5463    }
5464
5465    fn compute_spectrum(&mut self) {
5466        let n = SPECTRUM_FFT_SIZE;
5467        // Fill FFT scratch from circular buffer with windowing
5468        let mut data_l = vec![(0.0f32, 0.0f32); n];
5469        let mut data_r = vec![(0.0f32, 0.0f32); n];
5470        for i in 0..n {
5471            let idx = (self.buffer_pos + i) % n;
5472            let w = self.window[i];
5473            data_l[i] = (self.input_buffer_l[idx] * w, 0.0);
5474            data_r[i] = (self.input_buffer_r[idx] * w, 0.0);
5475        }
5476        Self::fft_inplace(&mut data_l);
5477        Self::fft_inplace(&mut data_r);
5478        // Map FFT bins to display bands (logarithmic)
5479        let min_freq = 20.0f32;
5480        let max_freq = (SAMPLE_RATE * 0.5).min(20000.0);
5481        let nb = self.num_bands;
5482        for b in 0..nb {
5483            let t_lo = b as f32 / nb as f32;
5484            let t_hi = (b + 1) as f32 / nb as f32;
5485            let f_lo = min_freq * (max_freq / min_freq).powf(t_lo);
5486            let f_hi = min_freq * (max_freq / min_freq).powf(t_hi);
5487            let bin_lo = ((f_lo / SAMPLE_RATE) * n as f32) as usize;
5488            let bin_hi = ((f_hi / SAMPLE_RATE) * n as f32).ceil() as usize;
5489            let bin_lo = bin_lo.max(1).min(n / 2);
5490            let bin_hi = bin_hi.max(bin_lo + 1).min(n / 2);
5491            let count = (bin_hi - bin_lo) as f32;
5492            let mag_l: f32 = data_l[bin_lo..bin_hi].iter()
5493                .map(|&(re, im)| (re * re + im * im).sqrt())
5494                .sum::<f32>() / count;
5495            let mag_r: f32 = data_r[bin_lo..bin_hi].iter()
5496                .map(|&(re, im)| (re * re + im * im).sqrt())
5497                .sum::<f32>() / count;
5498            let norm = 2.0 / n as f32;
5499            let mag_l_db = if mag_l * norm > 1e-10 { 20.0 * (mag_l * norm).log10() } else { -120.0 };
5500            let mag_r_db = if mag_r * norm > 1e-10 { 20.0 * (mag_r * norm).log10() } else { -120.0 };
5501            // Smooth
5502            self.magnitude_l[b] = self.magnitude_l[b] * self.smoothing + mag_l_db * (1.0 - self.smoothing);
5503            self.magnitude_r[b] = self.magnitude_r[b] * self.smoothing + mag_r_db * (1.0 - self.smoothing);
5504            // Peak hold
5505            if self.magnitude_l[b] > self.peak_l[b] {
5506                self.peak_l[b] = self.magnitude_l[b];
5507                self.peak_hold_counter[b] = self.peak_hold_frames;
5508            } else {
5509                if self.peak_hold_counter[b] > 0 {
5510                    self.peak_hold_counter[b] -= 1;
5511                } else {
5512                    self.peak_l[b] = (self.peak_l[b] - 0.5).max(self.magnitude_l[b]);
5513                }
5514            }
5515        }
5516    }
5517
5518    pub fn get_band_db(&self, band: usize) -> (f32, f32) {
5519        if band < self.num_bands {
5520            (self.magnitude_l[band], self.magnitude_r[band])
5521        } else {
5522            (-120.0, -120.0)
5523        }
5524    }
5525
5526    pub fn get_peak_db(&self, band: usize) -> f32 {
5527        if band < self.num_bands { self.peak_l[band] } else { -120.0 }
5528    }
5529
5530    pub fn band_center_frequency(&self, band: usize) -> f32 {
5531        let min_freq = 20.0f32;
5532        let max_freq = 20000.0f32;
5533        let t = (band as f32 + 0.5) / self.num_bands as f32;
5534        min_freq * (max_freq / min_freq).powf(t)
5535    }
5536}
5537
5538// ============================================================
5539// SECTION: Loudness History & Waveform Display Buffer
5540// ============================================================
5541
5542#[derive(Clone, Debug)]
5543pub struct LoudnessHistory {
5544    pub history_seconds: f32,
5545    ring_buffer: VecDeque<f32>,
5546    pub current_rms_db: f32,
5547    pub current_peak_db: f32,
5548    pub integrated_lufs: f32,
5549    pub true_peak_db: f32,
5550    square_sum: f32,
5551    sample_count: usize,
5552    block_size: usize,
5553    lufs_blocks: VecDeque<f32>,   // 400ms block powers
5554    lufs_gated_sum: f32,
5555    lufs_gated_count: usize,
5556}
5557
5558impl LoudnessHistory {
5559    pub fn new(history_seconds: f32) -> Self {
5560        let capacity = (history_seconds * 10.0) as usize; // 10 updates/sec
5561        Self {
5562            history_seconds,
5563            ring_buffer: VecDeque::with_capacity(capacity),
5564            current_rms_db: -120.0,
5565            current_peak_db: -120.0,
5566            integrated_lufs: -120.0,
5567            true_peak_db: -120.0,
5568            square_sum: 0.0,
5569            sample_count: 0,
5570            block_size: (SAMPLE_RATE * 0.1) as usize, // 100ms blocks
5571            lufs_blocks: VecDeque::with_capacity(16),
5572            lufs_gated_sum: 0.0,
5573            lufs_gated_count: 0,
5574        }
5575    }
5576
5577    pub fn push_sample(&mut self, left: f32, right: f32) {
5578        let power = (left * left + right * right) * 0.5;
5579        self.square_sum += power;
5580        if left.abs() > db_to_linear(self.true_peak_db) {
5581            self.true_peak_db = 20.0 * left.abs().log10();
5582        }
5583        if right.abs() > db_to_linear(self.true_peak_db) {
5584            self.true_peak_db = 20.0 * right.abs().log10();
5585        }
5586        self.sample_count += 1;
5587        if self.sample_count >= self.block_size {
5588            let rms = (self.square_sum / self.sample_count as f32).sqrt();
5589            self.current_rms_db = if rms > 1e-10 { 20.0 * rms.log10() } else { -120.0 };
5590            let block_power = self.square_sum / self.sample_count as f32;
5591            // LUFS gating: absolute gate at -70 LUFS, relative gate at -10 from ungated
5592            const ABSOLUTE_GATE: f32 = 1e-7; // -70 LUFS
5593            if block_power > ABSOLUTE_GATE {
5594                self.lufs_blocks.push_back(block_power);
5595                if self.lufs_blocks.len() > 40 { // 4 second window
5596                    let removed = self.lufs_blocks.pop_front().unwrap_or(0.0);
5597                    if removed > ABSOLUTE_GATE {
5598                        self.lufs_gated_sum -= removed;
5599                        self.lufs_gated_count = self.lufs_gated_count.saturating_sub(1);
5600                    }
5601                }
5602                self.lufs_gated_sum += block_power;
5603                self.lufs_gated_count += 1;
5604            }
5605            if self.lufs_gated_count > 0 {
5606                let mean_power = self.lufs_gated_sum / self.lufs_gated_count as f32;
5607                self.integrated_lufs = -0.691 + 10.0 * mean_power.log10();
5608            }
5609            // Push to history ring buffer
5610            if self.ring_buffer.len() >= self.ring_buffer.capacity().max(1) {
5611                self.ring_buffer.pop_front();
5612            }
5613            self.ring_buffer.push_back(self.current_rms_db);
5614            self.square_sum = 0.0;
5615            self.sample_count = 0;
5616        }
5617    }
5618
5619    pub fn get_history_slice(&self) -> Vec<f32> {
5620        self.ring_buffer.iter().copied().collect()
5621    }
5622
5623    pub fn short_term_lufs(&self) -> f32 {
5624        // Average of last 30 blocks (3 seconds at 100ms)
5625        let n = self.ring_buffer.len().min(30);
5626        if n == 0 { return -120.0; }
5627        let sum: f32 = self.ring_buffer.iter().rev().take(n)
5628            .map(|&db| db_to_linear(db).powi(2))
5629            .sum();
5630        let mean = sum / n as f32;
5631        if mean > 1e-30 { -0.691 + 10.0 * mean.log10() } else { -120.0 }
5632    }
5633
5634    pub fn momentary_lufs(&self) -> f32 {
5635        // Last 4 blocks (400ms)
5636        let n = self.ring_buffer.len().min(4);
5637        if n == 0 { return -120.0; }
5638        let sum: f32 = self.ring_buffer.iter().rev().take(n)
5639            .map(|&db| db_to_linear(db).powi(2))
5640            .sum();
5641        let mean = sum / n as f32;
5642        if mean > 1e-30 { -0.691 + 10.0 * mean.log10() } else { -120.0 }
5643    }
5644
5645    pub fn dynamic_range(&self) -> f32 {
5646        if self.ring_buffer.len() < 2 { return 0.0; }
5647        let max_db = self.ring_buffer.iter().copied().fold(f32::NEG_INFINITY, f32::max);
5648        let min_db = self.ring_buffer.iter().copied().fold(f32::INFINITY, f32::min);
5649        (max_db - min_db).max(0.0)
5650    }
5651}
5652
5653// ============================================================
5654// SECTION: Audio Bus Channel Strip (Complete)
5655// ============================================================
5656
5657#[derive(Clone, Debug)]
5658pub struct ChannelStrip {
5659    pub name: String,
5660    pub input_gain_db: f32,
5661    pub output_gain_db: f32,
5662    pub mute: bool,
5663    pub solo: bool,
5664    pub phase_invert: bool,
5665    pub bypass_all: bool,
5666    // Processing chain
5667    pub gate: NoiseGate,
5668    pub eq_strip: ParametricEqStrip,
5669    pub compressor: Compressor,
5670    pub saturator: HarmonicExciter,
5671    pub transient_shaper: TransientShaper,
5672    pub stereo_width: StereoWidthProcessor,
5673    pub chorus: StereoChorus,
5674    pub reverb_send_level: f32,
5675    pub delay_send_level: f32,
5676    // Metering
5677    pub spectrum: SpectrumAnalyzer,
5678    pub loudness: LoudnessHistory,
5679    // Pan
5680    pub pan: f32, // -1.0..1.0
5681    // Fader automation
5682    pub fader_automation: Vec<AutomationPoint>,
5683    pub fader_position: f32, // playback head
5684    pub fader_value: f32,
5685}
5686
5687#[derive(Clone, Debug)]
5688pub struct AutomationPoint {
5689    pub time_seconds: f32,
5690    pub value: f32,
5691    pub curve_type: AutomationCurve,
5692}
5693
5694#[derive(Clone, Debug, PartialEq)]
5695pub enum AutomationCurve {
5696    Linear,
5697    Smooth,
5698    Hold,
5699}
5700
5701impl ChannelStrip {
5702    pub fn new(name: &str) -> Self {
5703        let mut eq = ParametricEqStrip::new(name);
5704        // Default 4-band EQ
5705        eq.add_band(EqBand::new(EqBandType::LowCut,  80.0,  0.0, 0.707));
5706        eq.add_band(EqBand::new(EqBandType::LowShelf, 200.0, 0.0, 0.707));
5707        eq.add_band(EqBand::new(EqBandType::Peak,    1000.0, 0.0, 1.0));
5708        eq.add_band(EqBand::new(EqBandType::HighShelf, 8000.0, 0.0, 0.707));
5709        Self {
5710            name: name.to_string(),
5711            input_gain_db: 0.0,
5712            output_gain_db: 0.0,
5713            mute: false,
5714            solo: false,
5715            phase_invert: false,
5716            bypass_all: false,
5717            gate: NoiseGate::new(),
5718            eq_strip: eq,
5719            compressor: Compressor::new(CompressorParams::default()),
5720            saturator: HarmonicExciter::new(),
5721            transient_shaper: TransientShaper::new(),
5722            stereo_width: StereoWidthProcessor::new(),
5723            chorus: StereoChorus::new(ChorusMode::Chorus),
5724            reverb_send_level: 0.0,
5725            delay_send_level: 0.0,
5726            spectrum: SpectrumAnalyzer::new(32),
5727            loudness: LoudnessHistory::new(30.0),
5728            pan: 0.0,
5729            fader_automation: Vec::new(),
5730            fader_position: 0.0,
5731            fader_value: 1.0,
5732        }
5733    }
5734
5735    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5736        if self.mute { return (0.0, 0.0); }
5737        let in_gain = db_to_linear(self.input_gain_db);
5738        let mut l = left * in_gain;
5739        let mut r = right * in_gain;
5740        if self.phase_invert { l = -l; r = -r; }
5741        if !self.bypass_all {
5742            (l, r) = self.gate.process_sample(l, r);
5743            (l, r) = self.eq_strip.process_sample(l, r);
5744            (l, r) = self.compressor.process_sample(l, r);
5745            (l, r) = self.saturator.process_sample(l, r);
5746            (l, r) = self.transient_shaper.process_sample(l, r);
5747            (l, r) = self.stereo_width.process_sample(l, r);
5748            (l, r) = self.chorus.process_sample(l, r);
5749        }
5750        // Pan (constant power)
5751        let pan_rad = (self.pan + 1.0) * 0.5 * std::f32::consts::FRAC_PI_2;
5752        let pan_l = pan_rad.cos() * std::f32::consts::SQRT_2;
5753        let pan_r = pan_rad.sin() * std::f32::consts::SQRT_2;
5754        l *= pan_l;
5755        r *= pan_r;
5756        // Fader
5757        l *= self.fader_value;
5758        r *= self.fader_value;
5759        let out_gain = db_to_linear(self.output_gain_db);
5760        l *= out_gain;
5761        r *= out_gain;
5762        // Metering (after gain)
5763        self.spectrum.push_samples(l, r);
5764        self.loudness.push_sample(l, r);
5765        (l, r)
5766    }
5767
5768    pub fn evaluate_fader_automation(&mut self, time_seconds: f32) {
5769        self.fader_position = time_seconds;
5770        if self.fader_automation.is_empty() { return; }
5771        // Find surrounding keyframes
5772        let pts = &self.fader_automation;
5773        if time_seconds <= pts[0].time_seconds {
5774            self.fader_value = pts[0].value;
5775            return;
5776        }
5777        if time_seconds >= pts[pts.len() - 1].time_seconds {
5778            self.fader_value = pts[pts.len() - 1].value;
5779            return;
5780        }
5781        let mut lo = 0usize;
5782        let mut hi = pts.len() - 1;
5783        while hi - lo > 1 {
5784            let mid = (lo + hi) / 2;
5785            if pts[mid].time_seconds <= time_seconds { lo = mid; } else { hi = mid; }
5786        }
5787        let p0 = &pts[lo];
5788        let p1 = &pts[hi];
5789        let span = p1.time_seconds - p0.time_seconds;
5790        if span < 1e-6 {
5791            self.fader_value = p1.value;
5792            return;
5793        }
5794        let t = (time_seconds - p0.time_seconds) / span;
5795        self.fader_value = match p0.curve_type {
5796            AutomationCurve::Linear => p0.value + (p1.value - p0.value) * t,
5797            AutomationCurve::Smooth => {
5798                let s = t * t * (3.0 - 2.0 * t);
5799                p0.value + (p1.value - p0.value) * s
5800            }
5801            AutomationCurve::Hold => p0.value,
5802        };
5803    }
5804
5805    pub fn add_automation_point(&mut self, time_seconds: f32, value: f32, curve: AutomationCurve) {
5806        let pt = AutomationPoint { time_seconds, value, curve_type: curve };
5807        // Insert sorted by time
5808        let pos = self.fader_automation.partition_point(|p| p.time_seconds < time_seconds);
5809        self.fader_automation.insert(pos, pt);
5810    }
5811
5812    pub fn remove_automation_point(&mut self, time_seconds: f32, tolerance: f32) {
5813        self.fader_automation.retain(|p| (p.time_seconds - time_seconds).abs() > tolerance);
5814    }
5815
5816    pub fn rms_db(&self) -> f32 {
5817        self.loudness.current_rms_db
5818    }
5819
5820    pub fn peak_db(&self) -> f32 {
5821        self.loudness.true_peak_db
5822    }
5823
5824    pub fn lufs(&self) -> f32 {
5825        self.loudness.integrated_lufs
5826    }
5827}
5828
5829// ============================================================
5830// SECTION: Send/Return FX Bus
5831// ============================================================
5832
5833#[derive(Clone, Debug)]
5834pub struct SendReturnBus {
5835    pub name: String,
5836    pub return_gain_db: f32,
5837    pub mute: bool,
5838    pub bypass: bool,
5839    pub reverb: ConvolutionReverb,
5840    pub eq: ParametricEqStrip,
5841    pub width: StereoWidthProcessor,
5842    // Mix accumulator
5843    mix_l: f32,
5844    mix_r: f32,
5845}
5846
5847impl SendReturnBus {
5848    pub fn new_reverb_bus(name: &str, room_size: f32) -> Self {
5849        Self {
5850            name: name.to_string(),
5851            return_gain_db: -6.0,
5852            mute: false,
5853            bypass: false,
5854            reverb: ConvolutionReverb::new_synthetic_room(room_size),
5855            eq: ParametricEqStrip::new(name),
5856            width: StereoWidthProcessor::new(),
5857            mix_l: 0.0,
5858            mix_r: 0.0,
5859        }
5860    }
5861
5862    pub fn receive_send(&mut self, left: f32, right: f32, send_level: f32) {
5863        self.mix_l += left * send_level;
5864        self.mix_r += right * send_level;
5865    }
5866
5867    pub fn process_and_clear(&mut self) -> (f32, f32) {
5868        if self.mute || self.bypass {
5869            self.mix_l = 0.0;
5870            self.mix_r = 0.0;
5871            return (0.0, 0.0);
5872        }
5873        let (mut l, mut r) = self.reverb.process_sample(self.mix_l, self.mix_r);
5874        (l, r) = self.eq.process_sample(l, r);
5875        (l, r) = self.width.process_sample(l, r);
5876        let gain = db_to_linear(self.return_gain_db);
5877        self.mix_l = 0.0;
5878        self.mix_r = 0.0;
5879        (l * gain, r * gain)
5880    }
5881}
5882
5883// ============================================================
5884// SECTION: Master Bus Processing
5885// ============================================================
5886
5887#[derive(Clone, Debug)]
5888pub struct MasterBusProcessor {
5889    pub gain_db: f32,
5890    pub limiter_ceiling_db: f32,
5891    pub limiter_enabled: bool,
5892    pub dithering_enabled: bool,
5893    pub dither_bits: u32,
5894    pub eq: ParametricEqStrip,
5895    pub multiband: MultiBandCompressor,
5896    pub width: StereoWidthProcessor,
5897    pub loudness: LoudnessHistory,
5898    pub spectrum: SpectrumAnalyzer,
5899    // True Peak limiter state
5900    limiter_gain: f32,
5901    limiter_attack_coeff: f32,
5902    limiter_release_coeff: f32,
5903    // Dithering
5904    dither_seed: u32,
5905    dither_prev: f32,
5906}
5907
5908impl MasterBusProcessor {
5909    pub fn new() -> Self {
5910        let mut eq = ParametricEqStrip::new("Master");
5911        eq.add_band(EqBand::new(EqBandType::LowCut,  30.0, 0.0, 0.707));
5912        eq.add_band(EqBand::new(EqBandType::Peak,   100.0, 0.0, 0.707));
5913        eq.add_band(EqBand::new(EqBandType::Peak,  1000.0, 0.0, 0.707));
5914        eq.add_band(EqBand::new(EqBandType::HighShelf, 10000.0, 0.0, 0.707));
5915        Self {
5916            gain_db: 0.0,
5917            limiter_ceiling_db: -0.3,
5918            limiter_enabled: true,
5919            dithering_enabled: false,
5920            dither_bits: 24,
5921            eq,
5922            multiband: MultiBandCompressor::new_three_band(250.0, 4000.0),
5923            width: StereoWidthProcessor::new(),
5924            loudness: LoudnessHistory::new(60.0),
5925            spectrum: SpectrumAnalyzer::new(64),
5926            limiter_gain: 1.0,
5927            limiter_attack_coeff: (-1.0f32 / (0.0001 * SAMPLE_RATE)).exp(),
5928            limiter_release_coeff: (-1.0f32 / (0.1 * SAMPLE_RATE)).exp(),
5929            dither_seed: 0xDEADBEEF,
5930            dither_prev: 0.0,
5931        }
5932    }
5933
5934    fn next_dither(&mut self) -> f32 {
5935        self.dither_seed = self.dither_seed.wrapping_mul(1664525).wrapping_add(1013904223);
5936        let raw = (self.dither_seed as f32 / u32::MAX as f32) * 2.0 - 1.0;
5937        // TPDF: difference of two random values
5938        let tpdf = raw - self.dither_prev;
5939        self.dither_prev = raw;
5940        let lsb = 1.0 / (1u64 << self.dither_bits) as f32;
5941        tpdf * lsb
5942    }
5943
5944    pub fn process_sample(&mut self, left: f32, right: f32) -> (f32, f32) {
5945        let in_gain = db_to_linear(self.gain_db);
5946        let (mut l, mut r) = (left * in_gain, right * in_gain);
5947        (l, r) = self.eq.process_sample(l, r);
5948        (l, r) = self.multiband.process_sample(l, r);
5949        (l, r) = self.width.process_sample(l, r);
5950        // True Peak limiter (brick-wall)
5951        if self.limiter_enabled {
5952            let ceiling = db_to_linear(self.limiter_ceiling_db);
5953            let peak = l.abs().max(r.abs());
5954            let target_gain = if peak > ceiling { ceiling / peak.max(1e-10) } else { 1.0 };
5955            if target_gain < self.limiter_gain {
5956                self.limiter_gain = self.limiter_gain * self.limiter_attack_coeff
5957                    + target_gain * (1.0 - self.limiter_attack_coeff);
5958            } else {
5959                self.limiter_gain = self.limiter_gain * self.limiter_release_coeff
5960                    + target_gain * (1.0 - self.limiter_release_coeff);
5961            }
5962            l *= self.limiter_gain;
5963            r *= self.limiter_gain;
5964        }
5965        // Dithering
5966        if self.dithering_enabled {
5967            l += self.next_dither();
5968            r += self.next_dither();
5969        }
5970        // Metering
5971        self.loudness.push_sample(l, r);
5972        self.spectrum.push_samples(l, r);
5973        (l, r)
5974    }
5975
5976    pub fn lufs_integrated(&self) -> f32 { self.loudness.integrated_lufs }
5977    pub fn lufs_short_term(&self) -> f32 { self.loudness.short_term_lufs() }
5978    pub fn lufs_momentary(&self) -> f32 { self.loudness.momentary_lufs() }
5979    pub fn true_peak_db(&self) -> f32 { self.loudness.true_peak_db }
5980    pub fn dynamic_range(&self) -> f32 { self.loudness.dynamic_range() }
5981    pub fn limiter_gain_reduction_db(&self) -> f32 {
5982        if self.limiter_gain < 1.0 { 20.0 * self.limiter_gain.log10() } else { 0.0 }
5983    }
5984}
5985
5986// ============================================================
5987// SECTION: Mixer Session / Project
5988// ============================================================
5989
5990#[derive(Clone, Debug)]
5991pub struct MixerSession {
5992    pub session_name: String,
5993    pub sample_rate: f32,
5994    pub bit_depth: u32,
5995    pub channel_strips: Vec<ChannelStrip>,
5996    pub send_buses: Vec<SendReturnBus>,
5997    pub master_bus: MasterBusProcessor,
5998    pub bpm: f32,
5999    pub time_signature_numerator: u32,
6000    pub time_signature_denominator: u32,
6001    pub play_head_seconds: f32,
6002    pub is_playing: bool,
6003    pub is_recording: bool,
6004    pub solo_exclusive: bool,
6005    pub monitor_input: bool,
6006}
6007
6008impl MixerSession {
6009    pub fn new(session_name: &str, sample_rate: f32, bit_depth: u32) -> Self {
6010        Self {
6011            session_name: session_name.to_string(),
6012            sample_rate,
6013            bit_depth,
6014            channel_strips: Vec::new(),
6015            send_buses: vec![
6016                SendReturnBus::new_reverb_bus("Reverb A", 1.5),
6017                SendReturnBus::new_reverb_bus("Reverb B", 3.0),
6018            ],
6019            master_bus: MasterBusProcessor::new(),
6020            bpm: 120.0,
6021            time_signature_numerator: 4,
6022            time_signature_denominator: 4,
6023            play_head_seconds: 0.0,
6024            is_playing: false,
6025            is_recording: false,
6026            solo_exclusive: true,
6027            monitor_input: false,
6028        }
6029    }
6030
6031    pub fn add_channel(&mut self, name: &str) -> usize {
6032        let idx = self.channel_strips.len();
6033        self.channel_strips.push(ChannelStrip::new(name));
6034        idx
6035    }
6036
6037    pub fn remove_channel(&mut self, index: usize) {
6038        if index < self.channel_strips.len() {
6039            self.channel_strips.remove(index);
6040        }
6041    }
6042
6043    pub fn process_frame(&mut self, inputs: &[(f32, f32)]) -> (f32, f32) {
6044        let any_solo = self.channel_strips.iter().any(|s| s.solo);
6045        let mut master_l = 0.0f32;
6046        let mut master_r = 0.0f32;
6047        for (i, strip) in self.channel_strips.iter_mut().enumerate() {
6048            let (in_l, in_r) = inputs.get(i).copied().unwrap_or((0.0, 0.0));
6049            if any_solo && !strip.solo { continue; }
6050            strip.evaluate_fader_automation(self.play_head_seconds);
6051            let (l, r) = strip.process_sample(in_l, in_r);
6052            // Feed send buses
6053            for bus in &mut self.send_buses {
6054                bus.receive_send(l, r, strip.reverb_send_level);
6055            }
6056            master_l += l;
6057            master_r += r;
6058        }
6059        // Return buses
6060        for bus in &mut self.send_buses {
6061            let (rl, rr) = bus.process_and_clear();
6062            master_l += rl;
6063            master_r += rr;
6064        }
6065        self.master_bus.process_sample(master_l, master_r)
6066    }
6067
6068    pub fn advance_play_head(&mut self, delta_seconds: f32) {
6069        if self.is_playing {
6070            self.play_head_seconds += delta_seconds;
6071        }
6072    }
6073
6074    pub fn beats_per_second(&self) -> f32 {
6075        self.bpm / 60.0
6076    }
6077
6078    pub fn current_beat(&self) -> f32 {
6079        self.play_head_seconds * self.beats_per_second()
6080    }
6081
6082    pub fn current_bar_beat(&self) -> (u32, f32) {
6083        let beat = self.current_beat();
6084        let bar = (beat / self.time_signature_numerator as f32).floor() as u32;
6085        let beat_in_bar = beat - bar as f32 * self.time_signature_numerator as f32;
6086        (bar, beat_in_bar)
6087    }
6088
6089    pub fn seconds_per_beat(&self) -> f32 { 60.0 / self.bpm }
6090    pub fn seconds_per_bar(&self) -> f32 { self.seconds_per_beat() * self.time_signature_numerator as f32 }
6091
6092    pub fn channel_count(&self) -> usize { self.channel_strips.len() }
6093
6094    pub fn mute_channel(&mut self, index: usize, mute: bool) {
6095        if let Some(s) = self.channel_strips.get_mut(index) { s.mute = mute; }
6096    }
6097
6098    pub fn solo_channel(&mut self, index: usize, solo: bool) {
6099        if self.solo_exclusive && solo {
6100            for s in &mut self.channel_strips { s.solo = false; }
6101        }
6102        if let Some(s) = self.channel_strips.get_mut(index) { s.solo = solo; }
6103    }
6104
6105    pub fn set_channel_pan(&mut self, index: usize, pan: f32) {
6106        if let Some(s) = self.channel_strips.get_mut(index) {
6107            s.pan = pan.clamp(-1.0, 1.0);
6108        }
6109    }
6110
6111    pub fn set_channel_fader(&mut self, index: usize, value: f32) {
6112        if let Some(s) = self.channel_strips.get_mut(index) {
6113            s.fader_value = value.max(0.0);
6114        }
6115    }
6116
6117    pub fn get_channel_rms(&self, index: usize) -> f32 {
6118        self.channel_strips.get(index).map(|s| s.rms_db()).unwrap_or(-120.0)
6119    }
6120
6121    pub fn get_channel_lufs(&self, index: usize) -> f32 {
6122        self.channel_strips.get(index).map(|s| s.lufs()).unwrap_or(-120.0)
6123    }
6124
6125    pub fn master_lufs(&self) -> f32 { self.master_bus.lufs_integrated() }
6126    pub fn master_peak(&self) -> f32 { self.master_bus.true_peak_db() }
6127}
6128
6129// ============================================================
6130// SECTION: MIDI-to-Synth routing for adaptive music
6131// ============================================================
6132
6133#[derive(Clone, Debug)]
6134pub struct MidiNote {
6135    pub channel: u8,
6136    pub note: u8,     // 0-127
6137    pub velocity: u8, // 0-127
6138    pub duration_beats: f32,
6139    pub start_beat: f32,
6140}
6141
6142impl MidiNote {
6143    pub fn new(channel: u8, note: u8, velocity: u8, start_beat: f32, duration_beats: f32) -> Self {
6144        Self { channel, note, velocity, duration_beats, start_beat }
6145    }
6146
6147    pub fn frequency_hz(&self) -> f32 {
6148        // A4 = 440 Hz = MIDI note 69
6149        440.0 * 2.0f32.powf((self.note as f32 - 69.0) / 12.0)
6150    }
6151
6152    pub fn velocity_linear(&self) -> f32 {
6153        self.velocity as f32 / 127.0
6154    }
6155
6156    pub fn end_beat(&self) -> f32 {
6157        self.start_beat + self.duration_beats
6158    }
6159}
6160
6161#[derive(Clone, Debug)]
6162pub struct MidiTrack {
6163    pub name: String,
6164    pub channel: u8,
6165    pub notes: Vec<MidiNote>,
6166    pub transpose_semitones: i32,
6167    pub velocity_scale: f32,
6168    pub mute: bool,
6169}
6170
6171impl MidiTrack {
6172    pub fn new(name: &str, channel: u8) -> Self {
6173        Self {
6174            name: name.to_string(),
6175            channel,
6176            notes: Vec::new(),
6177            transpose_semitones: 0,
6178            velocity_scale: 1.0,
6179            mute: false,
6180        }
6181    }
6182
6183    pub fn add_note(&mut self, note: u8, velocity: u8, start_beat: f32, duration_beats: f32) {
6184        self.notes.push(MidiNote::new(self.channel, note, velocity, start_beat, duration_beats));
6185    }
6186
6187    pub fn get_active_notes_at(&self, beat: f32) -> Vec<&MidiNote> {
6188        if self.mute { return vec![]; }
6189        self.notes.iter()
6190            .filter(|n| beat >= n.start_beat && beat < n.end_beat())
6191            .collect()
6192    }
6193
6194    pub fn transpose(&mut self, semitones: i32) {
6195        self.transpose_semitones += semitones;
6196        for note in &mut self.notes {
6197            let new_note = note.note as i32 + semitones;
6198            note.note = new_note.clamp(0, 127) as u8;
6199        }
6200    }
6201
6202    pub fn quantize_to_grid(&mut self, grid_beats: f32) {
6203        for note in &mut self.notes {
6204            note.start_beat = (note.start_beat / grid_beats).round() * grid_beats;
6205            note.duration_beats = (note.duration_beats / grid_beats).round() * grid_beats;
6206            if note.duration_beats < grid_beats { note.duration_beats = grid_beats; }
6207        }
6208    }
6209
6210    pub fn legato_overlap_beats(&self) -> f32 {
6211        // Average overlap between consecutive notes
6212        let mut sorted: Vec<&MidiNote> = self.notes.iter().collect();
6213        sorted.sort_by(|a, b| a.start_beat.partial_cmp(&b.start_beat).unwrap());
6214        if sorted.len() < 2 { return 0.0; }
6215        let overlap_sum: f32 = sorted.windows(2).map(|w| {
6216            let gap = w[1].start_beat - w[0].end_beat();
6217            if gap < 0.0 { -gap } else { 0.0 }
6218        }).sum();
6219        overlap_sum / (sorted.len() - 1) as f32
6220    }
6221
6222    pub fn note_density_per_beat(&self) -> f32 {
6223        if self.notes.is_empty() { return 0.0; }
6224        let max_beat = self.notes.iter()
6225            .map(|n| n.end_beat())
6226            .fold(0.0f32, f32::max);
6227        if max_beat < 1e-6 { return 0.0; }
6228        self.notes.len() as f32 / max_beat
6229    }
6230}
6231
6232// ============================================================
6233// SECTION: Spatial Audio Panner (HRTF approximation)
6234// ============================================================
6235
6236#[derive(Clone, Debug)]
6237pub struct HrtfPanner {
6238    pub azimuth_deg: f32,     // -180..180
6239    pub elevation_deg: f32,   // -90..90
6240    pub distance: f32,
6241    pub bypass: bool,
6242    itd_buffer_l: VecDeque<f32>,
6243    itd_buffer_r: VecDeque<f32>,
6244    itd_delay_samples: f32,
6245    head_radius_m: f32,
6246    // ILD filters (frequency-dependent level difference)
6247    ild_filter_l: BiquadCoefficients,
6248    ild_filter_r: BiquadCoefficients,
6249    ild_state_l: BiquadState,
6250    ild_state_r: BiquadState,
6251}
6252
6253impl HrtfPanner {
6254    pub fn new() -> Self {
6255        let max_itd_samples = 50; // ~1ms at 44100 Hz
6256        let mut panner = Self {
6257            azimuth_deg: 0.0,
6258            elevation_deg: 0.0,
6259            distance: 1.0,
6260            bypass: false,
6261            itd_buffer_l: VecDeque::from(vec![0.0f32; max_itd_samples]),
6262            itd_buffer_r: VecDeque::from(vec![0.0f32; max_itd_samples]),
6263            itd_delay_samples: 0.0,
6264            head_radius_m: 0.0875,
6265            ild_filter_l: BiquadCoefficients::identity(),
6266            ild_filter_r: BiquadCoefficients::identity(),
6267            ild_state_l: BiquadState::new(),
6268            ild_state_r: BiquadState::new(),
6269        };
6270        panner.update_panning();
6271        panner
6272    }
6273
6274    pub fn set_position(&mut self, azimuth_deg: f32, elevation_deg: f32, distance: f32) {
6275        self.azimuth_deg = azimuth_deg;
6276        self.elevation_deg = elevation_deg;
6277        self.distance = distance.max(0.01);
6278        self.update_panning();
6279    }
6280
6281    fn update_panning(&mut self) {
6282        const SPEED_OF_SOUND: f32 = 343.0;
6283        let az_rad = self.azimuth_deg.to_radians();
6284        // Woodworth ITD formula: ITD = (r/c) * (sin(az) + az)
6285        let sin_az = az_rad.sin();
6286        let itd_seconds = (self.head_radius_m / SPEED_OF_SOUND) * (sin_az + az_rad);
6287        self.itd_delay_samples = (itd_seconds.abs() * SAMPLE_RATE).min(45.0);
6288        // ILD: boost ipsilateral ear, attenuate contralateral
6289        // Approximate with a shelf filter
6290        let ild_db = sin_az * 6.0 * (self.elevation_deg.to_radians().cos());
6291        if ild_db >= 0.0 {
6292            // Left ear boosted
6293            self.ild_filter_l = BiquadCoefficients::high_shelf(3000.0, 0.707, ild_db, SAMPLE_RATE);
6294            self.ild_filter_r = BiquadCoefficients::high_shelf(3000.0, 0.707, -ild_db, SAMPLE_RATE);
6295        } else {
6296            self.ild_filter_l = BiquadCoefficients::high_shelf(3000.0, 0.707, ild_db, SAMPLE_RATE);
6297            self.ild_filter_r = BiquadCoefficients::high_shelf(3000.0, 0.707, -ild_db, SAMPLE_RATE);
6298        }
6299    }
6300
6301    pub fn process_mono_sample(&mut self, mono: f32) -> (f32, f32) {
6302        if self.bypass { return (mono, mono); }
6303        // Distance attenuation (inverse square law)
6304        let dist_atten = 1.0 / self.distance.max(1.0);
6305        let s = mono * dist_atten;
6306        // Apply ITD: delay one channel
6307        let az_sign = self.azimuth_deg.signum();
6308        let (l_in, r_in) = if az_sign >= 0.0 {
6309            // Source to right: right ear gets direct, left ear gets delayed
6310            self.itd_buffer_l.push_back(s);
6311            let delayed_l = self.itd_buffer_l.pop_front().unwrap_or(s);
6312            (delayed_l, s)
6313        } else {
6314            self.itd_buffer_r.push_back(s);
6315            let delayed_r = self.itd_buffer_r.pop_front().unwrap_or(s);
6316            (s, delayed_r)
6317        };
6318        // Apply ILD
6319        let l_out = self.ild_state_l.process(l_in, &self.ild_filter_l);
6320        let r_out = self.ild_state_r.process(r_in, &self.ild_filter_r);
6321        (l_out, r_out)
6322    }
6323}
6324
6325// ============================================================
6326// SECTION: Room Acoustics Simulation
6327// ============================================================
6328
6329#[derive(Clone, Debug)]
6330pub struct RoomAcoustics {
6331    pub room_width_m: f32,
6332    pub room_depth_m: f32,
6333    pub room_height_m: f32,
6334    pub absorption_coefficient: f32, // 0.0=reflective, 1.0=anechoic
6335    pub air_absorption_db_per_m: f32,
6336    // Early reflections (image source method, 1st order only)
6337    early_reflections: Vec<EarlyReflection>,
6338    reverb_tail: SchroederReverb,
6339    pub bypass: bool,
6340}
6341
6342#[derive(Clone, Debug)]
6343pub struct EarlyReflection {
6344    pub delay_samples: usize,
6345    pub gain: f32,
6346    delay_buffer: VecDeque<f32>,
6347}
6348
6349impl EarlyReflection {
6350    pub fn new(delay_ms: f32, gain: f32) -> Self {
6351        let samples = (delay_ms * 0.001 * SAMPLE_RATE) as usize + 1;
6352        Self {
6353            delay_samples: samples,
6354            gain,
6355            delay_buffer: VecDeque::from(vec![0.0f32; samples]),
6356        }
6357    }
6358
6359    pub fn process(&mut self, input: f32) -> f32 {
6360        self.delay_buffer.push_back(input);
6361        let out = self.delay_buffer.pop_front().unwrap_or(0.0);
6362        out * self.gain
6363    }
6364}
6365
6366impl RoomAcoustics {
6367    pub fn new(width: f32, depth: f32, height: f32, absorption: f32) -> Self {
6368        // Generate 1st-order image source reflections
6369        // Listener at center, source at 1/3 from front wall
6370        let source_x = width * 0.5;
6371        let source_z = depth * 0.333;
6372        let listener_x = width * 0.5;
6373        let listener_z = depth * 0.5;
6374        const SPEED_OF_SOUND: f32 = 343.0;
6375        let direct_dist = ((source_x - listener_x).powi(2) + (source_z - listener_z).powi(2)).sqrt();
6376        // 6 first-order image sources
6377        let images: [(f32, f32); 6] = [
6378            (-source_x, source_z),          // left wall
6379            (2.0 * width - source_x, source_z), // right wall
6380            (source_x, -source_z),          // front wall
6381            (source_x, 2.0 * depth - source_z), // back wall
6382            (source_x, source_z),           // floor (via elevation trick)
6383            (source_x, source_z),           // ceiling
6384        ];
6385        let reflection_coeff = (1.0 - absorption).sqrt();
6386        let early_reflections: Vec<EarlyReflection> = images.iter().map(|&(ix, iz)| {
6387            let dist = ((ix - listener_x).powi(2) + (iz - listener_z).powi(2)).sqrt();
6388            let extra_dist = (dist - direct_dist).max(0.0);
6389            let delay_ms = extra_dist / SPEED_OF_SOUND * 1000.0;
6390            let gain = reflection_coeff / (dist / direct_dist).max(1.0);
6391            EarlyReflection::new(delay_ms, gain)
6392        }).collect();
6393        // RT60 from Sabine formula: RT60 = 0.161 * V / (absorption * S)
6394        let volume = width * depth * height;
6395        let surface = 2.0 * (width * depth + width * height + depth * height);
6396        let rt60 = 0.161 * volume / (absorption * surface + 1e-10);
6397        let reverb = SchroederReverb::new();
6398        Self {
6399            room_width_m: width,
6400            room_depth_m: depth,
6401            room_height_m: height,
6402            absorption_coefficient: absorption,
6403            air_absorption_db_per_m: 0.01,
6404            early_reflections,
6405            reverb_tail: reverb,
6406            bypass: false,
6407        }
6408    }
6409
6410    pub fn rt60(&self) -> f32 {
6411        let volume = self.room_width_m * self.room_depth_m * self.room_height_m;
6412        let surface = 2.0 * (self.room_width_m * self.room_depth_m
6413            + self.room_width_m * self.room_height_m
6414            + self.room_depth_m * self.room_height_m);
6415        0.161 * volume / (self.absorption_coefficient * surface + 1e-10)
6416    }
6417
6418    pub fn process_mono_sample(&mut self, input: f32, distance_m: f32) -> (f32, f32) {
6419        if self.bypass { return (input, input); }
6420        // Air absorption
6421        let air_atten_db = -self.air_absorption_db_per_m * distance_m;
6422        let air_atten = db_to_linear(air_atten_db);
6423        let s = input * air_atten;
6424        // Sum early reflections
6425        let mut early_sum = 0.0f32;
6426        for er in &mut self.early_reflections {
6427            early_sum += er.process(s);
6428        }
6429        // Feed into reverb tail
6430        let (tail_l, tail_r) = self.reverb_tail.process_sample(early_sum, early_sum);
6431        let out_l = s + early_sum * 0.5 + tail_l * 0.3;
6432        let out_r = s + early_sum * 0.5 + tail_r * 0.3;
6433        (out_l, out_r)
6434    }
6435
6436    pub fn modal_frequencies(&self) -> Vec<f32> {
6437        // Axial room modes: f = c/(2L) * n
6438        const SPEED_OF_SOUND: f32 = 343.0;
6439        let mut modes = Vec::new();
6440        for n in 1..=5u32 {
6441            modes.push(SPEED_OF_SOUND / (2.0 * self.room_width_m) * n as f32);
6442            modes.push(SPEED_OF_SOUND / (2.0 * self.room_depth_m) * n as f32);
6443            modes.push(SPEED_OF_SOUND / (2.0 * self.room_height_m) * n as f32);
6444        }
6445        modes.sort_by(|a, b| a.partial_cmp(b).unwrap());
6446        modes.dedup_by(|a, b| (*a - *b).abs() < 1.0);
6447        modes
6448    }
6449}
6450
6451// ============================================================
6452// SECTION: Music Composition Tools (Scale/Chord Helper)
6453// ============================================================
6454
6455#[derive(Clone, Debug, PartialEq)]
6456pub enum MusicalScale {
6457    Major,
6458    NaturalMinor,
6459    HarmonicMinor,
6460    MelodicMinor,
6461    Dorian,
6462    Phrygian,
6463    Lydian,
6464    Mixolydian,
6465    Locrian,
6466    WholeTone,
6467    Diminished,
6468    Chromatic,
6469}
6470
6471impl MusicalScale {
6472    pub fn intervals(&self) -> Vec<u8> {
6473        match self {
6474            MusicalScale::Major          => vec![0, 2, 4, 5, 7, 9, 11],
6475            MusicalScale::NaturalMinor   => vec![0, 2, 3, 5, 7, 8, 10],
6476            MusicalScale::HarmonicMinor  => vec![0, 2, 3, 5, 7, 8, 11],
6477            MusicalScale::MelodicMinor   => vec![0, 2, 3, 5, 7, 9, 11],
6478            MusicalScale::Dorian         => vec![0, 2, 3, 5, 7, 9, 10],
6479            MusicalScale::Phrygian       => vec![0, 1, 3, 5, 7, 8, 10],
6480            MusicalScale::Lydian         => vec![0, 2, 4, 6, 7, 9, 11],
6481            MusicalScale::Mixolydian     => vec![0, 2, 4, 5, 7, 9, 10],
6482            MusicalScale::Locrian        => vec![0, 1, 3, 5, 6, 8, 10],
6483            MusicalScale::WholeTone      => vec![0, 2, 4, 6, 8, 10],
6484            MusicalScale::Diminished     => vec![0, 2, 3, 5, 6, 8, 9, 11],
6485            MusicalScale::Chromatic      => (0..12).collect(),
6486        }
6487    }
6488
6489    pub fn note_in_scale(&self, note: u8, root: u8) -> bool {
6490        let interval = note % 12;
6491        let root_norm = root % 12;
6492        let relative = (interval as i32 - root_norm as i32).rem_euclid(12) as u8;
6493        self.intervals().contains(&relative)
6494    }
6495
6496    pub fn scale_notes(&self, root: u8, octave_start: u8, octave_end: u8) -> Vec<u8> {
6497        let mut notes = Vec::new();
6498        let root_norm = root % 12;
6499        for octave in octave_start..=octave_end {
6500            for &interval in &self.intervals() {
6501                let note = root_norm + interval + octave * 12;
6502                if note < 128 { notes.push(note); }
6503            }
6504        }
6505        notes
6506    }
6507}
6508
6509#[derive(Clone, Debug)]
6510pub struct ChordVoicing {
6511    pub root: u8,
6512    pub chord_type: ChordType,
6513    pub inversion: u8,  // 0=root, 1=first, 2=second
6514    pub spread: ChordSpread,
6515}
6516
6517#[derive(Clone, Debug, PartialEq)]
6518pub enum ChordType {
6519    Major,
6520    Minor,
6521    Dominant7,
6522    Major7,
6523    Minor7,
6524    Diminished,
6525    Augmented,
6526    Sus2,
6527    Sus4,
6528    Add9,
6529}
6530
6531#[derive(Clone, Debug, PartialEq)]
6532pub enum ChordSpread {
6533    Close,
6534    Open,
6535    Wide,
6536}
6537
6538impl ChordVoicing {
6539    pub fn intervals(&self) -> Vec<u8> {
6540        let base = match self.chord_type {
6541            ChordType::Major      => vec![0, 4, 7],
6542            ChordType::Minor      => vec![0, 3, 7],
6543            ChordType::Dominant7  => vec![0, 4, 7, 10],
6544            ChordType::Major7     => vec![0, 4, 7, 11],
6545            ChordType::Minor7     => vec![0, 3, 7, 10],
6546            ChordType::Diminished => vec![0, 3, 6],
6547            ChordType::Augmented  => vec![0, 4, 8],
6548            ChordType::Sus2       => vec![0, 2, 7],
6549            ChordType::Sus4       => vec![0, 5, 7],
6550            ChordType::Add9       => vec![0, 4, 7, 14],
6551        };
6552        base
6553    }
6554
6555    pub fn midi_notes(&self, base_octave: u8) -> Vec<u8> {
6556        let mut intervals = self.intervals();
6557        // Apply inversion
6558        for _ in 0..self.inversion.min(intervals.len() as u8 - 1) {
6559            let first = intervals.remove(0);
6560            intervals.push(first + 12);
6561        }
6562        // Apply spread
6563        let spread_add: Vec<u8> = match self.spread {
6564            ChordSpread::Close => vec![0; intervals.len()],
6565            ChordSpread::Open  => (0..intervals.len()).map(|i| if i % 2 == 0 { 0 } else { 12 }).collect(),
6566            ChordSpread::Wide  => (0..intervals.len()).map(|i| i as u8 * 7 % 12).collect(),
6567        };
6568        intervals.iter().zip(spread_add.iter()).map(|(&interval, &extra)| {
6569            let note = self.root + interval + extra + base_octave * 12;
6570            note.min(127)
6571        }).collect()
6572    }
6573
6574    pub fn root_frequency_hz(&self) -> f32 {
6575        440.0 * 2.0f32.powf((self.root as f32 - 69.0) / 12.0)
6576    }
6577}
6578
6579// ============================================================
6580// SECTION: Audio Clock & Sync
6581// ============================================================
6582
6583#[derive(Clone, Debug)]
6584pub struct AudioClock {
6585    pub sample_rate: f32,
6586    pub bpm: f32,
6587    pub ppqn: u32,           // pulses per quarter note
6588    pub sample_count: u64,
6589    pub beat_count: f64,
6590    pub bar_count: u32,
6591    pub time_signature_num: u32,
6592    pub time_signature_den: u32,
6593    pub is_running: bool,
6594    pub loop_start_beat: f64,
6595    pub loop_end_beat: f64,
6596    pub loop_enabled: bool,
6597    // Sync sources
6598    pub midi_clock_received: bool,
6599    pub midi_clock_ticks: u32,
6600    last_midi_clock_sample: u64,
6601    midi_bpm_estimate: f32,
6602}
6603
6604impl AudioClock {
6605    pub fn new(sample_rate: f32, bpm: f32) -> Self {
6606        Self {
6607            sample_rate,
6608            bpm,
6609            ppqn: 960,
6610            sample_count: 0,
6611            beat_count: 0.0,
6612            bar_count: 0,
6613            time_signature_num: 4,
6614            time_signature_den: 4,
6615            is_running: false,
6616            loop_start_beat: 0.0,
6617            loop_end_beat: 16.0,
6618            loop_enabled: false,
6619            midi_clock_received: false,
6620            midi_clock_ticks: 0,
6621            last_midi_clock_sample: 0,
6622            midi_bpm_estimate: bpm,
6623        }
6624    }
6625
6626    pub fn advance(&mut self, num_samples: u64) {
6627        if !self.is_running { return; }
6628        self.sample_count += num_samples;
6629        let beats_per_sample = self.bpm as f64 / (60.0 * self.sample_rate as f64);
6630        self.beat_count += num_samples as f64 * beats_per_sample;
6631        // Loop handling
6632        if self.loop_enabled && self.beat_count >= self.loop_end_beat {
6633            let overshoot = self.beat_count - self.loop_end_beat;
6634            let loop_len = self.loop_end_beat - self.loop_start_beat;
6635            self.beat_count = self.loop_start_beat + overshoot % loop_len;
6636        }
6637        // Bar tracking
6638        self.bar_count = (self.beat_count / self.time_signature_num as f64) as u32;
6639    }
6640
6641    pub fn beat_in_bar(&self) -> f64 {
6642        self.beat_count % self.time_signature_num as f64
6643    }
6644
6645    pub fn seconds_elapsed(&self) -> f64 {
6646        self.sample_count as f64 / self.sample_rate as f64
6647    }
6648
6649    pub fn sample_at_beat(&self, beat: f64) -> u64 {
6650        let seconds = beat * 60.0 / self.bpm as f64;
6651        (seconds * self.sample_rate as f64) as u64
6652    }
6653
6654    pub fn beat_at_sample(&self, sample: u64) -> f64 {
6655        let seconds = sample as f64 / self.sample_rate as f64;
6656        seconds * self.bpm as f64 / 60.0
6657    }
6658
6659    pub fn receive_midi_clock_tick(&mut self) {
6660        // MIDI clock = 24 ticks per quarter note
6661        self.midi_clock_ticks += 1;
6662        if self.midi_clock_ticks >= 24 {
6663            let elapsed_samples = self.sample_count - self.last_midi_clock_sample;
6664            if elapsed_samples > 0 {
6665                let seconds_per_beat = elapsed_samples as f32 / self.sample_rate;
6666                self.midi_bpm_estimate = 60.0 / seconds_per_beat;
6667                // Smooth the BPM estimate
6668                self.bpm = self.bpm * 0.9 + self.midi_bpm_estimate * 0.1;
6669            }
6670            self.midi_clock_ticks = 0;
6671            self.last_midi_clock_sample = self.sample_count;
6672        }
6673        self.midi_clock_received = true;
6674    }
6675
6676    pub fn pulse_position(&self) -> u64 {
6677        let beats_per_sample = self.bpm as f64 / (60.0 * self.sample_rate as f64);
6678        (self.beat_count * self.ppqn as f64 * beats_per_sample) as u64
6679    }
6680
6681    pub fn humanize_timing(&self, beat: f64, max_deviation_ms: f32) -> f64 {
6682        // Subtle human timing variation using hash of beat position
6683        let hash_input = (beat * 1000.0) as u64;
6684        let hash = hash_input.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
6685        let deviation = (hash as f64 / u64::MAX as f64) * 2.0 - 1.0;
6686        beat + deviation * max_deviation_ms as f64 * 0.001 * self.bpm as f64 / 60.0
6687    }
6688}
6689
6690// ============================================================
6691// SECTION: Audio File Metadata Parser
6692// ============================================================
6693
6694#[derive(Clone, Debug)]
6695pub struct AudioFileMetadata {
6696    pub file_path: String,
6697    pub sample_rate: u32,
6698    pub num_channels: u32,
6699    pub bit_depth: u32,
6700    pub num_frames: u64,
6701    pub duration_seconds: f64,
6702    pub format: AudioFormat,
6703    pub tags: HashMap<String, String>,
6704    pub loop_points: Option<(u64, u64)>,
6705    pub cue_points: Vec<CuePoint>,
6706    pub embedded_bpm: Option<f32>,
6707    pub embedded_key: Option<String>,
6708}
6709
6710#[derive(Clone, Debug, PartialEq)]
6711pub enum AudioFormat {
6712    Wav,
6713    Aiff,
6714    Flac,
6715    Mp3,
6716    Ogg,
6717    Opus,
6718    Unknown,
6719}
6720
6721#[derive(Clone, Debug)]
6722pub struct CuePoint {
6723    pub id: u32,
6724    pub name: String,
6725    pub position_frames: u64,
6726    pub color: u32,
6727}
6728
6729impl AudioFileMetadata {
6730    pub fn new(file_path: &str) -> Self {
6731        Self {
6732            file_path: file_path.to_string(),
6733            sample_rate: 44100,
6734            num_channels: 2,
6735            bit_depth: 24,
6736            num_frames: 0,
6737            duration_seconds: 0.0,
6738            format: AudioFormat::Unknown,
6739            tags: HashMap::new(),
6740            loop_points: None,
6741            cue_points: Vec::new(),
6742            embedded_bpm: None,
6743            embedded_key: None,
6744        }
6745    }
6746
6747    pub fn with_sample_rate(mut self, sr: u32) -> Self { self.sample_rate = sr; self }
6748    pub fn with_channels(mut self, ch: u32) -> Self { self.num_channels = ch; self }
6749    pub fn with_frames(mut self, frames: u64) -> Self {
6750        self.num_frames = frames;
6751        self.duration_seconds = frames as f64 / self.sample_rate as f64;
6752        self
6753    }
6754
6755    pub fn duration_bars_at_bpm(&self, bpm: f32, time_sig_num: u32) -> f32 {
6756        let beats = self.duration_seconds as f32 * bpm / 60.0;
6757        beats / time_sig_num as f32
6758    }
6759
6760    pub fn frames_at_time(&self, time_seconds: f64) -> u64 {
6761        (time_seconds * self.sample_rate as f64) as u64
6762    }
6763
6764    pub fn pitch_shift_to_bpm(&self, target_bpm: f32) -> f32 {
6765        if let Some(src_bpm) = self.embedded_bpm {
6766            target_bpm / src_bpm
6767        } else {
6768            1.0
6769        }
6770    }
6771
6772    pub fn add_tag(&mut self, key: &str, value: &str) {
6773        self.tags.insert(key.to_string(), value.to_string());
6774    }
6775
6776    pub fn get_tag(&self, key: &str) -> Option<&str> {
6777        self.tags.get(key).map(|s| s.as_str())
6778    }
6779
6780    pub fn add_cue_point(&mut self, id: u32, name: &str, position_frames: u64, color: u32) {
6781        self.cue_points.push(CuePoint { id, name: name.to_string(), position_frames, color });
6782    }
6783
6784    pub fn file_size_estimate_bytes(&self) -> u64 {
6785        let bytes_per_frame = (self.bit_depth / 8) as u64 * self.num_channels as u64;
6786        self.num_frames * bytes_per_frame
6787    }
6788}
6789
6790// ============================================================
6791// SECTION: Audio Clip / Region (for timeline editing)
6792// ============================================================
6793
6794#[derive(Clone, Debug)]
6795pub struct AudioClip {
6796    pub id: u64,
6797    pub name: String,
6798    pub metadata: AudioFileMetadata,
6799    pub timeline_start_seconds: f64,
6800    pub timeline_end_seconds: f64,
6801    pub source_offset_seconds: f64,
6802    pub gain_db: f32,
6803    pub fade_in_seconds: f32,
6804    pub fade_out_seconds: f32,
6805    pub pitch_semitones: f32,
6806    pub time_stretch_ratio: f32,
6807    pub mute: bool,
6808    pub color: u32,
6809    pub locked: bool,
6810}
6811
6812impl AudioClip {
6813    pub fn new(id: u64, name: &str, metadata: AudioFileMetadata) -> Self {
6814        let duration = metadata.duration_seconds;
6815        Self {
6816            id,
6817            name: name.to_string(),
6818            metadata,
6819            timeline_start_seconds: 0.0,
6820            timeline_end_seconds: duration,
6821            source_offset_seconds: 0.0,
6822            gain_db: 0.0,
6823            fade_in_seconds: 0.0,
6824            fade_out_seconds: 0.0,
6825            pitch_semitones: 0.0,
6826            time_stretch_ratio: 1.0,
6827            mute: false,
6828            color: 0xFF8844FF,
6829            locked: false,
6830        }
6831    }
6832
6833    pub fn duration_seconds(&self) -> f64 {
6834        self.timeline_end_seconds - self.timeline_start_seconds
6835    }
6836
6837    pub fn contains_time(&self, time: f64) -> bool {
6838        time >= self.timeline_start_seconds && time < self.timeline_end_seconds
6839    }
6840
6841    pub fn source_time_at(&self, timeline_time: f64) -> f64 {
6842        let rel = timeline_time - self.timeline_start_seconds;
6843        self.source_offset_seconds + rel * self.time_stretch_ratio as f64
6844    }
6845
6846    pub fn gain_at_time(&self, timeline_time: f64) -> f32 {
6847        let rel = timeline_time - self.timeline_start_seconds;
6848        let dur = self.duration_seconds();
6849        let base = db_to_linear(self.gain_db);
6850        // Fade in
6851        let fade_in_gain = if self.fade_in_seconds > 0.0 && rel < self.fade_in_seconds as f64 {
6852            (rel as f32 / self.fade_in_seconds).clamp(0.0, 1.0)
6853        } else { 1.0 };
6854        // Fade out
6855        let remaining = dur - rel;
6856        let fade_out_gain = if self.fade_out_seconds > 0.0 && remaining < self.fade_out_seconds as f64 {
6857            (remaining as f32 / self.fade_out_seconds).clamp(0.0, 1.0)
6858        } else { 1.0 };
6859        base * fade_in_gain * fade_out_gain
6860    }
6861
6862    pub fn move_to(&mut self, new_start: f64) {
6863        if self.locked { return; }
6864        let dur = self.duration_seconds();
6865        self.timeline_start_seconds = new_start;
6866        self.timeline_end_seconds = new_start + dur;
6867    }
6868
6869    pub fn trim_start(&mut self, new_start: f64) {
6870        if self.locked { return; }
6871        let extra = new_start - self.timeline_start_seconds;
6872        self.source_offset_seconds += extra * self.time_stretch_ratio as f64;
6873        self.timeline_start_seconds = new_start;
6874    }
6875
6876    pub fn trim_end(&mut self, new_end: f64) {
6877        if self.locked { return; }
6878        self.timeline_end_seconds = new_end.max(self.timeline_start_seconds + 0.01);
6879    }
6880
6881    pub fn split_at(&self, time: f64) -> Option<(AudioClip, AudioClip)> {
6882        if !self.contains_time(time) { return None; }
6883        let mut left = self.clone();
6884        let mut right = self.clone();
6885        left.timeline_end_seconds = time;
6886        right.timeline_start_seconds = time;
6887        right.source_offset_seconds = self.source_time_at(time);
6888        right.id = self.id + 1000000;
6889        Some((left, right))
6890    }
6891}
6892
6893// ============================================================
6894// SECTION: Audio Timeline Track
6895// ============================================================
6896
6897#[derive(Clone, Debug)]
6898pub struct AudioTimelineTrack {
6899    pub id: u32,
6900    pub name: String,
6901    pub clips: Vec<AudioClip>,
6902    pub channel_strip_index: usize,
6903    pub mute: bool,
6904    pub solo: bool,
6905    pub arm_record: bool,
6906    pub color: u32,
6907    pub height_pixels: u32,
6908}
6909
6910impl AudioTimelineTrack {
6911    pub fn new(id: u32, name: &str) -> Self {
6912        Self {
6913            id,
6914            name: name.to_string(),
6915            clips: Vec::new(),
6916            channel_strip_index: 0,
6917            mute: false,
6918            solo: false,
6919            arm_record: false,
6920            color: 0x4488FFFF,
6921            height_pixels: 80,
6922        }
6923    }
6924
6925    pub fn add_clip(&mut self, clip: AudioClip) {
6926        // Insert sorted by start time
6927        let pos = self.clips.partition_point(|c| c.timeline_start_seconds < clip.timeline_start_seconds);
6928        self.clips.insert(pos, clip);
6929    }
6930
6931    pub fn remove_clip(&mut self, id: u64) {
6932        self.clips.retain(|c| c.id != id);
6933    }
6934
6935    pub fn clips_at_time(&self, time: f64) -> Vec<&AudioClip> {
6936        self.clips.iter().filter(|c| c.contains_time(time) && !c.mute).collect()
6937    }
6938
6939    pub fn overlapping_clips(&self) -> Vec<(usize, usize)> {
6940        let mut overlaps = Vec::new();
6941        for i in 0..self.clips.len() {
6942            for j in (i + 1)..self.clips.len() {
6943                if self.clips[i].timeline_start_seconds < self.clips[j].timeline_end_seconds
6944                    && self.clips[j].timeline_start_seconds < self.clips[i].timeline_end_seconds {
6945                    overlaps.push((i, j));
6946                }
6947            }
6948        }
6949        overlaps
6950    }
6951
6952    pub fn total_duration_seconds(&self) -> f64 {
6953        self.clips.iter()
6954            .map(|c| c.timeline_end_seconds)
6955            .fold(0.0f64, f64::max)
6956    }
6957
6958    pub fn clip_at_position(&self, time: f64) -> Option<&AudioClip> {
6959        self.clips.iter().find(|c| c.contains_time(time))
6960    }
6961
6962    pub fn fill_gaps_with_silence(&self) -> Vec<(f64, f64)> {
6963        // Returns list of (start, end) gaps between clips
6964        let mut gaps = Vec::new();
6965        if self.clips.is_empty() { return gaps; }
6966        let mut sorted_clips = self.clips.clone();
6967        sorted_clips.sort_by(|a, b| a.timeline_start_seconds.partial_cmp(&b.timeline_start_seconds).unwrap());
6968        let mut prev_end = sorted_clips[0].timeline_end_seconds;
6969        for clip in sorted_clips.iter().skip(1) {
6970            if clip.timeline_start_seconds > prev_end + 1e-6 {
6971                gaps.push((prev_end, clip.timeline_start_seconds));
6972            }
6973            prev_end = prev_end.max(clip.timeline_end_seconds);
6974        }
6975        gaps
6976    }
6977}
6978
6979// ============================================================
6980// SECTION: Full Audio Mixer Editor (Extended)
6981// ============================================================
6982
6983#[derive(Clone, Debug)]
6984pub struct AudioMixerEditorExtended {
6985    pub session: MixerSession,
6986    pub tracks: Vec<AudioTimelineTrack>,
6987    pub clock: AudioClock,
6988    pub room_acoustics: RoomAcoustics,
6989    pub hrtf_panners: Vec<HrtfPanner>,
6990    pub next_clip_id: u64,
6991    pub next_track_id: u32,
6992    pub selected_track: Option<usize>,
6993    pub selected_clip: Option<u64>,
6994    pub zoom_level: f32,
6995    pub scroll_offset_seconds: f64,
6996    pub view_start_seconds: f64,
6997    pub view_end_seconds: f64,
6998    pub snap_enabled: bool,
6999    pub snap_grid_beats: f32,
7000    pub undo_history: VecDeque<MixerEditorState>,
7001    pub redo_history: VecDeque<MixerEditorState>,
7002    pub max_history: usize,
7003}
7004
7005#[derive(Clone, Debug)]
7006pub struct MixerEditorState {
7007    pub description: String,
7008    pub track_count: usize,
7009    pub play_head: f64,
7010    pub bpm: f32,
7011}
7012
7013impl AudioMixerEditorExtended {
7014    pub fn new() -> Self {
7015        let session = MixerSession::new("Untitled Session", SAMPLE_RATE, 24);
7016        let clock = AudioClock::new(SAMPLE_RATE, 120.0);
7017        let room = RoomAcoustics::new(10.0, 8.0, 3.0, 0.2);
7018        Self {
7019            session,
7020            tracks: Vec::new(),
7021            clock,
7022            room_acoustics: room,
7023            hrtf_panners: Vec::new(),
7024            next_clip_id: 1,
7025            next_track_id: 1,
7026            selected_track: None,
7027            selected_clip: None,
7028            zoom_level: 1.0,
7029            scroll_offset_seconds: 0.0,
7030            view_start_seconds: 0.0,
7031            view_end_seconds: 60.0,
7032            snap_enabled: true,
7033            snap_grid_beats: 0.25,
7034            undo_history: VecDeque::new(),
7035            redo_history: VecDeque::new(),
7036            max_history: 50,
7037        }
7038    }
7039
7040    pub fn add_audio_track(&mut self, name: &str) -> u32 {
7041        let id = self.next_track_id;
7042        self.next_track_id += 1;
7043        let channel_idx = self.session.add_channel(name);
7044        let mut track = AudioTimelineTrack::new(id, name);
7045        track.channel_strip_index = channel_idx;
7046        self.tracks.push(track);
7047        id
7048    }
7049
7050    pub fn remove_audio_track(&mut self, id: u32) {
7051        self.tracks.retain(|t| t.id != id);
7052    }
7053
7054    pub fn add_clip_to_track(&mut self, track_id: u32, metadata: AudioFileMetadata, start_seconds: f64) -> Option<u64> {
7055        let clip_id = self.next_clip_id;
7056        self.next_clip_id += 1;
7057        let start = if self.snap_enabled {
7058            self.snap_to_grid(start_seconds)
7059        } else {
7060            start_seconds
7061        };
7062        let mut clip = AudioClip::new(clip_id, &metadata.file_path.clone(), metadata);
7063        clip.move_to(start);
7064        if let Some(track) = self.tracks.iter_mut().find(|t| t.id == track_id) {
7065            track.add_clip(clip);
7066            Some(clip_id)
7067        } else {
7068            None
7069        }
7070    }
7071
7072    fn snap_to_grid(&self, time_seconds: f64) -> f64 {
7073        let beat_duration = 60.0 / self.clock.bpm as f64;
7074        let grid_duration = beat_duration * self.snap_grid_beats as f64;
7075        (time_seconds / grid_duration).round() * grid_duration
7076    }
7077
7078    pub fn play(&mut self) {
7079        self.clock.is_running = true;
7080        self.session.is_playing = true;
7081    }
7082
7083    pub fn stop(&mut self) {
7084        self.clock.is_running = false;
7085        self.session.is_playing = false;
7086    }
7087
7088    pub fn seek_to(&mut self, time_seconds: f64) {
7089        self.session.play_head_seconds = time_seconds as f32;
7090        // Reset all biquad states on seek to avoid zipper noise
7091        for strip in &mut self.session.channel_strips {
7092            strip.eq_strip.reset_states();
7093        }
7094    }
7095
7096    pub fn set_bpm(&mut self, bpm: f32) {
7097        self.session.bpm = bpm;
7098        self.clock.bpm = bpm;
7099    }
7100
7101    pub fn get_active_clips_at_play_head(&self) -> Vec<(u32, &AudioClip)> {
7102        let time = self.session.play_head_seconds as f64;
7103        let mut result = Vec::new();
7104        for track in &self.tracks {
7105            if track.mute { continue; }
7106            for clip in track.clips_at_time(time) {
7107                result.push((track.id, clip));
7108            }
7109        }
7110        result
7111    }
7112
7113    pub fn push_undo_state(&mut self, description: &str) {
7114        let state = MixerEditorState {
7115            description: description.to_string(),
7116            track_count: self.tracks.len(),
7117            play_head: self.session.play_head_seconds as f64,
7118            bpm: self.session.bpm,
7119        };
7120        if self.undo_history.len() >= self.max_history {
7121            self.undo_history.pop_front();
7122        }
7123        self.undo_history.push_back(state);
7124        self.redo_history.clear();
7125    }
7126
7127    pub fn undo(&mut self) -> bool {
7128        if let Some(state) = self.undo_history.pop_back() {
7129            self.redo_history.push_back(state.clone());
7130            // Basic undo: restore BPM and play head
7131            self.set_bpm(state.bpm);
7132            self.seek_to(state.play_head);
7133            true
7134        } else { false }
7135    }
7136
7137    pub fn redo(&mut self) -> bool {
7138        if let Some(state) = self.redo_history.pop_back() {
7139            self.undo_history.push_back(state.clone());
7140            self.set_bpm(state.bpm);
7141            self.seek_to(state.play_head);
7142            true
7143        } else { false }
7144    }
7145
7146    pub fn total_duration_seconds(&self) -> f64 {
7147        self.tracks.iter()
7148            .map(|t| t.total_duration_seconds())
7149            .fold(0.0f64, f64::max)
7150    }
7151
7152    pub fn find_clip_mut(&mut self, clip_id: u64) -> Option<&mut AudioClip> {
7153        for track in &mut self.tracks {
7154            if let Some(clip) = track.clips.iter_mut().find(|c| c.id == clip_id) {
7155                return Some(clip);
7156            }
7157        }
7158        None
7159    }
7160
7161    pub fn move_clip(&mut self, clip_id: u64, new_start: f64) {
7162        let start = if self.snap_enabled { self.snap_to_grid(new_start) } else { new_start };
7163        if let Some(clip) = self.find_clip_mut(clip_id) {
7164            clip.move_to(start);
7165        }
7166    }
7167
7168    pub fn export_mix_info(&self) -> HashMap<String, String> {
7169        let mut info = HashMap::new();
7170        info.insert("session_name".to_string(), self.session.session_name.clone());
7171        info.insert("bpm".to_string(), self.session.bpm.to_string());
7172        info.insert("track_count".to_string(), self.tracks.len().to_string());
7173        info.insert("duration_seconds".to_string(), self.total_duration_seconds().to_string());
7174        info.insert("lufs_integrated".to_string(), self.session.master_lufs().to_string());
7175        info.insert("true_peak_db".to_string(), self.session.master_peak().to_string());
7176        info.insert("rt60_seconds".to_string(), self.room_acoustics.rt60().to_string());
7177        info
7178    }
7179
7180    pub fn apply_global_eq_preset(&mut self, preset: &str) {
7181        for strip in &mut self.session.channel_strips {
7182            match preset {
7183                "Bright" => {
7184                    if let Some(band) = strip.eq_strip.bands.get_mut(3) {
7185                        band.update_parameters(8000.0, 3.0, 0.707);
7186                    }
7187                }
7188                "Warm" => {
7189                    if let Some(band) = strip.eq_strip.bands.get_mut(0) {
7190                        band.update_parameters(80.0, 0.0, 0.707);
7191                    }
7192                    if let Some(band) = strip.eq_strip.bands.get_mut(3) {
7193                        band.update_parameters(8000.0, -2.0, 0.707);
7194                    }
7195                }
7196                "Reset" => {
7197                    for band in &mut strip.eq_strip.bands {
7198                        band.update_parameters(band.frequency_hz, 0.0, band.q);
7199                    }
7200                }
7201                _ => {}
7202            }
7203        }
7204    }
7205}
7206
7207// ============================================================
7208// SECTION: Extended Tests
7209// ============================================================
7210
7211#[cfg(test)]
7212mod extended_tests {
7213    use super::*;
7214
7215    #[test]
7216    fn test_eq_band_frequency_response() {
7217        let band = EqBand::new(EqBandType::Peak, 1000.0, 6.0, 1.0);
7218        let gain_at_1k = band.frequency_response_at(1000.0);
7219        let gain_db = 20.0 * gain_at_1k.log10();
7220        // Should be approximately +6 dB at center frequency
7221        assert!((gain_db - 6.0).abs() < 1.0, "Peak EQ should boost by ~6dB at center: got {}", gain_db);
7222    }
7223
7224    #[test]
7225    fn test_eq_strip_response_curve() {
7226        let mut strip = ParametricEqStrip::new("test");
7227        strip.add_band(EqBand::new(EqBandType::LowCut, 100.0, 0.0, 0.707));
7228        let curve = strip.compute_response_curve(50);
7229        assert_eq!(curve.len(), 50);
7230        // Below cutoff should be attenuated
7231        let low = curve.iter().find(|(f, _)| *f < 50.0);
7232        if let Some((_, db)) = low {
7233            assert!(*db < -3.0, "Low cut should attenuate below cutoff");
7234        }
7235    }
7236
7237    #[test]
7238    fn test_noise_gate_opens() {
7239        let mut gate = NoiseGate::new();
7240        gate.threshold_db = -40.0;
7241        // Process loud signal to open gate
7242        for _ in 0..1000 {
7243            gate.process_sample(0.5, 0.5);
7244        }
7245        assert!(gate.is_open(), "Gate should open with signal above threshold");
7246    }
7247
7248    #[test]
7249    fn test_noise_gate_stays_closed() {
7250        let mut gate = NoiseGate::new();
7251        gate.threshold_db = -20.0;
7252        // Process quiet signal
7253        for _ in 0..1000 {
7254            gate.process_sample(0.001, 0.001);
7255        }
7256        // Gate should remain closed
7257        assert!(!gate.is_open());
7258    }
7259
7260    #[test]
7261    fn test_stereo_width_mono() {
7262        let mut proc = StereoWidthProcessor::new();
7263        proc.width = 0.0; // mono
7264        let (l, r) = proc.process_sample(0.8, -0.2);
7265        let mid = (0.8 - 0.2) * 0.5; // original mid
7266        let _ = mid;
7267        // Width=0: L and R should be equal (mono)
7268        assert!((l - r).abs() < 0.01, "Width=0 should produce mono output");
7269    }
7270
7271    #[test]
7272    fn test_harmonic_exciter_finite() {
7273        let mut exc = HarmonicExciter::new();
7274        for i in 0..1000 {
7275            let s = (i as f32 / 100.0).sin() * 0.5;
7276            let (l, r) = exc.process_sample(s, -s);
7277            assert!(l.is_finite() && r.is_finite());
7278        }
7279    }
7280
7281    #[test]
7282    fn test_transient_shaper_finite() {
7283        let mut ts = TransientShaper::new();
7284        for i in 0..500 {
7285            let s = if i % 50 == 0 { 0.9f32 } else { 0.1 };
7286            let (l, r) = ts.process_sample(s, s);
7287            assert!(l.is_finite() && r.is_finite());
7288        }
7289    }
7290
7291    #[test]
7292    fn test_convolution_reverb_energy() {
7293        let ir = vec![1.0f32, 0.5, 0.25, 0.125, 0.0625];
7294        let mut cr = ConvolutionReverb::new_with_ir(ir);
7295        cr.wet_dry = 1.0;
7296        let (l, _r) = cr.process_sample(1.0, 0.0);
7297        // First output should be proportional to IR[0]
7298        assert!(l.is_finite() && l > 0.0);
7299    }
7300
7301    #[test]
7302    fn test_convolution_reverb_rt60() {
7303        let reverb = ConvolutionReverb::new_synthetic_room(2.0);
7304        let rt60 = reverb.energy_rt60_estimate();
7305        // RT60 should be roughly around the room size in seconds (within order of magnitude)
7306        assert!(rt60 > 0.0 && rt60 < 10.0, "RT60 should be in 0-10 range: {}", rt60);
7307    }
7308
7309    #[test]
7310    fn test_chorus_output_finite() {
7311        let mut chorus = StereoChorus::new(ChorusMode::Chorus);
7312        for i in 0..2000 {
7313            let s = (i as f32 * 0.01).sin() * 0.3;
7314            let (l, r) = chorus.process_sample(s, s);
7315            assert!(l.is_finite() && r.is_finite());
7316        }
7317    }
7318
7319    #[test]
7320    fn test_flanger_output_finite() {
7321        let mut flanger = StereoChorus::new(ChorusMode::Flanger);
7322        for i in 0..2000 {
7323            let s = (i as f32 * 0.01).sin() * 0.3;
7324            let (l, r) = flanger.process_sample(s, s);
7325            assert!(l.is_finite() && r.is_finite());
7326        }
7327    }
7328
7329    #[test]
7330    fn test_spectrum_analyzer_push() {
7331        let mut analyzer = SpectrumAnalyzer::new(16);
7332        for i in 0..(SPECTRUM_FFT_SIZE * 2) {
7333            let s = ((i as f32 * TWO_PI * 440.0) / SAMPLE_RATE).sin();
7334            analyzer.push_samples(s, s);
7335        }
7336        // After enough samples, magnitude should be populated
7337        let (l, _r) = analyzer.get_band_db(5);
7338        assert!(l.is_finite());
7339    }
7340
7341    #[test]
7342    fn test_loudness_history_push() {
7343        let mut hist = LoudnessHistory::new(10.0);
7344        for _ in 0..10000 {
7345            hist.push_sample(0.5, -0.3);
7346        }
7347        assert!(hist.current_rms_db.is_finite() && hist.current_rms_db > -60.0);
7348    }
7349
7350    #[test]
7351    fn test_channel_strip_process() {
7352        let mut strip = ChannelStrip::new("test");
7353        let (l, r) = strip.process_sample(0.5, -0.5);
7354        assert!(l.is_finite() && r.is_finite());
7355    }
7356
7357    #[test]
7358    fn test_channel_strip_mute() {
7359        let mut strip = ChannelStrip::new("muted");
7360        strip.mute = true;
7361        let (l, r) = strip.process_sample(1.0, 1.0);
7362        assert_eq!(l, 0.0);
7363        assert_eq!(r, 0.0);
7364    }
7365
7366    #[test]
7367    fn test_channel_strip_automation() {
7368        let mut strip = ChannelStrip::new("auto");
7369        strip.add_automation_point(0.0, 1.0, AutomationCurve::Linear);
7370        strip.add_automation_point(1.0, 0.0, AutomationCurve::Linear);
7371        strip.evaluate_fader_automation(0.5);
7372        assert!((strip.fader_value - 0.5).abs() < 0.01);
7373    }
7374
7375    #[test]
7376    fn test_automation_smooth_curve() {
7377        let mut strip = ChannelStrip::new("smooth");
7378        strip.add_automation_point(0.0, 0.0, AutomationCurve::Smooth);
7379        strip.add_automation_point(1.0, 1.0, AutomationCurve::Smooth);
7380        strip.evaluate_fader_automation(0.5);
7381        // Smooth at midpoint = 0.5 * 0.5 * (3 - 2*0.5) = 0.5
7382        assert!((strip.fader_value - 0.5).abs() < 0.01);
7383    }
7384
7385    #[test]
7386    fn test_automation_hold_curve() {
7387        let mut strip = ChannelStrip::new("hold");
7388        strip.add_automation_point(0.0, 0.75, AutomationCurve::Hold);
7389        strip.add_automation_point(2.0, 0.25, AutomationCurve::Hold);
7390        strip.evaluate_fader_automation(1.0);
7391        assert!((strip.fader_value - 0.75).abs() < 0.01);
7392    }
7393
7394    #[test]
7395    fn test_master_bus_limiter() {
7396        let mut master = MasterBusProcessor::new();
7397        master.limiter_enabled = true;
7398        master.limiter_ceiling_db = -0.3;
7399        // Feed a very loud signal
7400        let ceiling = db_to_linear(-0.3);
7401        for _ in 0..1000 {
7402            let (l, r) = master.process_sample(10.0, -10.0);
7403            assert!(l.abs() <= ceiling * 1.01, "Limiter should cap output at ceiling");
7404            assert!(r.abs() <= ceiling * 1.01);
7405        }
7406    }
7407
7408    #[test]
7409    fn test_master_bus_lufs() {
7410        let mut master = MasterBusProcessor::new();
7411        for _ in 0..100000 {
7412            master.process_sample(0.3, -0.2);
7413        }
7414        let lufs = master.lufs_integrated();
7415        assert!(lufs.is_finite() && lufs > -60.0 && lufs < 0.0);
7416    }
7417
7418    #[test]
7419    fn test_mixer_session_process() {
7420        let mut session = MixerSession::new("test", 44100.0, 24);
7421        let _ch = session.add_channel("ch1");
7422        let inputs = vec![(0.5f32, -0.3f32)];
7423        let (l, r) = session.process_frame(&inputs);
7424        assert!(l.is_finite() && r.is_finite());
7425    }
7426
7427    #[test]
7428    fn test_mixer_session_solo() {
7429        let mut session = MixerSession::new("test", 44100.0, 24);
7430        session.add_channel("A");
7431        session.add_channel("B");
7432        session.solo_channel(0, true);
7433        assert!(session.channel_strips[0].solo);
7434        assert!(!session.channel_strips[1].solo);
7435    }
7436
7437    #[test]
7438    fn test_midi_note_frequency() {
7439        let note = MidiNote::new(0, 69, 100, 0.0, 1.0);
7440        assert!((note.frequency_hz() - 440.0).abs() < 0.01);
7441        let note_c4 = MidiNote::new(0, 60, 100, 0.0, 1.0);
7442        assert!((note_c4.frequency_hz() - 261.63).abs() < 0.1);
7443    }
7444
7445    #[test]
7446    fn test_midi_track_density() {
7447        let mut track = MidiTrack::new("drums", 9);
7448        for i in 0..16 {
7449            track.add_note(36, 100, i as f32, 0.5);
7450        }
7451        let density = track.note_density_per_beat();
7452        // 16 notes over ~16.5 beats
7453        assert!(density > 0.0 && density.is_finite());
7454    }
7455
7456    #[test]
7457    fn test_midi_track_quantize() {
7458        let mut track = MidiTrack::new("test", 0);
7459        track.add_note(60, 100, 0.1, 0.9);
7460        track.quantize_to_grid(0.25);
7461        assert!((track.notes[0].start_beat - 0.0).abs() < 0.001 ||
7462                (track.notes[0].start_beat - 0.25).abs() < 0.001);
7463    }
7464
7465    #[test]
7466    fn test_scale_contains_notes() {
7467        let scale = MusicalScale::Major;
7468        // C major: C D E F G A B (0,2,4,5,7,9,11)
7469        assert!(scale.note_in_scale(60, 60)); // C is in C major
7470        assert!(scale.note_in_scale(62, 60)); // D is in C major
7471        assert!(!scale.note_in_scale(61, 60)); // C# is NOT in C major
7472    }
7473
7474    #[test]
7475    fn test_chord_voicing_major() {
7476        let chord = ChordVoicing {
7477            root: 60,
7478            chord_type: ChordType::Major,
7479            inversion: 0,
7480            spread: ChordSpread::Close,
7481        };
7482        let notes = chord.midi_notes(0);
7483        // C major triad: C E G (60, 64, 67)
7484        assert_eq!(notes.len(), 3);
7485        assert_eq!(notes[0], 60);
7486        assert_eq!(notes[1], 64);
7487        assert_eq!(notes[2], 67);
7488    }
7489
7490    #[test]
7491    fn test_chord_voicing_first_inversion() {
7492        let chord = ChordVoicing {
7493            root: 60,
7494            chord_type: ChordType::Major,
7495            inversion: 1,
7496            spread: ChordSpread::Close,
7497        };
7498        let notes = chord.midi_notes(0);
7499        // First inversion: E G C8va
7500        assert_eq!(notes[0], 64); // E
7501        assert_eq!(notes[1], 67); // G
7502        assert_eq!(notes[2], 72); // C an octave up
7503    }
7504
7505    #[test]
7506    fn test_audio_clock_advance() {
7507        let mut clock = AudioClock::new(44100.0, 120.0);
7508        clock.is_running = true;
7509        clock.advance(44100);
7510        // At 120 BPM: 1 second = 2 beats
7511        assert!((clock.beat_count - 2.0).abs() < 0.01);
7512        assert_eq!(clock.sample_count, 44100);
7513    }
7514
7515    #[test]
7516    fn test_audio_clock_loop() {
7517        let mut clock = AudioClock::new(44100.0, 120.0);
7518        clock.is_running = true;
7519        clock.loop_enabled = true;
7520        clock.loop_start_beat = 0.0;
7521        clock.loop_end_beat = 4.0;
7522        // Advance past loop end (4 beats = 2 seconds = 88200 samples)
7523        clock.advance(100000);
7524        // Should have looped
7525        assert!(clock.beat_count < 4.0);
7526    }
7527
7528    #[test]
7529    fn test_audio_clock_beat_sample_conversion() {
7530        let clock = AudioClock::new(44100.0, 120.0);
7531        // 1 beat at 120 BPM = 0.5 seconds = 22050 samples
7532        let samples = clock.sample_at_beat(1.0);
7533        assert_eq!(samples, 22050);
7534        let beat = clock.beat_at_sample(22050);
7535        assert!((beat - 1.0).abs() < 0.001);
7536    }
7537
7538    #[test]
7539    fn test_audio_file_metadata_duration() {
7540        let meta = AudioFileMetadata::new("test.wav")
7541            .with_sample_rate(44100)
7542            .with_channels(2)
7543            .with_frames(441000);
7544        assert!((meta.duration_seconds - 10.0).abs() < 0.001);
7545    }
7546
7547    #[test]
7548    fn test_audio_clip_split() {
7549        let meta = AudioFileMetadata::new("test.wav").with_frames(88200);
7550        let clip = AudioClip::new(1, "clip", meta);
7551        let result = clip.split_at(0.5);
7552        assert!(result.is_some());
7553        let (left, right) = result.unwrap();
7554        assert!((left.timeline_end_seconds - 0.5).abs() < 0.001);
7555        assert!((right.timeline_start_seconds - 0.5).abs() < 0.001);
7556    }
7557
7558    #[test]
7559    fn test_audio_clip_fade_gain() {
7560        let meta = AudioFileMetadata::new("test.wav").with_frames(44100);
7561        let mut clip = AudioClip::new(1, "clip", meta);
7562        clip.fade_in_seconds = 0.5;
7563        clip.timeline_start_seconds = 0.0;
7564        clip.timeline_end_seconds = 1.0;
7565        // At 0.25 seconds: fade_in_gain = 0.25/0.5 = 0.5
7566        let gain = clip.gain_at_time(0.25);
7567        assert!((gain - 0.5).abs() < 0.01);
7568        // At 0.75 seconds: fully open
7569        let gain_full = clip.gain_at_time(0.75);
7570        assert!((gain_full - 1.0).abs() < 0.01);
7571    }
7572
7573    #[test]
7574    fn test_timeline_track_overlaps() {
7575        let mut track = AudioTimelineTrack::new(1, "track");
7576        let meta1 = AudioFileMetadata::new("a.wav").with_frames(44100);
7577        let meta2 = AudioFileMetadata::new("b.wav").with_frames(44100);
7578        let mut clip1 = AudioClip::new(1, "a", meta1);
7579        let mut clip2 = AudioClip::new(2, "b", meta2);
7580        clip1.timeline_start_seconds = 0.0;
7581        clip1.timeline_end_seconds = 2.0;
7582        clip2.timeline_start_seconds = 1.0;
7583        clip2.timeline_end_seconds = 3.0;
7584        track.add_clip(clip1);
7585        track.add_clip(clip2);
7586        let overlaps = track.overlapping_clips();
7587        assert_eq!(overlaps.len(), 1);
7588    }
7589
7590    #[test]
7591    fn test_timeline_track_gaps() {
7592        let mut track = AudioTimelineTrack::new(1, "track");
7593        let meta1 = AudioFileMetadata::new("a.wav").with_frames(44100);
7594        let meta2 = AudioFileMetadata::new("b.wav").with_frames(44100);
7595        let mut clip1 = AudioClip::new(1, "a", meta1);
7596        let mut clip2 = AudioClip::new(2, "b", meta2);
7597        clip1.timeline_start_seconds = 0.0;
7598        clip1.timeline_end_seconds = 1.0;
7599        clip2.timeline_start_seconds = 2.0;
7600        clip2.timeline_end_seconds = 3.0;
7601        track.add_clip(clip1);
7602        track.add_clip(clip2);
7603        let gaps = track.fill_gaps_with_silence();
7604        assert_eq!(gaps.len(), 1);
7605        assert!((gaps[0].0 - 1.0).abs() < 0.001);
7606        assert!((gaps[0].1 - 2.0).abs() < 0.001);
7607    }
7608
7609    #[test]
7610    fn test_send_return_bus() {
7611        let mut bus = SendReturnBus::new_reverb_bus("Reverb", 1.5);
7612        bus.receive_send(0.5, -0.3, 0.5);
7613        let (l, r) = bus.process_and_clear();
7614        assert!(l.is_finite() && r.is_finite());
7615    }
7616
7617    #[test]
7618    fn test_hrtf_panner_left() {
7619        let mut panner = HrtfPanner::new();
7620        panner.set_position(-90.0, 0.0, 1.0);
7621        let (l, r) = panner.process_mono_sample(1.0);
7622        assert!(l.is_finite() && r.is_finite());
7623    }
7624
7625    #[test]
7626    fn test_hrtf_panner_distance_atten() {
7627        let mut panner_near = HrtfPanner::new();
7628        panner_near.set_position(0.0, 0.0, 1.0);
7629        let mut panner_far = HrtfPanner::new();
7630        panner_far.set_position(0.0, 0.0, 100.0);
7631        let (l_near, _) = panner_near.process_mono_sample(1.0);
7632        let (l_far, _) = panner_far.process_mono_sample(1.0);
7633        assert!(l_near > l_far, "Closer source should be louder");
7634    }
7635
7636    #[test]
7637    fn test_room_acoustics_rt60() {
7638        let room = RoomAcoustics::new(10.0, 8.0, 3.0, 0.2);
7639        let rt60 = room.rt60();
7640        // Sabine: V=240, S=268, a=0.2 => RT60 = 0.161*240/(0.2*268) ≈ 0.72
7641        assert!(rt60 > 0.1 && rt60 < 5.0, "RT60 should be in reasonable range: {}", rt60);
7642    }
7643
7644    #[test]
7645    fn test_room_modal_frequencies() {
7646        let room = RoomAcoustics::new(5.0, 4.0, 2.5, 0.3);
7647        let modes = room.modal_frequencies();
7648        assert!(!modes.is_empty());
7649        // First mode of 5m room: 343/(2*5) = 34.3 Hz
7650        assert!(modes[0] > 20.0 && modes[0] < 100.0);
7651    }
7652
7653    #[test]
7654    fn test_room_process_sample_finite() {
7655        let mut room = RoomAcoustics::new(8.0, 6.0, 3.0, 0.15);
7656        for i in 0..1000 {
7657            let s = (i as f32 * 0.01).sin() * 0.3;
7658            let (l, r) = room.process_mono_sample(s, 2.0);
7659            assert!(l.is_finite() && r.is_finite());
7660        }
7661    }
7662
7663    #[test]
7664    fn test_whole_tone_scale() {
7665        let scale = MusicalScale::WholeTone;
7666        let notes = scale.scale_notes(60, 5, 5);
7667        assert_eq!(notes.len(), 6);
7668        // All intervals should be 2 semitones
7669        for w in notes.windows(2) {
7670            assert_eq!(w[1] - w[0], 2);
7671        }
7672    }
7673
7674    #[test]
7675    fn test_mixer_editor_extended() {
7676        let mut editor = AudioMixerEditorExtended::new();
7677        let track_id = editor.add_audio_track("Guitar");
7678        assert_eq!(track_id, 1);
7679        let meta = AudioFileMetadata::new("guitar.wav")
7680            .with_sample_rate(44100)
7681            .with_channels(2)
7682            .with_frames(88200);
7683        let clip_id = editor.add_clip_to_track(track_id, meta, 0.0);
7684        assert!(clip_id.is_some());
7685        editor.play();
7686        assert!(editor.session.is_playing);
7687        editor.stop();
7688        assert!(!editor.session.is_playing);
7689    }
7690
7691    #[test]
7692    fn test_mixer_editor_undo_redo() {
7693        let mut editor = AudioMixerEditorExtended::new();
7694        editor.set_bpm(120.0);
7695        editor.push_undo_state("Change BPM to 120");
7696        editor.set_bpm(140.0);
7697        editor.push_undo_state("Change BPM to 140");
7698        let result = editor.undo();
7699        assert!(result);
7700        // After undo we restore to previous state (140->120)
7701        assert!((editor.session.bpm - 120.0).abs() < 0.01);
7702        let redo_result = editor.redo();
7703        assert!(redo_result);
7704        assert!((editor.session.bpm - 140.0).abs() < 0.01);
7705    }
7706
7707    #[test]
7708    fn test_mixer_export_info() {
7709        let mut editor = AudioMixerEditorExtended::new();
7710        editor.add_audio_track("Lead");
7711        let info = editor.export_mix_info();
7712        assert!(info.contains_key("bpm"));
7713        assert!(info.contains_key("track_count"));
7714        assert_eq!(info["track_count"], "1");
7715    }
7716}