1use super::common::{env_coef, read_interpolated, sanitize_audio};
4use crate::port::{GraphModule, PortDef, PortSpec, PortValues, SignalKind};
5use alloc::vec;
6use alloc::vec::Vec;
7use core::f64::consts::TAU;
8use libm::Libm;
9
10pub struct UnitDelay {
14 buffer: f64,
15 spec: PortSpec,
16}
17
18impl UnitDelay {
19 pub fn new() -> Self {
20 Self {
21 buffer: 0.0,
22 spec: PortSpec {
23 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
24 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
25 },
26 }
27 }
28}
29
30impl Default for UnitDelay {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl GraphModule for UnitDelay {
37 fn port_spec(&self) -> &PortSpec {
38 &self.spec
39 }
40
41 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
42 let input = inputs.get_or(0, 0.0);
43 outputs.set(10, self.buffer);
44 self.buffer = input;
45 }
46
47 fn reset(&mut self) {
48 self.buffer = 0.0;
49 }
50
51 fn set_sample_rate(&mut self, _: f64) {}
52
53 fn breaks_feedback_cycle(&self) -> bool {
54 true
55 }
56
57 fn type_id(&self) -> &'static str {
58 "unit_delay"
59 }
60}
61
62pub struct DelayLine {
69 buffer: Vec<f64>,
70 write_pos: usize,
71 sample_rate: f64,
72 smoothed_delay: f64,
75 delay_smooth_coef: f64,
77 delay_primed: bool,
79 spec: PortSpec,
80}
81
82impl DelayLine {
83 const MAX_DELAY_SECS: f64 = 2.0;
85
86 const DELAY_SMOOTH_SECS: f64 = 0.005;
89
90 pub fn new(sample_rate: f64) -> Self {
91 let buffer_size = (sample_rate * Self::MAX_DELAY_SECS) as usize + 1;
92 Self {
93 buffer: vec![0.0; buffer_size],
94 write_pos: 0,
95 sample_rate,
96 smoothed_delay: 0.0,
97 delay_smooth_coef: env_coef(Self::DELAY_SMOOTH_SECS, sample_rate),
98 delay_primed: false,
99 spec: PortSpec {
100 inputs: vec![
101 PortDef::new(0, "in", SignalKind::Audio),
102 PortDef::new(1, "time", SignalKind::CvUnipolar)
103 .with_default(0.5)
104 .with_attenuverter(),
105 PortDef::new(2, "feedback", SignalKind::CvUnipolar)
106 .with_default(0.0)
107 .with_attenuverter(),
108 PortDef::new(3, "mix", SignalKind::CvUnipolar)
109 .with_default(0.5)
110 .with_attenuverter(),
111 ],
112 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
113 },
114 }
115 }
116}
117
118impl Default for DelayLine {
119 fn default() -> Self {
120 Self::new(44100.0)
121 }
122}
123
124impl GraphModule for DelayLine {
125 fn port_spec(&self) -> &PortSpec {
126 &self.spec
127 }
128
129 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
130 let input = sanitize_audio(inputs.get_or(0, 0.0));
133 let time_cv = inputs.get_or(1, 0.5).clamp(0.0, 1.0);
134 let feedback = inputs.get_or(2, 0.0).clamp(0.0, 0.99); let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
136
137 let min_delay_ms = 1.0;
139 let max_delay_ms = Self::MAX_DELAY_SECS * 1000.0;
140 let delay_ms = min_delay_ms * Libm::<f64>::pow(max_delay_ms / min_delay_ms, time_cv);
141 let target_delay =
142 (delay_ms * self.sample_rate / 1000.0).clamp(1.0, (self.buffer.len() - 1) as f64);
143
144 if self.delay_primed {
148 self.smoothed_delay =
149 target_delay + (self.smoothed_delay - target_delay) * self.delay_smooth_coef;
150 } else {
151 self.smoothed_delay = target_delay;
152 self.delay_primed = true;
153 }
154 let delay_samples = self.smoothed_delay;
155
156 let delayed = read_interpolated(&self.buffer, self.write_pos, delay_samples);
158
159 self.buffer[self.write_pos] = input + delayed * feedback;
161
162 self.write_pos = (self.write_pos + 1) % self.buffer.len();
164
165 let output = input * (1.0 - mix) + delayed * mix;
167 outputs.set(10, output);
168 }
169
170 fn reset(&mut self) {
171 self.buffer.fill(0.0);
172 self.write_pos = 0;
173 self.smoothed_delay = 0.0;
174 self.delay_primed = false;
175 }
176
177 fn set_sample_rate(&mut self, sample_rate: f64) {
178 self.sample_rate = sample_rate;
179 let buffer_size = (sample_rate * Self::MAX_DELAY_SECS) as usize + 1;
180 self.buffer = vec![0.0; buffer_size];
181 self.write_pos = 0;
182 self.smoothed_delay = 0.0;
183 self.delay_smooth_coef = env_coef(Self::DELAY_SMOOTH_SECS, sample_rate);
184 self.delay_primed = false;
185 }
186
187 fn breaks_feedback_cycle(&self) -> bool {
188 true
189 }
190
191 fn type_id(&self) -> &'static str {
192 "delay_line"
193 }
194}
195
196pub struct Chorus {
202 delay_buffers: [Vec<f64>; 3],
204 write_pos: usize,
205 lfo_phases: [f64; 3],
207 sample_rate: f64,
208 spec: PortSpec,
209}
210
211impl Chorus {
212 const MAX_MOD_DELAY_MS: f64 = 25.0;
214 const BASE_DELAY_MS: f64 = 7.0;
216
217 #[inline]
226 fn voice_delay_samples(base_delay_samples: f64, mod_depth_samples: f64, lfo_val: f64) -> f64 {
227 base_delay_samples + (lfo_val * 0.5 + 0.5) * mod_depth_samples
228 }
229
230 pub fn new(sample_rate: f64) -> Self {
231 let buffer_size =
232 ((Self::MAX_MOD_DELAY_MS + Self::BASE_DELAY_MS) * sample_rate / 1000.0) as usize + 10;
233 Self {
234 delay_buffers: [
235 vec![0.0; buffer_size],
236 vec![0.0; buffer_size],
237 vec![0.0; buffer_size],
238 ],
239 write_pos: 0,
240 lfo_phases: [0.0, 0.33, 0.67],
242 sample_rate,
243 spec: PortSpec {
244 inputs: vec![
245 PortDef::new(0, "in", SignalKind::Audio),
246 PortDef::new(1, "rate", SignalKind::CvUnipolar)
247 .with_default(0.3)
248 .with_attenuverter(),
249 PortDef::new(2, "depth", SignalKind::CvUnipolar)
250 .with_default(0.5)
251 .with_attenuverter(),
252 PortDef::new(3, "mix", SignalKind::CvUnipolar)
253 .with_default(0.5)
254 .with_attenuverter(),
255 ],
256 outputs: vec![
257 PortDef::new(10, "out", SignalKind::Audio),
258 PortDef::new(11, "left", SignalKind::Audio),
259 PortDef::new(12, "right", SignalKind::Audio),
260 ],
261 },
262 }
263 }
264}
265
266impl Default for Chorus {
267 fn default() -> Self {
268 Self::new(44100.0)
269 }
270}
271
272impl GraphModule for Chorus {
273 fn port_spec(&self) -> &PortSpec {
274 &self.spec
275 }
276
277 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
278 let input = sanitize_audio(inputs.get_or(0, 0.0));
281 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
282 let depth_cv = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
283 let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
284
285 let lfo_freq = 0.1 * Libm::<f64>::pow(50.0, rate_cv);
287
288 let mod_depth_ms = depth_cv * Self::MAX_MOD_DELAY_MS;
290
291 let base_delay_samples = Self::BASE_DELAY_MS * self.sample_rate / 1000.0;
292 let mod_depth_samples = mod_depth_ms * self.sample_rate / 1000.0;
293
294 let mut wet_sum = 0.0;
295 let mut left_sum = 0.0;
296 let mut right_sum = 0.0;
297
298 for i in 0..3 {
299 let lfo_val = Libm::<f64>::sin(self.lfo_phases[i] * core::f64::consts::TAU);
301 let delay_samples =
302 Self::voice_delay_samples(base_delay_samples, mod_depth_samples, lfo_val)
303 .clamp(1.0, (self.delay_buffers[i].len() - 1) as f64);
304
305 let delayed = read_interpolated(&self.delay_buffers[i], self.write_pos, delay_samples);
307
308 wet_sum += delayed;
309
310 match i {
312 0 => {
313 left_sum += delayed * 0.5;
314 right_sum += delayed * 0.5;
315 }
316 1 => left_sum += delayed,
317 2 => right_sum += delayed,
318 _ => {}
319 }
320
321 self.delay_buffers[i][self.write_pos] = input;
323
324 let freq_mult = 1.0 + (i as f64 - 1.0) * 0.1; let phase_inc = lfo_freq * freq_mult / self.sample_rate;
327 self.lfo_phases[i] += phase_inc;
328 if self.lfo_phases[i] >= 1.0 {
329 self.lfo_phases[i] -= 1.0;
330 }
331 }
332
333 wet_sum /= 3.0;
335 left_sum /= 2.0;
336 right_sum /= 2.0;
337
338 self.write_pos = (self.write_pos + 1) % self.delay_buffers[0].len();
340
341 let mono_out = input * (1.0 - mix) + wet_sum * mix;
343 let left_out = input * (1.0 - mix) + left_sum * mix;
344 let right_out = input * (1.0 - mix) + right_sum * mix;
345
346 outputs.set(10, mono_out);
347 outputs.set(11, left_out);
348 outputs.set(12, right_out);
349 }
350
351 fn reset(&mut self) {
352 for buffer in &mut self.delay_buffers {
353 buffer.fill(0.0);
354 }
355 self.write_pos = 0;
356 self.lfo_phases = [0.0, 0.33, 0.67];
357 }
358
359 fn set_sample_rate(&mut self, sample_rate: f64) {
360 self.sample_rate = sample_rate;
361 let buffer_size =
362 ((Self::MAX_MOD_DELAY_MS + Self::BASE_DELAY_MS) * sample_rate / 1000.0) as usize + 10;
363 for buffer in &mut self.delay_buffers {
364 *buffer = vec![0.0; buffer_size];
365 }
366 self.write_pos = 0;
367 }
368
369 fn type_id(&self) -> &'static str {
370 "chorus"
371 }
372}
373
374pub struct Flanger {
384 buffers: [Vec<f64>; 2],
386 write_pos: usize,
387 lfo_phase: f64,
388 sample_rate: f64,
389 spec: PortSpec,
390}
391
392impl Flanger {
393 const MAX_DELAY_MS: f64 = 10.0;
394
395 pub fn new(sample_rate: f64) -> Self {
396 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
397 Self {
398 buffers: [vec![0.0; buffer_size], vec![0.0; buffer_size]],
399 write_pos: 0,
400 lfo_phase: 0.0,
401 sample_rate,
402 spec: PortSpec {
403 inputs: vec![
404 PortDef::new(0, "in", SignalKind::Audio),
405 PortDef::new(1, "rate", SignalKind::CvUnipolar)
406 .with_default(0.3)
407 .with_attenuverter(),
408 PortDef::new(2, "depth", SignalKind::CvUnipolar)
409 .with_default(0.5)
410 .with_attenuverter(),
411 PortDef::new(3, "feedback", SignalKind::CvBipolar)
412 .with_default(0.0)
413 .with_attenuverter(),
414 PortDef::new(4, "mix", SignalKind::CvUnipolar)
415 .with_default(0.5)
416 .with_attenuverter(),
417 PortDef::new(5, "spread", SignalKind::CvUnipolar)
420 .with_default(0.5)
421 .with_attenuverter(),
422 ],
423 outputs: vec![
424 PortDef::new(10, "out", SignalKind::Audio),
425 PortDef::new(11, "left", SignalKind::Audio),
426 PortDef::new(12, "right", SignalKind::Audio),
427 ],
428 },
429 }
430 }
431}
432
433impl Default for Flanger {
434 fn default() -> Self {
435 Self::new(44100.0)
436 }
437}
438
439impl GraphModule for Flanger {
440 fn port_spec(&self) -> &PortSpec {
441 &self.spec
442 }
443
444 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
445 let input = sanitize_audio(inputs.get_or(0, 0.0));
448 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
449 let depth_cv = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
450 let feedback = inputs.get_or(3, 0.0).clamp(-0.95, 0.95);
451 let mix = inputs.get_or(4, 0.5).clamp(0.0, 1.0);
452 let spread = inputs.get_or(5, 0.5).clamp(0.0, 1.0);
453
454 let lfo_freq = 0.05 * Libm::<f64>::pow(100.0, rate_cv);
455 let base_delay_ms = 1.0;
456 let mod_depth_ms = depth_cv * (Self::MAX_DELAY_MS - base_delay_ms);
457
458 let phase_offset = spread * 0.5;
463 let max_read = (self.buffers[0].len() - 1) as f64;
464
465 let mut wet = [0.0; 2];
466 for (ch, w) in wet.iter_mut().enumerate() {
467 let phase = self.lfo_phase + if ch == 0 { 0.0 } else { phase_offset };
468 let lfo = (Libm::<f64>::sin(phase * TAU) + 1.0) * 0.5;
469 let delay_ms = base_delay_ms + lfo * mod_depth_ms;
470 let delay_samples = (delay_ms * self.sample_rate / 1000.0).clamp(1.0, max_read);
471 let delayed = read_interpolated(&self.buffers[ch], self.write_pos, delay_samples);
472 self.buffers[ch][self.write_pos] = input + delayed * feedback;
474 *w = delayed;
475 }
476
477 self.lfo_phase += lfo_freq / self.sample_rate;
478 if self.lfo_phase >= 1.0 {
479 self.lfo_phase -= 1.0;
480 }
481 self.write_pos = (self.write_pos + 1) % self.buffers[0].len();
482
483 let left = input * (1.0 - mix) + wet[0] * mix;
484 let right = input * (1.0 - mix) + wet[1] * mix;
485 outputs.set(10, left);
487 outputs.set(11, left);
488 outputs.set(12, right);
489 }
490
491 fn reset(&mut self) {
492 for buffer in &mut self.buffers {
493 buffer.fill(0.0);
494 }
495 self.write_pos = 0;
496 self.lfo_phase = 0.0;
497 }
498
499 fn set_sample_rate(&mut self, sample_rate: f64) {
500 self.sample_rate = sample_rate;
501 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
502 for buffer in &mut self.buffers {
503 *buffer = vec![0.0; buffer_size];
504 }
505 self.write_pos = 0;
506 }
507
508 fn type_id(&self) -> &'static str {
509 "flanger"
510 }
511}
512
513pub struct Phaser {
523 allpass_x1: [[f64; 6]; 2],
525 allpass_y1: [[f64; 6]; 2],
527 lfo_phase: f64,
528 sample_rate: f64,
529 spec: PortSpec,
530}
531
532impl Phaser {
533 pub fn new(sample_rate: f64) -> Self {
534 Self {
535 allpass_x1: [[0.0; 6]; 2],
536 allpass_y1: [[0.0; 6]; 2],
537 lfo_phase: 0.0,
538 sample_rate,
539 spec: PortSpec {
540 inputs: vec![
541 PortDef::new(0, "in", SignalKind::Audio),
542 PortDef::new(1, "rate", SignalKind::CvUnipolar)
543 .with_default(0.3)
544 .with_attenuverter(),
545 PortDef::new(2, "depth", SignalKind::CvUnipolar)
546 .with_default(0.7)
547 .with_attenuverter(),
548 PortDef::new(3, "feedback", SignalKind::CvBipolar)
549 .with_default(0.0)
550 .with_attenuverter(),
551 PortDef::new(4, "mix", SignalKind::CvUnipolar)
552 .with_default(0.5)
553 .with_attenuverter(),
554 PortDef::new(5, "stages", SignalKind::CvUnipolar).with_default(1.0),
555 PortDef::new(6, "spread", SignalKind::CvUnipolar)
558 .with_default(0.5)
559 .with_attenuverter(),
560 ],
561 outputs: vec![
562 PortDef::new(10, "out", SignalKind::Audio),
563 PortDef::new(11, "left", SignalKind::Audio),
564 PortDef::new(12, "right", SignalKind::Audio),
565 ],
566 },
567 }
568 }
569
570 fn allpass(input: f64, x1: &mut f64, y1: &mut f64, coef: f64) -> f64 {
579 let output = coef * input + *x1 - coef * *y1;
580 *x1 = input;
581 *y1 = output;
582 output
583 }
584}
585
586impl Default for Phaser {
587 fn default() -> Self {
588 Self::new(44100.0)
589 }
590}
591
592impl GraphModule for Phaser {
593 fn port_spec(&self) -> &PortSpec {
594 &self.spec
595 }
596
597 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
598 let input = sanitize_audio(inputs.get_or(0, 0.0));
601 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
602 let depth = inputs.get_or(2, 0.7).clamp(0.0, 1.0);
603 let feedback = inputs.get_or(3, 0.0).clamp(-0.95, 0.95);
604 let mix = inputs.get_or(4, 0.5).clamp(0.0, 1.0);
605 let stages_cv = inputs.get_or(5, 1.0).clamp(0.0, 1.0);
606
607 let num_stages = if stages_cv < 0.33 {
608 2
609 } else if stages_cv < 0.66 {
610 4
611 } else {
612 6
613 };
614
615 let spread = inputs.get_or(6, 0.5).clamp(0.0, 1.0);
616
617 let lfo_freq = 0.05 * Libm::<f64>::pow(100.0, rate_cv);
618
619 let min_freq = 200.0;
620 let max_freq = 4000.0;
621
622 let phase_offset = spread * 0.5;
627
628 let mut wet = [0.0; 2];
629 for (ch, w) in wet.iter_mut().enumerate() {
630 let phase = self.lfo_phase + if ch == 0 { 0.0 } else { phase_offset };
631 let lfo = Libm::<f64>::sin(phase * TAU);
632 let freq = min_freq + (lfo * 0.5 + 0.5) * depth * (max_freq - min_freq);
633
634 let omega = TAU * freq / self.sample_rate;
635 let tan_w = Libm::<f64>::tan(omega * 0.5);
636 let coef = (1.0 - tan_w) / (1.0 + tan_w);
637
638 let mut signal = input + self.allpass_y1[ch][num_stages - 1] * feedback;
640 for i in 0..num_stages {
641 signal = Self::allpass(
642 signal,
643 &mut self.allpass_x1[ch][i],
644 &mut self.allpass_y1[ch][i],
645 coef,
646 );
647 }
648 *w = signal;
649 }
650
651 self.lfo_phase += lfo_freq / self.sample_rate;
652 if self.lfo_phase >= 1.0 {
653 self.lfo_phase -= 1.0;
654 }
655
656 let left = input * (1.0 - mix) + wet[0] * mix;
657 let right = input * (1.0 - mix) + wet[1] * mix;
658 outputs.set(10, left);
660 outputs.set(11, left);
661 outputs.set(12, right);
662 }
663
664 fn reset(&mut self) {
665 self.allpass_x1 = [[0.0; 6]; 2];
666 self.allpass_y1 = [[0.0; 6]; 2];
667 self.lfo_phase = 0.0;
668 }
669
670 fn set_sample_rate(&mut self, sample_rate: f64) {
671 self.sample_rate = sample_rate;
672 }
673
674 fn type_id(&self) -> &'static str {
675 "phaser"
676 }
677}
678
679pub struct Tremolo {
688 lfo_phase: f64,
689 sample_rate: f64,
690 spec: PortSpec,
691}
692
693impl Tremolo {
694 pub fn new(sample_rate: f64) -> Self {
695 Self {
696 lfo_phase: 0.0,
697 sample_rate,
698 spec: PortSpec {
699 inputs: vec![
700 PortDef::new(0, "in", SignalKind::Audio),
701 PortDef::new(1, "rate", SignalKind::CvUnipolar)
702 .with_default(0.3)
703 .with_attenuverter(),
704 PortDef::new(2, "depth", SignalKind::CvUnipolar)
705 .with_default(0.5)
706 .with_attenuverter(),
707 PortDef::new(3, "shape", SignalKind::CvUnipolar)
708 .with_default(0.0)
709 .with_attenuverter(),
710 ],
711 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
712 },
713 }
714 }
715}
716
717impl Default for Tremolo {
718 fn default() -> Self {
719 Self::new(44100.0)
720 }
721}
722
723impl GraphModule for Tremolo {
724 fn port_spec(&self) -> &PortSpec {
725 &self.spec
726 }
727
728 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
729 let input = inputs.get_or(0, 0.0);
730 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
731 let depth = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
732 let shape = inputs.get_or(3, 0.0).clamp(0.0, 1.0);
733
734 let lfo_freq = 0.1 * Libm::<f64>::pow(200.0, rate_cv);
736
737 let phase_rad = self.lfo_phase * TAU;
739 let sine = Libm::<f64>::sin(phase_rad);
740 let triangle = 1.0 - 4.0 * Libm::<f64>::fabs(self.lfo_phase - 0.5);
741 let lfo = sine * (1.0 - shape) + triangle * shape;
742
743 self.lfo_phase += lfo_freq / self.sample_rate;
745 if self.lfo_phase >= 1.0 {
746 self.lfo_phase -= 1.0;
747 }
748
749 let modulation = 1.0 - depth * 0.5 * (1.0 - lfo);
752 outputs.set(10, input * modulation);
753 }
754
755 fn reset(&mut self) {
756 self.lfo_phase = 0.0;
757 }
758
759 fn set_sample_rate(&mut self, sample_rate: f64) {
760 self.sample_rate = sample_rate;
761 }
762
763 fn type_id(&self) -> &'static str {
764 "tremolo"
765 }
766}
767
768pub struct Vibrato {
773 buffer: Vec<f64>,
774 write_pos: usize,
775 lfo_phase: f64,
776 sample_rate: f64,
777 spec: PortSpec,
778}
779
780impl Vibrato {
781 const MAX_DELAY_MS: f64 = 20.0;
782
783 pub fn new(sample_rate: f64) -> Self {
784 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
785 Self {
786 buffer: vec![0.0; buffer_size],
787 write_pos: 0,
788 lfo_phase: 0.0,
789 sample_rate,
790 spec: PortSpec {
791 inputs: vec![
792 PortDef::new(0, "in", SignalKind::Audio),
793 PortDef::new(1, "rate", SignalKind::CvUnipolar)
794 .with_default(0.3)
795 .with_attenuverter(),
796 PortDef::new(2, "depth", SignalKind::CvUnipolar)
797 .with_default(0.5)
798 .with_attenuverter(),
799 PortDef::new(3, "mix", SignalKind::CvUnipolar)
800 .with_default(1.0)
801 .with_attenuverter(),
802 ],
803 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
804 },
805 }
806 }
807}
808
809impl Default for Vibrato {
810 fn default() -> Self {
811 Self::new(44100.0)
812 }
813}
814
815impl GraphModule for Vibrato {
816 fn port_spec(&self) -> &PortSpec {
817 &self.spec
818 }
819
820 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
821 let input = inputs.get_or(0, 0.0);
822 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
823 let depth = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
824 let mix = inputs.get_or(3, 1.0).clamp(0.0, 1.0);
825
826 let lfo_freq = 0.1 * Libm::<f64>::pow(150.0, rate_cv);
828
829 let base_delay_ms = Self::MAX_DELAY_MS * 0.5;
831 let mod_depth_ms = depth * base_delay_ms * 0.9;
832
833 let lfo = Libm::<f64>::sin(self.lfo_phase * TAU);
835 self.lfo_phase += lfo_freq / self.sample_rate;
836 if self.lfo_phase >= 1.0 {
837 self.lfo_phase -= 1.0;
838 }
839
840 let delay_ms = base_delay_ms + lfo * mod_depth_ms;
842 let delay_samples =
843 (delay_ms * self.sample_rate / 1000.0).clamp(1.0, (self.buffer.len() - 1) as f64);
844
845 let delayed = read_interpolated(&self.buffer, self.write_pos, delay_samples);
848
849 self.buffer[self.write_pos] = input;
851 self.write_pos = (self.write_pos + 1) % self.buffer.len();
852
853 outputs.set(10, input * (1.0 - mix) + delayed * mix);
854 }
855
856 fn reset(&mut self) {
857 self.buffer.fill(0.0);
858 self.write_pos = 0;
859 self.lfo_phase = 0.0;
860 }
861
862 fn set_sample_rate(&mut self, sample_rate: f64) {
863 self.sample_rate = sample_rate;
864 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
865 self.buffer = vec![0.0; buffer_size];
870 self.write_pos = 0;
871 self.lfo_phase = 0.0;
872 }
873
874 fn type_id(&self) -> &'static str {
875 "vibrato"
876 }
877}
878
879const COMB_TUNINGS_44100: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
881
882const ALLPASS_TUNINGS_44100: [usize; 4] = [556, 441, 341, 225];
884
885const STEREO_SPREAD: usize = 23;
887
888const MAX_COMB_SIZE: usize = 4096;
890
891const MAX_ALLPASS_SIZE: usize = 1500;
893
894const MAX_PREDELAY_SIZE: usize = 9600;
896
897pub struct Reverb {
911 comb_buffers_l: Vec<Vec<f64>>,
913 comb_buffers_r: Vec<Vec<f64>>,
914 comb_pos_l: [usize; 8],
915 comb_pos_r: [usize; 8],
916 comb_filter_state_l: [f64; 8], comb_filter_state_r: [f64; 8],
918
919 allpass_buffers_l: Vec<Vec<f64>>,
921 allpass_buffers_r: Vec<Vec<f64>>,
922 allpass_pos_l: [usize; 4],
923 allpass_pos_r: [usize; 4],
924
925 predelay_buffer: Vec<f64>,
927 predelay_pos: usize,
928
929 comb_lengths: [usize; 8],
931 allpass_lengths: [usize; 4],
932 stereo_spread: usize,
934
935 sample_rate: f64,
936 spec: PortSpec,
937}
938
939impl Reverb {
940 pub fn new(sample_rate: f64) -> Self {
942 let mut reverb = Self {
943 comb_buffers_l: (0..8).map(|_| vec![0.0; MAX_COMB_SIZE]).collect(),
944 comb_buffers_r: (0..8).map(|_| vec![0.0; MAX_COMB_SIZE]).collect(),
945 comb_pos_l: [0; 8],
946 comb_pos_r: [0; 8],
947 comb_filter_state_l: [0.0; 8],
948 comb_filter_state_r: [0.0; 8],
949
950 allpass_buffers_l: (0..4).map(|_| vec![0.0; MAX_ALLPASS_SIZE]).collect(),
951 allpass_buffers_r: (0..4).map(|_| vec![0.0; MAX_ALLPASS_SIZE]).collect(),
952 allpass_pos_l: [0; 4],
953 allpass_pos_r: [0; 4],
954
955 predelay_buffer: vec![0.0; MAX_PREDELAY_SIZE],
956 predelay_pos: 0,
957
958 comb_lengths: [0; 8],
959 allpass_lengths: [0; 4],
960 stereo_spread: STEREO_SPREAD,
961
962 sample_rate,
963 spec: PortSpec {
964 inputs: vec![
965 PortDef::new(0, "in", SignalKind::Audio),
966 PortDef::new(1, "size", SignalKind::CvUnipolar).with_default(0.5),
967 PortDef::new(2, "damping", SignalKind::CvUnipolar).with_default(0.5),
968 PortDef::new(3, "mix", SignalKind::CvUnipolar).with_default(0.5),
969 PortDef::new(4, "predelay", SignalKind::CvUnipolar).with_default(0.0),
970 ],
971 outputs: vec![
972 PortDef::new(10, "left", SignalKind::Audio),
973 PortDef::new(11, "right", SignalKind::Audio),
974 ],
975 },
976 };
977 reverb.update_tunings();
978 reverb
979 }
980
981 fn update_tunings(&mut self) {
983 let ratio = self.sample_rate / 44100.0;
984
985 for (i, &base) in COMB_TUNINGS_44100.iter().enumerate() {
986 self.comb_lengths[i] = ((base as f64 * ratio) as usize).min(MAX_COMB_SIZE - 1);
987 }
988
989 for (i, &base) in ALLPASS_TUNINGS_44100.iter().enumerate() {
990 self.allpass_lengths[i] = ((base as f64 * ratio) as usize).min(MAX_ALLPASS_SIZE - 1);
991 }
992
993 self.stereo_spread = (Libm::<f64>::round(STEREO_SPREAD as f64 * ratio) as usize).max(1);
997 }
998
999 #[inline]
1001 fn process_comb(
1002 buffer: &mut [f64],
1003 pos: &mut usize,
1004 filter_state: &mut f64,
1005 input: f64,
1006 length: usize,
1007 feedback: f64,
1008 damping: f64,
1009 ) -> f64 {
1010 let output = buffer[*pos];
1011
1012 *filter_state = output * (1.0 - damping) + *filter_state * damping;
1014
1015 buffer[*pos] = input + *filter_state * feedback;
1017
1018 *pos += 1;
1019 if *pos >= length {
1020 *pos = 0;
1021 }
1022
1023 output
1024 }
1025
1026 #[inline]
1028 fn process_allpass(buffer: &mut [f64], pos: &mut usize, input: f64, length: usize) -> f64 {
1029 const ALLPASS_FEEDBACK: f64 = 0.5;
1030
1031 let buffered = buffer[*pos];
1032 let output = -input + buffered;
1033
1034 buffer[*pos] = input + buffered * ALLPASS_FEEDBACK;
1035
1036 *pos += 1;
1037 if *pos >= length {
1038 *pos = 0;
1039 }
1040
1041 output
1042 }
1043}
1044
1045impl Default for Reverb {
1046 fn default() -> Self {
1047 Self::new(44100.0)
1048 }
1049}
1050
1051impl GraphModule for Reverb {
1052 fn port_spec(&self) -> &PortSpec {
1053 &self.spec
1054 }
1055
1056 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
1057 let input = sanitize_audio(inputs.get_or(0, 0.0));
1060 let size = inputs.get_or(1, 0.5).clamp(0.0, 1.0);
1061 let damping = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
1062 let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
1063 let predelay_cv = inputs.get_or(4, 0.0).clamp(0.0, 1.0);
1064
1065 let room_scale = 0.28 + size * 0.7;
1067 let damp = damping * 0.4;
1068
1069 let predelay_samples =
1071 (predelay_cv * 0.1 * self.sample_rate).min(MAX_PREDELAY_SIZE as f64 - 1.0) as usize;
1072
1073 self.predelay_buffer[self.predelay_pos] = input;
1075 let predelay_read_pos = if self.predelay_pos >= predelay_samples {
1076 self.predelay_pos - predelay_samples
1077 } else {
1078 MAX_PREDELAY_SIZE - (predelay_samples - self.predelay_pos)
1079 };
1080 let predelayed = if predelay_samples > 0 {
1081 self.predelay_buffer[predelay_read_pos]
1082 } else {
1083 input
1084 };
1085 self.predelay_pos = (self.predelay_pos + 1) % MAX_PREDELAY_SIZE;
1086
1087 let mut comb_out_l = 0.0;
1089 let mut comb_out_r = 0.0;
1090
1091 for i in 0..8 {
1092 let length_l = self.comb_lengths[i];
1094 comb_out_l += Self::process_comb(
1095 &mut self.comb_buffers_l[i],
1096 &mut self.comb_pos_l[i],
1097 &mut self.comb_filter_state_l[i],
1098 predelayed,
1099 length_l,
1100 room_scale,
1101 damp,
1102 );
1103
1104 let length_r = (self.comb_lengths[i] + self.stereo_spread).min(MAX_COMB_SIZE - 1);
1106 comb_out_r += Self::process_comb(
1107 &mut self.comb_buffers_r[i],
1108 &mut self.comb_pos_r[i],
1109 &mut self.comb_filter_state_r[i],
1110 predelayed,
1111 length_r,
1112 room_scale,
1113 damp,
1114 );
1115 }
1116
1117 comb_out_l *= 0.125;
1119 comb_out_r *= 0.125;
1120
1121 let mut allpass_out_l = comb_out_l;
1123 let mut allpass_out_r = comb_out_r;
1124
1125 for i in 0..4 {
1126 let length_l = self.allpass_lengths[i];
1127 allpass_out_l = Self::process_allpass(
1128 &mut self.allpass_buffers_l[i],
1129 &mut self.allpass_pos_l[i],
1130 allpass_out_l,
1131 length_l,
1132 );
1133
1134 let length_r = (self.allpass_lengths[i] + self.stereo_spread).min(MAX_ALLPASS_SIZE - 1);
1135 allpass_out_r = Self::process_allpass(
1136 &mut self.allpass_buffers_r[i],
1137 &mut self.allpass_pos_r[i],
1138 allpass_out_r,
1139 length_r,
1140 );
1141 }
1142
1143 let left = input * (1.0 - mix) + allpass_out_l * mix;
1145 let right = input * (1.0 - mix) + allpass_out_r * mix;
1146
1147 outputs.set(10, left);
1148 outputs.set(11, right);
1149 }
1150
1151 fn reset(&mut self) {
1152 for buf in &mut self.comb_buffers_l {
1153 buf.iter_mut().for_each(|x| *x = 0.0);
1154 }
1155 for buf in &mut self.comb_buffers_r {
1156 buf.iter_mut().for_each(|x| *x = 0.0);
1157 }
1158 self.comb_pos_l = [0; 8];
1159 self.comb_pos_r = [0; 8];
1160 self.comb_filter_state_l = [0.0; 8];
1161 self.comb_filter_state_r = [0.0; 8];
1162
1163 for buf in &mut self.allpass_buffers_l {
1164 buf.iter_mut().for_each(|x| *x = 0.0);
1165 }
1166 for buf in &mut self.allpass_buffers_r {
1167 buf.iter_mut().for_each(|x| *x = 0.0);
1168 }
1169 self.allpass_pos_l = [0; 4];
1170 self.allpass_pos_r = [0; 4];
1171
1172 self.predelay_buffer.iter_mut().for_each(|x| *x = 0.0);
1173 self.predelay_pos = 0;
1174 }
1175
1176 fn set_sample_rate(&mut self, sample_rate: f64) {
1177 self.sample_rate = sample_rate;
1178 self.update_tunings();
1179 self.reset();
1180 }
1181
1182 fn type_id(&self) -> &'static str {
1183 "reverb"
1184 }
1185}
1186
1187#[cfg(test)]
1192mod tests {
1193 use super::*;
1194
1195 #[test]
1196 fn test_unit_delay() {
1197 let mut delay = UnitDelay::new();
1198 let mut inputs = PortValues::new();
1199 let mut outputs = PortValues::new();
1200
1201 inputs.set(0, 1.0);
1203 delay.tick(&inputs, &mut outputs);
1204 assert!((outputs.get(10).unwrap() - 0.0).abs() < 0.01); inputs.set(0, 2.0);
1208 delay.tick(&inputs, &mut outputs);
1209 assert!((outputs.get(10).unwrap() - 1.0).abs() < 0.01); }
1211 #[test]
1212 fn test_delay_line() {
1213 let mut delay = DelayLine::new(44100.0);
1214 let mut inputs = PortValues::new();
1215 let mut outputs = PortValues::new();
1216
1217 inputs.set(1, 0.0); inputs.set(2, 0.0); inputs.set(3, 1.0); inputs.set(0, 1.0);
1224 delay.tick(&inputs, &mut outputs);
1225
1226 let first_out = outputs.get(10).unwrap();
1228 assert!(first_out.abs() < 0.1);
1229
1230 inputs.set(0, 0.0);
1232 for _ in 0..100 {
1233 delay.tick(&inputs, &mut outputs);
1234 }
1235
1236 let out = outputs.get(10).unwrap();
1238 assert!(out.is_finite());
1239 }
1240 #[test]
1241 fn test_delay_line_feedback() {
1242 let mut delay = DelayLine::new(44100.0);
1243 let mut inputs = PortValues::new();
1244 let mut outputs = PortValues::new();
1245
1246 inputs.set(1, 0.0); inputs.set(2, 0.5); inputs.set(3, 0.5); inputs.set(0, 1.0);
1253 delay.tick(&inputs, &mut outputs);
1254
1255 inputs.set(0, 0.0);
1257 for _ in 0..1000 {
1258 delay.tick(&inputs, &mut outputs);
1259 }
1260
1261 let out = outputs.get(10).unwrap();
1263 assert!(out.is_finite());
1264 }
1265 #[test]
1266 fn test_delay_line_reset() {
1267 let mut delay = DelayLine::new(44100.0);
1268 let mut inputs = PortValues::new();
1269 let mut outputs = PortValues::new();
1270
1271 inputs.set(0, 1.0);
1273 for _ in 0..100 {
1274 delay.tick(&inputs, &mut outputs);
1275 }
1276
1277 delay.reset();
1279
1280 inputs.set(0, 0.0);
1282 inputs.set(3, 1.0); delay.tick(&inputs, &mut outputs);
1284 let out = outputs.get(10).unwrap();
1285 assert!(out.abs() < 0.01);
1286 }
1287 #[test]
1288 fn test_chorus() {
1289 let mut chorus = Chorus::new(44100.0);
1290 let mut inputs = PortValues::new();
1291 let mut outputs = PortValues::new();
1292
1293 inputs.set(0, 0.5); for _ in 0..1000 {
1298 chorus.tick(&inputs, &mut outputs);
1299 }
1300
1301 let mono = outputs.get(10).unwrap();
1303 let left = outputs.get(11).unwrap();
1304 let right = outputs.get(12).unwrap();
1305
1306 assert!(mono.is_finite());
1307 assert!(left.is_finite());
1308 assert!(right.is_finite());
1309 }
1310 #[test]
1311 fn test_chorus_stereo_spread() {
1312 let mut chorus = Chorus::new(44100.0);
1313 let mut inputs = PortValues::new();
1314 let mut outputs = PortValues::new();
1315
1316 inputs.set(0, 1.0); inputs.set(1, 0.5); inputs.set(2, 0.5); inputs.set(3, 1.0); let mut left_sum = 0.0;
1324 let mut right_sum = 0.0;
1325 for _ in 0..10000 {
1326 chorus.tick(&inputs, &mut outputs);
1327 left_sum += outputs.get(11).unwrap().abs();
1328 right_sum += outputs.get(12).unwrap().abs();
1329 }
1330
1331 assert!(left_sum > 1.0);
1333 assert!(right_sum > 1.0);
1334 }
1335 #[test]
1336 fn test_chorus_reset() {
1337 let mut chorus = Chorus::new(44100.0);
1338 let mut inputs = PortValues::new();
1339 let mut outputs = PortValues::new();
1340
1341 inputs.set(0, 1.0);
1343 for _ in 0..1000 {
1344 chorus.tick(&inputs, &mut outputs);
1345 }
1346
1347 chorus.reset();
1349
1350 inputs.set(0, 0.0);
1352 inputs.set(3, 1.0); chorus.tick(&inputs, &mut outputs);
1354
1355 let out = outputs.get(10).unwrap();
1357 assert!(out.abs() < 0.1);
1358 }
1359 #[test]
1360 fn test_delay_line_type_id() {
1361 let delay = DelayLine::new(44100.0);
1362 assert_eq!(delay.type_id(), "delay_line");
1363 }
1364 #[test]
1365 fn test_chorus_type_id() {
1366 let chorus = Chorus::new(44100.0);
1367 assert_eq!(chorus.type_id(), "chorus");
1368 }
1369 #[test]
1370 fn test_delay_line_default() {
1371 let delay = DelayLine::default();
1372 assert_eq!(delay.type_id(), "delay_line");
1373 }
1374 #[test]
1375 fn test_chorus_default() {
1376 let chorus = Chorus::default();
1377 assert_eq!(chorus.type_id(), "chorus");
1378 }
1379 #[test]
1380 fn test_flanger() {
1381 let mut flanger = Flanger::new(44100.0);
1382 let mut inputs = PortValues::new();
1383 let mut outputs = PortValues::new();
1384
1385 inputs.set(0, 1.0);
1386 for _ in 0..1000 {
1387 flanger.tick(&inputs, &mut outputs);
1388 }
1389
1390 let out = outputs.get(10).unwrap();
1391 assert!(out.is_finite());
1392 }
1393 #[test]
1394 fn test_flanger_default() {
1395 let flanger = Flanger::default();
1396 assert_eq!(flanger.type_id(), "flanger");
1397 }
1398 #[test]
1399 fn test_phaser() {
1400 let mut phaser = Phaser::new(44100.0);
1401 let mut inputs = PortValues::new();
1402 let mut outputs = PortValues::new();
1403
1404 inputs.set(0, 1.0);
1405 for _ in 0..1000 {
1406 phaser.tick(&inputs, &mut outputs);
1407 }
1408
1409 let out = outputs.get(10).unwrap();
1410 assert!(out.is_finite());
1411 }
1412 #[test]
1413 fn test_phaser_default() {
1414 let phaser = Phaser::default();
1415 assert_eq!(phaser.type_id(), "phaser");
1416 }
1417 #[test]
1418 fn test_phaser_stages() {
1419 let mut phaser = Phaser::new(44100.0);
1420 let mut inputs = PortValues::new();
1421 let mut outputs = PortValues::new();
1422
1423 inputs.set(0, 1.0);
1424 inputs.set(5, 0.0); for _ in 0..100 {
1427 phaser.tick(&inputs, &mut outputs);
1428 }
1429 let out_2 = outputs.get(10).unwrap();
1430
1431 phaser.reset();
1432 inputs.set(5, 1.0); for _ in 0..100 {
1435 phaser.tick(&inputs, &mut outputs);
1436 }
1437 let out_6 = outputs.get(10).unwrap();
1438
1439 assert!(out_2.is_finite());
1441 assert!(out_6.is_finite());
1442 }
1443
1444 #[test]
1450 fn test_flanger_out_mirrors_left_and_mono_at_zero_spread() {
1451 let mut flanger = Flanger::new(44100.0);
1452 let mut inputs = PortValues::new();
1453 let mut outputs = PortValues::new();
1454
1455 inputs.set(2, 0.8); inputs.set(4, 1.0); inputs.set(5, 0.0); for k in 0..5000 {
1460 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.03));
1461 flanger.tick(&inputs, &mut outputs);
1462 let out = outputs.get(10).unwrap();
1463 let left = outputs.get(11).unwrap();
1464 let right = outputs.get(12).unwrap();
1465 assert_eq!(out, left, "out must equal left");
1466 assert_eq!(left, right, "spread=0 must give bit-identical L/R");
1467 }
1468 }
1469
1470 #[test]
1471 fn test_flanger_stereo_decorrelation() {
1472 let mut flanger = Flanger::new(44100.0);
1473 let mut inputs = PortValues::new();
1474 let mut outputs = PortValues::new();
1475
1476 inputs.set(1, 0.5); inputs.set(2, 0.9); inputs.set(4, 1.0); inputs.set(5, 1.0); let mut diff = 0.0;
1482 for k in 0..20000 {
1483 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.05));
1484 flanger.tick(&inputs, &mut outputs);
1485 assert_eq!(outputs.get(10).unwrap(), outputs.get(11).unwrap());
1487 let left = outputs.get(11).unwrap();
1488 let right = outputs.get(12).unwrap();
1489 diff += (left - right).abs();
1490 }
1491 assert!(
1492 diff > 1.0,
1493 "left/right should decorrelate with spread; diff = {diff}"
1494 );
1495 }
1496
1497 #[test]
1498 fn test_phaser_out_mirrors_left_and_mono_at_zero_spread() {
1499 let mut phaser = Phaser::new(44100.0);
1500 let mut inputs = PortValues::new();
1501 let mut outputs = PortValues::new();
1502
1503 inputs.set(2, 0.8); inputs.set(4, 1.0); inputs.set(6, 0.0); for k in 0..5000 {
1508 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.03));
1509 phaser.tick(&inputs, &mut outputs);
1510 let out = outputs.get(10).unwrap();
1511 let left = outputs.get(11).unwrap();
1512 let right = outputs.get(12).unwrap();
1513 assert_eq!(out, left, "out must equal left");
1514 assert_eq!(left, right, "spread=0 must give bit-identical L/R");
1515 }
1516 }
1517
1518 #[test]
1519 fn test_phaser_stereo_decorrelation() {
1520 let mut phaser = Phaser::new(44100.0);
1521 let mut inputs = PortValues::new();
1522 let mut outputs = PortValues::new();
1523
1524 inputs.set(2, 0.9); inputs.set(4, 1.0); inputs.set(6, 1.0); let mut diff = 0.0;
1529 for k in 0..20000 {
1530 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.07));
1531 phaser.tick(&inputs, &mut outputs);
1532 assert_eq!(outputs.get(10).unwrap(), outputs.get(11).unwrap());
1533 let left = outputs.get(11).unwrap();
1534 let right = outputs.get(12).unwrap();
1535 diff += (left - right).abs();
1536 }
1537 assert!(
1538 diff > 1.0,
1539 "phaser left/right should decorrelate with spread; diff = {diff}"
1540 );
1541 }
1542
1543 #[test]
1544 fn test_unit_delay_default_reset_sample_rate() {
1545 let mut delay = UnitDelay::default();
1546 let mut inputs = PortValues::new();
1547 let mut outputs = PortValues::new();
1548 inputs.set(0, 5.0);
1549 delay.tick(&inputs, &mut outputs);
1550
1551 delay.reset();
1552 assert!(delay.buffer == 0.0);
1553
1554 delay.set_sample_rate(48000.0);
1555 assert_eq!(delay.type_id(), "unit_delay");
1556 }
1557 #[test]
1558 fn test_reverb_default_reset_sample_rate() {
1559 let mut reverb = Reverb::default();
1560 assert_eq!(reverb.sample_rate, 44100.0);
1561
1562 let mut inputs = PortValues::new();
1564 let mut outputs = PortValues::new();
1565 inputs.set(0, 0.5);
1566 reverb.tick(&inputs, &mut outputs);
1567
1568 reverb.reset();
1570 assert_eq!(reverb.predelay_pos, 0);
1571 assert_eq!(reverb.comb_pos_l, [0; 8]);
1572 assert_eq!(reverb.comb_pos_r, [0; 8]);
1573 assert_eq!(reverb.allpass_pos_l, [0; 4]);
1574 assert_eq!(reverb.allpass_pos_r, [0; 4]);
1575
1576 reverb.set_sample_rate(48000.0);
1578 assert_eq!(reverb.sample_rate, 48000.0);
1579
1580 assert_eq!(reverb.type_id(), "reverb");
1581 assert_eq!(reverb.port_spec().inputs.len(), 5);
1582 assert_eq!(reverb.port_spec().outputs.len(), 2);
1583 }
1584 #[test]
1585 fn test_reverb_stereo_output() {
1586 let mut reverb = Reverb::new(44100.0);
1587 let mut inputs = PortValues::new();
1588 let mut outputs = PortValues::new();
1589
1590 inputs.set(0, 1.0);
1592 inputs.set(3, 1.0); reverb.tick(&inputs, &mut outputs);
1594
1595 inputs.set(0, 0.0);
1597 let mut total_energy = 0.0;
1598 for _ in 0..3000 {
1599 reverb.tick(&inputs, &mut outputs);
1600 total_energy += outputs.get(10).unwrap().abs();
1601 total_energy += outputs.get(11).unwrap().abs();
1602 }
1603
1604 assert!(
1606 total_energy > 0.01,
1607 "Reverb should produce output after impulse, got total_energy={}",
1608 total_energy
1609 );
1610 }
1611 #[test]
1612 fn test_reverb_dry_signal() {
1613 let mut reverb = Reverb::new(44100.0);
1614 let mut inputs = PortValues::new();
1615 let mut outputs = PortValues::new();
1616
1617 inputs.set(0, 0.75);
1619 inputs.set(3, 0.0); reverb.tick(&inputs, &mut outputs);
1621
1622 let left = outputs.get(10).unwrap();
1623 let right = outputs.get(11).unwrap();
1624
1625 assert!(
1627 (left - 0.75).abs() < 0.001,
1628 "Full dry should pass through: got {}",
1629 left
1630 );
1631 assert!(
1632 (right - 0.75).abs() < 0.001,
1633 "Full dry should pass through: got {}",
1634 right
1635 );
1636 }
1637 #[test]
1638 fn test_reverb_room_size() {
1639 let mut reverb1 = Reverb::new(44100.0);
1640 let mut reverb2 = Reverb::new(44100.0);
1641 let mut inputs1 = PortValues::new();
1642 let mut inputs2 = PortValues::new();
1643 let mut outputs1 = PortValues::new();
1644 let mut outputs2 = PortValues::new();
1645
1646 inputs1.set(0, 1.0);
1648 inputs1.set(1, 0.1); inputs1.set(3, 1.0); reverb1.tick(&inputs1, &mut outputs1);
1651
1652 inputs2.set(0, 1.0);
1653 inputs2.set(1, 0.9); inputs2.set(3, 1.0); reverb2.tick(&inputs2, &mut outputs2);
1656
1657 inputs1.set(0, 0.0);
1659 inputs2.set(0, 0.0);
1660 let mut energy1 = 0.0;
1661 let mut energy2 = 0.0;
1662
1663 for _ in 0..5000 {
1664 reverb1.tick(&inputs1, &mut outputs1);
1665 reverb2.tick(&inputs2, &mut outputs2);
1666 energy1 += outputs1.get(10).unwrap().abs();
1667 energy2 += outputs2.get(10).unwrap().abs();
1668 }
1669
1670 assert!(
1672 energy2 > energy1,
1673 "Larger room should have longer decay: small={}, large={}",
1674 energy1,
1675 energy2
1676 );
1677 }
1678 #[test]
1679 fn test_reverb_predelay() {
1680 let mut reverb = Reverb::new(44100.0);
1681 let mut inputs = PortValues::new();
1682 let mut outputs = PortValues::new();
1683
1684 inputs.set(0, 1.0); inputs.set(3, 1.0); inputs.set(4, 1.0); reverb.tick(&inputs, &mut outputs);
1691
1692 let first_output = outputs.get(10).unwrap();
1694
1695 inputs.set(0, 0.0);
1697 let mut total_energy = 0.0;
1698
1699 for _ in 0..6000 {
1701 reverb.tick(&inputs, &mut outputs);
1702 total_energy += outputs.get(10).unwrap().abs();
1703 }
1704
1705 assert!(
1706 total_energy > 0.01,
1707 "Reverb should appear after predelay period, got energy={}",
1708 total_energy
1709 );
1710 assert!(
1711 first_output.abs() < 0.001,
1712 "First sample should be near zero due to predelay, got {}",
1713 first_output
1714 );
1715 }
1716 #[test]
1717 fn test_reverb_damping() {
1718 let mut reverb_low = Reverb::new(44100.0);
1719 let mut reverb_high = Reverb::new(44100.0);
1720 let mut inputs = PortValues::new();
1721 let mut outputs_low = PortValues::new();
1722 let mut outputs_high = PortValues::new();
1723
1724 inputs.set(0, 1.0);
1726 inputs.set(2, 0.1); inputs.set(3, 1.0);
1728 reverb_low.tick(&inputs, &mut outputs_low);
1729
1730 inputs.set(2, 0.9); reverb_high.tick(&inputs, &mut outputs_high);
1732
1733 inputs.set(0, 0.0);
1735 for _ in 0..3000 {
1736 reverb_low.tick(&inputs, &mut outputs_low);
1737 reverb_high.tick(&inputs, &mut outputs_high);
1738 }
1739
1740 let out_low = outputs_low.get(10).unwrap();
1743 let out_high = outputs_high.get(10).unwrap();
1744
1745 assert!(out_low.is_finite());
1747 assert!(out_high.is_finite());
1748 }
1749 #[test]
1750 fn test_reverb_tunings_scale_with_sample_rate() {
1751 let reverb_44 = Reverb::new(44100.0);
1752 let reverb_48 = Reverb::new(48000.0);
1753
1754 let ratio = 48000.0 / 44100.0;
1756
1757 for i in 0..8 {
1758 let expected = (reverb_44.comb_lengths[i] as f64 * ratio) as usize;
1759 assert!(
1760 (reverb_48.comb_lengths[i] as i64 - expected as i64).abs() < 2,
1761 "Comb filter {} should scale with sample rate",
1762 i
1763 );
1764 }
1765 }
1766
1767 #[cfg(test)]
1770 fn allpass_rms_gain(coef: f64, freq_norm: f64) -> f64 {
1771 let mut x1 = 0.0;
1772 let mut y1 = 0.0;
1773 let n = 40_000;
1774 let warmup = 8_000;
1775 let mut sum_in = 0.0;
1776 let mut sum_out = 0.0;
1777 for i in 0..n {
1778 let x = Libm::<f64>::sin(TAU * freq_norm * i as f64);
1779 let y = Phaser::allpass(x, &mut x1, &mut y1, coef);
1780 if i >= warmup {
1781 sum_in += x * x;
1782 sum_out += y * y;
1783 }
1784 }
1785 Libm::<f64>::sqrt(sum_out / sum_in)
1786 }
1787
1788 #[test]
1789 fn test_phaser_allpass_unit_magnitude() {
1790 for &coef in &[-0.6, -0.2, 0.2, 0.5, 0.8] {
1795 let dc_gain = allpass_rms_gain(coef, 0.001);
1796 let nyq_gain = allpass_rms_gain(coef, 0.499);
1797 assert!(
1798 (dc_gain - 1.0).abs() < 0.01,
1799 "DC gain {} not ~1.0 for coef {}",
1800 dc_gain,
1801 coef
1802 );
1803 assert!(
1804 (nyq_gain - 1.0).abs() < 0.01,
1805 "Nyquist gain {} not ~1.0 for coef {}",
1806 nyq_gain,
1807 coef
1808 );
1809 }
1810 }
1811
1812 #[test]
1813 fn test_chorus_delay_stays_positive() {
1814 let sample_rate = 44100.0;
1818 let base = Chorus::BASE_DELAY_MS * sample_rate / 1000.0;
1819 let mod_depth = Chorus::MAX_MOD_DELAY_MS * sample_rate / 1000.0;
1820
1821 let steps = 2000;
1822 let mut min_delay = f64::INFINITY;
1823 for k in 0..steps {
1824 let lfo = Libm::<f64>::sin((k as f64 / steps as f64) * TAU);
1825 let delay = Chorus::voice_delay_samples(base, mod_depth, lfo);
1826 min_delay = min_delay.min(delay);
1827 }
1828 assert!(
1829 min_delay > 1.0,
1830 "minimum chorus delay {} hit the clamp floor",
1831 min_delay
1832 );
1833 let trough = Chorus::voice_delay_samples(base, mod_depth, -1.0);
1835 assert!(
1836 (trough - base).abs() < 1e-9,
1837 "trough {} != base {}",
1838 trough,
1839 base
1840 );
1841 }
1842
1843 #[test]
1844 fn test_delay_line_time_smoothing() {
1845 let mut delay = DelayLine::new(44100.0);
1848 let mut inputs = PortValues::new();
1849 let mut outputs = PortValues::new();
1850
1851 inputs.set(0, 0.0);
1852 inputs.set(1, 0.0); inputs.set(2, 0.0);
1854 inputs.set(3, 0.5);
1855 delay.tick(&inputs, &mut outputs); let start = delay.smoothed_delay;
1857
1858 inputs.set(1, 1.0);
1860 delay.tick(&inputs, &mut outputs);
1861 let after_one = delay.smoothed_delay;
1862
1863 let full_jump = (delay.buffer.len() - 1) as f64 - start;
1864 let moved = after_one - start;
1865 assert!(moved > 0.0, "smoother did not move toward setpoint");
1866 assert!(
1867 moved < full_jump * 0.05,
1868 "smoother jumped {} of a {}-sample step in one tick",
1869 moved,
1870 full_jump
1871 );
1872
1873 let mut ticks = 1;
1875 while delay.smoothed_delay < start + full_jump * 0.9 && ticks < 100_000 {
1876 delay.tick(&inputs, &mut outputs);
1877 ticks += 1;
1878 }
1879 assert!(
1880 ticks > 100,
1881 "smoother converged too fast in {} ticks",
1882 ticks
1883 );
1884 }
1885
1886 #[test]
1887 fn test_vibrato_exact_delay() {
1888 let sample_rate = 44100.0;
1892 let mut vib = Vibrato::new(sample_rate);
1893 let mut inputs = PortValues::new();
1894 let mut outputs = PortValues::new();
1895
1896 inputs.set(1, 0.3); inputs.set(2, 0.0); inputs.set(3, 1.0); let expected = (Vibrato::MAX_DELAY_MS * 0.5 * sample_rate / 1000.0).round() as usize;
1901
1902 inputs.set(0, 1.0); vib.tick(&inputs, &mut outputs);
1904 let mut peak_idx = if outputs.get(10).unwrap().abs() > 0.5 {
1905 Some(0usize)
1906 } else {
1907 None
1908 };
1909
1910 inputs.set(0, 0.0);
1911 for i in 1..(expected + 50) {
1912 vib.tick(&inputs, &mut outputs);
1913 if peak_idx.is_none() && outputs.get(10).unwrap().abs() > 0.5 {
1914 peak_idx = Some(i);
1915 }
1916 }
1917 assert_eq!(
1918 peak_idx,
1919 Some(expected),
1920 "impulse emerged at {:?}, expected {}",
1921 peak_idx,
1922 expected
1923 );
1924 }
1925
1926 #[test]
1927 fn test_reverb_stereo_spread_scales_with_sample_rate() {
1928 let reverb_44 = Reverb::new(44100.0);
1931 assert_eq!(reverb_44.stereo_spread, STEREO_SPREAD);
1932
1933 let reverb_88 = Reverb::new(88200.0);
1934 assert_eq!(reverb_88.stereo_spread, 46);
1936
1937 for i in 0..8 {
1938 let length_l = reverb_88.comb_lengths[i];
1939 let length_r = (length_l + reverb_88.stereo_spread).min(MAX_COMB_SIZE - 1);
1940 assert_eq!(
1941 length_r - length_l,
1942 46,
1943 "right comb {} should lead left by the scaled spread",
1944 i
1945 );
1946 }
1947 }
1948
1949 #[test]
1952 fn test_tremolo_am_depth() {
1953 let mut trem = Tremolo::new(44100.0);
1957 let mut inputs = PortValues::new();
1958 let mut outputs = PortValues::new();
1959 inputs.set(0, 1.0); inputs.set(1, 1.0); inputs.set(3, 0.0); inputs.set(2, 1.0); let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
1965 for _ in 0..44_100 {
1966 trem.tick(&inputs, &mut outputs);
1967 let o = outputs.get(10).unwrap();
1968 assert!(o.is_finite());
1969 lo = lo.min(o);
1970 hi = hi.max(o);
1971 }
1972 assert!(
1973 lo < 0.1 && hi > 0.9,
1974 "full-depth AM must reach near 0 and near the carrier: lo={lo} hi={hi}"
1975 );
1976
1977 trem.reset();
1978 inputs.set(2, 0.0); let (mut lo0, mut hi0) = (f64::INFINITY, f64::NEG_INFINITY);
1980 for _ in 0..4410 {
1981 trem.tick(&inputs, &mut outputs);
1982 let o = outputs.get(10).unwrap();
1983 lo0 = lo0.min(o);
1984 hi0 = hi0.max(o);
1985 }
1986 assert!(
1987 (hi0 - lo0) < 1e-9 && (hi0 - 1.0).abs() < 1e-9,
1988 "zero-depth tremolo must pass the carrier unchanged: span={}",
1989 hi0 - lo0
1990 );
1991 }
1992
1993 #[test]
1994 fn test_tremolo_reset_and_sample_rate() {
1995 let mut trem = Tremolo::default();
1996 assert_eq!(trem.type_id(), "tremolo");
1997 assert_eq!(trem.sample_rate, 44100.0);
1998 let mut inputs = PortValues::new();
1999 let mut outputs = PortValues::new();
2000 inputs.set(0, 1.0);
2001 inputs.set(1, 0.5);
2002 for _ in 0..500 {
2003 trem.tick(&inputs, &mut outputs);
2004 }
2005 assert!(trem.lfo_phase != 0.0);
2006 trem.reset();
2007 assert_eq!(trem.lfo_phase, 0.0);
2008 trem.set_sample_rate(48000.0);
2009 assert_eq!(trem.sample_rate, 48000.0);
2010 trem.tick(&inputs, &mut outputs);
2011 assert!(outputs.get(10).unwrap().is_finite());
2012 }
2013
2014 fn zero_crossing_interval_spread(sig: &[f64]) -> f64 {
2019 let mut crossings = Vec::new();
2020 for i in 1..sig.len() {
2021 if sig[i - 1] <= 0.0 && sig[i] > 0.0 {
2022 crossings.push(i);
2023 }
2024 }
2025 if crossings.len() < 3 {
2026 return 0.0;
2027 }
2028 let mut min_iv = f64::INFINITY;
2029 let mut max_iv = f64::NEG_INFINITY;
2030 for w in crossings.windows(2) {
2031 let iv = (w[1] - w[0]) as f64;
2032 min_iv = min_iv.min(iv);
2033 max_iv = max_iv.max(iv);
2034 }
2035 max_iv - min_iv
2036 }
2037
2038 #[test]
2039 fn test_vibrato_pitch_modulation_depth() {
2040 let run = |depth: f64| -> f64 {
2044 let mut vib = Vibrato::new(44100.0);
2045 let mut inputs = PortValues::new();
2046 let mut outputs = PortValues::new();
2047 inputs.set(1, 0.78); inputs.set(2, depth);
2049 inputs.set(3, 1.0); let mut out = Vec::with_capacity(20_000);
2051 let dt = 500.0 / 44100.0;
2052 let mut phase = 0.0f64;
2053 for _ in 0..20_000 {
2054 let s = Libm::<f64>::sin(TAU * phase);
2055 phase += dt;
2056 if phase >= 1.0 {
2057 phase -= 1.0;
2058 }
2059 inputs.set(0, s);
2060 vib.tick(&inputs, &mut outputs);
2061 out.push(outputs.get(10).unwrap());
2062 }
2063 zero_crossing_interval_spread(&out)
2064 };
2065
2066 let spread_off = run(0.0);
2067 let spread_on = run(0.8);
2068 assert!(
2069 spread_off < 3.0,
2070 "zero-depth vibrato should have near-constant pitch: spread={spread_off}"
2071 );
2072 assert!(
2073 spread_on > spread_off + 10.0,
2074 "depth-0.8 vibrato must wobble the pitch: on={spread_on} off={spread_off}"
2075 );
2076 }
2077
2078 #[test]
2079 fn test_vibrato_reset_and_sample_rate() {
2080 let mut vib = Vibrato::default();
2081 assert_eq!(vib.type_id(), "vibrato");
2082 let mut inputs = PortValues::new();
2083 let mut outputs = PortValues::new();
2084 inputs.set(0, 1.0);
2085 inputs.set(2, 0.5);
2086 for _ in 0..500 {
2087 vib.tick(&inputs, &mut outputs);
2088 }
2089 assert!(vib.lfo_phase != 0.0);
2090 vib.reset();
2091 assert_eq!(vib.lfo_phase, 0.0);
2092 assert_eq!(vib.write_pos, 0);
2093 assert!(vib.buffer.iter().all(|&x| x == 0.0));
2094 vib.set_sample_rate(48000.0);
2095 assert_eq!(vib.sample_rate, 48000.0);
2096 vib.tick(&inputs, &mut outputs);
2097 assert!(outputs.get(10).unwrap().is_finite());
2098 }
2099
2100 #[test]
2101 fn test_vibrato_lowering_sample_rate_does_not_panic() {
2102 let mut vib = Vibrato::new(96000.0);
2108 let mut inputs = PortValues::new();
2109 let mut outputs = PortValues::new();
2110 inputs.set(0, 0.5);
2111 inputs.set(2, 0.5);
2112 for _ in 0..1000 {
2114 vib.tick(&inputs, &mut outputs);
2115 }
2116 vib.set_sample_rate(22050.0);
2118 assert_eq!(vib.write_pos, 0, "write_pos must be reset after resize");
2119 vib.tick(&inputs, &mut outputs);
2121 assert!(outputs.get(10).unwrap().is_finite());
2122 }
2123}