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
6const 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; const SPEED_OF_SOUND: f32 = 343.0; const 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; const 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
30pub 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#[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#[derive(Debug, Clone)]
174pub struct BiquadCoefficients {
175 pub b0: f32,
176 pub b1: f32,
177 pub b2: f32,
178 pub a1: f32, pub a2: f32, }
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 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 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 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 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 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); 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 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 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 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, pub x2: f32, pub y1: f32, pub y2: f32, }
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 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 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#[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 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#[derive(Debug, Clone)]
447pub struct CompressorParams {
448 pub threshold_db: f32,
449 pub ratio: f32, pub knee_db: f32, 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, }
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 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 let gain_reduction = overshoot * (1.0 - 1.0 / r);
528 -gain_reduction
529 } else {
530 0.0
532 }
533 }
534
535 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 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 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 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#[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 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, pub damping: f32, pub wet_mix: f32,
689 pub dry_mix: f32,
690 pub width: f32, 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 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 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 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 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 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#[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, 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; 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 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#[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 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#[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; 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#[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 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#[derive(Debug, Clone)]
1125pub struct DistortionEffect {
1126 pub mode: DistortionMode,
1127 pub drive: f32, pub output_gain: f32, pub mix: f32,
1130 pub tone: f32, pub tone_filter_l: BiquadState,
1132 pub tone_filter_r: BiquadState,
1133 pub tone_coeff: BiquadCoefficients,
1134 pub is_enabled: bool,
1135 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 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 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 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#[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#[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#[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#[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 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 let r = 0.0875; 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 let ild_db = 10.0 * azimuth_rad.sin().abs();
1446 let ild_linear = db_to_linear(-ild_db * 0.5);
1447
1448 let hann = |n: usize| -> f32 {
1450 0.5 * (1.0 - (TWO_PI * n as f32 / (len as f32 - 1.0)).cos())
1451 };
1452
1453 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 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 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#[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 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 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; 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 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 let occ_gain = db_to_linear(self.occlusion_db + self.obstruction_db);
1615 self.distance_gain *= occ_gain;
1616
1617 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 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#[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 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 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#[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#[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, pub mute: bool,
1848 pub solo: bool,
1849 pub effect_chain: EffectChain,
1850 pub send_levels: HashMap<u64, f32>, pub sidechain_source: Option<u64>, 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 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 let gain = db_to_linear(self.gain_db);
1907 left *= gain;
1908 right *= gain;
1909
1910 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 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 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#[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>, }
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 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 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 } 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 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#[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, 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#[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, 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#[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, 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#[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, }
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, pub priority: u32,
2402}
2403
2404#[derive(Debug, Clone)]
2405pub struct BeatTracker {
2406 pub bpm: f32,
2407 pub time_signature_num: u32, pub time_signature_den: u32, 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>>, pub intensity: f32, }
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 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 self.update_intensity_layers();
2538
2539 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 stem.is_active = stem.category == state || state == "all";
2552 }
2553 }
2554
2555 fn update_intensity_layers(&mut self) {
2556 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 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#[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 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 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#[derive(Debug)]
2782pub struct LevelMeter {
2783 pub rms_window: VecDeque<f32>, 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#[derive(Debug)]
2868pub struct LufsMeter {
2869 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 pub block_buffer: VecDeque<f32>,
2878 pub block_size: usize,
2879 pub blocks: Vec<f32>, 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 pub momentary_buffer: VecDeque<f32>,
2888 pub momentary_size: usize,
2889}
2890
2891impl LufsMeter {
2892 pub fn new() -> Self {
2893 let pre_coeff = BiquadCoefficients::high_shelf(1681.0, 1.0, 4.0, SAMPLE_RATE);
2895 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; 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 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; 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 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 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 self.compute_integrated_lufs();
2959 }
2960
2961 fn compute_integrated_lufs(&mut self) {
2962 if self.blocks.is_empty() { return; }
2963 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) .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 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 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#[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>, }
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; }
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#[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>, 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 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#[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, 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, 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
3328pub struct DopplerCalculator;
3333
3334impl DopplerCalculator {
3335 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 let v_source = source_vel.dot(-unit);
3352 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 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#[derive(Debug, Clone)]
3375pub struct AcousticMaterial {
3376 pub name: String,
3377 pub transmission_loss_db: f32, pub absorption_coefficients: [f32; 6], }
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 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, pub obstruction_factor: f32, pub materials: Vec<AcousticMaterial>,
3427 pub total_transmission_loss_db: f32,
3428 pub wet_occlusion_db: f32, }
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#[derive(Debug, Clone)]
3465pub struct AutomationKeyframe {
3466 pub time_s: f32,
3467 pub value: f32,
3468 pub curve: f32, }
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#[derive(Debug)]
3542pub struct AudioMixerEditor {
3543 pub signal_flow: SignalFlowGraph,
3545
3546 pub voice_manager: VoiceManager,
3548
3549 pub music_system: AdaptiveMusicSystem,
3551
3552 pub level_meters: HashMap<u64, LevelMeter>, pub master_spectrum: SpectrumAnalyzer,
3555 pub lufs_meter: LufsMeter,
3556
3557 pub reverb_zones: ReverbZoneManager,
3559 pub occlusion_queries: HashMap<u64, OcclusionQuery>,
3560
3561 pub snapshot_system: SnapshotSystem,
3563
3564 pub automation_lanes: Vec<AutomationLane>,
3566
3567 pub profiler: AudioProfiler,
3569
3570 pub ui_state: MixerEditorUiState,
3572
3573 pub time_s: f64,
3575 pub sample_rate: f32,
3576
3577 pub master_volume_db: f32,
3579 pub master_mute: bool,
3580
3581 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 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 let mut bus_outputs: HashMap<u64, (f32, f32)> = HashMap::new();
3684
3685 for &bus_id in &self.signal_flow.topology_order.clone() {
3686 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 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 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 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 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 }
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#[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 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 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#[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 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 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 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#[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#[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#[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 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 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 let (l, r) = comp.process_sample(0.001, 0.001);
4346 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 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 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 let (l, r) = reverb.process_sample(1.0, 1.0);
4389 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 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), Vec3::ZERO, Vec3::new(-10.0, 0.0, 0.0), Vec3::ZERO,
4423 1.0,
4424 );
4425 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 let error = (out - x).abs();
4438 max_error = max_error.max(error);
4439 }
4440 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 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 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; let dt = 1.0 / 60.0; 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 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 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 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 assert!(!graph.topology_order.is_empty());
4557 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); let (l, r) = hrtf.process_mono(1.0);
4574 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)); 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 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 assert!((semitones_to_ratio(12.0) - 2.0).abs() < 1e-5);
4612 assert!((semitones_to_ratio(0.0) - 1.0).abs() < 1e-5);
4614 assert!((semitones_to_ratio(7.0) - 1.498).abs() < 0.01);
4616 }
4617}
4618
4619#[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 let w = TWO_PI * freq_hz / SAMPLE_RATE;
4693 let z_re = w.cos();
4694 let z_im = -w.sin();
4695 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 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#[derive(Clone, Debug)]
4786pub struct StereoWidthProcessor {
4787 pub width: f32, pub balance: f32, pub bypass: bool,
4790 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 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 let mid = (left + right) * 0.5;
4823 let side = (left - right) * 0.5;
4824 let side_scaled = side * self.width;
4826 let mut l_out = mid + side_scaled;
4828 let mut r_out = mid - side_scaled;
4829 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 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#[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 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 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 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 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; (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#[derive(Clone, Debug)]
5002pub struct HarmonicExciter {
5003 pub drive: f32, pub mix: f32, pub harmonic_order: u32, 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 let shaped = driven - driven * driven * driven.signum() * 0.333;
5037 shaped.tanh()
5038 }
5039 3 => {
5040 driven.tanh()
5042 }
5043 _ => {
5044 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 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 let l_harm = self.generate_harmonics(l_hi);
5058 let r_harm = self.generate_harmonics(r_hi);
5059 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 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#[derive(Clone, Debug)]
5074pub struct TransientShaper {
5075 pub attack_gain_db: f32, pub sustain_gain_db: f32, pub attack_speed: f32, pub release_speed: f32, 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 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 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 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 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 let transient = (self.fast_env - self.slow_env).max(0.0);
5131 let sustain = self.slow_env;
5132 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#[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 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(); let mut env = 1.0f32;
5187 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 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 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 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 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 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 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#[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 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 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#[derive(Clone, Debug)]
5378pub struct SpectrumAnalyzer {
5379 pub num_bands: usize,
5380 pub smoothing: f32, 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 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 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 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 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 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 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 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 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#[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>, 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; 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, 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 const ABSOLUTE_GATE: f32 = 1e-7; if block_power > ABSOLUTE_GATE {
5594 self.lufs_blocks.push_back(block_power);
5595 if self.lufs_blocks.len() > 40 { 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 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 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 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#[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 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 pub spectrum: SpectrumAnalyzer,
5678 pub loudness: LoudnessHistory,
5679 pub pan: f32, pub fader_automation: Vec<AutomationPoint>,
5683 pub fader_position: f32, 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 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 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 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 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 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 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#[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_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#[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 limiter_gain: f32,
5901 limiter_attack_coeff: f32,
5902 limiter_release_coeff: f32,
5903 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 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 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 if self.dithering_enabled {
5967 l += self.next_dither();
5968 r += self.next_dither();
5969 }
5970 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#[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 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 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#[derive(Clone, Debug)]
6134pub struct MidiNote {
6135 pub channel: u8,
6136 pub note: u8, pub velocity: u8, 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 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 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#[derive(Clone, Debug)]
6237pub struct HrtfPanner {
6238 pub azimuth_deg: f32, pub elevation_deg: f32, 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_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; 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 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 let ild_db = sin_az * 6.0 * (self.elevation_deg.to_radians().cos());
6291 if ild_db >= 0.0 {
6292 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 let dist_atten = 1.0 / self.distance.max(1.0);
6305 let s = mono * dist_atten;
6306 let az_sign = self.azimuth_deg.signum();
6308 let (l_in, r_in) = if az_sign >= 0.0 {
6309 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 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#[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, pub air_absorption_db_per_m: f32,
6336 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 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 let images: [(f32, f32); 6] = [
6378 (-source_x, source_z), (2.0 * width - source_x, source_z), (source_x, -source_z), (source_x, 2.0 * depth - source_z), (source_x, source_z), (source_x, source_z), ];
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 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 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 let mut early_sum = 0.0f32;
6426 for er in &mut self.early_reflections {
6427 early_sum += er.process(s);
6428 }
6429 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 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#[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, 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 for _ in 0..self.inversion.min(intervals.len() as u8 - 1) {
6559 let first = intervals.remove(0);
6560 intervals.push(first + 12);
6561 }
6562 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#[derive(Clone, Debug)]
6584pub struct AudioClock {
6585 pub sample_rate: f32,
6586 pub bpm: f32,
6587 pub ppqn: u32, 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 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 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 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 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 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 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#[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#[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 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 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#[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 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 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#[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 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 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#[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 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 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 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 for _ in 0..1000 {
7254 gate.process_sample(0.001, 0.001);
7255 }
7256 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; let (l, r) = proc.process_sample(0.8, -0.2);
7265 let mid = (0.8 - 0.2) * 0.5; let _ = mid;
7267 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 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 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 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 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 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 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 assert!(scale.note_in_scale(60, 60)); assert!(scale.note_in_scale(62, 60)); assert!(!scale.note_in_scale(61, 60)); }
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 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 assert_eq!(notes[0], 64); assert_eq!(notes[1], 67); assert_eq!(notes[2], 72); }
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 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 clock.advance(100000);
7524 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 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 let gain = clip.gain_at_time(0.25);
7567 assert!((gain - 0.5).abs() < 0.01);
7568 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 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 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 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 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}