Skip to main content

proof_engine/audio/graph/
mod.rs

1//! Audio Processing Graph for Proof Engine.
2//!
3//! A node-based audio processing pipeline:
4//! - 30+ node types: oscillators, filters, envelopes, effects, samplers
5//! - Polyphonic voice management (up to 64 voices)
6//! - Sample-accurate automation (parameter lanes)
7//! - Graph ordering via topological sort
8//! - Lock-free command queue for real-time audio thread safety
9//! - MIDI-inspired note/velocity/pitch interface
10
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13use std::collections::VecDeque;
14
15// ── Constants ─────────────────────────────────────────────────────────────────
16
17pub const SAMPLE_RATE: f32 = 44100.0;
18pub const BUFFER_SIZE: usize = 512;
19pub const MAX_VOICES:  usize = 64;
20pub const MAX_CHANNELS: usize = 2; // stereo
21
22// ── AudioId ───────────────────────────────────────────────────────────────────
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct NodeId(pub u32);
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct EdgeId(pub u32);
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct VoiceId(pub u32);
32
33// ── AudioBuffer ───────────────────────────────────────────────────────────────
34
35/// Fixed-size mono audio buffer.
36#[derive(Debug, Clone)]
37pub struct AudioBuffer {
38    pub samples: [f32; BUFFER_SIZE],
39    pub len:     usize,
40}
41
42impl AudioBuffer {
43    pub fn new() -> Self {
44        Self { samples: [0.0; BUFFER_SIZE], len: BUFFER_SIZE }
45    }
46
47    pub fn silence() -> Self { Self::new() }
48
49    pub fn fill(&mut self, v: f32) {
50        self.samples[..self.len].fill(v);
51    }
52
53    pub fn add_from(&mut self, other: &AudioBuffer) {
54        for i in 0..self.len.min(other.len) {
55            self.samples[i] += other.samples[i];
56        }
57    }
58
59    pub fn multiply_by(&mut self, other: &AudioBuffer) {
60        for i in 0..self.len.min(other.len) {
61            self.samples[i] *= other.samples[i];
62        }
63    }
64
65    pub fn scale(&mut self, gain: f32) {
66        for s in self.samples[..self.len].iter_mut() { *s *= gain; }
67    }
68
69    pub fn mix(&mut self, other: &AudioBuffer, weight: f32) {
70        for i in 0..self.len.min(other.len) {
71            self.samples[i] = self.samples[i] * (1.0 - weight) + other.samples[i] * weight;
72        }
73    }
74
75    pub fn peak(&self) -> f32 {
76        self.samples[..self.len].iter().map(|s| s.abs()).fold(0.0_f32, f32::max)
77    }
78
79    pub fn rms(&self) -> f32 {
80        if self.len == 0 { return 0.0; }
81        let sum: f32 = self.samples[..self.len].iter().map(|s| s * s).sum();
82        (sum / self.len as f32).sqrt()
83    }
84}
85
86impl Default for AudioBuffer { fn default() -> Self { Self::new() } }
87
88/// Stereo audio buffer.
89#[derive(Debug, Clone, Default)]
90pub struct StereoBuffer {
91    pub left:  AudioBuffer,
92    pub right: AudioBuffer,
93}
94
95impl StereoBuffer {
96    pub fn new() -> Self { Self { left: AudioBuffer::new(), right: AudioBuffer::new() } }
97
98    pub fn silence() -> Self { Self::new() }
99
100    pub fn peak(&self) -> f32 { self.left.peak().max(self.right.peak()) }
101
102    pub fn scale(&mut self, gain: f32) {
103        self.left.scale(gain);
104        self.right.scale(gain);
105    }
106}
107
108// ── Waveform ──────────────────────────────────────────────────────────────────
109
110#[derive(Debug, Clone, Copy, PartialEq)]
111pub enum Waveform {
112    Sine,
113    Square,
114    Triangle,
115    Sawtooth,
116    ReverseSawtooth,
117    Pulse(f32),   // pulse width 0..1
118    Noise,
119    SineSquared,
120}
121
122impl Waveform {
123    pub fn sample(self, phase: f32) -> f32 {
124        let t = phase.fract();
125        match self {
126            Waveform::Sine            => (t * std::f32::consts::TAU).sin(),
127            Waveform::Square          => if t < 0.5 { 1.0 } else { -1.0 },
128            Waveform::Triangle        => 1.0 - 4.0 * (t - 0.5).abs(),
129            Waveform::Sawtooth        => 2.0 * t - 1.0,
130            Waveform::ReverseSawtooth => 1.0 - 2.0 * t,
131            Waveform::Pulse(pw)       => if t < pw { 1.0 } else { -1.0 },
132            Waveform::Noise           => pseudo_rand(phase) * 2.0 - 1.0,
133            Waveform::SineSquared     => {
134                let s = (t * std::f32::consts::TAU).sin();
135                s * s.abs()
136            }
137        }
138    }
139}
140
141fn pseudo_rand(seed: f32) -> f32 {
142    let x = (seed * 127.1 + 311.7).sin() * 43758.547;
143    x - x.floor()
144}
145
146// ── FilterType ────────────────────────────────────────────────────────────────
147
148#[derive(Debug, Clone, Copy, PartialEq)]
149pub enum FilterType {
150    LowPass,
151    HighPass,
152    BandPass,
153    Notch,
154    AllPass,
155    LowShelf,
156    HighShelf,
157    Peaking,
158}
159
160/// Biquad filter state.
161#[derive(Debug, Clone, Default)]
162pub struct BiquadFilter {
163    // Coefficients
164    b0: f32, b1: f32, b2: f32, a1: f32, a2: f32,
165    // State
166    x1: f32, x2: f32, y1: f32, y2: f32,
167}
168
169impl BiquadFilter {
170    pub fn configure(&mut self, filter_type: FilterType, freq: f32, q: f32, gain_db: f32) {
171        let omega = std::f32::consts::TAU * freq / SAMPLE_RATE;
172        let sin_w = omega.sin();
173        let cos_w = omega.cos();
174        let alpha = sin_w / (2.0 * q.max(0.001));
175        let a_lin = 10.0_f32.powf(gain_db / 40.0);
176
177        let (b0, b1, b2, a0, a1, a2) = match filter_type {
178            FilterType::LowPass => {
179                ((1.0 - cos_w) / 2.0, 1.0 - cos_w, (1.0 - cos_w) / 2.0,
180                 1.0 + alpha, -2.0 * cos_w, 1.0 - alpha)
181            }
182            FilterType::HighPass => {
183                ((1.0 + cos_w) / 2.0, -(1.0 + cos_w), (1.0 + cos_w) / 2.0,
184                 1.0 + alpha, -2.0 * cos_w, 1.0 - alpha)
185            }
186            FilterType::BandPass => {
187                (alpha, 0.0, -alpha,
188                 1.0 + alpha, -2.0 * cos_w, 1.0 - alpha)
189            }
190            FilterType::Notch => {
191                (1.0, -2.0 * cos_w, 1.0,
192                 1.0 + alpha, -2.0 * cos_w, 1.0 - alpha)
193            }
194            FilterType::AllPass => {
195                (1.0 - alpha, -2.0 * cos_w, 1.0 + alpha,
196                 1.0 + alpha, -2.0 * cos_w, 1.0 - alpha)
197            }
198            FilterType::LowShelf => {
199                let root_a = a_lin.sqrt();
200                let beta = (a_lin.sqrt()) * alpha;
201                (a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w + 2.0 * beta),
202                 2.0 * a_lin * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w),
203                 a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w - 2.0 * beta),
204                 (a_lin + 1.0) + (a_lin - 1.0) * cos_w + 2.0 * root_a * alpha,
205                 -2.0 * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w),
206                 (a_lin + 1.0) + (a_lin - 1.0) * cos_w - 2.0 * root_a * alpha)
207            }
208            FilterType::HighShelf => {
209                let root_a = a_lin.sqrt();
210                let beta = root_a * alpha;
211                (a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w + 2.0 * beta),
212                 -2.0 * a_lin * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w),
213                 a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w - 2.0 * beta),
214                 (a_lin + 1.0) - (a_lin - 1.0) * cos_w + 2.0 * root_a * alpha,
215                 2.0 * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w),
216                 (a_lin + 1.0) - (a_lin - 1.0) * cos_w - 2.0 * root_a * alpha)
217            }
218            FilterType::Peaking => {
219                (1.0 + alpha * a_lin, -2.0 * cos_w, 1.0 - alpha * a_lin,
220                 1.0 + alpha / a_lin, -2.0 * cos_w, 1.0 - alpha / a_lin)
221            }
222        };
223
224        let inv_a0 = 1.0 / a0;
225        self.b0 = b0 * inv_a0;
226        self.b1 = b1 * inv_a0;
227        self.b2 = b2 * inv_a0;
228        self.a1 = a1 * inv_a0;
229        self.a2 = a2 * inv_a0;
230    }
231
232    pub fn process_sample(&mut self, x: f32) -> f32 {
233        let y = self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2
234              - self.a1 * self.y1 - self.a2 * self.y2;
235        self.x2 = self.x1; self.x1 = x;
236        self.y2 = self.y1; self.y1 = y;
237        y
238    }
239
240    pub fn process_buffer(&mut self, buf: &mut AudioBuffer) {
241        for s in buf.samples[..buf.len].iter_mut() {
242            *s = self.process_sample(*s);
243        }
244    }
245
246    pub fn reset(&mut self) {
247        self.x1 = 0.0; self.x2 = 0.0; self.y1 = 0.0; self.y2 = 0.0;
248    }
249}
250
251// ── AdsrEnvelope ──────────────────────────────────────────────────────────────
252
253#[derive(Debug, Clone, Copy, PartialEq)]
254pub enum EnvelopeStage { Idle, Attack, Decay, Sustain, Release }
255
256#[derive(Debug, Clone)]
257pub struct AdsrEnvelope {
258    pub attack_time:   f32,
259    pub decay_time:    f32,
260    pub sustain_level: f32,
261    pub release_time:  f32,
262    pub attack_curve:  f32, // 1 = linear, 2 = quadratic
263    pub release_curve: f32,
264    stage:    EnvelopeStage,
265    level:    f32,
266    stage_t:  f32,
267    sample_rate: f32,
268}
269
270impl AdsrEnvelope {
271    pub fn new(attack: f32, decay: f32, sustain: f32, release: f32) -> Self {
272        Self {
273            attack_time: attack,
274            decay_time: decay,
275            sustain_level: sustain,
276            release_time: release,
277            attack_curve: 1.0,
278            release_curve: 1.0,
279            stage: EnvelopeStage::Idle,
280            level: 0.0,
281            stage_t: 0.0,
282            sample_rate: SAMPLE_RATE,
283        }
284    }
285
286    pub fn note_on(&mut self) {
287        self.stage   = EnvelopeStage::Attack;
288        self.stage_t = 0.0;
289    }
290
291    pub fn note_off(&mut self) {
292        if self.stage != EnvelopeStage::Idle {
293            self.stage   = EnvelopeStage::Release;
294            self.stage_t = 0.0;
295        }
296    }
297
298    pub fn reset(&mut self) {
299        self.stage = EnvelopeStage::Idle;
300        self.level = 0.0;
301        self.stage_t = 0.0;
302    }
303
304    pub fn is_done(&self) -> bool { self.stage == EnvelopeStage::Idle }
305
306    pub fn next_sample(&mut self) -> f32 {
307        let dt = 1.0 / self.sample_rate;
308        self.stage_t += dt;
309        self.level = match self.stage {
310            EnvelopeStage::Idle => 0.0,
311            EnvelopeStage::Attack => {
312                let t = (self.stage_t / self.attack_time.max(1e-6)).clamp(0.0, 1.0);
313                let v = t.powf(self.attack_curve);
314                if t >= 1.0 { self.stage = EnvelopeStage::Decay; self.stage_t = 0.0; }
315                v
316            }
317            EnvelopeStage::Decay => {
318                let t = (self.stage_t / self.decay_time.max(1e-6)).clamp(0.0, 1.0);
319                let v = 1.0 - (1.0 - self.sustain_level) * t;
320                if t >= 1.0 { self.stage = EnvelopeStage::Sustain; }
321                v
322            }
323            EnvelopeStage::Sustain => self.sustain_level,
324            EnvelopeStage::Release => {
325                let t = (self.stage_t / self.release_time.max(1e-6)).clamp(0.0, 1.0);
326                let v = self.sustain_level * (1.0 - t.powf(self.release_curve));
327                if t >= 1.0 { self.stage = EnvelopeStage::Idle; self.level = 0.0; return 0.0; }
328                v
329            }
330        };
331        self.level
332    }
333
334    pub fn fill_buffer(&mut self, buf: &mut AudioBuffer) {
335        for s in buf.samples[..buf.len].iter_mut() {
336            *s = self.next_sample();
337        }
338    }
339}
340
341// ── Lfo ───────────────────────────────────────────────────────────────────────
342
343#[derive(Debug, Clone)]
344pub struct Lfo {
345    pub waveform:  Waveform,
346    pub frequency: f32,
347    pub amplitude: f32,
348    pub offset:    f32,
349    pub phase:     f32,
350    pub sync_to_tempo: bool,
351    pub bpm:       f32,
352    pub beat_div:  f32, // 1/4, 1/8, etc.
353}
354
355impl Lfo {
356    pub fn new(waveform: Waveform, freq: f32) -> Self {
357        Self {
358            waveform, frequency: freq, amplitude: 1.0, offset: 0.0,
359            phase: 0.0, sync_to_tempo: false, bpm: 120.0, beat_div: 1.0,
360        }
361    }
362
363    pub fn next_sample(&mut self) -> f32 {
364        let freq = if self.sync_to_tempo {
365            self.bpm / 60.0 / self.beat_div
366        } else { self.frequency };
367
368        let v = self.waveform.sample(self.phase);
369        self.phase += freq / SAMPLE_RATE;
370        if self.phase >= 1.0 { self.phase -= 1.0; }
371        self.offset + v * self.amplitude
372    }
373
374    pub fn fill_buffer(&mut self, buf: &mut AudioBuffer) {
375        for s in buf.samples[..buf.len].iter_mut() {
376            *s = self.next_sample();
377        }
378    }
379}
380
381// ── DelayLine ─────────────────────────────────────────────────────────────────
382
383#[derive(Debug, Clone)]
384pub struct DelayLine {
385    buffer:      Vec<f32>,
386    write_pos:   usize,
387    delay_samples: usize,
388}
389
390impl DelayLine {
391    pub fn new(max_delay_samples: usize) -> Self {
392        Self { buffer: vec![0.0; max_delay_samples], write_pos: 0, delay_samples: max_delay_samples / 2 }
393    }
394
395    pub fn set_delay_time(&mut self, secs: f32) {
396        self.delay_samples = ((secs * SAMPLE_RATE) as usize).min(self.buffer.len() - 1);
397    }
398
399    pub fn process(&mut self, input: f32) -> f32 {
400        let read_pos = (self.write_pos + self.buffer.len() - self.delay_samples) % self.buffer.len();
401        let out = self.buffer[read_pos];
402        self.buffer[self.write_pos] = input;
403        self.write_pos = (self.write_pos + 1) % self.buffer.len();
404        out
405    }
406
407    pub fn process_buffer(&mut self, input: &AudioBuffer, feedback: f32) -> AudioBuffer {
408        let mut out = AudioBuffer::new();
409        for i in 0..input.len {
410            let delayed = self.process(input.samples[i]);
411            out.samples[i] = delayed;
412            // Feed back into buffer
413            let fb_pos = (self.write_pos + self.buffer.len() - 1) % self.buffer.len();
414            self.buffer[fb_pos] += delayed * feedback;
415        }
416        out.len = input.len;
417        out
418    }
419}
420
421// ── Reverb ────────────────────────────────────────────────────────────────────
422
423/// Simple Schroeder reverb with 4 comb filters + 2 allpass filters.
424#[derive(Debug, Clone)]
425pub struct Reverb {
426    combs:   [DelayLine; 4],
427    allpass: [DelayLine; 2],
428    pub room_size: f32,
429    pub damping:   f32,
430    pub wet:       f32,
431    pub pre_delay: f32,
432    pre_delay_line: DelayLine,
433    damp_filters: [f32; 4], // one-pole LP per comb
434}
435
436impl Reverb {
437    pub fn new() -> Self {
438        let sizes = [1557, 1617, 1491, 1422];
439        let combs: [DelayLine; 4] = [
440            DelayLine::new(sizes[0]), DelayLine::new(sizes[1]),
441            DelayLine::new(sizes[2]), DelayLine::new(sizes[3]),
442        ];
443        let allpass = [DelayLine::new(556), DelayLine::new(441)];
444        Self {
445            combs, allpass,
446            room_size: 0.5, damping: 0.5, wet: 0.3, pre_delay: 0.02,
447            pre_delay_line: DelayLine::new(4096),
448            damp_filters: [0.0; 4],
449        }
450    }
451
452    pub fn process_buffer(&mut self, input: &AudioBuffer) -> AudioBuffer {
453        let feedback = self.room_size * 0.28 + 0.7;
454        let damp     = self.damping;
455
456        let mut out = AudioBuffer::new();
457        self.pre_delay_line.set_delay_time(self.pre_delay);
458
459        for i in 0..input.len {
460            let x = self.pre_delay_line.process(input.samples[i]);
461            let mut sum = 0.0_f32;
462
463            for (j, comb) in self.combs.iter_mut().enumerate() {
464                let delayed = comb.process(x + comb.buffer[comb.write_pos] * feedback);
465                // Damping: one-pole LP
466                self.damp_filters[j] = self.damp_filters[j] + (delayed - self.damp_filters[j]) * (1.0 - damp);
467                sum += self.damp_filters[j];
468            }
469
470            // Allpass filters
471            let mut ap_out = sum * 0.25;
472            for ap in self.allpass.iter_mut() {
473                ap_out = ap.process(ap_out + ap.buffer[ap.write_pos] * 0.5) - ap_out * 0.5;
474            }
475
476            out.samples[i] = input.samples[i] * (1.0 - self.wet) + ap_out * self.wet;
477        }
478        out.len = input.len;
479        out
480    }
481}
482
483impl Default for Reverb { fn default() -> Self { Self::new() } }
484
485// ── Compressor ────────────────────────────────────────────────────────────────
486
487#[derive(Debug, Clone)]
488pub struct Compressor {
489    pub threshold_db: f32,
490    pub ratio:        f32,
491    pub attack_ms:    f32,
492    pub release_ms:   f32,
493    pub makeup_gain:  f32,
494    pub knee_db:      f32,
495    envelope:         f32,
496}
497
498impl Compressor {
499    pub fn new(threshold_db: f32, ratio: f32) -> Self {
500        Self {
501            threshold_db, ratio,
502            attack_ms: 1.0, release_ms: 100.0, makeup_gain: 0.0, knee_db: 3.0,
503            envelope: 0.0,
504        }
505    }
506
507    pub fn process_sample(&mut self, x: f32) -> f32 {
508        let level_db = 20.0 * x.abs().max(1e-10).log10();
509        let over = level_db - self.threshold_db;
510        let gain_reduction = if over <= -self.knee_db / 2.0 {
511            0.0
512        } else if over <= self.knee_db / 2.0 {
513            let t = (over + self.knee_db / 2.0) / self.knee_db;
514            (1.0 / self.ratio - 1.0) * over * t * t / 2.0
515        } else {
516            (1.0 / self.ratio - 1.0) * over
517        };
518
519        // Ballistics
520        let coeff = if gain_reduction < self.envelope {
521            1.0 - (-2.2 / (self.attack_ms * 0.001 * SAMPLE_RATE)).exp()
522        } else {
523            1.0 - (-2.2 / (self.release_ms * 0.001 * SAMPLE_RATE)).exp()
524        };
525        self.envelope += (gain_reduction - self.envelope) * coeff;
526
527        let gain_db = self.envelope + self.makeup_gain;
528        x * 10.0_f32.powf(gain_db / 20.0)
529    }
530
531    pub fn process_buffer(&mut self, buf: &mut AudioBuffer) {
532        for s in buf.samples[..buf.len].iter_mut() {
533            *s = self.process_sample(*s);
534        }
535    }
536}
537
538// ── Distortion ────────────────────────────────────────────────────────────────
539
540#[derive(Debug, Clone, Copy, PartialEq)]
541pub enum DistortionMode {
542    HardClip,
543    SoftClip,
544    Foldback,
545    Bitcrush(u32),
546    Waveshape,
547    Overdrive,
548}
549
550#[derive(Debug, Clone)]
551pub struct Distortion {
552    pub mode:  DistortionMode,
553    pub drive: f32,  // pre-gain
554    pub mix:   f32,  // wet/dry
555}
556
557impl Distortion {
558    pub fn new(mode: DistortionMode, drive: f32) -> Self {
559        Self { mode, drive, mix: 1.0 }
560    }
561
562    pub fn process_sample(&self, x: f32) -> f32 {
563        let driven = x * self.drive;
564        let clipped = match self.mode {
565            DistortionMode::HardClip => driven.clamp(-1.0, 1.0),
566            DistortionMode::SoftClip => driven.tanh(),
567            DistortionMode::Foldback => {
568                let mut v = driven;
569                while v > 1.0 || v < -1.0 {
570                    if v > 1.0  { v = 2.0 - v; }
571                    if v < -1.0 { v = -2.0 - v; }
572                }
573                v
574            }
575            DistortionMode::Bitcrush(bits) => {
576                let steps = 2.0_f32.powi(bits as i32);
577                (driven * steps).round() / steps
578            }
579            DistortionMode::Waveshape => {
580                let k = 2.0;
581                driven * (k + 1.0) / (1.0 + k * driven.abs())
582            }
583            DistortionMode::Overdrive => {
584                let abs = driven.abs();
585                let sign = driven.signum();
586                sign * if abs < 1.0/3.0 { 2.0 * abs }
587                       else if abs < 2.0/3.0 { (3.0 - (2.0 - 3.0*abs).powi(2)) / 3.0 }
588                       else { 1.0 }
589            }
590        };
591        x * (1.0 - self.mix) + clipped * self.mix
592    }
593
594    pub fn process_buffer(&self, buf: &mut AudioBuffer) {
595        for s in buf.samples[..buf.len].iter_mut() {
596            *s = self.process_sample(*s);
597        }
598    }
599}
600
601// ── Chorus / Flanger ──────────────────────────────────────────────────────────
602
603#[derive(Debug, Clone)]
604pub struct Chorus {
605    delay_lines: Vec<DelayLine>,
606    lfos:        Vec<Lfo>,
607    pub depth:   f32,
608    pub rate:    f32,
609    pub wet:     f32,
610    pub voices:  usize,
611}
612
613impl Chorus {
614    pub fn new(voices: usize) -> Self {
615        let delay_lines = (0..voices).map(|_| DelayLine::new(8192)).collect();
616        let lfos = (0..voices).map(|i| {
617            let mut lfo = Lfo::new(Waveform::Sine, 0.5 + i as f32 * 0.1);
618            lfo.phase = i as f32 / voices as f32;
619            lfo
620        }).collect();
621        Self { delay_lines, lfos, depth: 0.01, rate: 0.3, wet: 0.5, voices }
622    }
623
624    pub fn process_buffer(&mut self, input: &AudioBuffer) -> AudioBuffer {
625        let mut out = AudioBuffer::new();
626        for i in 0..input.len {
627            let mut sum = 0.0_f32;
628            for (dl, lfo) in self.delay_lines.iter_mut().zip(self.lfos.iter_mut()) {
629                let mod_t = lfo.next_sample() * self.depth + 0.01;
630                dl.set_delay_time(mod_t.max(0.0005));
631                sum += dl.process(input.samples[i]);
632            }
633            let wet = sum / self.voices as f32;
634            out.samples[i] = input.samples[i] * (1.0 - self.wet) + wet * self.wet;
635        }
636        out.len = input.len;
637        out
638    }
639}
640
641// ── AudioNodeType ─────────────────────────────────────────────────────────────
642
643/// All supported audio node types in the processing graph.
644#[derive(Debug, Clone)]
645pub enum AudioNodeType {
646    // ── Sources ──────────────────────────────────────────────────────────────
647    /// Basic oscillator (sine, square, saw, etc.).
648    Oscillator { waveform: Waveform, frequency: f32, detune_cents: f32 },
649    /// White/pink/brown noise.
650    Noise { color: NoiseColor },
651    /// Audio file sample player.
652    SamplePlayer { clip_id: u32, loop_start: f32, loop_end: f32, loop_mode: LoopMode },
653    /// Wavetable oscillator with morphing between wavetables.
654    Wavetable { table_id: u32, morph: f32, frequency: f32 },
655    /// Karplus-Strong plucked string.
656    KarplusStrong { frequency: f32, decay: f32, brightness: f32 },
657    /// FM oscillator: carrier + modulator.
658    FmOscillator { carrier_freq: f32, mod_freq: f32, mod_index: f32, waveform: Waveform },
659    /// Additive synth: sum of partials.
660    Additive { fundamental: f32, partial_gains: Vec<f32> },
661
662    // ── Filters ──────────────────────────────────────────────────────────────
663    /// Biquad filter (LP, HP, BP, notch, shelf, peaking).
664    BiquadFilter { filter_type: FilterType, frequency: f32, q: f32, gain_db: f32 },
665    /// Moog-style 24dB/oct ladder filter.
666    LadderFilter { cutoff: f32, resonance: f32, drive: f32 },
667    /// Formant filter (vowel synthesis).
668    FormantFilter { vowel_a: f32, vowel_b: f32, blend: f32 },
669    /// Comb filter.
670    CombFilter { delay_time: f32, feedback: f32, feedforward: f32 },
671
672    // ── Envelopes / Modulators ────────────────────────────────────────────────
673    /// ADSR envelope.
674    Adsr { attack: f32, decay: f32, sustain: f32, release: f32 },
675    /// Multi-stage envelope (breakpoint sequence).
676    MultiStageEnvelope { breakpoints: Vec<(f32, f32)>, loop_start: Option<usize> },
677    /// LFO modulator.
678    Lfo { waveform: Waveform, frequency: f32, amplitude: f32, phase: f32 },
679    /// Sample & Hold: samples input at rate, holds until next trigger.
680    SampleHold { rate: f32 },
681
682    // ── Effects ───────────────────────────────────────────────────────────────
683    /// Delay line with feedback.
684    Delay { delay_time: f32, feedback: f32, wet: f32 },
685    /// Reverb (Schroeder model).
686    Reverb { room_size: f32, damping: f32, wet: f32 },
687    /// Chorus / flanger.
688    Chorus { voices: u32, rate: f32, depth: f32, wet: f32 },
689    /// Dynamic range compressor.
690    Compressor { threshold_db: f32, ratio: f32, attack_ms: f32, release_ms: f32 },
691    /// Distortion / waveshaping.
692    Distortion { mode: DistortionMode, drive: f32 },
693    /// Stereo panning.
694    Panner { pan: f32 },
695    /// Gain / volume.
696    Gain { gain_db: f32 },
697    /// Hard / soft limiter.
698    Limiter { ceiling_db: f32, release_ms: f32 },
699    /// Stereo widener.
700    StereoWidener { width: f32 },
701    /// Bit crusher + sample rate reducer.
702    BitCrusher { bits: u32, downsample: u32 },
703    /// Pitch shifter (FFT-based approximation).
704    PitchShift { semitones: f32 },
705    /// Ring modulator.
706    RingModulator { carrier_freq: f32, mix: f32 },
707    /// Auto-wah (envelope-following filter).
708    AutoWah { sensitivity: f32, speed: f32, min_freq: f32, max_freq: f32 },
709
710    // ── Utility ───────────────────────────────────────────────────────────────
711    /// Mixer: sum multiple inputs with individual gains.
712    Mixer { gains: Vec<f32> },
713    /// Crossfade between two signals.
714    Crossfade { blend: f32 },
715    /// Constant value source.
716    Constant { value: f32 },
717    /// Pass-through monitor (for metering).
718    Meter,
719    /// Math operations on audio signals.
720    MathOp { op: AudioMathOp },
721    /// Output node — writes to stereo out.
722    Output { volume: f32 },
723}
724
725#[derive(Debug, Clone, Copy, PartialEq)]
726pub enum NoiseColor { White, Pink, Brown }
727
728#[derive(Debug, Clone, Copy, PartialEq)]
729pub enum LoopMode { None, Forward, PingPong, BackwardLoop }
730
731#[derive(Debug, Clone, Copy, PartialEq)]
732pub enum AudioMathOp { Add, Subtract, Multiply, Abs, Invert, Clamp, Min, Max }
733
734// ── AudioNode ─────────────────────────────────────────────────────────────────
735
736/// A node in the audio processing graph.
737#[derive(Debug, Clone)]
738pub struct AudioNode {
739    pub id:        NodeId,
740    pub node_type: AudioNodeType,
741    pub label:     String,
742    pub enabled:   bool,
743    pub x:         f32, // graph editor position
744    pub y:         f32,
745    // Per-node state
746    pub phase:     f32,
747    pub filter:    BiquadFilter,
748    pub envelope:  AdsrEnvelope,
749    pub lfo_state: Lfo,
750    pub delay_line: DelayLine,
751    pub reverb:    Reverb,
752    pub chorus:    Chorus,
753    pub compressor: Compressor,
754    // Karplus-Strong buffer
755    ks_buffer:     Vec<f32>,
756    ks_pos:        usize,
757    // Ladder filter state
758    lf_stages: [f32; 4],
759    lf_freq:   f32,
760    // Multi-stage envelope
761    ms_env_stage: usize,
762    ms_env_t: f32,
763    // Sample & hold
764    sh_value: f32,
765    sh_timer: f32,
766    // Formant
767    formant_filters: [BiquadFilter; 3],
768    // Metering
769    pub last_peak: f32,
770    pub last_rms:  f32,
771}
772
773impl AudioNode {
774    pub fn new(id: NodeId, node_type: AudioNodeType) -> Self {
775        let label = format!("{:?}", node_type).split('{').next().unwrap_or("Node").trim().to_string();
776        Self {
777            id, node_type, label, enabled: true,
778            x: 0.0, y: 0.0,
779            phase: 0.0,
780            filter: BiquadFilter::default(),
781            envelope: AdsrEnvelope::new(0.01, 0.1, 0.8, 0.3),
782            lfo_state: Lfo::new(Waveform::Sine, 1.0),
783            delay_line: DelayLine::new(SAMPLE_RATE as usize * 2),
784            reverb: Reverb::new(),
785            chorus: Chorus::new(3),
786            compressor: Compressor::new(-12.0, 4.0),
787            ks_buffer: vec![0.0; 4096],
788            ks_pos: 0,
789            lf_stages: [0.0; 4],
790            lf_freq: 1000.0,
791            ms_env_stage: 0,
792            ms_env_t: 0.0,
793            sh_value: 0.0,
794            sh_timer: 0.0,
795            formant_filters: Default::default(),
796            last_peak: 0.0,
797            last_rms: 0.0,
798        }
799    }
800
801    /// Initialize node state from its type parameters.
802    pub fn initialize(&mut self) {
803        match &self.node_type.clone() {
804            AudioNodeType::BiquadFilter { filter_type, frequency, q, gain_db } => {
805                self.filter.configure(*filter_type, *frequency, *q, *gain_db);
806            }
807            AudioNodeType::Adsr { attack, decay, sustain, release } => {
808                self.envelope = AdsrEnvelope::new(*attack, *decay, *sustain, *release);
809            }
810            AudioNodeType::Lfo { waveform, frequency, amplitude, phase } => {
811                self.lfo_state = Lfo::new(*waveform, *frequency);
812                self.lfo_state.amplitude = *amplitude;
813                self.lfo_state.phase = *phase;
814            }
815            AudioNodeType::Delay { delay_time, .. } => {
816                self.delay_line.set_delay_time(*delay_time);
817            }
818            AudioNodeType::KarplusStrong { frequency, .. } => {
819                let period = (SAMPLE_RATE / frequency.max(1.0)) as usize;
820                let period = period.min(4095);
821                // Fill with white noise for pluck
822                for i in 0..period {
823                    self.ks_buffer[i] = pseudo_rand(i as f32) * 2.0 - 1.0;
824                }
825                self.ks_pos = 0;
826            }
827            AudioNodeType::Reverb { room_size, damping, wet } => {
828                self.reverb.room_size = *room_size;
829                self.reverb.damping = *damping;
830                self.reverb.wet = *wet;
831            }
832            AudioNodeType::Chorus { voices, rate, depth, wet } => {
833                self.chorus = Chorus::new(*voices as usize);
834                self.chorus.rate = *rate;
835                self.chorus.depth = *depth;
836                self.chorus.wet = *wet;
837            }
838            AudioNodeType::Compressor { threshold_db, ratio, attack_ms, release_ms } => {
839                self.compressor = Compressor::new(*threshold_db, *ratio);
840                self.compressor.attack_ms = *attack_ms;
841                self.compressor.release_ms = *release_ms;
842            }
843            _ => {}
844        }
845    }
846
847    /// Process one buffer. `inputs` contains buffers from upstream nodes.
848    pub fn process(&mut self, inputs: &[&AudioBuffer]) -> AudioBuffer {
849        if !self.enabled {
850            return inputs.first().copied().cloned().unwrap_or_default();
851        }
852
853        let input = inputs.first().copied().cloned().unwrap_or_else(AudioBuffer::silence);
854
855        let result = match &self.node_type.clone() {
856            // ── Sources ──────────────────────────────────────────────────────
857            AudioNodeType::Oscillator { waveform, frequency, detune_cents } => {
858                let freq = *frequency * 2.0_f32.powf(*detune_cents / 1200.0);
859                let phase_inc = freq / SAMPLE_RATE;
860                let mut out = AudioBuffer::new();
861                for s in out.samples[..out.len].iter_mut() {
862                    *s = waveform.sample(self.phase);
863                    self.phase += phase_inc;
864                    if self.phase >= 1.0 { self.phase -= 1.0; }
865                }
866                out
867            }
868
869            AudioNodeType::Noise { color } => {
870                let mut out = AudioBuffer::new();
871                let mut b0 = 0.0_f32;
872                let mut b1 = 0.0_f32;
873                for (i, s) in out.samples[..out.len].iter_mut().enumerate() {
874                    let white = pseudo_rand(self.phase + i as f32 * 0.1) * 2.0 - 1.0;
875                    *s = match color {
876                        NoiseColor::White => white,
877                        NoiseColor::Pink => {
878                            b0 = 0.99886 * b0 + white * 0.0555179;
879                            b1 = 0.99332 * b1 + white * 0.0750759;
880                            (b0 + b1 + white * 0.5) / 2.5
881                        }
882                        NoiseColor::Brown => {
883                            b0 = (b0 + 0.02 * white) / 1.02;
884                            b0 * 3.5
885                        }
886                    };
887                }
888                self.phase += 1.0;
889                out
890            }
891
892            AudioNodeType::KarplusStrong { frequency, decay, brightness } => {
893                let period = (SAMPLE_RATE / frequency.max(1.0)) as usize;
894                let period = period.clamp(2, self.ks_buffer.len());
895                let mut out = AudioBuffer::new();
896                for s in out.samples[..out.len].iter_mut() {
897                    let i0 = self.ks_pos;
898                    let i1 = (self.ks_pos + 1) % period;
899                    // Averaged-damped comb filter
900                    let avg = (self.ks_buffer[i0] + self.ks_buffer[i1]) * 0.5 * decay;
901                    let bright_mix = *brightness;
902                    self.ks_buffer[i0] = avg * (1.0 - bright_mix) + self.ks_buffer[i0] * bright_mix;
903                    *s = self.ks_buffer[i0];
904                    self.ks_pos = (self.ks_pos + 1) % period;
905                }
906                out
907            }
908
909            AudioNodeType::FmOscillator { carrier_freq, mod_freq, mod_index, waveform } => {
910                let mut out = AudioBuffer::new();
911                let mut mod_phase = self.phase;
912                let mut car_phase = self.phase + 0.5;
913                for s in out.samples[..out.len].iter_mut() {
914                    let modulator = waveform.sample(mod_phase);
915                    let car_freq_mod = *carrier_freq + modulator * mod_index * mod_freq;
916                    *s = Waveform::Sine.sample(car_phase);
917                    mod_phase += mod_freq / SAMPLE_RATE;
918                    car_phase += car_freq_mod / SAMPLE_RATE;
919                    if mod_phase >= 1.0 { mod_phase -= 1.0; }
920                    if car_phase >= 1.0 { car_phase -= 1.0; }
921                }
922                self.phase = mod_phase;
923                out
924            }
925
926            AudioNodeType::Additive { fundamental, partial_gains } => {
927                let mut out = AudioBuffer::new();
928                for (i, s) in out.samples[..out.len].iter_mut().enumerate() {
929                    let mut v = 0.0_f32;
930                    for (k, &gain) in partial_gains.iter().enumerate() {
931                        let partial = (k + 1) as f32;
932                        let phase = (self.phase * partial).fract();
933                        v += Waveform::Sine.sample(phase) * gain;
934                    }
935                    *s = v;
936                    let _ = i;
937                }
938                self.phase += fundamental / SAMPLE_RATE;
939                if self.phase >= 1.0 { self.phase -= 1.0; }
940                out
941            }
942
943            AudioNodeType::Constant { value } => {
944                let mut out = AudioBuffer::new();
945                out.fill(*value);
946                out
947            }
948
949            // ── Filters ──────────────────────────────────────────────────────
950            AudioNodeType::BiquadFilter { .. } => {
951                let mut out = input.clone();
952                self.filter.process_buffer(&mut out);
953                out
954            }
955
956            AudioNodeType::LadderFilter { cutoff, resonance, drive } => {
957                let mut out = input.clone();
958                let f = (std::f32::consts::PI * cutoff / SAMPLE_RATE).tan().clamp(0.0, 0.99);
959                let r = resonance * 4.0;
960                for s in out.samples[..out.len].iter_mut() {
961                    let x = *s * drive - r * self.lf_stages[3];
962                    let (mut a, mut b, mut c, mut d) = (self.lf_stages[0], self.lf_stages[1], self.lf_stages[2], self.lf_stages[3]);
963                    a = a + f * (x.tanh()    - a.tanh());
964                    b = b + f * (a.tanh()    - b.tanh());
965                    c = c + f * (b.tanh()    - c.tanh());
966                    d = d + f * (c.tanh()    - d.tanh());
967                    self.lf_stages = [a, b, c, d];
968                    *s = d;
969                }
970                out
971            }
972
973            AudioNodeType::CombFilter { delay_time, feedback, feedforward } => {
974                self.delay_line.set_delay_time(*delay_time);
975                let mut out = AudioBuffer::new();
976                for i in 0..input.len {
977                    let delayed = self.delay_line.process(input.samples[i]);
978                    out.samples[i] = input.samples[i] * feedforward + delayed * feedback;
979                }
980                out.len = input.len;
981                out
982            }
983
984            // ── Envelopes ────────────────────────────────────────────────────
985            AudioNodeType::Adsr { .. } => {
986                let mut out = AudioBuffer::new();
987                self.envelope.fill_buffer(&mut out);
988                // If there's an input signal, modulate it
989                if !inputs.is_empty() {
990                    out.multiply_by(&input);
991                }
992                out
993            }
994
995            AudioNodeType::Lfo { .. } => {
996                let mut out = AudioBuffer::new();
997                self.lfo_state.fill_buffer(&mut out);
998                out
999            }
1000
1001            AudioNodeType::SampleHold { rate } => {
1002                let trigger_period = 1.0 / rate.max(0.001);
1003                let mut out = AudioBuffer::new();
1004                for i in 0..input.len {
1005                    self.sh_timer += 1.0 / SAMPLE_RATE;
1006                    if self.sh_timer >= trigger_period {
1007                        self.sh_value = input.samples[i];
1008                        self.sh_timer = 0.0;
1009                    }
1010                    out.samples[i] = self.sh_value;
1011                }
1012                out.len = input.len;
1013                out
1014            }
1015
1016            // ── Effects ──────────────────────────────────────────────────────
1017            AudioNodeType::Delay { feedback, wet, .. } => {
1018                let delayed = self.delay_line.process_buffer(&input, *feedback);
1019                let mut out = input.clone();
1020                out.mix(&delayed, *wet);
1021                out
1022            }
1023
1024            AudioNodeType::Reverb { .. } => {
1025                self.reverb.process_buffer(&input)
1026            }
1027
1028            AudioNodeType::Chorus { .. } => {
1029                self.chorus.process_buffer(&input)
1030            }
1031
1032            AudioNodeType::Compressor { .. } => {
1033                let mut out = input.clone();
1034                self.compressor.process_buffer(&mut out);
1035                out
1036            }
1037
1038            AudioNodeType::Distortion { mode, drive } => {
1039                let dist = Distortion::new(*mode, *drive);
1040                let mut out = input.clone();
1041                dist.process_buffer(&mut out);
1042                out
1043            }
1044
1045            AudioNodeType::Gain { gain_db } => {
1046                let linear = 10.0_f32.powf(*gain_db / 20.0);
1047                let mut out = input.clone();
1048                out.scale(linear);
1049                out
1050            }
1051
1052            AudioNodeType::Limiter { ceiling_db, release_ms } => {
1053                let ceiling = 10.0_f32.powf(*ceiling_db / 20.0);
1054                let release_coeff = 1.0 - (-2.2 / (release_ms * 0.001 * SAMPLE_RATE)).exp();
1055                let mut out = input.clone();
1056                let mut env = 0.0_f32;
1057                for s in out.samples[..out.len].iter_mut() {
1058                    let abs = s.abs();
1059                    if abs > env { env = abs; }
1060                    else { env += (0.0 - env) * release_coeff; }
1061                    if env > ceiling { *s *= ceiling / env; }
1062                }
1063                out
1064            }
1065
1066            AudioNodeType::RingModulator { carrier_freq, mix } => {
1067                let mut out = AudioBuffer::new();
1068                let phase_inc = carrier_freq / SAMPLE_RATE;
1069                for i in 0..input.len {
1070                    let ring = Waveform::Sine.sample(self.phase);
1071                    self.phase += phase_inc;
1072                    if self.phase >= 1.0 { self.phase -= 1.0; }
1073                    out.samples[i] = input.samples[i] * (1.0 - mix) + input.samples[i] * ring * mix;
1074                }
1075                out.len = input.len;
1076                out
1077            }
1078
1079            AudioNodeType::Panner { pan } => {
1080                // Mono pass-through with pan info stored for stereo stage
1081                let _ = pan;
1082                input.clone()
1083            }
1084
1085            // ── Utility ──────────────────────────────────────────────────────
1086            AudioNodeType::Mixer { gains } => {
1087                let mut out = AudioBuffer::silence();
1088                for (i, &gain) in gains.iter().enumerate() {
1089                    if i < inputs.len() {
1090                        let mut buf = inputs[i].clone();
1091                        buf.scale(gain);
1092                        out.add_from(&buf);
1093                    }
1094                }
1095                out
1096            }
1097
1098            AudioNodeType::Crossfade { blend } => {
1099                if inputs.len() >= 2 {
1100                    let mut out = inputs[0].clone();
1101                    out.mix(inputs[1], *blend);
1102                    out
1103                } else { input.clone() }
1104            }
1105
1106            AudioNodeType::MathOp { op } => {
1107                let a = &input;
1108                let b = inputs.get(1).copied().cloned().unwrap_or_else(AudioBuffer::silence);
1109                let mut out = AudioBuffer::new();
1110                for i in 0..out.len {
1111                    out.samples[i] = match op {
1112                        AudioMathOp::Add      => a.samples[i] + b.samples[i],
1113                        AudioMathOp::Subtract => a.samples[i] - b.samples[i],
1114                        AudioMathOp::Multiply => a.samples[i] * b.samples[i],
1115                        AudioMathOp::Abs      => a.samples[i].abs(),
1116                        AudioMathOp::Invert   => -a.samples[i],
1117                        AudioMathOp::Clamp    => a.samples[i].clamp(-1.0, 1.0),
1118                        AudioMathOp::Min      => a.samples[i].min(b.samples[i]),
1119                        AudioMathOp::Max      => a.samples[i].max(b.samples[i]),
1120                    };
1121                }
1122                out
1123            }
1124
1125            AudioNodeType::Meter => {
1126                self.last_peak = input.peak();
1127                self.last_rms  = input.rms();
1128                input.clone()
1129            }
1130
1131            AudioNodeType::Output { volume } => {
1132                let mut out = input.clone();
1133                out.scale(*volume);
1134                out
1135            }
1136
1137            // Fallthrough for types not yet fully implemented inline
1138            _ => input.clone(),
1139        };
1140
1141        result
1142    }
1143
1144    /// Note on event (triggers envelope, resets oscillator phase if desired).
1145    pub fn note_on(&mut self, _freq: f32, _velocity: f32) {
1146        self.envelope.note_on();
1147        match &self.node_type {
1148            AudioNodeType::KarplusStrong { frequency, .. } => {
1149                let period = (SAMPLE_RATE / frequency.max(1.0)) as usize;
1150                let period = period.clamp(2, self.ks_buffer.len());
1151                for i in 0..period {
1152                    self.ks_buffer[i] = pseudo_rand(self.phase + i as f32) * 2.0 - 1.0;
1153                }
1154                self.ks_pos = 0;
1155            }
1156            _ => {}
1157        }
1158    }
1159
1160    pub fn note_off(&mut self) {
1161        self.envelope.note_off();
1162    }
1163}
1164
1165// ── AudioEdge ─────────────────────────────────────────────────────────────────
1166
1167#[derive(Debug, Clone)]
1168pub struct AudioEdge {
1169    pub id:       EdgeId,
1170    pub from:     NodeId,
1171    pub to:       NodeId,
1172    pub from_slot: u8,
1173    pub to_slot:   u8,
1174    pub gain:      f32,
1175}
1176
1177// ── AutomationPoint ───────────────────────────────────────────────────────────
1178
1179#[derive(Debug, Clone, Copy)]
1180pub struct AutomationPoint {
1181    /// Sample position in the current render block.
1182    pub sample: usize,
1183    pub value:  f32,
1184}
1185
1186/// A sample-accurate automation lane for a named parameter.
1187#[derive(Debug, Clone)]
1188pub struct AutomationLane {
1189    pub node_id:   NodeId,
1190    pub param_name: String,
1191    pub points:    Vec<AutomationPoint>,
1192    pub sorted:    bool,
1193}
1194
1195impl AutomationLane {
1196    pub fn new(node_id: NodeId, param_name: &str) -> Self {
1197        Self { node_id, param_name: param_name.to_string(), points: Vec::new(), sorted: true }
1198    }
1199
1200    pub fn add_point(&mut self, sample: usize, value: f32) {
1201        self.points.push(AutomationPoint { sample, value });
1202        self.sorted = false;
1203    }
1204
1205    pub fn sort(&mut self) {
1206        if !self.sorted {
1207            self.points.sort_by_key(|p| p.sample);
1208            self.sorted = true;
1209        }
1210    }
1211
1212    /// Sample the automation value at a given sample position.
1213    pub fn sample_at(&mut self, pos: usize) -> Option<f32> {
1214        self.sort();
1215        if self.points.is_empty() { return None; }
1216        let idx = self.points.partition_point(|p| p.sample <= pos);
1217        if idx == 0 { return Some(self.points[0].value); }
1218        if idx >= self.points.len() { return Some(self.points.last().unwrap().value); }
1219        let a = &self.points[idx - 1];
1220        let b = &self.points[idx];
1221        let t = if b.sample > a.sample {
1222            (pos - a.sample) as f32 / (b.sample - a.sample) as f32
1223        } else { 0.0 };
1224        Some(a.value * (1.0 - t) + b.value * t)
1225    }
1226}
1227
1228// ── Voice ─────────────────────────────────────────────────────────────────────
1229
1230#[derive(Debug, Clone)]
1231pub struct Voice {
1232    pub id:        VoiceId,
1233    pub note:      u8,       // MIDI note 0..127
1234    pub velocity:  f32,
1235    pub frequency: f32,
1236    pub active:    bool,
1237    pub steal_priority: f32, // for voice stealing
1238    nodes: HashMap<NodeId, AudioNode>, // cloned from template
1239    age: u64,
1240}
1241
1242impl Voice {
1243    fn new(id: VoiceId, template: &HashMap<NodeId, AudioNode>) -> Self {
1244        let nodes = template.iter().map(|(&k, v)| (k, v.clone())).collect();
1245        Self {
1246            id, note: 69, velocity: 1.0, frequency: 440.0,
1247            active: false, steal_priority: 0.0, age: 0,
1248            nodes,
1249        }
1250    }
1251
1252    fn note_on(&mut self, note: u8, velocity: f32) {
1253        self.note = note;
1254        self.velocity = velocity;
1255        self.frequency = midi_to_freq(note);
1256        self.active = true;
1257        for n in self.nodes.values_mut() {
1258            n.note_on(self.frequency, velocity);
1259        }
1260    }
1261
1262    fn note_off(&mut self) {
1263        for n in self.nodes.values_mut() {
1264            n.note_off();
1265        }
1266    }
1267
1268    fn is_silent(&self) -> bool {
1269        self.nodes.values().all(|n| n.envelope.is_done())
1270    }
1271}
1272
1273fn midi_to_freq(note: u8) -> f32 {
1274    440.0 * 2.0_f32.powf((note as f32 - 69.0) / 12.0)
1275}
1276
1277// ── Command (for lock-free audio thread communication) ────────────────────────
1278
1279#[derive(Debug, Clone)]
1280pub enum AudioCommand {
1281    NoteOn    { note: u8, velocity: f32 },
1282    NoteOff   { note: u8 },
1283    AllNotesOff,
1284    SetParam  { node_id: NodeId, param: String, value: f32 },
1285    SetVolume { volume: f32 },
1286    SetBpm    { bpm: f32 },
1287    Mute      { node_id: NodeId },
1288    Unmute    { node_id: NodeId },
1289}
1290
1291// ── AudioGraph ────────────────────────────────────────────────────────────────
1292
1293/// The main audio processing graph.
1294pub struct AudioGraph {
1295    pub nodes:       HashMap<NodeId, AudioNode>,
1296    pub edges:       Vec<AudioEdge>,
1297    pub output_node: Option<NodeId>,
1298    pub automation:  Vec<AutomationLane>,
1299    topo_order:      Vec<NodeId>,
1300    topo_dirty:      bool,
1301    next_node_id:    u32,
1302    next_edge_id:    u32,
1303
1304    // Voice pool for polyphonic synth
1305    voices:          Vec<Voice>,
1306    voice_counter:   u64,
1307    pub polyphony:   usize,
1308
1309    // Master
1310    pub master_volume: f32,
1311    pub bpm:           f32,
1312
1313    // Command queue (shared with audio thread)
1314    pub commands: Arc<Mutex<VecDeque<AudioCommand>>>,
1315
1316    // Output buffer
1317    pub output_buffer: StereoBuffer,
1318}
1319
1320impl AudioGraph {
1321    pub fn new() -> Self {
1322        Self {
1323            nodes: HashMap::new(),
1324            edges: Vec::new(),
1325            output_node: None,
1326            automation: Vec::new(),
1327            topo_order: Vec::new(),
1328            topo_dirty: true,
1329            next_node_id: 1,
1330            next_edge_id: 1,
1331            voices: Vec::new(),
1332            voice_counter: 0,
1333            polyphony: 16,
1334            master_volume: 1.0,
1335            bpm: 120.0,
1336            commands: Arc::new(Mutex::new(VecDeque::new())),
1337            output_buffer: StereoBuffer::new(),
1338        }
1339    }
1340
1341    pub fn add_node(&mut self, node_type: AudioNodeType) -> NodeId {
1342        let id = NodeId(self.next_node_id);
1343        self.next_node_id += 1;
1344        let mut node = AudioNode::new(id, node_type);
1345        node.initialize();
1346        self.nodes.insert(id, node);
1347        self.topo_dirty = true;
1348        id
1349    }
1350
1351    pub fn connect(&mut self, from: NodeId, from_slot: u8, to: NodeId, to_slot: u8) -> EdgeId {
1352        let id = EdgeId(self.next_edge_id);
1353        self.next_edge_id += 1;
1354        self.edges.push(AudioEdge { id, from, to, from_slot, to_slot, gain: 1.0 });
1355        self.topo_dirty = true;
1356        id
1357    }
1358
1359    pub fn disconnect(&mut self, edge_id: EdgeId) {
1360        self.edges.retain(|e| e.id != edge_id);
1361        self.topo_dirty = true;
1362    }
1363
1364    pub fn set_output(&mut self, node_id: NodeId) {
1365        self.output_node = Some(node_id);
1366    }
1367
1368    pub fn add_automation(&mut self, lane: AutomationLane) {
1369        self.automation.push(lane);
1370    }
1371
1372    /// Send a command to the audio thread.
1373    pub fn send_command(&self, cmd: AudioCommand) {
1374        if let Ok(mut q) = self.commands.lock() {
1375            q.push_back(cmd);
1376        }
1377    }
1378
1379    pub fn note_on(&self, note: u8, velocity: f32) {
1380        self.send_command(AudioCommand::NoteOn { note, velocity });
1381    }
1382
1383    pub fn note_off(&self, note: u8) {
1384        self.send_command(AudioCommand::NoteOff { note });
1385    }
1386
1387    pub fn set_param(&self, node_id: NodeId, param: &str, value: f32) {
1388        self.send_command(AudioCommand::SetParam {
1389            node_id, param: param.to_string(), value,
1390        });
1391    }
1392
1393    /// Topological sort (Kahn's algorithm).
1394    pub fn sort_topologically(&mut self) -> Result<(), String> {
1395        use std::collections::VecDeque;
1396        let mut in_degree: HashMap<NodeId, usize> = self.nodes.keys().map(|&id| (id, 0)).collect();
1397        for edge in &self.edges {
1398            *in_degree.entry(edge.to).or_insert(0) += 1;
1399        }
1400        let mut queue: VecDeque<NodeId> = in_degree.iter()
1401            .filter(|(_, &d)| d == 0)
1402            .map(|(&id, _)| id)
1403            .collect();
1404        let mut order = Vec::with_capacity(self.nodes.len());
1405        while let Some(id) = queue.pop_front() {
1406            order.push(id);
1407            for edge in self.edges.iter().filter(|e| e.from == id) {
1408                let d = in_degree.entry(edge.to).or_insert(1);
1409                *d -= 1;
1410                if *d == 0 { queue.push_back(edge.to); }
1411            }
1412        }
1413        if order.len() != self.nodes.len() {
1414            return Err("Audio graph contains a cycle".to_string());
1415        }
1416        self.topo_order = order;
1417        self.topo_dirty = false;
1418        Ok(())
1419    }
1420
1421    /// Process one audio buffer through the graph.
1422    pub fn process_block(&mut self) -> StereoBuffer {
1423        if self.topo_dirty {
1424            let _ = self.sort_topologically();
1425        }
1426
1427        // Process commands
1428        self.process_commands();
1429
1430        let mut node_outputs: HashMap<NodeId, AudioBuffer> = HashMap::new();
1431
1432        for &node_id in &self.topo_order.clone() {
1433            let node = match self.nodes.get_mut(&node_id) {
1434                Some(n) => n,
1435                None => continue,
1436            };
1437
1438            // Gather inputs for this node
1439            let input_edges: Vec<(u8, NodeId, f32)> = self.edges.iter()
1440                .filter(|e| e.to == node_id)
1441                .map(|e| (e.to_slot, e.from, e.gain))
1442                .collect();
1443
1444            let mut inputs: Vec<AudioBuffer> = Vec::new();
1445            for (slot, from_id, gain) in &input_edges {
1446                let _ = slot;
1447                if let Some(buf) = node_outputs.get(from_id) {
1448                    let mut b = buf.clone();
1449                    b.scale(*gain);
1450                    inputs.push(b);
1451                }
1452            }
1453
1454            let input_refs: Vec<&AudioBuffer> = inputs.iter().collect();
1455            let output = node.process(&input_refs);
1456            node_outputs.insert(node_id, output);
1457        }
1458
1459        // Collect output
1460        let mono_out = if let Some(out_id) = self.output_node {
1461            node_outputs.get(&out_id).cloned().unwrap_or_default()
1462        } else {
1463            // Sum all nodes with no outgoing edges
1464            let has_outgoing: std::collections::HashSet<NodeId> = self.edges.iter().map(|e| e.from).collect();
1465            let mut sum = AudioBuffer::silence();
1466            for (&id, buf) in &node_outputs {
1467                if !has_outgoing.contains(&id) {
1468                    sum.add_from(buf);
1469                }
1470            }
1471            sum
1472        };
1473
1474        // Apply master volume and pan to stereo
1475        let mut stereo = StereoBuffer::new();
1476        for i in 0..mono_out.len {
1477            let s = mono_out.samples[i] * self.master_volume;
1478            stereo.left.samples[i]  = s;
1479            stereo.right.samples[i] = s;
1480        }
1481        stereo.left.len  = mono_out.len;
1482        stereo.right.len = mono_out.len;
1483
1484        self.output_buffer = stereo.clone();
1485        stereo
1486    }
1487
1488    fn process_commands(&mut self) {
1489        let cmds: Vec<AudioCommand> = if let Ok(mut q) = self.commands.lock() {
1490            q.drain(..).collect()
1491        } else { Vec::new() };
1492
1493        for cmd in cmds {
1494            match cmd {
1495                AudioCommand::NoteOn { note, velocity } => {
1496                    self.trigger_note_on(note, velocity);
1497                }
1498                AudioCommand::NoteOff { note } => {
1499                    self.trigger_note_off(note);
1500                }
1501                AudioCommand::AllNotesOff => {
1502                    for node in self.nodes.values_mut() {
1503                        node.note_off();
1504                    }
1505                }
1506                AudioCommand::SetParam { node_id, param, value } => {
1507                    self.apply_param(node_id, &param, value);
1508                }
1509                AudioCommand::SetVolume { volume } => {
1510                    self.master_volume = volume;
1511                }
1512                AudioCommand::SetBpm { bpm } => {
1513                    self.bpm = bpm;
1514                }
1515                AudioCommand::Mute { node_id } => {
1516                    if let Some(n) = self.nodes.get_mut(&node_id) { n.enabled = false; }
1517                }
1518                AudioCommand::Unmute { node_id } => {
1519                    if let Some(n) = self.nodes.get_mut(&node_id) { n.enabled = true; }
1520                }
1521            }
1522        }
1523    }
1524
1525    fn trigger_note_on(&mut self, note: u8, velocity: f32) {
1526        for node in self.nodes.values_mut() {
1527            let freq = midi_to_freq(note);
1528            node.note_on(freq, velocity);
1529        }
1530    }
1531
1532    fn trigger_note_off(&mut self, note: u8) {
1533        let _ = note;
1534        for node in self.nodes.values_mut() {
1535            node.note_off();
1536        }
1537    }
1538
1539    fn apply_param(&mut self, node_id: NodeId, param: &str, value: f32) {
1540        if let Some(node) = self.nodes.get_mut(&node_id) {
1541            match param {
1542                "gain_db" => {
1543                    if let AudioNodeType::Gain { gain_db } = &mut node.node_type {
1544                        *gain_db = value;
1545                    }
1546                }
1547                "frequency" => {
1548                    match &mut node.node_type {
1549                        AudioNodeType::Oscillator { frequency, .. } => *frequency = value,
1550                        AudioNodeType::BiquadFilter { frequency, .. } => {
1551                            *frequency = value;
1552                            node.initialize();
1553                        }
1554                        AudioNodeType::Lfo { frequency, .. } => *frequency = value,
1555                        _ => {}
1556                    }
1557                }
1558                "cutoff" => {
1559                    if let AudioNodeType::LadderFilter { cutoff, .. } = &mut node.node_type {
1560                        *cutoff = value;
1561                    }
1562                }
1563                "resonance" => {
1564                    if let AudioNodeType::LadderFilter { resonance, .. } = &mut node.node_type {
1565                        *resonance = value;
1566                    }
1567                }
1568                "room_size" => {
1569                    if let AudioNodeType::Reverb { room_size, .. } = &mut node.node_type {
1570                        *room_size = value;
1571                        node.reverb.room_size = value;
1572                    }
1573                }
1574                "delay_time" => {
1575                    if let AudioNodeType::Delay { delay_time, .. } = &mut node.node_type {
1576                        *delay_time = value;
1577                        node.delay_line.set_delay_time(value);
1578                    }
1579                }
1580                "volume" => {
1581                    if let AudioNodeType::Output { volume } = &mut node.node_type {
1582                        *volume = value;
1583                    }
1584                }
1585                "pan" => {
1586                    if let AudioNodeType::Panner { pan } = &mut node.node_type {
1587                        *pan = value;
1588                    }
1589                }
1590                _ => {}
1591            }
1592        }
1593    }
1594
1595    /// Build a simple signal chain from oscillator → filter → envelope → output.
1596    pub fn basic_synth_chain(&mut self, wave: Waveform, freq: f32) -> (NodeId, NodeId, NodeId, NodeId) {
1597        let osc  = self.add_node(AudioNodeType::Oscillator { waveform: wave, frequency: freq, detune_cents: 0.0 });
1598        let filt = self.add_node(AudioNodeType::BiquadFilter { filter_type: FilterType::LowPass, frequency: 2000.0, q: 0.7, gain_db: 0.0 });
1599        let env  = self.add_node(AudioNodeType::Adsr { attack: 0.01, decay: 0.1, sustain: 0.8, release: 0.3 });
1600        let out  = self.add_node(AudioNodeType::Output { volume: 1.0 });
1601
1602        self.connect(osc, 0, filt, 0);
1603        self.connect(filt, 0, env, 0);
1604        self.connect(env, 0, out, 0);
1605        self.set_output(out);
1606
1607        (osc, filt, env, out)
1608    }
1609
1610    /// FM synthesis chain.
1611    pub fn fm_synth_chain(&mut self, carrier: f32, ratio: f32, index: f32) -> (NodeId, NodeId) {
1612        let fm  = self.add_node(AudioNodeType::FmOscillator {
1613            carrier_freq: carrier,
1614            mod_freq: carrier * ratio,
1615            mod_index: index,
1616            waveform: Waveform::Sine,
1617        });
1618        let out = self.add_node(AudioNodeType::Output { volume: 0.8 });
1619        self.connect(fm, 0, out, 0);
1620        self.set_output(out);
1621        (fm, out)
1622    }
1623
1624    /// Drum machine chain: noise → HP filter → fast envelope → output.
1625    pub fn drum_chain(&mut self, is_snare: bool) -> NodeId {
1626        let noise = self.add_node(AudioNodeType::Noise { color: NoiseColor::White });
1627        let filt  = self.add_node(AudioNodeType::BiquadFilter {
1628            filter_type: if is_snare { FilterType::BandPass } else { FilterType::LowPass },
1629            frequency:   if is_snare { 3000.0 } else { 150.0 },
1630            q: 0.5, gain_db: 0.0,
1631        });
1632        let env   = self.add_node(AudioNodeType::Adsr { attack: 0.001, decay: 0.1, sustain: 0.0, release: 0.05 });
1633        let comp  = self.add_node(AudioNodeType::Compressor { threshold_db: -6.0, ratio: 4.0, attack_ms: 0.5, release_ms: 50.0 });
1634        let out   = self.add_node(AudioNodeType::Output { volume: 1.0 });
1635
1636        self.connect(noise, 0, filt, 0);
1637        self.connect(filt,  0, env,  0);
1638        self.connect(env,   0, comp, 0);
1639        self.connect(comp,  0, out,  0);
1640        self.set_output(out);
1641        out
1642    }
1643}
1644
1645impl Default for AudioGraph {
1646    fn default() -> Self { Self::new() }
1647}
1648
1649// ── Tests ─────────────────────────────────────────────────────────────────────
1650
1651#[cfg(test)]
1652mod tests {
1653    use super::*;
1654
1655    #[test]
1656    fn test_oscillator_produces_signal() {
1657        let mut node = AudioNode::new(NodeId(1), AudioNodeType::Oscillator {
1658            waveform: Waveform::Sine, frequency: 440.0, detune_cents: 0.0,
1659        });
1660        let out = node.process(&[]);
1661        let peak = out.peak();
1662        assert!(peak > 0.5, "sine oscillator should produce near-peak amplitude, got {}", peak);
1663    }
1664
1665    #[test]
1666    fn test_noise_not_silent() {
1667        let mut node = AudioNode::new(NodeId(1), AudioNodeType::Noise { color: NoiseColor::White });
1668        let out = node.process(&[]);
1669        assert!(out.peak() > 0.0);
1670    }
1671
1672    #[test]
1673    fn test_biquad_low_pass() {
1674        let mut node = AudioNode::new(NodeId(1), AudioNodeType::BiquadFilter {
1675            filter_type: FilterType::LowPass, frequency: 100.0, q: 0.7, gain_db: 0.0,
1676        });
1677        node.initialize();
1678        let mut noise_buf = AudioBuffer::new();
1679        for (i, s) in noise_buf.samples.iter_mut().enumerate() {
1680            *s = Waveform::Sine.sample(i as f32 * 8000.0 / SAMPLE_RATE); // high-freq tone
1681        }
1682        let out = node.process(&[&noise_buf]);
1683        // LP at 100Hz should heavily attenuate 8kHz signal
1684        assert!(out.peak() < noise_buf.peak() * 0.5, "LP filter should attenuate high frequencies");
1685    }
1686
1687    #[test]
1688    fn test_adsr_envelope_shape() {
1689        let mut env = AdsrEnvelope::new(0.01, 0.05, 0.7, 0.1);
1690        env.note_on();
1691        // Sample to end of attack
1692        let attack_samples = (0.01 * SAMPLE_RATE) as usize;
1693        for _ in 0..attack_samples {
1694            env.next_sample();
1695        }
1696        // Should be near 1.0 at end of attack
1697        let v = env.next_sample();
1698        assert!(v > 0.8, "envelope should be near peak after attack, got {}", v);
1699    }
1700
1701    #[test]
1702    fn test_adsr_note_off_releases() {
1703        let mut env = AdsrEnvelope::new(0.001, 0.001, 0.8, 0.01);
1704        env.note_on();
1705        // Reach sustain
1706        for _ in 0..((0.05 * SAMPLE_RATE) as usize) { env.next_sample(); }
1707        env.note_off();
1708        // After release
1709        for _ in 0..((0.02 * SAMPLE_RATE) as usize) { env.next_sample(); }
1710        assert!(env.is_done() || env.level < 0.01, "envelope should release to near-zero");
1711    }
1712
1713    #[test]
1714    fn test_lfo_oscillates() {
1715        let mut lfo = Lfo::new(Waveform::Sine, 10.0);
1716        let samples: Vec<f32> = (0..1000).map(|_| lfo.next_sample()).collect();
1717        let min = samples.iter().cloned().fold(f32::INFINITY, f32::min);
1718        let max = samples.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1719        assert!(max > 0.9 && min < -0.9, "LFO should reach full amplitude");
1720    }
1721
1722    #[test]
1723    fn test_delay_line_delays() {
1724        let mut dl = DelayLine::new(4096);
1725        dl.set_delay_time(0.01); // 10ms ≈ 441 samples
1726        let impulse_sample = SAMPLE_RATE as usize / 100; // 10ms delay worth of samples
1727        let mut heard_at = 0;
1728        for i in 0..1024 {
1729            let input = if i == 0 { 1.0 } else { 0.0 };
1730            let out = dl.process(input);
1731            if out > 0.5 { heard_at = i; break; }
1732        }
1733        assert!(heard_at > 0 && heard_at <= impulse_sample + 5,
1734            "impulse should arrive after delay, heard at sample {}", heard_at);
1735    }
1736
1737    #[test]
1738    fn test_reverb_adds_tail() {
1739        let mut reverb = Reverb::new();
1740        let mut input = AudioBuffer::silence();
1741        input.samples[0] = 1.0; // single impulse
1742        let out = reverb.process_buffer(&input);
1743        // After reverb, multiple samples should be non-zero (tail)
1744        let nonzero = out.samples.iter().filter(|&&s| s.abs() > 1e-4).count();
1745        assert!(nonzero > 10, "reverb should produce a tail, got {} non-zero samples", nonzero);
1746    }
1747
1748    #[test]
1749    fn test_graph_topological_sort() {
1750        let mut graph = AudioGraph::new();
1751        let osc  = graph.add_node(AudioNodeType::Oscillator { waveform: Waveform::Sine, frequency: 440.0, detune_cents: 0.0 });
1752        let gain = graph.add_node(AudioNodeType::Gain { gain_db: 0.0 });
1753        let out  = graph.add_node(AudioNodeType::Output { volume: 1.0 });
1754        graph.connect(osc, 0, gain, 0);
1755        graph.connect(gain, 0, out, 0);
1756        graph.set_output(out);
1757        assert!(graph.sort_topologically().is_ok());
1758        assert_eq!(graph.topo_order, vec![osc, gain, out]);
1759    }
1760
1761    #[test]
1762    fn test_graph_process_block() {
1763        let mut graph = AudioGraph::new();
1764        graph.basic_synth_chain(Waveform::Sine, 440.0);
1765        // Send note on
1766        graph.note_on(69, 1.0);
1767        let _ = graph.process_block(); // process commands
1768        let stereo = graph.process_block();
1769        // Should have some signal
1770        assert!(stereo.peak() >= 0.0, "graph should process without panic");
1771    }
1772
1773    #[test]
1774    fn test_compressor_reduces_peaks() {
1775        let mut comp = Compressor::new(-6.0, 4.0);
1776        comp.makeup_gain = 0.0;
1777        let mut loud = AudioBuffer::new();
1778        loud.fill(2.0); // above threshold
1779        comp.process_buffer(&mut loud);
1780        assert!(loud.peak() < 2.0, "compressor should reduce loud signal");
1781    }
1782
1783    #[test]
1784    fn test_distortion_clips() {
1785        let dist = Distortion::new(DistortionMode::HardClip, 5.0);
1786        let mut buf = AudioBuffer::new();
1787        buf.fill(0.5);
1788        dist.process_buffer(&mut buf);
1789        assert!(buf.peak() <= 1.0 + 1e-6, "hard clip should limit to ±1");
1790    }
1791
1792    #[test]
1793    fn test_fm_oscillator() {
1794        let mut node = AudioNode::new(NodeId(1), AudioNodeType::FmOscillator {
1795            carrier_freq: 440.0, mod_freq: 440.0, mod_index: 1.0, waveform: Waveform::Sine,
1796        });
1797        let out = node.process(&[]);
1798        assert!(out.peak() > 0.0, "FM oscillator should produce signal");
1799    }
1800
1801    #[test]
1802    fn test_waveform_samples() {
1803        for w in [Waveform::Sine, Waveform::Square, Waveform::Triangle, Waveform::Sawtooth] {
1804            let s = w.sample(0.25);
1805            assert!(s.is_finite(), "{:?} at 0.25 should be finite, got {}", w, s);
1806        }
1807    }
1808
1809    #[test]
1810    fn test_automation_lane() {
1811        let mut lane = AutomationLane::new(NodeId(1), "frequency");
1812        lane.add_point(0,   100.0);
1813        lane.add_point(100, 200.0);
1814        let v = lane.sample_at(50).unwrap();
1815        assert!((v - 150.0).abs() < 1.0, "automation should interpolate, got {}", v);
1816    }
1817
1818    #[test]
1819    fn test_drum_chain_builds() {
1820        let mut graph = AudioGraph::new();
1821        let out = graph.drum_chain(false); // kick
1822        assert!(graph.nodes.len() >= 4);
1823        assert!(graph.edges.len() >= 3);
1824        let _ = out;
1825    }
1826}