1use crate::media::SAMPLE_RATE;
22use crate::model::{AudioFilter, Bus, FilterKind, Id, Project};
23
24pub fn db_to_lin(db: f32) -> f32 {
26 10f32.powf(db / 20.0)
27}
28
29pub fn lin_to_db(v: f32) -> f32 {
31 20.0 * v.abs().max(1e-6).log10()
32}
33
34fn coef(ms: f32, sr: f32) -> f32 {
36 let n = (ms.max(0.01) * 0.001 * sr).max(1.0);
37 1.0 - (-1.0 / n).exp()
38}
39
40#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44pub enum Band {
45 LowPass,
46 HighPass,
47 Peak,
48 LowShelf,
49 HighShelf,
50}
51
52pub fn coeffs(band: Band, f0: f32, q: f32, gain_db: f32, sr: f32) -> [f32; 5] {
55 let f0 = f0.clamp(10.0, sr * 0.45);
56 let q = q.clamp(0.05, 20.0);
57 let w0 = std::f32::consts::TAU * f0 / sr;
58 let (sn, cs) = w0.sin_cos();
59 let alpha = sn / (2.0 * q);
60 let a = 10f32.powf(gain_db / 40.0);
61 let tsa = 2.0 * a.sqrt() * alpha;
62 let (b0, b1, b2, a0, a1, a2) = match band {
63 Band::LowPass => ((1.0 - cs) * 0.5, 1.0 - cs, (1.0 - cs) * 0.5, 1.0 + alpha, -2.0 * cs, 1.0 - alpha),
64 Band::HighPass => ((1.0 + cs) * 0.5, -(1.0 + cs), (1.0 + cs) * 0.5, 1.0 + alpha, -2.0 * cs, 1.0 - alpha),
65 Band::Peak => (1.0 + alpha * a, -2.0 * cs, 1.0 - alpha * a, 1.0 + alpha / a, -2.0 * cs, 1.0 - alpha / a),
66 Band::LowShelf => (
67 a * ((a + 1.0) - (a - 1.0) * cs + tsa),
68 2.0 * a * ((a - 1.0) - (a + 1.0) * cs),
69 a * ((a + 1.0) - (a - 1.0) * cs - tsa),
70 (a + 1.0) + (a - 1.0) * cs + tsa,
71 -2.0 * ((a - 1.0) + (a + 1.0) * cs),
72 (a + 1.0) + (a - 1.0) * cs - tsa,
73 ),
74 Band::HighShelf => (
75 a * ((a + 1.0) + (a - 1.0) * cs + tsa),
76 -2.0 * a * ((a - 1.0) + (a + 1.0) * cs),
77 a * ((a + 1.0) + (a - 1.0) * cs - tsa),
78 (a + 1.0) - (a - 1.0) * cs + tsa,
79 2.0 * ((a - 1.0) - (a + 1.0) * cs),
80 (a + 1.0) - (a - 1.0) * cs - tsa,
81 ),
82 };
83 if a0.abs() < 1e-12 {
84 return [1.0, 0.0, 0.0, 0.0, 0.0];
85 }
86 [b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0]
87}
88
89pub fn response_db(c: &[f32; 5], freq: f32, sr: f32) -> f32 {
91 let w = std::f32::consts::TAU * freq.clamp(1.0, sr * 0.5) / sr;
92 let (s1, c1) = (-w).sin_cos();
93 let (s2, c2) = (-2.0 * w).sin_cos();
94 let nr = c[0] + c[1] * c1 + c[2] * c2;
95 let ni = c[1] * s1 + c[2] * s2;
96 let dr = 1.0 + c[3] * c1 + c[4] * c2;
97 let di = c[3] * s1 + c[4] * s2;
98 let num = (nr * nr + ni * ni).sqrt();
99 let den = (dr * dr + di * di).sqrt().max(1e-12);
100 lin_to_db(num / den)
101}
102
103pub const EQ_BANDS: [(Band, usize, usize, usize); 5] = [
107 (Band::LowShelf, 0, 1, 7),
108 (Band::Peak, 9, 10, 11),
109 (Band::Peak, 2, 3, 4),
110 (Band::Peak, 12, 13, 14),
111 (Band::HighShelf, 5, 6, 8),
112];
113
114pub fn filter_bands(f: &AudioFilter, t: f64) -> Vec<(Band, f32, f32, f32)> {
117 let p = |i: usize| f.at(i, t) as f32;
118 match f.kind {
119 FilterKind::Eq => EQ_BANDS.iter().map(|&(b, gi, fi, qi)| (b, p(fi), p(qi), p(gi))).collect(),
120 FilterKind::HighPass => vec![(Band::HighPass, p(0), p(1), 0.0)],
121 FilterKind::LowPass => vec![(Band::LowPass, p(0), p(1), 0.0)],
122 _ => Vec::new(),
123 }
124}
125
126pub fn filter_response_db(f: &AudioFilter, t: f64, freq: f32) -> f32 {
128 let sr = SAMPLE_RATE as f32;
129 filter_bands(f, t).iter().map(|&(b, f0, q, g)| response_db(&coeffs(b, f0, q, g, sr), freq, sr)).sum()
130}
131
132#[derive(Clone, Copy, Default)]
134struct Biquad {
135 c: [f32; 5],
136 z1: f32,
137 z2: f32,
138}
139
140impl Biquad {
141 fn tick(&mut self, x: f32) -> f32 {
142 let y = self.c[0] * x + self.z1;
143 self.z1 = self.c[1] * x - self.c[3] * y + self.z2;
144 self.z2 = self.c[2] * x - self.c[4] * y;
145 if !y.is_finite() {
146 self.z1 = 0.0;
147 self.z2 = 0.0;
148 return 0.0;
149 }
150 y
151 }
152}
153
154const COMB_LEN: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
157const ALLP_LEN: [usize; 4] = [556, 441, 341, 225];
158const SPREAD: usize = 23;
160const REV_IN_GAIN: f32 = 0.015;
162
163struct Comb {
164 buf: Vec<f32>,
165 pos: usize,
166 store: f32,
167}
168
169impl Comb {
170 fn new(n: usize) -> Self {
171 Self { buf: vec![0.0; n.max(1)], pos: 0, store: 0.0 }
172 }
173 fn tick(&mut self, x: f32, fb: f32, damp: f32) -> f32 {
174 let y = self.buf[self.pos];
175 self.store = y * (1.0 - damp) + self.store * damp;
176 let v = x + self.store * fb;
177 self.buf[self.pos] = if v.is_finite() { v } else { 0.0 };
178 self.pos = (self.pos + 1) % self.buf.len();
179 y
180 }
181}
182
183struct Allpass {
184 buf: Vec<f32>,
185 pos: usize,
186}
187
188impl Allpass {
189 fn new(n: usize) -> Self {
190 Self { buf: vec![0.0; n.max(1)], pos: 0 }
191 }
192 fn tick(&mut self, x: f32) -> f32 {
193 let y = self.buf[self.pos];
194 let v = x + y * 0.5;
195 self.buf[self.pos] = if v.is_finite() { v } else { 0.0 };
196 self.pos = (self.pos + 1) % self.buf.len();
197 y - x
198 }
199}
200
201struct Freeverb {
202 combs: [[Comb; 8]; 2],
203 allp: [[Allpass; 4]; 2],
204 pre: Vec<f32>,
206 pre_pos: usize,
207}
208
209impl Freeverb {
210 fn new(sr: f32) -> Self {
211 let scale = sr / 44100.0;
212 let n = |base: usize, off: usize| ((base + off) as f32 * scale) as usize;
213 Self {
214 combs: [
215 std::array::from_fn(|i| Comb::new(n(COMB_LEN[i], 0))),
216 std::array::from_fn(|i| Comb::new(n(COMB_LEN[i], SPREAD))),
217 ],
218 allp: [
219 std::array::from_fn(|i| Allpass::new(n(ALLP_LEN[i], 0))),
220 std::array::from_fn(|i| Allpass::new(n(ALLP_LEN[i], SPREAD))),
221 ],
222 pre: vec![0.0; ((sr * 0.201) as usize + 2) * 2],
223 pre_pos: 0,
224 }
225 }
226
227 fn tick(&mut self, l: f32, r: f32, fb: f32, damp: f32, w1: f32, w2: f32, pre_frames: usize) -> (f32, f32) {
229 let cap = self.pre.len() / 2;
230 let wp = self.pre_pos * 2;
231 self.pre[wp] = l;
232 self.pre[wp + 1] = r;
233 let d = pre_frames.min(cap - 1);
234 let rp = (self.pre_pos + cap - d) % cap;
235 let (dl, dr) = (self.pre[rp * 2], self.pre[rp * 2 + 1]);
236 self.pre_pos = (self.pre_pos + 1) % cap;
237
238 let input = (dl + dr) * REV_IN_GAIN;
239 let mut wet = [0.0f32; 2];
240 for ch in 0..2 {
241 let mut acc = 0.0;
242 for c in self.combs[ch].iter_mut() {
243 acc += c.tick(input, fb, damp);
244 }
245 for a in self.allp[ch].iter_mut() {
246 acc = a.tick(acc);
247 }
248 wet[ch] = acc;
249 }
250 (wet[0] * w1 + wet[1] * w2, wet[1] * w1 + wet[0] * w2)
251 }
252}
253
254struct Delay {
258 buf: Vec<f32>,
259 pos: usize,
260}
261
262enum Dsp {
264 Biquads(Vec<Biquad>),
266 Reverb(Box<Freeverb>),
267 Echo(Delay),
268 Tone([f32; 2]),
270 Dyn {
272 env: f32,
273 gain: f32,
274 },
275 Noise {
276 rng: u32,
277 pink: [[f32; 3]; 2],
278 phase: f32,
279 prev: f32,
280 },
281 Gain {
282 prev: f32,
283 },
284}
285
286pub struct FilterState {
288 kind: FilterKind,
289 sr: f32,
290 dsp: Dsp,
291}
292
293const MAX_ECHO_S: f32 = 2.0;
295
296impl FilterState {
297 pub fn new(f: &AudioFilter, sample_rate: u32) -> Self {
298 let sr = if sample_rate == 0 { SAMPLE_RATE as f32 } else { sample_rate as f32 };
299 let dsp = match f.kind {
300 FilterKind::Eq => Dsp::Biquads(vec![Biquad::default(); EQ_BANDS.len() * 2]),
301 FilterKind::HighPass | FilterKind::LowPass => Dsp::Biquads(vec![Biquad::default(); 2]),
302 FilterKind::Reverb => Dsp::Reverb(Box::new(Freeverb::new(sr))),
303 FilterKind::Echo => Dsp::Echo(Delay { buf: vec![0.0; ((sr * MAX_ECHO_S) as usize + 2) * 2], pos: 0 }),
304 FilterKind::Distortion => Dsp::Tone([0.0; 2]),
305 FilterKind::Compressor | FilterKind::NoiseGate => Dsp::Dyn { env: 0.0, gain: 1.0 },
306 FilterKind::Noise => Dsp::Noise { rng: 0x1234_5678, pink: [[0.0; 3]; 2], phase: 0.0, prev: f32::NAN },
307 FilterKind::Gain => Dsp::Gain { prev: f32::NAN },
308 };
309 Self { kind: f.kind, sr, dsp }
310 }
311
312 pub fn kind(&self) -> FilterKind {
314 self.kind
315 }
316
317 pub fn process(&mut self, f: &AudioFilter, t: f64, buf: &mut [f32]) {
319 if f.kind != self.kind || buf.len() < 2 {
320 return;
321 }
322 let sr = self.sr;
323 let p = |i: usize| f.at(i, t) as f32;
324 match (&mut self.dsp, f.kind) {
325 (Dsp::Biquads(bq), FilterKind::Eq) => {
326 for (b, &(band, gi, fi, qi)) in bq.chunks_exact_mut(2).zip(EQ_BANDS.iter()) {
327 let c = coeffs(band, p(fi), p(qi).max(0.1), p(gi), sr);
328 b[0].c = c;
329 b[1].c = c;
330 }
331 for fr in buf.chunks_exact_mut(2) {
332 for b in bq.chunks_exact_mut(2) {
333 fr[0] = b[0].tick(fr[0]);
334 fr[1] = b[1].tick(fr[1]);
335 }
336 }
337 }
338 (Dsp::Biquads(bq), FilterKind::HighPass | FilterKind::LowPass) => {
339 let band = if f.kind == FilterKind::HighPass { Band::HighPass } else { Band::LowPass };
340 let c = coeffs(band, p(0), p(1), 0.0, sr);
341 bq[0].c = c;
342 bq[1].c = c;
343 for fr in buf.chunks_exact_mut(2) {
344 fr[0] = bq[0].tick(fr[0]);
345 fr[1] = bq[1].tick(fr[1]);
346 }
347 }
348 (Dsp::Reverb(rev), _) => {
349 let room = p(0).clamp(0.0, 1.0);
350 let damp = p(1).clamp(0.0, 1.0) * 0.4;
351 let width = p(2).clamp(0.0, 1.0);
352 let mix = p(3).clamp(0.0, 1.0);
353 let pre = ((p(4).clamp(0.0, 200.0) * 0.001) * sr) as usize;
354 let fb = room * 0.28 + 0.7;
355 let (w1, w2) = (width * 0.5 + 0.5, (1.0 - width) * 0.5);
356 for fr in buf.chunks_exact_mut(2) {
357 let (wl, wr) = rev.tick(fr[0], fr[1], fb, damp, w1, w2, pre);
358 fr[0] = fr[0] * (1.0 - mix) + wl * mix;
359 fr[1] = fr[1] * (1.0 - mix) + wr * mix;
360 }
361 }
362 (Dsp::Echo(d), _) => {
363 let cap = d.buf.len() / 2;
364 let frames = ((p(0).clamp(1.0, MAX_ECHO_S * 1000.0) * 0.001) * sr) as usize;
365 let frames = frames.clamp(1, cap - 1);
366 let fb = p(1).clamp(0.0, 0.95);
367 let mix = p(2).clamp(0.0, 1.0);
368 let ping = p(3) >= 0.5;
369 for fr in buf.chunks_exact_mut(2) {
370 let rp = (d.pos + cap - frames) % cap;
371 let (dl, dr) = (d.buf[rp * 2], d.buf[rp * 2 + 1]);
372 let (yl, yr) = if ping { (dr, dl) } else { (dl, dr) };
373 d.buf[d.pos * 2] = fr[0] + yl * fb;
374 d.buf[d.pos * 2 + 1] = fr[1] + yr * fb;
375 d.pos = (d.pos + 1) % cap;
376 fr[0] = fr[0] * (1.0 - mix) + yl * mix;
377 fr[1] = fr[1] * (1.0 - mix) + yr * mix;
378 }
379 }
380 (Dsp::Tone(lp), _) => {
381 let drive = p(0).clamp(1.0, 50.0);
382 let tone = p(1).clamp(0.0, 1.0);
383 let mix = p(2).clamp(0.0, 1.0);
384 let norm = 1.0 / drive.tanh();
385 let a = coef(0.053, sr);
387 for fr in buf.chunks_exact_mut(2) {
388 for ch in 0..2 {
389 let x = fr[ch];
390 let y = (x * drive).tanh() * norm;
391 lp[ch] += (y - lp[ch]) * a;
392 let y = lp[ch] + (y - lp[ch]) * tone;
393 fr[ch] = x * (1.0 - mix) + y * mix;
394 }
395 }
396 }
397 (Dsp::Dyn { env, gain }, FilterKind::Compressor) => {
398 let thr = db_to_lin(p(0).clamp(-60.0, 0.0));
399 let ratio = p(1).max(1.0);
400 let at = coef(p(2), sr);
401 let rl = coef(p(3), sr);
402 let makeup = db_to_lin(p(4));
403 let ex = 1.0 - 1.0 / ratio;
404 for fr in buf.chunks_exact_mut(2) {
405 let peak = fr[0].abs().max(fr[1].abs());
406 let c = if peak > *env { at } else { rl };
407 *env += (peak - *env) * c;
408 *gain = if *env > thr { (thr / *env).powf(ex) } else { 1.0 };
409 fr[0] *= *gain * makeup;
410 fr[1] *= *gain * makeup;
411 }
412 }
413 (Dsp::Dyn { env, gain }, FilterKind::NoiseGate) => {
414 let thr = db_to_lin(p(0).clamp(-80.0, 0.0));
415 let at = coef(p(1), sr);
416 let rl = coef(p(2), sr);
417 for fr in buf.chunks_exact_mut(2) {
418 let peak = fr[0].abs().max(fr[1].abs());
419 let c = if peak > *env { at } else { rl };
420 *env += (peak - *env) * c;
421 let target = if *env > thr { 1.0 } else { 0.0 };
422 *gain += (target - *gain) * if target > *gain { at } else { rl };
423 fr[0] *= *gain;
424 fr[1] *= *gain;
425 }
426 }
427 (Dsp::Noise { rng, pink, phase, prev }, _) => {
428 let level = db_to_lin(p(0).clamp(-80.0, 0.0));
429 let ty = p(1).round().clamp(0.0, 2.0) as u8;
430 let step = std::f32::consts::TAU * p(2).clamp(20.0, sr * 0.45) / sr;
431 let n = (buf.len() / 2) as f32;
432 let g0 = if prev.is_finite() { *prev } else { level };
433 let dg = (level - g0) / n;
434 *prev = level;
435 for (i, fr) in buf.chunks_exact_mut(2).enumerate() {
436 let g = g0 + dg * i as f32;
437 match ty {
438 0 => {
439 fr[0] += white(rng) * g;
440 fr[1] += white(rng) * g;
441 }
442 1 => {
443 for ch in 0..2 {
444 fr[ch] += pink_tick(&mut pink[ch], white(rng)) * g;
445 }
446 }
447 _ => {
448 let s = phase.sin() * g;
449 fr[0] += s;
450 fr[1] += s;
451 *phase = (*phase + step) % std::f32::consts::TAU;
452 }
453 }
454 }
455 }
456 (Dsp::Gain { prev }, _) => {
457 let g1 = db_to_lin(p(0).clamp(-60.0, 24.0));
458 let n = (buf.len() / 2) as f32;
459 let g0 = if prev.is_finite() { *prev } else { g1 };
460 let dg = (g1 - g0) / n;
461 *prev = g1;
462 for (i, fr) in buf.chunks_exact_mut(2).enumerate() {
463 let g = g0 + dg * i as f32;
464 fr[0] *= g;
465 fr[1] *= g;
466 }
467 }
468 _ => {}
469 }
470 }
471}
472
473fn white(rng: &mut u32) -> f32 {
475 *rng ^= *rng << 13;
476 *rng ^= *rng >> 17;
477 *rng ^= *rng << 5;
478 (*rng >> 8) as f32 / 8_388_608.0 - 1.0
479}
480
481fn pink_tick(s: &mut [f32; 3], w: f32) -> f32 {
483 s[0] = 0.99765 * s[0] + w * 0.0990460;
484 s[1] = 0.96300 * s[1] + w * 0.2965164;
485 s[2] = 0.57000 * s[2] + w * 1.0526913;
486 (s[0] + s[1] + s[2] + w * 0.1848) * 0.2
487}
488
489struct Slot {
492 id: Id,
493 dest: Id,
495 depth: u32,
497 audible: bool,
498 buf: Vec<f32>,
499 states: Vec<FilterState>,
500 meter: (f32, f32),
501}
502
503#[derive(Default)]
505pub struct BusGraph {
506 slots: Vec<Slot>,
507 order: Vec<Id>,
508 main: Id,
509 have_main: bool,
510}
511
512const MAX_HOPS: u32 = 64;
514
515impl BusGraph {
516 pub fn new() -> Self {
517 Self::default()
518 }
519
520 pub fn sync(&mut self, project: &Project) {
522 let buses = &project.buses;
523 self.have_main = !buses.is_empty();
524 self.main = buses.first().map(|b| b.id).unwrap_or(0);
525 self.slots.retain(|s| buses.iter().any(|b| b.id == s.id));
527 for b in buses {
528 if !self.slots.iter().any(|s| s.id == b.id) {
529 self.slots.push(Slot {
530 id: b.id,
531 dest: b.id,
532 depth: 0,
533 audible: true,
534 buf: Vec::new(),
535 states: Vec::new(),
536 meter: (0.0, 0.0),
537 });
538 }
539 }
540 let any_solo = buses.iter().any(|b| b.solo);
541 for b in buses {
542 let dest = resolve(buses, b, self.main);
543 let depth = hops(buses, b.id, self.main);
544 let audible = !b.muted && (b.id == self.main || !any_solo || b.solo);
546 if let Some(s) = self.slots.iter_mut().find(|s| s.id == b.id) {
547 s.dest = dest;
548 s.depth = depth;
549 s.audible = audible;
550 }
551 }
552 let Self { slots, order, .. } = self;
553 order.clear();
554 order.extend(buses.iter().map(|b| b.id));
555 order.sort_by_key(|id| std::cmp::Reverse(slots.iter().find(|s| s.id == *id).map(|s| s.depth).unwrap_or(0)));
556 }
557
558 pub fn order(&self) -> Vec<Id> {
560 self.order.clone()
561 }
562
563 pub fn order_ref(&self) -> &[Id] {
565 &self.order
566 }
567
568 pub fn begin(&mut self, frames: usize) {
570 for s in &mut self.slots {
571 s.buf.clear();
572 s.buf.resize(frames * 2, 0.0);
573 }
574 }
575
576 pub fn buffer(&mut self, bus: Id, frames: usize) -> &mut [f32] {
578 let i = match self.slots.iter().position(|s| s.id == bus) {
579 Some(i) => i,
580 None => {
581 self.slots.push(Slot {
582 id: bus,
583 dest: bus,
584 depth: 0,
585 audible: true,
586 buf: Vec::new(),
587 states: Vec::new(),
588 meter: (0.0, 0.0),
589 });
590 self.slots.len() - 1
591 }
592 };
593 let b = &mut self.slots[i].buf;
594 if b.len() != frames * 2 {
595 b.clear();
596 b.resize(frames * 2, 0.0);
597 }
598 b
599 }
600
601 pub fn flush(&mut self, bus: &Bus, t: f64, out: &mut [f32]) {
603 let Some(i) = self.slots.iter().position(|s| s.id == bus.id) else { return };
604 if !self.slots[i].audible {
605 self.slots[i].buf.fill(0.0);
606 self.slots[i].meter = (0.0, 0.0);
607 return;
608 }
609 let mut buf = std::mem::take(&mut self.slots[i].buf);
610 let dest = self.slots[i].dest;
611 {
612 let states = &mut self.slots[i].states;
613 if states.len() > bus.filters.len() {
615 states.truncate(bus.filters.len());
616 }
617 for (j, f) in bus.filters.iter().enumerate() {
618 match states.get(j) {
619 Some(s) if s.kind() == f.kind => {}
620 Some(_) => states[j] = FilterState::new(f, SAMPLE_RATE),
621 None => states.push(FilterState::new(f, SAMPLE_RATE)),
622 }
623 if f.enabled {
624 states[j].process(f, t, &mut buf);
625 }
626 }
627 }
628
629 let g = bus.gain.at(t) as f32;
630 let pan = bus.pan.at(t).clamp(-1.0, 1.0) as f32;
631 let (gl, gr) = (g * (1.0 - pan).min(1.0), g * (1.0 + pan).min(1.0));
632 let mut peak = (0.0f32, 0.0f32);
633 for fr in buf.chunks_exact_mut(2) {
634 let (mut l, mut r) = (fr[0] * gl, fr[1] * gr);
635 if bus.mono {
636 let m = (l + r) * 0.5;
637 l = m;
638 r = m;
639 }
640 fr[0] = l;
641 fr[1] = r;
642 peak.0 = peak.0.max(l.abs());
643 peak.1 = peak.1.max(r.abs());
644 }
645
646 let frames = buf.len() / 2;
647 let decay = (-(frames as f32) / (0.25 * SAMPLE_RATE as f32)).exp();
648 let m = self.slots[i].meter;
649 self.slots[i].meter = ((m.0 * decay).max(peak.0), (m.1 * decay).max(peak.1));
650
651 let terminal = dest == bus.id || !self.have_main;
652 if terminal {
653 add_into(out, &buf);
654 } else if let Some(j) = self.slots.iter().position(|s| s.id == dest) {
655 let mut dbuf = std::mem::take(&mut self.slots[j].buf);
656 if dbuf.len() < buf.len() {
657 dbuf.resize(buf.len(), 0.0);
658 }
659 add_into(&mut dbuf, &buf);
660 self.slots[j].buf = dbuf;
661 }
662 self.slots[i].buf = buf;
663 }
664
665 pub fn meter(&self, bus: Id) -> (f32, f32) {
667 self.slots.iter().find(|s| s.id == bus).map(|s| s.meter).unwrap_or((0.0, 0.0))
668 }
669}
670
671fn add_into(dst: &mut [f32], src: &[f32]) {
672 for (d, s) in dst.iter_mut().zip(src) {
673 *d += *s;
674 }
675}
676
677fn resolve(buses: &[Bus], b: &Bus, main: Id) -> Id {
679 if b.id == main || b.output == 0 || b.output == b.id || !buses.iter().any(|o| o.id == b.output) {
680 return if b.id == main { b.id } else { main };
681 }
682 let mut cur = b.output;
684 for _ in 0..MAX_HOPS {
685 if cur == main {
686 return b.output;
687 }
688 match buses.iter().find(|o| o.id == cur) {
689 Some(o) if o.output != 0 && o.output != o.id && buses.iter().any(|x| x.id == o.output) => cur = o.output,
690 _ => return b.output, }
692 }
693 main
694}
695
696fn hops(buses: &[Bus], id: Id, main: Id) -> u32 {
698 let mut cur = id;
699 for n in 0..MAX_HOPS {
700 if cur == main {
701 return n;
702 }
703 let Some(b) = buses.iter().find(|o| o.id == cur) else { return n + 1 };
704 let next = resolve(buses, b, main);
705 if next == cur {
706 return n + 1;
707 }
708 cur = next;
709 }
710 MAX_HOPS
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716 use crate::model::AudioFilter;
717
718 const SR: u32 = SAMPLE_RATE;
719
720 fn filt(kind: FilterKind, params: &[(usize, f64)]) -> AudioFilter {
721 let mut f = AudioFilter::new(kind);
722 for &(i, v) in params {
723 f.params[i].value = v;
724 }
725 f
726 }
727
728 fn sine(hz: f32, amp: f32, n: usize) -> Vec<f32> {
730 let mut v = vec![0.0; n * 2];
731 for (i, fr) in v.chunks_exact_mut(2).enumerate() {
732 let s = amp * (std::f32::consts::TAU * hz * i as f32 / SR as f32).sin();
733 fr[0] = s;
734 fr[1] = s;
735 }
736 v
737 }
738
739 fn rms(buf: &[f32]) -> f32 {
740 if buf.is_empty() {
741 return 0.0;
742 }
743 (buf.iter().map(|s| s * s).sum::<f32>() / buf.len() as f32).sqrt()
744 }
745
746 fn peak(buf: &[f32]) -> f32 {
747 buf.iter().fold(0.0f32, |a, s| a.max(s.abs()))
748 }
749
750 fn thru_rms(f: &AudioFilter, hz: f32) -> f32 {
752 let mut st = FilterState::new(f, SR);
753 let mut buf = sine(hz, 0.5, 9600);
754 st.process(f, 0.0, &mut buf);
755 rms(&buf[buf.len() / 2..])
756 }
757
758 #[test]
759 fn low_pass_attenuates_highs() {
760 let f = filt(FilterKind::LowPass, &[(0, 500.0), (1, 0.707)]);
761 let low = thru_rms(&f, 100.0);
762 let high = thru_rms(&f, 10_000.0);
763 assert!(low > 0.3, "100 Hz should pass: {low}");
764 assert!(high < low * 0.05, "10 kHz {high} vs 100 Hz {low}");
765 }
766
767 #[test]
768 fn high_pass_attenuates_lows() {
769 let f = filt(FilterKind::HighPass, &[(0, 2000.0), (1, 0.707)]);
770 let low = thru_rms(&f, 100.0);
771 let high = thru_rms(&f, 10_000.0);
772 assert!(high > 0.3, "10 kHz should pass: {high}");
773 assert!(low < high * 0.05, "100 Hz {low} vs 10 kHz {high}");
774 }
775
776 #[test]
777 fn eq_boost_raises_that_band() {
778 let f = filt(FilterKind::Eq, &[(2, 12.0), (3, 1000.0), (4, 1.0)]);
780 let flat = AudioFilter::new(FilterKind::Eq);
781 let boosted = thru_rms(&f, 1000.0);
782 let plain = thru_rms(&flat, 1000.0);
783 let ratio = boosted / plain;
784 assert!((ratio - 4.0).abs() < 0.4, "+12 dB ≈ ×4, got ×{ratio}");
785 let far = thru_rms(&f, 60.0) / thru_rms(&flat, 60.0);
787 assert!((far - 1.0).abs() < 0.15, "60 Hz should be flat, got ×{far}");
788 let db = filter_response_db(&f, 0.0, 1000.0);
790 assert!((db - 12.0).abs() < 0.6, "response {db} dB");
791 assert!(filter_response_db(&flat, 0.0, 1000.0).abs() < 0.01);
792 }
793
794 #[test]
795 fn eq_shelves() {
796 let low = filt(FilterKind::Eq, &[(0, -12.0), (1, 200.0)]);
797 assert!(filter_response_db(&low, 0.0, 40.0) < -10.0);
798 assert!(filter_response_db(&low, 0.0, 8000.0).abs() < 0.5);
799 let high = filt(FilterKind::Eq, &[(5, 6.0), (6, 4000.0)]);
800 assert!(filter_response_db(&high, 0.0, 16000.0) > 5.0);
801 assert!(filter_response_db(&high, 0.0, 100.0).abs() < 0.5);
802 }
803
804 #[test]
805 fn eq_five_bands() {
806 let flat = AudioFilter::new(FilterKind::Eq);
807 for hz in [20.0, 100.0, 1000.0, 10_000.0, 20_000.0] {
808 assert!(filter_response_db(&flat, 0.0, hz).abs() < 0.01, "default EQ must be flat at {hz} Hz");
809 }
810 for &(band, gi, fi, _) in &EQ_BANDS {
812 let f0 = FilterKind::Eq.params()[fi].default as f32;
813 let probe = match band {
814 Band::LowShelf => f0 * 0.2,
815 Band::HighShelf => f0 * 3.0,
816 _ => f0,
817 };
818 let db = filter_response_db(&filt(FilterKind::Eq, &[(gi, 12.0)]), 0.0, probe);
819 assert!(db > 9.0, "{band:?} at {probe} Hz: {db} dB");
820 }
821 let ratio = thru_rms(&filt(FilterKind::Eq, &[(9, 12.0)]), 400.0) / thru_rms(&flat, 400.0);
823 assert!((ratio - 4.0).abs() < 0.5, "+12 dB at 400 Hz ≈ ×4, got ×{ratio}");
824 let tight = filt(FilterKind::Eq, &[(0, 12.0), (7, 2.0)]);
827 let wide = filt(FilterKind::Eq, &[(0, 12.0), (7, 0.4)]);
828 let (t, w) = (filter_response_db(&tight, 0.0, 240.0), filter_response_db(&wide, 0.0, 240.0));
829 assert!((t - w).abs() > 2.0, "shelf Q must reshape the knee: {t} vs {w}");
830 for f in [&tight, &wide] {
831 let plateau = filter_response_db(f, 0.0, 20.0);
832 assert!((plateau - 12.0).abs() < 1.0, "the shelf still reaches +12 dB: {plateau}");
833 }
834 let mut old = filt(FilterKind::Eq, &[(0, -12.0), (1, 200.0)]);
836 old.params.truncate(7);
837 assert!(filter_response_db(&old, 0.0, 40.0) < -10.0, "old low shelf");
838 assert!(filter_response_db(&old, 0.0, 8000.0).abs() < 0.5, "old EQ flat up top");
839 }
840
841 #[test]
842 fn gain_db_is_exact() {
843 let f = filt(FilterKind::Gain, &[(0, -6.0)]);
844 let mut st = FilterState::new(&f, SR);
845 let mut buf = vec![1.0f32; 4800 * 2];
846 st.process(&f, 0.0, &mut buf);
847 let want = db_to_lin(-6.0);
849 assert!(buf.iter().all(|s| (s - want).abs() < 1e-6), "{} vs {want}", buf[0]);
850 let mut buf2 = vec![1.0f32; 512 * 2];
852 st.process(&f, 0.0, &mut buf2);
853 assert!((buf2[0] - want).abs() < 1e-6);
854 let f2 = filt(FilterKind::Gain, &[(0, 0.0)]);
856 let mut buf3 = vec![1.0f32; 512 * 2];
857 st.process(&f2, 0.0, &mut buf3);
858 assert!((buf3[0] - want).abs() < 1e-3, "ramp starts at the old gain: {}", buf3[0]);
859 assert!(buf3[buf3.len() - 1] > 0.99, "ramp ends at the new gain: {}", buf3[buf3.len() - 1]);
860 }
861
862 #[test]
863 fn gate_silences_below_threshold() {
864 let f = filt(FilterKind::NoiseGate, &[(0, -45.0), (1, 2.0), (2, 50.0)]);
865 let mut st = FilterState::new(&f, SR);
867 let mut quiet = sine(440.0, db_to_lin(-60.0), 48000);
868 st.process(&f, 0.0, &mut quiet);
869 assert!(peak(&quiet[quiet.len() / 2..]) < 1e-6, "{}", peak(&quiet[quiet.len() / 2..]));
870 let mut st = FilterState::new(&f, SR);
872 let mut loud = sine(440.0, db_to_lin(-6.0), 48000);
873 st.process(&f, 0.0, &mut loud);
874 let p = peak(&loud[loud.len() / 2..]);
875 assert!((p - db_to_lin(-6.0)).abs() < 1e-3, "{p}");
876 }
877
878 #[test]
879 fn compressor_reduces_peaks_by_the_ratio() {
880 let f = filt(FilterKind::Compressor, &[(0, -18.0), (1, 4.0), (2, 1.0), (3, 1000.0), (4, 0.0)]);
883 let mut st = FilterState::new(&f, SR);
884 let mut buf = sine(440.0, 1.0, 48000);
885 st.process(&f, 0.0, &mut buf);
886 let out_db = lin_to_db(peak(&buf[buf.len() / 2..]));
887 assert!((out_db + 13.5).abs() < 1.0, "{out_db} dB");
888 let mut st = FilterState::new(&f, SR);
890 let mut quiet = sine(440.0, db_to_lin(-30.0), 48000);
891 st.process(&f, 0.0, &mut quiet);
892 let q = lin_to_db(peak(&quiet[quiet.len() / 2..]));
893 assert!((q + 30.0).abs() < 0.5, "{q} dB");
894 let g = filt(FilterKind::Compressor, &[(0, -18.0), (1, 4.0), (2, 1.0), (3, 1000.0), (4, 6.0)]);
896 let mut st = FilterState::new(&g, SR);
897 let mut buf = sine(440.0, 1.0, 48000);
898 st.process(&g, 0.0, &mut buf);
899 let with_makeup = lin_to_db(peak(&buf[buf.len() / 2..]));
900 assert!((with_makeup - out_db - 6.0).abs() < 0.6, "{with_makeup} vs {out_db}");
901 }
902
903 #[test]
904 fn echo_delays_by_the_right_offset() {
905 let f = filt(FilterKind::Echo, &[(0, 100.0), (1, 0.0), (2, 1.0), (3, 0.0)]);
907 let mut st = FilterState::new(&f, SR);
908 let mut buf = vec![0.0f32; 48000 * 2];
909 buf[0] = 1.0;
910 buf[1] = 1.0;
911 st.process(&f, 0.0, &mut buf);
912 assert!(buf[0].abs() < 1e-6, "dry is gone at mix=1: {}", buf[0]);
913 assert!((buf[4800 * 2] - 1.0).abs() < 1e-6, "echo at 100 ms: {}", buf[4800 * 2]);
914 assert!(buf[4799 * 2].abs() < 1e-6);
915 assert!(buf[4801 * 2].abs() < 1e-6);
916 let f = filt(FilterKind::Echo, &[(0, 100.0), (1, 0.5), (2, 1.0), (3, 0.0)]);
918 let mut st = FilterState::new(&f, SR);
919 let mut buf = vec![0.0f32; 48000 * 2];
920 buf[0] = 1.0;
921 st.process(&f, 0.0, &mut buf);
922 assert!((buf[9600 * 2] - 0.5).abs() < 1e-6, "second tap: {}", buf[9600 * 2]);
923 let f = filt(FilterKind::Echo, &[(0, 100.0), (1, 0.0), (2, 1.0), (3, 1.0)]);
925 let mut st = FilterState::new(&f, SR);
926 let mut buf = vec![0.0f32; 48000 * 2];
927 buf[0] = 1.0; st.process(&f, 0.0, &mut buf);
929 assert!(buf[4800 * 2].abs() < 1e-6, "L stays empty: {}", buf[4800 * 2]);
930 assert!((buf[4800 * 2 + 1] - 1.0).abs() < 1e-6, "R gets it: {}", buf[4800 * 2 + 1]);
931 }
932
933 #[test]
934 fn reverb_tail_decays() {
935 let f = filt(FilterKind::Reverb, &[(0, 0.8), (1, 0.2), (2, 1.0), (3, 1.0), (4, 0.0)]);
936 let mut st = FilterState::new(&f, SR);
937 let mut buf = sine(500.0, 0.5, 2400);
939 buf.resize(2400 * 2 + 48000 * 2 * 2, 0.0);
940 st.process(&f, 0.0, &mut buf);
941 let tail = |from: usize, to: usize| rms(&buf[from * 2..to * 2]);
942 let early = tail(4800, 9600);
943 let late = tail(48000, 52800);
944 assert!(early > 1e-4, "tail after the input stops: {early}");
945 assert!(late < early, "tail decays: {early} → {late}");
946 assert!(buf.iter().all(|s| s.is_finite()));
947 let f = filt(FilterKind::Reverb, &[(0, 0.8), (1, 0.2), (2, 1.0), (3, 1.0), (4, 100.0)]);
949 let mut st = FilterState::new(&f, SR);
950 let mut buf = vec![0.0f32; 48000 * 2];
951 buf[0] = 1.0;
952 buf[1] = 1.0;
953 st.process(&f, 0.0, &mut buf);
954 assert!(rms(&buf[..2400 * 2]) < 1e-7, "silent before the pre-delay");
955 assert!(rms(&buf[4800 * 2..9600 * 2]) > 1e-6, "audible after it");
956 }
957
958 #[test]
959 fn distortion_clips_and_mixes() {
960 let f = filt(FilterKind::Distortion, &[(0, 20.0), (1, 1.0), (2, 1.0)]);
961 let mut st = FilterState::new(&f, SR);
962 let mut buf = sine(220.0, 0.2, 4800);
963 st.process(&f, 0.0, &mut buf);
964 assert!(peak(&buf[buf.len() / 2..]) > 0.9, "{}", peak(&buf));
966 let f = filt(FilterKind::Distortion, &[(0, 20.0), (1, 1.0), (2, 0.0)]);
968 let mut st = FilterState::new(&f, SR);
969 let mut buf = sine(220.0, 0.2, 480);
970 let orig = buf.clone();
971 st.process(&f, 0.0, &mut buf);
972 assert!(buf.iter().zip(&orig).all(|(a, b)| (a - b).abs() < 1e-6));
973 }
974
975 #[test]
976 fn noise_kinds() {
977 for (ty, name) in [(0.0, "white"), (1.0, "pink"), (2.0, "tone")] {
978 let f = filt(FilterKind::Noise, &[(0, -6.0), (1, ty), (2, 1000.0)]);
979 let mut st = FilterState::new(&f, SR);
980 let mut buf = vec![0.0f32; 4800 * 2];
981 st.process(&f, 0.0, &mut buf);
982 let r = rms(&buf);
983 assert!(r > 1e-3, "{name} produced nothing");
984 assert!(buf.iter().all(|s| s.is_finite() && s.abs() < 4.0), "{name} blew up");
985 }
986 let f = filt(FilterKind::Noise, &[(0, 0.0), (1, 2.0), (2, 1000.0)]);
988 let mut st = FilterState::new(&f, SR);
989 let mut buf = vec![0.0f32; 4800 * 2];
990 st.process(&f, 0.0, &mut buf);
991 let left: Vec<f32> = buf.chunks_exact(2).map(|f| f[0]).collect();
992 let zero_crossings = left.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
993 assert!((zero_crossings as i32 - 200).abs() <= 4, "{zero_crossings} crossings in 100 ms");
994 }
995
996 #[test]
997 fn disabled_and_mismatched_kinds_are_noops() {
998 let f = filt(FilterKind::Gain, &[(0, -60.0)]);
999 let mut st = FilterState::new(&f, SR);
1000 let other = AudioFilter::new(FilterKind::LowPass);
1001 let mut buf = vec![1.0f32; 64];
1002 st.process(&other, 0.0, &mut buf);
1003 assert!(buf.iter().all(|s| *s == 1.0), "a kind mismatch must not touch the block");
1004 }
1005
1006 fn bus_project() -> Project {
1009 let mut p = Project::new();
1010 p.main_bus();
1011 p
1012 }
1013
1014 #[test]
1015 fn order_is_leaves_first_main_last() {
1016 let mut p = bus_project();
1017 let main = p.buses[0].id;
1018 let a = p.add_bus("A");
1019 let b = p.add_bus("B");
1020 p.bus_mut(b).unwrap().output = a; let mut g = BusGraph::new();
1022 g.sync(&p);
1023 let order = g.order();
1024 assert_eq!(order.len(), 3);
1025 assert_eq!(*order.last().unwrap(), main, "Main is last");
1026 let pos = |id| order.iter().position(|&x| x == id).unwrap();
1027 assert!(pos(b) < pos(a), "B before A");
1028 }
1029
1030 #[test]
1031 fn cycles_fall_back_to_main() {
1032 let mut p = bus_project();
1033 let main = p.buses[0].id;
1034 let a = p.add_bus("A");
1035 let b = p.add_bus("B");
1036 p.bus_mut(a).unwrap().output = b;
1037 p.bus_mut(b).unwrap().output = a; let mut g = BusGraph::new();
1039 g.sync(&p);
1040 assert_eq!(g.order().len(), 3);
1041 assert_eq!(*g.order().last().unwrap(), main);
1042 g.begin(4);
1044 g.buffer(a, 4).fill(0.5);
1045 let mut out = vec![0.0f32; 8];
1046 for id in g.order() {
1047 let bus = p.bus(id).unwrap().clone();
1048 g.flush(&bus, 0.0, &mut out);
1049 }
1050 assert!(out.iter().all(|s| (s - 0.5).abs() < 1e-6), "{out:?}");
1051 }
1052
1053 #[test]
1054 fn flush_gain_pan_mono_and_meter() {
1055 let mut p = bus_project();
1056 let main = p.buses[0].id;
1057 let a = p.add_bus("A");
1058 p.bus_mut(a).unwrap().gain.value = 0.5;
1059 let mut g = BusGraph::new();
1060 g.sync(&p);
1061 let mut out = vec![0.0f32; 8];
1062 g.begin(4);
1063 g.buffer(a, 4).fill(1.0);
1064 for id in g.order() {
1065 let bus = p.bus(id).unwrap().clone();
1066 g.flush(&bus, 0.0, &mut out);
1067 }
1068 assert!(out.iter().all(|s| (s - 0.5).abs() < 1e-6), "gain: {out:?}");
1069 assert!((g.meter(a).0 - 0.5).abs() < 1e-6);
1070 assert!((g.meter(main).0 - 0.5).abs() < 1e-6);
1071
1072 p.bus_mut(a).unwrap().gain.value = 1.0;
1074 p.bus_mut(a).unwrap().mono = true;
1075 g.sync(&p);
1076 g.begin(2);
1077 let buf = g.buffer(a, 2);
1078 buf[0] = 1.0;
1079 buf[1] = 0.0;
1080 buf[2] = 1.0;
1081 buf[3] = 0.0;
1082 let mut out = vec![0.0f32; 4];
1083 for id in g.order() {
1084 let bus = p.bus(id).unwrap().clone();
1085 g.flush(&bus, 0.0, &mut out);
1086 }
1087 assert!(out.iter().all(|s| (s - 0.5).abs() < 1e-6), "mono: {out:?}");
1088
1089 p.bus_mut(a).unwrap().mono = false;
1091 p.bus_mut(a).unwrap().pan.value = 1.0;
1092 g.sync(&p);
1093 g.begin(2);
1094 g.buffer(a, 2).fill(1.0);
1095 let mut out = vec![0.0f32; 4];
1096 for id in g.order() {
1097 let bus = p.bus(id).unwrap().clone();
1098 g.flush(&bus, 0.0, &mut out);
1099 }
1100 assert!(out[0].abs() < 1e-6 && (out[1] - 1.0).abs() < 1e-6, "pan: {out:?}");
1101 }
1102
1103 #[test]
1104 fn mute_and_solo() {
1105 let mut p = bus_project();
1106 let a = p.add_bus("A");
1107 let b = p.add_bus("B");
1108 let run = |g: &mut BusGraph, p: &Project| {
1109 g.begin(2);
1110 g.buffer(a, 2).fill(1.0);
1111 g.buffer(b, 2).fill(1.0);
1112 let mut out = vec![0.0f32; 4];
1113 for id in g.order() {
1114 if let Some(bus) = p.bus(id) {
1115 let bus = bus.clone();
1116 g.flush(&bus, 0.0, &mut out);
1117 }
1118 }
1119 out[0]
1120 };
1121 let mut g = BusGraph::new();
1122 g.sync(&p);
1123 assert!((run(&mut g, &p) - 2.0).abs() < 1e-6);
1124 p.bus_mut(a).unwrap().muted = true;
1126 g.sync(&p);
1127 assert!((run(&mut g, &p) - 1.0).abs() < 1e-6);
1128 assert_eq!(g.meter(a), (0.0, 0.0));
1129 p.bus_mut(a).unwrap().muted = false;
1130 p.bus_mut(b).unwrap().solo = true;
1132 g.sync(&p);
1133 assert!((run(&mut g, &p) - 1.0).abs() < 1e-6, "solo B");
1134 }
1135
1136 #[test]
1137 fn filter_state_survives_removal_and_kind_change() {
1138 let mut p = bus_project();
1139 let a = p.add_bus("A");
1140 p.bus_mut(a).unwrap().filters.push(filt(FilterKind::Gain, &[(0, -6.0)]));
1141 let mut g = BusGraph::new();
1142 g.sync(&p);
1143 let mut out = vec![0.0f32; 4];
1144 g.begin(2);
1145 g.buffer(a, 2).fill(1.0);
1146 for id in g.order() {
1147 let bus = p.bus(id).unwrap().clone();
1148 g.flush(&bus, 0.0, &mut out);
1149 }
1150 assert!((out[0] - db_to_lin(-6.0)).abs() < 1e-5, "{}", out[0]);
1151 p.bus_mut(a).unwrap().filters[0] = filt(FilterKind::LowPass, &[(0, 20000.0), (1, 0.707)]);
1153 g.sync(&p);
1154 let mut out = vec![0.0f32; 4];
1155 g.begin(2);
1156 g.buffer(a, 2).fill(1.0);
1157 for id in g.order() {
1158 let bus = p.bus(id).unwrap().clone();
1159 g.flush(&bus, 0.0, &mut out);
1160 }
1161 assert!(out[0] > 0.5, "low-pass at 20 kHz passes DC: {}", out[0]);
1162 p.bus_mut(a).unwrap().filters[0] = filt(FilterKind::Gain, &[(0, -60.0)]);
1164 p.bus_mut(a).unwrap().filters[0].enabled = false;
1165 g.sync(&p);
1166 let mut out = vec![0.0f32; 4];
1167 g.begin(2);
1168 g.buffer(a, 2).fill(1.0);
1169 for id in g.order() {
1170 let bus = p.bus(id).unwrap().clone();
1171 g.flush(&bus, 0.0, &mut out);
1172 }
1173 assert!((out[0] - 1.0).abs() < 1e-6, "{}", out[0]);
1174 }
1175
1176 #[test]
1177 fn removing_a_bus_drops_its_slot() {
1178 let mut p = bus_project();
1179 let a = p.add_bus("A");
1180 let mut g = BusGraph::new();
1181 g.sync(&p);
1182 assert_eq!(g.order().len(), 2);
1183 p.remove_bus(a);
1184 g.sync(&p);
1185 assert_eq!(g.order().len(), 1);
1186 assert_eq!(g.meter(a), (0.0, 0.0));
1187 }
1188}