1use super::common::{env_coef, read_interpolated, sanitize_audio, Memo};
4use crate::analog::saturation;
5use crate::port::{GraphModule, PortDef, PortSpec, PortValues, SignalKind};
6use alloc::vec;
7use alloc::vec::Vec;
8use core::f64::consts::TAU;
9use libm::Libm;
10
11pub struct UnitDelay {
15 buffer: f64,
16 spec: PortSpec,
17}
18
19impl UnitDelay {
20 pub fn new() -> Self {
21 Self {
22 buffer: 0.0,
23 spec: PortSpec {
24 inputs: vec![PortDef::new(0, "in", SignalKind::Audio)],
25 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
26 },
27 }
28 }
29}
30
31impl Default for UnitDelay {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl GraphModule for UnitDelay {
38 fn port_spec(&self) -> &PortSpec {
39 &self.spec
40 }
41
42 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
43 let input = inputs.get_or(0, 0.0);
44 outputs.set(10, self.buffer);
45 self.buffer = input;
46 }
47
48 fn reset(&mut self) {
49 self.buffer = 0.0;
50 }
51
52 fn set_sample_rate(&mut self, _: f64) {}
53
54 fn breaks_feedback_cycle(&self) -> bool {
55 true
56 }
57
58 fn type_id(&self) -> &'static str {
59 "unit_delay"
60 }
61}
62
63pub struct DelayLine {
76 buffer: Vec<f64>,
77 write_pos: usize,
78 sample_rate: f64,
79 max_delay_secs: f64,
81 unclamped_feedback: bool,
83 linear_time: bool,
85 type_id_str: &'static str,
87 smoothed_delay: f64,
90 delay_smooth_coef: f64,
92 delay_primed: bool,
94 delay_ms_memo: Memo<1, f64>,
96 spec: PortSpec,
97}
98
99impl DelayLine {
100 const MAX_DELAY_SECS: f64 = 2.0;
102
103 const UNCLAMPED_FEEDBACK_MAX: f64 = 1.5;
106
107 const TAPE_MAX_DELAY_SECS: f64 = 12.0;
110
111 const DELAY_SMOOTH_SECS: f64 = 0.005;
114
115 pub fn new(sample_rate: f64) -> Self {
116 Self::with_max_delay(sample_rate, Self::MAX_DELAY_SECS)
117 }
118
119 pub fn with_max_delay(sample_rate: f64, max_delay_secs: f64) -> Self {
125 let max_delay_secs = max_delay_secs.max(0.001);
126 let buffer_size = (sample_rate * max_delay_secs) as usize + 1;
127 Self {
128 buffer: vec![0.0; buffer_size],
129 write_pos: 0,
130 sample_rate,
131 max_delay_secs,
132 unclamped_feedback: false,
133 linear_time: false,
134 type_id_str: "delay_line",
135 smoothed_delay: 0.0,
136 delay_smooth_coef: env_coef(Self::DELAY_SMOOTH_SECS, sample_rate),
137 delay_primed: false,
138 delay_ms_memo: Memo::new(0.0),
139 spec: PortSpec {
140 inputs: vec![
141 PortDef::new(0, "in", SignalKind::Audio),
142 PortDef::new(1, "time", SignalKind::CvUnipolar)
143 .with_default(0.5)
144 .with_attenuverter(),
145 PortDef::new(2, "feedback", SignalKind::CvUnipolar)
146 .with_default(0.0)
147 .with_attenuverter(),
148 PortDef::new(3, "mix", SignalKind::CvUnipolar)
149 .with_default(0.5)
150 .with_attenuverter(),
151 ],
152 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
153 },
154 }
155 }
156
157 pub fn with_unclamped_feedback(mut self) -> Self {
163 self.unclamped_feedback = true;
164 self
165 }
166
167 pub fn with_linear_time(mut self) -> Self {
172 self.linear_time = true;
173 self
174 }
175
176 pub fn tape(sample_rate: f64) -> Self {
180 let mut tape = Self::with_max_delay(sample_rate, Self::TAPE_MAX_DELAY_SECS)
181 .with_unclamped_feedback()
182 .with_linear_time();
183 tape.type_id_str = "tape_delay";
184 tape
185 }
186}
187
188impl Default for DelayLine {
189 fn default() -> Self {
190 Self::new(44100.0)
191 }
192}
193
194impl GraphModule for DelayLine {
195 fn port_spec(&self) -> &PortSpec {
196 &self.spec
197 }
198
199 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
200 let input = sanitize_audio(inputs.get_or(0, 0.0));
203 let feedback_ceiling = if self.unclamped_feedback {
204 Self::UNCLAMPED_FEEDBACK_MAX } else {
206 0.99 };
208 let feedback = inputs.get_or(2, 0.0).clamp(0.0, feedback_ceiling);
209 let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
210
211 let delay_ms = if self.linear_time {
212 inputs.get_or(1, 0.5).clamp(0.0, self.max_delay_secs) * 1000.0
214 } else {
215 let time_cv = inputs.get_or(1, 0.5).clamp(0.0, 1.0);
218 let max_delay_ms = self.max_delay_secs * 1000.0;
219 self.delay_ms_memo.get_or_compute([time_cv], || {
220 let min_delay_ms = 1.0;
221 min_delay_ms * Libm::<f64>::pow(max_delay_ms / min_delay_ms, time_cv)
222 })
223 };
224 let target_delay =
225 (delay_ms * self.sample_rate / 1000.0).clamp(1.0, (self.buffer.len() - 1) as f64);
226
227 if self.delay_primed {
231 self.smoothed_delay =
232 target_delay + (self.smoothed_delay - target_delay) * self.delay_smooth_coef;
233 } else {
234 self.smoothed_delay = target_delay;
235 self.delay_primed = true;
236 }
237 let delay_samples = self.smoothed_delay;
238
239 let delayed = read_interpolated(&self.buffer, self.write_pos, delay_samples);
241
242 let recirculated = input + delayed * feedback;
246 self.buffer[self.write_pos] = if self.unclamped_feedback {
247 saturation::tanh_sat(recirculated / 5.0, 1.0) * 5.0
248 } else {
249 recirculated
250 };
251
252 self.write_pos = (self.write_pos + 1) % self.buffer.len();
254
255 let output = input * (1.0 - mix) + delayed * mix;
257 outputs.set(10, output);
258 }
259
260 fn reset(&mut self) {
261 self.buffer.fill(0.0);
262 self.write_pos = 0;
263 self.smoothed_delay = 0.0;
264 self.delay_primed = false;
265 }
266
267 fn set_sample_rate(&mut self, sample_rate: f64) {
268 self.sample_rate = sample_rate;
269 let buffer_size = (sample_rate * self.max_delay_secs) as usize + 1;
270 self.buffer = vec![0.0; buffer_size];
271 self.write_pos = 0;
272 self.smoothed_delay = 0.0;
273 self.delay_smooth_coef = env_coef(Self::DELAY_SMOOTH_SECS, sample_rate);
274 self.delay_primed = false;
275 }
276
277 fn breaks_feedback_cycle(&self) -> bool {
278 true
279 }
280
281 fn type_id(&self) -> &'static str {
282 self.type_id_str
283 }
284}
285
286pub struct Chorus {
292 delay_buffers: [Vec<f64>; 3],
294 write_pos: usize,
295 lfo_phases: [f64; 3],
297 sample_rate: f64,
298 rate_memo: Memo<1, f64>,
300 spec: PortSpec,
301}
302
303impl Chorus {
304 const MAX_MOD_DELAY_MS: f64 = 25.0;
306 const BASE_DELAY_MS: f64 = 7.0;
308
309 #[inline]
318 fn voice_delay_samples(base_delay_samples: f64, mod_depth_samples: f64, lfo_val: f64) -> f64 {
319 base_delay_samples + (lfo_val * 0.5 + 0.5) * mod_depth_samples
320 }
321
322 pub fn new(sample_rate: f64) -> Self {
323 let buffer_size =
324 ((Self::MAX_MOD_DELAY_MS + Self::BASE_DELAY_MS) * sample_rate / 1000.0) as usize + 10;
325 Self {
326 delay_buffers: [
327 vec![0.0; buffer_size],
328 vec![0.0; buffer_size],
329 vec![0.0; buffer_size],
330 ],
331 write_pos: 0,
332 lfo_phases: [0.0, 0.33, 0.67],
334 sample_rate,
335 rate_memo: Memo::new(0.0),
336 spec: PortSpec {
337 inputs: vec![
338 PortDef::new(0, "in", SignalKind::Audio),
339 PortDef::new(1, "rate", SignalKind::CvUnipolar)
340 .with_default(0.3)
341 .with_attenuverter(),
342 PortDef::new(2, "depth", SignalKind::CvUnipolar)
343 .with_default(0.5)
344 .with_attenuverter(),
345 PortDef::new(3, "mix", SignalKind::CvUnipolar)
346 .with_default(0.5)
347 .with_attenuverter(),
348 ],
349 outputs: vec![
350 PortDef::new(10, "out", SignalKind::Audio),
351 PortDef::new(11, "left", SignalKind::Audio),
352 PortDef::new(12, "right", SignalKind::Audio),
353 ],
354 },
355 }
356 }
357}
358
359impl Default for Chorus {
360 fn default() -> Self {
361 Self::new(44100.0)
362 }
363}
364
365impl GraphModule for Chorus {
366 fn port_spec(&self) -> &PortSpec {
367 &self.spec
368 }
369
370 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
371 let input = sanitize_audio(inputs.get_or(0, 0.0));
374 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
375 let depth_cv = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
376 let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
377
378 let lfo_freq = self
381 .rate_memo
382 .get_or_compute([rate_cv], || 0.1 * Libm::<f64>::pow(50.0, rate_cv));
383
384 let mod_depth_ms = depth_cv * Self::MAX_MOD_DELAY_MS;
386
387 let base_delay_samples = Self::BASE_DELAY_MS * self.sample_rate / 1000.0;
388 let mod_depth_samples = mod_depth_ms * self.sample_rate / 1000.0;
389
390 let mut wet_sum = 0.0;
391 let mut left_sum = 0.0;
392 let mut right_sum = 0.0;
393
394 for i in 0..3 {
395 let lfo_val = Libm::<f64>::sin(self.lfo_phases[i] * core::f64::consts::TAU);
397 let delay_samples =
398 Self::voice_delay_samples(base_delay_samples, mod_depth_samples, lfo_val)
399 .clamp(1.0, (self.delay_buffers[i].len() - 1) as f64);
400
401 let delayed = read_interpolated(&self.delay_buffers[i], self.write_pos, delay_samples);
403
404 wet_sum += delayed;
405
406 match i {
408 0 => {
409 left_sum += delayed * 0.5;
410 right_sum += delayed * 0.5;
411 }
412 1 => left_sum += delayed,
413 2 => right_sum += delayed,
414 _ => {}
415 }
416
417 self.delay_buffers[i][self.write_pos] = input;
419
420 let freq_mult = 1.0 + (i as f64 - 1.0) * 0.1; let phase_inc = lfo_freq * freq_mult / self.sample_rate;
423 self.lfo_phases[i] += phase_inc;
424 if self.lfo_phases[i] >= 1.0 {
425 self.lfo_phases[i] -= 1.0;
426 }
427 }
428
429 wet_sum /= 3.0;
431 left_sum /= 2.0;
432 right_sum /= 2.0;
433
434 self.write_pos = (self.write_pos + 1) % self.delay_buffers[0].len();
436
437 let mono_out = input * (1.0 - mix) + wet_sum * mix;
439 let left_out = input * (1.0 - mix) + left_sum * mix;
440 let right_out = input * (1.0 - mix) + right_sum * mix;
441
442 outputs.set(10, mono_out);
443 outputs.set(11, left_out);
444 outputs.set(12, right_out);
445 }
446
447 fn reset(&mut self) {
448 for buffer in &mut self.delay_buffers {
449 buffer.fill(0.0);
450 }
451 self.write_pos = 0;
452 self.lfo_phases = [0.0, 0.33, 0.67];
453 }
454
455 fn set_sample_rate(&mut self, sample_rate: f64) {
456 self.sample_rate = sample_rate;
457 let buffer_size =
458 ((Self::MAX_MOD_DELAY_MS + Self::BASE_DELAY_MS) * sample_rate / 1000.0) as usize + 10;
459 for buffer in &mut self.delay_buffers {
460 *buffer = vec![0.0; buffer_size];
461 }
462 self.write_pos = 0;
463 }
464
465 fn type_id(&self) -> &'static str {
466 "chorus"
467 }
468}
469
470pub struct Flanger {
480 buffers: [Vec<f64>; 2],
482 write_pos: usize,
483 lfo_phase: f64,
484 sample_rate: f64,
485 rate_memo: Memo<1, f64>,
487 spec: PortSpec,
488}
489
490impl Flanger {
491 const MAX_DELAY_MS: f64 = 10.0;
492
493 pub fn new(sample_rate: f64) -> Self {
494 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
495 Self {
496 buffers: [vec![0.0; buffer_size], vec![0.0; buffer_size]],
497 write_pos: 0,
498 lfo_phase: 0.0,
499 sample_rate,
500 rate_memo: Memo::new(0.0),
501 spec: PortSpec {
502 inputs: vec![
503 PortDef::new(0, "in", SignalKind::Audio),
504 PortDef::new(1, "rate", SignalKind::CvUnipolar)
505 .with_default(0.3)
506 .with_attenuverter(),
507 PortDef::new(2, "depth", SignalKind::CvUnipolar)
508 .with_default(0.5)
509 .with_attenuverter(),
510 PortDef::new(3, "feedback", SignalKind::CvBipolar)
511 .with_default(0.0)
512 .with_attenuverter(),
513 PortDef::new(4, "mix", SignalKind::CvUnipolar)
514 .with_default(0.5)
515 .with_attenuverter(),
516 PortDef::new(5, "spread", SignalKind::CvUnipolar)
519 .with_default(0.5)
520 .with_attenuverter(),
521 ],
522 outputs: vec![
523 PortDef::new(10, "out", SignalKind::Audio),
524 PortDef::new(11, "left", SignalKind::Audio),
525 PortDef::new(12, "right", SignalKind::Audio),
526 ],
527 },
528 }
529 }
530}
531
532impl Default for Flanger {
533 fn default() -> Self {
534 Self::new(44100.0)
535 }
536}
537
538impl GraphModule for Flanger {
539 fn port_spec(&self) -> &PortSpec {
540 &self.spec
541 }
542
543 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
544 let input = sanitize_audio(inputs.get_or(0, 0.0));
547 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
548 let depth_cv = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
549 let feedback = inputs.get_or(3, 0.0).clamp(-0.95, 0.95);
550 let mix = inputs.get_or(4, 0.5).clamp(0.0, 1.0);
551 let spread = inputs.get_or(5, 0.5).clamp(0.0, 1.0);
552
553 let lfo_freq = self
555 .rate_memo
556 .get_or_compute([rate_cv], || 0.05 * Libm::<f64>::pow(100.0, rate_cv));
557 let base_delay_ms = 1.0;
558 let mod_depth_ms = depth_cv * (Self::MAX_DELAY_MS - base_delay_ms);
559
560 let phase_offset = spread * 0.5;
565 let max_read = (self.buffers[0].len() - 1) as f64;
566
567 let mut wet = [0.0; 2];
568 for (ch, w) in wet.iter_mut().enumerate() {
569 let phase = self.lfo_phase + if ch == 0 { 0.0 } else { phase_offset };
570 let lfo = (Libm::<f64>::sin(phase * TAU) + 1.0) * 0.5;
571 let delay_ms = base_delay_ms + lfo * mod_depth_ms;
572 let delay_samples = (delay_ms * self.sample_rate / 1000.0).clamp(1.0, max_read);
573 let delayed = read_interpolated(&self.buffers[ch], self.write_pos, delay_samples);
574 self.buffers[ch][self.write_pos] = input + delayed * feedback;
576 *w = delayed;
577 }
578
579 self.lfo_phase += lfo_freq / self.sample_rate;
580 if self.lfo_phase >= 1.0 {
581 self.lfo_phase -= 1.0;
582 }
583 self.write_pos = (self.write_pos + 1) % self.buffers[0].len();
584
585 let left = input * (1.0 - mix) + wet[0] * mix;
586 let right = input * (1.0 - mix) + wet[1] * mix;
587 outputs.set(10, left);
589 outputs.set(11, left);
590 outputs.set(12, right);
591 }
592
593 fn reset(&mut self) {
594 for buffer in &mut self.buffers {
595 buffer.fill(0.0);
596 }
597 self.write_pos = 0;
598 self.lfo_phase = 0.0;
599 }
600
601 fn set_sample_rate(&mut self, sample_rate: f64) {
602 self.sample_rate = sample_rate;
603 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
604 for buffer in &mut self.buffers {
605 *buffer = vec![0.0; buffer_size];
606 }
607 self.write_pos = 0;
608 }
609
610 fn type_id(&self) -> &'static str {
611 "flanger"
612 }
613}
614
615pub struct Phaser {
625 allpass_x1: [[f64; 6]; 2],
627 allpass_y1: [[f64; 6]; 2],
629 lfo_phase: f64,
630 sample_rate: f64,
631 rate_memo: Memo<1, f64>,
633 coef_memo: Memo<2, f64>,
638 spec: PortSpec,
639}
640
641impl Phaser {
642 pub fn new(sample_rate: f64) -> Self {
643 Self {
644 allpass_x1: [[0.0; 6]; 2],
645 allpass_y1: [[0.0; 6]; 2],
646 lfo_phase: 0.0,
647 sample_rate,
648 rate_memo: Memo::new(0.0),
649 coef_memo: Memo::new(0.0),
650 spec: PortSpec {
651 inputs: vec![
652 PortDef::new(0, "in", SignalKind::Audio),
653 PortDef::new(1, "rate", SignalKind::CvUnipolar)
654 .with_default(0.3)
655 .with_attenuverter(),
656 PortDef::new(2, "depth", SignalKind::CvUnipolar)
657 .with_default(0.7)
658 .with_attenuverter(),
659 PortDef::new(3, "feedback", SignalKind::CvBipolar)
660 .with_default(0.0)
661 .with_attenuverter(),
662 PortDef::new(4, "mix", SignalKind::CvUnipolar)
663 .with_default(0.5)
664 .with_attenuverter(),
665 PortDef::new(5, "stages", SignalKind::CvUnipolar).with_default(1.0),
666 PortDef::new(6, "spread", SignalKind::CvUnipolar)
669 .with_default(0.5)
670 .with_attenuverter(),
671 ],
672 outputs: vec![
673 PortDef::new(10, "out", SignalKind::Audio),
674 PortDef::new(11, "left", SignalKind::Audio),
675 PortDef::new(12, "right", SignalKind::Audio),
676 ],
677 },
678 }
679 }
680
681 fn allpass(input: f64, x1: &mut f64, y1: &mut f64, coef: f64) -> f64 {
690 let output = coef * input + *x1 - coef * *y1;
691 *x1 = input;
692 *y1 = output;
693 output
694 }
695}
696
697impl Default for Phaser {
698 fn default() -> Self {
699 Self::new(44100.0)
700 }
701}
702
703impl GraphModule for Phaser {
704 fn port_spec(&self) -> &PortSpec {
705 &self.spec
706 }
707
708 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
709 let input = sanitize_audio(inputs.get_or(0, 0.0));
712 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
713 let depth = inputs.get_or(2, 0.7).clamp(0.0, 1.0);
714 let feedback = inputs.get_or(3, 0.0).clamp(-0.95, 0.95);
715 let mix = inputs.get_or(4, 0.5).clamp(0.0, 1.0);
716 let stages_cv = inputs.get_or(5, 1.0).clamp(0.0, 1.0);
717
718 let num_stages = if stages_cv < 0.33 {
719 2
720 } else if stages_cv < 0.66 {
721 4
722 } else {
723 6
724 };
725
726 let spread = inputs.get_or(6, 0.5).clamp(0.0, 1.0);
727
728 let lfo_freq = self
730 .rate_memo
731 .get_or_compute([rate_cv], || 0.05 * Libm::<f64>::pow(100.0, rate_cv));
732
733 let min_freq = 200.0;
734 let max_freq = 4000.0;
735
736 let phase_offset = spread * 0.5;
741
742 let mut wet = [0.0; 2];
743 for (ch, w) in wet.iter_mut().enumerate() {
744 let phase = self.lfo_phase + if ch == 0 { 0.0 } else { phase_offset };
745 let lfo = Libm::<f64>::sin(phase * TAU);
746 let freq = min_freq + (lfo * 0.5 + 0.5) * depth * (max_freq - min_freq);
747
748 let sample_rate = self.sample_rate;
752 let coef = self.coef_memo.get_or_compute([freq, sample_rate], || {
753 let omega = TAU * freq / sample_rate;
754 let tan_w = Libm::<f64>::tan(omega * 0.5);
755 (1.0 - tan_w) / (1.0 + tan_w)
756 });
757
758 let mut signal = input + self.allpass_y1[ch][num_stages - 1] * feedback;
760 for i in 0..num_stages {
761 signal = Self::allpass(
762 signal,
763 &mut self.allpass_x1[ch][i],
764 &mut self.allpass_y1[ch][i],
765 coef,
766 );
767 }
768 *w = signal;
769 }
770
771 self.lfo_phase += lfo_freq / self.sample_rate;
772 if self.lfo_phase >= 1.0 {
773 self.lfo_phase -= 1.0;
774 }
775
776 let left = input * (1.0 - mix) + wet[0] * mix;
777 let right = input * (1.0 - mix) + wet[1] * mix;
778 outputs.set(10, left);
780 outputs.set(11, left);
781 outputs.set(12, right);
782 }
783
784 fn reset(&mut self) {
785 self.allpass_x1 = [[0.0; 6]; 2];
786 self.allpass_y1 = [[0.0; 6]; 2];
787 self.lfo_phase = 0.0;
788 }
789
790 fn set_sample_rate(&mut self, sample_rate: f64) {
791 self.sample_rate = sample_rate;
792 }
793
794 fn type_id(&self) -> &'static str {
795 "phaser"
796 }
797}
798
799pub struct Tremolo {
808 lfo_phase: f64,
809 sample_rate: f64,
810 rate_memo: Memo<1, f64>,
812 spec: PortSpec,
813}
814
815impl Tremolo {
816 pub fn new(sample_rate: f64) -> Self {
817 Self {
818 lfo_phase: 0.0,
819 sample_rate,
820 rate_memo: Memo::new(0.0),
821 spec: PortSpec {
822 inputs: vec![
823 PortDef::new(0, "in", SignalKind::Audio),
824 PortDef::new(1, "rate", SignalKind::CvUnipolar)
825 .with_default(0.3)
826 .with_attenuverter(),
827 PortDef::new(2, "depth", SignalKind::CvUnipolar)
828 .with_default(0.5)
829 .with_attenuverter(),
830 PortDef::new(3, "shape", SignalKind::CvUnipolar)
831 .with_default(0.0)
832 .with_attenuverter(),
833 ],
834 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
835 },
836 }
837 }
838}
839
840impl Default for Tremolo {
841 fn default() -> Self {
842 Self::new(44100.0)
843 }
844}
845
846impl GraphModule for Tremolo {
847 fn port_spec(&self) -> &PortSpec {
848 &self.spec
849 }
850
851 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
852 let input = inputs.get_or(0, 0.0);
853 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
854 let depth = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
855 let shape = inputs.get_or(3, 0.0).clamp(0.0, 1.0);
856
857 let lfo_freq = self
860 .rate_memo
861 .get_or_compute([rate_cv], || 0.1 * Libm::<f64>::pow(200.0, rate_cv));
862
863 let phase_rad = self.lfo_phase * TAU;
865 let sine = Libm::<f64>::sin(phase_rad);
866 let triangle = 1.0 - 4.0 * Libm::<f64>::fabs(self.lfo_phase - 0.5);
867 let lfo = sine * (1.0 - shape) + triangle * shape;
868
869 self.lfo_phase += lfo_freq / self.sample_rate;
871 if self.lfo_phase >= 1.0 {
872 self.lfo_phase -= 1.0;
873 }
874
875 let modulation = 1.0 - depth * 0.5 * (1.0 - lfo);
878 outputs.set(10, input * modulation);
879 }
880
881 fn reset(&mut self) {
882 self.lfo_phase = 0.0;
883 }
884
885 fn set_sample_rate(&mut self, sample_rate: f64) {
886 self.sample_rate = sample_rate;
887 }
888
889 fn type_id(&self) -> &'static str {
890 "tremolo"
891 }
892}
893
894pub struct Vibrato {
899 buffer: Vec<f64>,
900 write_pos: usize,
901 lfo_phase: f64,
902 sample_rate: f64,
903 rate_memo: Memo<1, f64>,
905 spec: PortSpec,
906}
907
908impl Vibrato {
909 const MAX_DELAY_MS: f64 = 20.0;
910
911 pub fn new(sample_rate: f64) -> Self {
912 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
913 Self {
914 buffer: vec![0.0; buffer_size],
915 write_pos: 0,
916 lfo_phase: 0.0,
917 sample_rate,
918 rate_memo: Memo::new(0.0),
919 spec: PortSpec {
920 inputs: vec![
921 PortDef::new(0, "in", SignalKind::Audio),
922 PortDef::new(1, "rate", SignalKind::CvUnipolar)
923 .with_default(0.3)
924 .with_attenuverter(),
925 PortDef::new(2, "depth", SignalKind::CvUnipolar)
926 .with_default(0.5)
927 .with_attenuverter(),
928 PortDef::new(3, "mix", SignalKind::CvUnipolar)
929 .with_default(1.0)
930 .with_attenuverter(),
931 ],
932 outputs: vec![PortDef::new(10, "out", SignalKind::Audio)],
933 },
934 }
935 }
936}
937
938impl Default for Vibrato {
939 fn default() -> Self {
940 Self::new(44100.0)
941 }
942}
943
944impl GraphModule for Vibrato {
945 fn port_spec(&self) -> &PortSpec {
946 &self.spec
947 }
948
949 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
950 let input = inputs.get_or(0, 0.0);
951 let rate_cv = inputs.get_or(1, 0.3).clamp(0.0, 1.0);
952 let depth = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
953 let mix = inputs.get_or(3, 1.0).clamp(0.0, 1.0);
954
955 let lfo_freq = self
958 .rate_memo
959 .get_or_compute([rate_cv], || 0.1 * Libm::<f64>::pow(150.0, rate_cv));
960
961 let base_delay_ms = Self::MAX_DELAY_MS * 0.5;
963 let mod_depth_ms = depth * base_delay_ms * 0.9;
964
965 let lfo = Libm::<f64>::sin(self.lfo_phase * TAU);
967 self.lfo_phase += lfo_freq / self.sample_rate;
968 if self.lfo_phase >= 1.0 {
969 self.lfo_phase -= 1.0;
970 }
971
972 let delay_ms = base_delay_ms + lfo * mod_depth_ms;
974 let delay_samples =
975 (delay_ms * self.sample_rate / 1000.0).clamp(1.0, (self.buffer.len() - 1) as f64);
976
977 let delayed = read_interpolated(&self.buffer, self.write_pos, delay_samples);
980
981 self.buffer[self.write_pos] = input;
983 self.write_pos = (self.write_pos + 1) % self.buffer.len();
984
985 outputs.set(10, input * (1.0 - mix) + delayed * mix);
986 }
987
988 fn reset(&mut self) {
989 self.buffer.fill(0.0);
990 self.write_pos = 0;
991 self.lfo_phase = 0.0;
992 }
993
994 fn set_sample_rate(&mut self, sample_rate: f64) {
995 self.sample_rate = sample_rate;
996 let buffer_size = (sample_rate * Self::MAX_DELAY_MS / 1000.0) as usize + 10;
997 self.buffer = vec![0.0; buffer_size];
1002 self.write_pos = 0;
1003 self.lfo_phase = 0.0;
1004 }
1005
1006 fn type_id(&self) -> &'static str {
1007 "vibrato"
1008 }
1009}
1010
1011const COMB_TUNINGS_44100: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
1013
1014const ALLPASS_TUNINGS_44100: [usize; 4] = [556, 441, 341, 225];
1016
1017const STEREO_SPREAD: usize = 23;
1019
1020const MAX_COMB_SIZE: usize = 4096;
1022
1023const MAX_ALLPASS_SIZE: usize = 1500;
1025
1026const MAX_PREDELAY_SIZE: usize = 9600;
1028
1029pub struct Reverb {
1043 comb_buffers_l: Vec<Vec<f64>>,
1045 comb_buffers_r: Vec<Vec<f64>>,
1046 comb_pos_l: [usize; 8],
1047 comb_pos_r: [usize; 8],
1048 comb_filter_state_l: [f64; 8], comb_filter_state_r: [f64; 8],
1050
1051 allpass_buffers_l: Vec<Vec<f64>>,
1053 allpass_buffers_r: Vec<Vec<f64>>,
1054 allpass_pos_l: [usize; 4],
1055 allpass_pos_r: [usize; 4],
1056
1057 predelay_buffer: Vec<f64>,
1059 predelay_pos: usize,
1060
1061 comb_lengths: [usize; 8],
1063 allpass_lengths: [usize; 4],
1064 stereo_spread: usize,
1066
1067 sample_rate: f64,
1068 spec: PortSpec,
1069}
1070
1071impl Reverb {
1072 pub fn new(sample_rate: f64) -> Self {
1074 let mut reverb = Self {
1075 comb_buffers_l: (0..8).map(|_| vec![0.0; MAX_COMB_SIZE]).collect(),
1076 comb_buffers_r: (0..8).map(|_| vec![0.0; MAX_COMB_SIZE]).collect(),
1077 comb_pos_l: [0; 8],
1078 comb_pos_r: [0; 8],
1079 comb_filter_state_l: [0.0; 8],
1080 comb_filter_state_r: [0.0; 8],
1081
1082 allpass_buffers_l: (0..4).map(|_| vec![0.0; MAX_ALLPASS_SIZE]).collect(),
1083 allpass_buffers_r: (0..4).map(|_| vec![0.0; MAX_ALLPASS_SIZE]).collect(),
1084 allpass_pos_l: [0; 4],
1085 allpass_pos_r: [0; 4],
1086
1087 predelay_buffer: vec![0.0; MAX_PREDELAY_SIZE],
1088 predelay_pos: 0,
1089
1090 comb_lengths: [0; 8],
1091 allpass_lengths: [0; 4],
1092 stereo_spread: STEREO_SPREAD,
1093
1094 sample_rate,
1095 spec: PortSpec {
1096 inputs: vec![
1097 PortDef::new(0, "in", SignalKind::Audio),
1098 PortDef::new(1, "size", SignalKind::CvUnipolar).with_default(0.5),
1099 PortDef::new(2, "damping", SignalKind::CvUnipolar).with_default(0.5),
1100 PortDef::new(3, "mix", SignalKind::CvUnipolar).with_default(0.5),
1101 PortDef::new(4, "predelay", SignalKind::CvUnipolar).with_default(0.0),
1102 ],
1103 outputs: vec![
1104 PortDef::new(10, "left", SignalKind::Audio),
1105 PortDef::new(11, "right", SignalKind::Audio),
1106 ],
1107 },
1108 };
1109 reverb.update_tunings();
1110 reverb
1111 }
1112
1113 fn update_tunings(&mut self) {
1115 let ratio = self.sample_rate / 44100.0;
1116
1117 for (i, &base) in COMB_TUNINGS_44100.iter().enumerate() {
1118 self.comb_lengths[i] = ((base as f64 * ratio) as usize).min(MAX_COMB_SIZE - 1);
1119 }
1120
1121 for (i, &base) in ALLPASS_TUNINGS_44100.iter().enumerate() {
1122 self.allpass_lengths[i] = ((base as f64 * ratio) as usize).min(MAX_ALLPASS_SIZE - 1);
1123 }
1124
1125 self.stereo_spread = (Libm::<f64>::round(STEREO_SPREAD as f64 * ratio) as usize).max(1);
1129 }
1130
1131 #[inline]
1133 fn process_comb(
1134 buffer: &mut [f64],
1135 pos: &mut usize,
1136 filter_state: &mut f64,
1137 input: f64,
1138 length: usize,
1139 feedback: f64,
1140 damping: f64,
1141 ) -> f64 {
1142 let output = buffer[*pos];
1143
1144 *filter_state = output * (1.0 - damping) + *filter_state * damping;
1146
1147 buffer[*pos] = input + *filter_state * feedback;
1149
1150 *pos += 1;
1151 if *pos >= length {
1152 *pos = 0;
1153 }
1154
1155 output
1156 }
1157
1158 #[inline]
1160 fn process_allpass(buffer: &mut [f64], pos: &mut usize, input: f64, length: usize) -> f64 {
1161 const ALLPASS_FEEDBACK: f64 = 0.5;
1162
1163 let buffered = buffer[*pos];
1164 let output = -input + buffered;
1165
1166 buffer[*pos] = input + buffered * ALLPASS_FEEDBACK;
1167
1168 *pos += 1;
1169 if *pos >= length {
1170 *pos = 0;
1171 }
1172
1173 output
1174 }
1175}
1176
1177impl Default for Reverb {
1178 fn default() -> Self {
1179 Self::new(44100.0)
1180 }
1181}
1182
1183impl GraphModule for Reverb {
1184 fn port_spec(&self) -> &PortSpec {
1185 &self.spec
1186 }
1187
1188 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
1189 let input = sanitize_audio(inputs.get_or(0, 0.0));
1192 let size = inputs.get_or(1, 0.5).clamp(0.0, 1.0);
1193 let damping = inputs.get_or(2, 0.5).clamp(0.0, 1.0);
1194 let mix = inputs.get_or(3, 0.5).clamp(0.0, 1.0);
1195 let predelay_cv = inputs.get_or(4, 0.0).clamp(0.0, 1.0);
1196
1197 let room_scale = 0.28 + size * 0.7;
1199 let damp = damping * 0.4;
1200
1201 let predelay_samples =
1203 (predelay_cv * 0.1 * self.sample_rate).min(MAX_PREDELAY_SIZE as f64 - 1.0) as usize;
1204
1205 self.predelay_buffer[self.predelay_pos] = input;
1207 let predelay_read_pos = if self.predelay_pos >= predelay_samples {
1208 self.predelay_pos - predelay_samples
1209 } else {
1210 MAX_PREDELAY_SIZE - (predelay_samples - self.predelay_pos)
1211 };
1212 let predelayed = if predelay_samples > 0 {
1213 self.predelay_buffer[predelay_read_pos]
1214 } else {
1215 input
1216 };
1217 self.predelay_pos = (self.predelay_pos + 1) % MAX_PREDELAY_SIZE;
1218
1219 let mut comb_out_l = 0.0;
1221 let mut comb_out_r = 0.0;
1222
1223 for i in 0..8 {
1224 let length_l = self.comb_lengths[i];
1226 comb_out_l += Self::process_comb(
1227 &mut self.comb_buffers_l[i],
1228 &mut self.comb_pos_l[i],
1229 &mut self.comb_filter_state_l[i],
1230 predelayed,
1231 length_l,
1232 room_scale,
1233 damp,
1234 );
1235
1236 let length_r = (self.comb_lengths[i] + self.stereo_spread).min(MAX_COMB_SIZE - 1);
1238 comb_out_r += Self::process_comb(
1239 &mut self.comb_buffers_r[i],
1240 &mut self.comb_pos_r[i],
1241 &mut self.comb_filter_state_r[i],
1242 predelayed,
1243 length_r,
1244 room_scale,
1245 damp,
1246 );
1247 }
1248
1249 comb_out_l *= 0.125;
1251 comb_out_r *= 0.125;
1252
1253 let mut allpass_out_l = comb_out_l;
1255 let mut allpass_out_r = comb_out_r;
1256
1257 for i in 0..4 {
1258 let length_l = self.allpass_lengths[i];
1259 allpass_out_l = Self::process_allpass(
1260 &mut self.allpass_buffers_l[i],
1261 &mut self.allpass_pos_l[i],
1262 allpass_out_l,
1263 length_l,
1264 );
1265
1266 let length_r = (self.allpass_lengths[i] + self.stereo_spread).min(MAX_ALLPASS_SIZE - 1);
1267 allpass_out_r = Self::process_allpass(
1268 &mut self.allpass_buffers_r[i],
1269 &mut self.allpass_pos_r[i],
1270 allpass_out_r,
1271 length_r,
1272 );
1273 }
1274
1275 let left = input * (1.0 - mix) + allpass_out_l * mix;
1277 let right = input * (1.0 - mix) + allpass_out_r * mix;
1278
1279 outputs.set(10, left);
1280 outputs.set(11, right);
1281 }
1282
1283 fn reset(&mut self) {
1284 for buf in &mut self.comb_buffers_l {
1285 buf.iter_mut().for_each(|x| *x = 0.0);
1286 }
1287 for buf in &mut self.comb_buffers_r {
1288 buf.iter_mut().for_each(|x| *x = 0.0);
1289 }
1290 self.comb_pos_l = [0; 8];
1291 self.comb_pos_r = [0; 8];
1292 self.comb_filter_state_l = [0.0; 8];
1293 self.comb_filter_state_r = [0.0; 8];
1294
1295 for buf in &mut self.allpass_buffers_l {
1296 buf.iter_mut().for_each(|x| *x = 0.0);
1297 }
1298 for buf in &mut self.allpass_buffers_r {
1299 buf.iter_mut().for_each(|x| *x = 0.0);
1300 }
1301 self.allpass_pos_l = [0; 4];
1302 self.allpass_pos_r = [0; 4];
1303
1304 self.predelay_buffer.iter_mut().for_each(|x| *x = 0.0);
1305 self.predelay_pos = 0;
1306 }
1307
1308 fn set_sample_rate(&mut self, sample_rate: f64) {
1309 self.sample_rate = sample_rate;
1310 self.update_tunings();
1311 self.reset();
1312 }
1313
1314 fn type_id(&self) -> &'static str {
1315 "reverb"
1316 }
1317}
1318
1319#[cfg(test)]
1324mod tests {
1325 use super::*;
1326
1327 #[test]
1328 fn test_unit_delay() {
1329 let mut delay = UnitDelay::new();
1330 let mut inputs = PortValues::new();
1331 let mut outputs = PortValues::new();
1332
1333 inputs.set(0, 1.0);
1335 delay.tick(&inputs, &mut outputs);
1336 assert!((outputs.get(10).unwrap() - 0.0).abs() < 0.01); inputs.set(0, 2.0);
1340 delay.tick(&inputs, &mut outputs);
1341 assert!((outputs.get(10).unwrap() - 1.0).abs() < 0.01); }
1343 #[test]
1344 fn test_delay_line() {
1345 let mut delay = DelayLine::new(44100.0);
1346 let mut inputs = PortValues::new();
1347 let mut outputs = PortValues::new();
1348
1349 inputs.set(1, 0.0); inputs.set(2, 0.0); inputs.set(3, 1.0); inputs.set(0, 1.0);
1356 delay.tick(&inputs, &mut outputs);
1357
1358 let first_out = outputs.get(10).unwrap();
1360 assert!(first_out.abs() < 0.1);
1361
1362 inputs.set(0, 0.0);
1364 for _ in 0..100 {
1365 delay.tick(&inputs, &mut outputs);
1366 }
1367
1368 let out = outputs.get(10).unwrap();
1370 assert!(out.is_finite());
1371 }
1372 #[test]
1373 fn test_delay_line_feedback() {
1374 let mut delay = DelayLine::new(44100.0);
1375 let mut inputs = PortValues::new();
1376 let mut outputs = PortValues::new();
1377
1378 inputs.set(1, 0.0); inputs.set(2, 0.5); inputs.set(3, 0.5); inputs.set(0, 1.0);
1385 delay.tick(&inputs, &mut outputs);
1386
1387 inputs.set(0, 0.0);
1389 for _ in 0..1000 {
1390 delay.tick(&inputs, &mut outputs);
1391 }
1392
1393 let out = outputs.get(10).unwrap();
1395 assert!(out.is_finite());
1396 }
1397 #[test]
1398 fn test_delay_line_reset() {
1399 let mut delay = DelayLine::new(44100.0);
1400 let mut inputs = PortValues::new();
1401 let mut outputs = PortValues::new();
1402
1403 inputs.set(0, 1.0);
1405 for _ in 0..100 {
1406 delay.tick(&inputs, &mut outputs);
1407 }
1408
1409 delay.reset();
1411
1412 inputs.set(0, 0.0);
1414 inputs.set(3, 1.0); delay.tick(&inputs, &mut outputs);
1416 let out = outputs.get(10).unwrap();
1417 assert!(out.abs() < 0.01);
1418 }
1419 #[test]
1420 fn test_delay_line_with_max_delay_sizes_buffer() {
1421 assert_eq!(DelayLine::new(1000.0).buffer.len(), 2001);
1423 assert_eq!(DelayLine::with_max_delay(1000.0, 8.0).buffer.len(), 8001);
1425 let mut long = DelayLine::with_max_delay(1000.0, 8.0);
1427 long.set_sample_rate(2000.0);
1428 assert_eq!(long.buffer.len(), 16001);
1429 }
1430
1431 #[test]
1432 fn test_delay_line_long_linear_delay_echoes() {
1433 let sr = 1000.0;
1435 let mut delay = DelayLine::with_max_delay(sr, 8.0).with_linear_time();
1436 let mut inputs = PortValues::new();
1437 let mut outputs = PortValues::new();
1438
1439 inputs.set(1, 3.0); inputs.set(2, 0.0); inputs.set(3, 1.0); inputs.set(0, 1.0);
1444 delay.tick(&inputs, &mut outputs);
1445 inputs.set(0, 0.0);
1446
1447 let mut peak_at = 0;
1448 let mut peak = 0.0_f64;
1449 for n in 1..3100 {
1450 delay.tick(&inputs, &mut outputs);
1451 let out = outputs.get(10).unwrap().abs();
1452 if out > peak {
1453 peak = out;
1454 peak_at = n;
1455 }
1456 }
1457 assert!(peak > 0.5, "echo should emerge, peak={peak}");
1458 assert!(
1459 (2990..=3010).contains(&peak_at),
1460 "echo should land ~3000 samples later, landed at {peak_at}"
1461 );
1462 }
1463
1464 #[test]
1465 fn test_delay_line_feedback_clamped_by_default() {
1466 let mut delay = DelayLine::new(1000.0);
1468 let mut inputs = PortValues::new();
1469 let mut outputs = PortValues::new();
1470
1471 inputs.set(1, 0.0); inputs.set(2, 1.2); inputs.set(3, 1.0); inputs.set(0, 1.0);
1476 delay.tick(&inputs, &mut outputs);
1477 inputs.set(0, 0.0);
1478
1479 let mut late_peak = 0.0_f64;
1480 for n in 0..4000 {
1481 delay.tick(&inputs, &mut outputs);
1482 if n >= 3900 {
1483 late_peak = late_peak.max(outputs.get(10).unwrap().abs());
1484 }
1485 }
1486 assert!(
1487 late_peak < 1e-3,
1488 "clamped feedback must decay, got {late_peak}"
1489 );
1490 }
1491
1492 #[test]
1493 fn test_delay_line_unclamped_feedback_self_oscillates_bounded() {
1494 let mut delay = DelayLine::new(1000.0).with_unclamped_feedback();
1497 let mut inputs = PortValues::new();
1498 let mut outputs = PortValues::new();
1499
1500 inputs.set(1, 0.0); inputs.set(2, 1.2); inputs.set(3, 1.0); inputs.set(0, 1.0);
1505 delay.tick(&inputs, &mut outputs);
1506 inputs.set(0, 0.0);
1507
1508 let mut late_peak = 0.0_f64;
1509 let mut overall_peak = 0.0_f64;
1510 for n in 0..4000 {
1511 delay.tick(&inputs, &mut outputs);
1512 let out = outputs.get(10).unwrap().abs();
1513 overall_peak = overall_peak.max(out);
1514 if n >= 3900 {
1515 late_peak = late_peak.max(out);
1516 }
1517 }
1518 assert!(
1519 late_peak > 0.5,
1520 "unclamped feedback must sustain, got {late_peak}"
1521 );
1522 assert!(
1523 overall_peak <= 5.0 + 1e-9,
1524 "saturation must bound the loop at the ±5V rail, got {overall_peak}"
1525 );
1526 }
1527
1528 #[test]
1529 fn test_delay_line_linear_time_preserves_slew() {
1530 let sr = 1000.0;
1533 let mut delay = DelayLine::with_max_delay(sr, 8.0).with_linear_time();
1534 let mut inputs = PortValues::new();
1535 let mut outputs = PortValues::new();
1536
1537 inputs.set(1, 1.0); inputs.set(0, 0.0);
1539 delay.tick(&inputs, &mut outputs);
1540 assert!((delay.smoothed_delay - 1000.0).abs() < 1e-6);
1542
1543 inputs.set(1, 2.0); delay.tick(&inputs, &mut outputs);
1545 assert!(
1549 delay.smoothed_delay > 1000.0 && delay.smoothed_delay < 1500.0,
1550 "time step must glide, smoothed_delay={}",
1551 delay.smoothed_delay
1552 );
1553 }
1554
1555 #[test]
1556 fn test_tape_delay_preset() {
1557 let tape = DelayLine::tape(1000.0);
1558 assert_eq!(tape.type_id(), "tape_delay");
1559 assert_eq!(tape.buffer.len(), 12001);
1560 assert!(tape.unclamped_feedback);
1561 assert!(tape.linear_time);
1562 assert_eq!(DelayLine::new(1000.0).type_id(), "delay_line");
1564 }
1565
1566 #[test]
1567 fn test_chorus() {
1568 let mut chorus = Chorus::new(44100.0);
1569 let mut inputs = PortValues::new();
1570 let mut outputs = PortValues::new();
1571
1572 inputs.set(0, 0.5); for _ in 0..1000 {
1577 chorus.tick(&inputs, &mut outputs);
1578 }
1579
1580 let mono = outputs.get(10).unwrap();
1582 let left = outputs.get(11).unwrap();
1583 let right = outputs.get(12).unwrap();
1584
1585 assert!(mono.is_finite());
1586 assert!(left.is_finite());
1587 assert!(right.is_finite());
1588 }
1589 #[test]
1590 fn test_chorus_stereo_spread() {
1591 let mut chorus = Chorus::new(44100.0);
1592 let mut inputs = PortValues::new();
1593 let mut outputs = PortValues::new();
1594
1595 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;
1603 let mut right_sum = 0.0;
1604 for _ in 0..10000 {
1605 chorus.tick(&inputs, &mut outputs);
1606 left_sum += outputs.get(11).unwrap().abs();
1607 right_sum += outputs.get(12).unwrap().abs();
1608 }
1609
1610 assert!(left_sum > 1.0);
1612 assert!(right_sum > 1.0);
1613 }
1614 #[test]
1615 fn test_chorus_reset() {
1616 let mut chorus = Chorus::new(44100.0);
1617 let mut inputs = PortValues::new();
1618 let mut outputs = PortValues::new();
1619
1620 inputs.set(0, 1.0);
1622 for _ in 0..1000 {
1623 chorus.tick(&inputs, &mut outputs);
1624 }
1625
1626 chorus.reset();
1628
1629 inputs.set(0, 0.0);
1631 inputs.set(3, 1.0); chorus.tick(&inputs, &mut outputs);
1633
1634 let out = outputs.get(10).unwrap();
1636 assert!(out.abs() < 0.1);
1637 }
1638 #[test]
1639 fn test_delay_line_type_id() {
1640 let delay = DelayLine::new(44100.0);
1641 assert_eq!(delay.type_id(), "delay_line");
1642 }
1643 #[test]
1644 fn test_chorus_type_id() {
1645 let chorus = Chorus::new(44100.0);
1646 assert_eq!(chorus.type_id(), "chorus");
1647 }
1648 #[test]
1649 fn test_delay_line_default() {
1650 let delay = DelayLine::default();
1651 assert_eq!(delay.type_id(), "delay_line");
1652 }
1653 #[test]
1654 fn test_chorus_default() {
1655 let chorus = Chorus::default();
1656 assert_eq!(chorus.type_id(), "chorus");
1657 }
1658 #[test]
1659 fn test_flanger() {
1660 let mut flanger = Flanger::new(44100.0);
1661 let mut inputs = PortValues::new();
1662 let mut outputs = PortValues::new();
1663
1664 inputs.set(0, 1.0);
1665 for _ in 0..1000 {
1666 flanger.tick(&inputs, &mut outputs);
1667 }
1668
1669 let out = outputs.get(10).unwrap();
1670 assert!(out.is_finite());
1671 }
1672 #[test]
1673 fn test_flanger_default() {
1674 let flanger = Flanger::default();
1675 assert_eq!(flanger.type_id(), "flanger");
1676 }
1677 #[test]
1678 fn test_phaser() {
1679 let mut phaser = Phaser::new(44100.0);
1680 let mut inputs = PortValues::new();
1681 let mut outputs = PortValues::new();
1682
1683 inputs.set(0, 1.0);
1684 for _ in 0..1000 {
1685 phaser.tick(&inputs, &mut outputs);
1686 }
1687
1688 let out = outputs.get(10).unwrap();
1689 assert!(out.is_finite());
1690 }
1691 #[test]
1692 fn test_phaser_default() {
1693 let phaser = Phaser::default();
1694 assert_eq!(phaser.type_id(), "phaser");
1695 }
1696 #[test]
1697 fn test_phaser_stages() {
1698 let mut phaser = Phaser::new(44100.0);
1699 let mut inputs = PortValues::new();
1700 let mut outputs = PortValues::new();
1701
1702 inputs.set(0, 1.0);
1703 inputs.set(5, 0.0); for _ in 0..100 {
1706 phaser.tick(&inputs, &mut outputs);
1707 }
1708 let out_2 = outputs.get(10).unwrap();
1709
1710 phaser.reset();
1711 inputs.set(5, 1.0); for _ in 0..100 {
1714 phaser.tick(&inputs, &mut outputs);
1715 }
1716 let out_6 = outputs.get(10).unwrap();
1717
1718 assert!(out_2.is_finite());
1720 assert!(out_6.is_finite());
1721 }
1722
1723 #[test]
1729 fn test_flanger_out_mirrors_left_and_mono_at_zero_spread() {
1730 let mut flanger = Flanger::new(44100.0);
1731 let mut inputs = PortValues::new();
1732 let mut outputs = PortValues::new();
1733
1734 inputs.set(2, 0.8); inputs.set(4, 1.0); inputs.set(5, 0.0); for k in 0..5000 {
1739 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.03));
1740 flanger.tick(&inputs, &mut outputs);
1741 let out = outputs.get(10).unwrap();
1742 let left = outputs.get(11).unwrap();
1743 let right = outputs.get(12).unwrap();
1744 assert_eq!(out, left, "out must equal left");
1745 assert_eq!(left, right, "spread=0 must give bit-identical L/R");
1746 }
1747 }
1748
1749 #[test]
1750 fn test_flanger_stereo_decorrelation() {
1751 let mut flanger = Flanger::new(44100.0);
1752 let mut inputs = PortValues::new();
1753 let mut outputs = PortValues::new();
1754
1755 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;
1761 for k in 0..20000 {
1762 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.05));
1763 flanger.tick(&inputs, &mut outputs);
1764 assert_eq!(outputs.get(10).unwrap(), outputs.get(11).unwrap());
1766 let left = outputs.get(11).unwrap();
1767 let right = outputs.get(12).unwrap();
1768 diff += (left - right).abs();
1769 }
1770 assert!(
1771 diff > 1.0,
1772 "left/right should decorrelate with spread; diff = {diff}"
1773 );
1774 }
1775
1776 #[test]
1777 fn test_phaser_out_mirrors_left_and_mono_at_zero_spread() {
1778 let mut phaser = Phaser::new(44100.0);
1779 let mut inputs = PortValues::new();
1780 let mut outputs = PortValues::new();
1781
1782 inputs.set(2, 0.8); inputs.set(4, 1.0); inputs.set(6, 0.0); for k in 0..5000 {
1787 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.03));
1788 phaser.tick(&inputs, &mut outputs);
1789 let out = outputs.get(10).unwrap();
1790 let left = outputs.get(11).unwrap();
1791 let right = outputs.get(12).unwrap();
1792 assert_eq!(out, left, "out must equal left");
1793 assert_eq!(left, right, "spread=0 must give bit-identical L/R");
1794 }
1795 }
1796
1797 #[test]
1798 fn test_phaser_stereo_decorrelation() {
1799 let mut phaser = Phaser::new(44100.0);
1800 let mut inputs = PortValues::new();
1801 let mut outputs = PortValues::new();
1802
1803 inputs.set(2, 0.9); inputs.set(4, 1.0); inputs.set(6, 1.0); let mut diff = 0.0;
1808 for k in 0..20000 {
1809 inputs.set(0, Libm::<f64>::sin(k as f64 * 0.07));
1810 phaser.tick(&inputs, &mut outputs);
1811 assert_eq!(outputs.get(10).unwrap(), outputs.get(11).unwrap());
1812 let left = outputs.get(11).unwrap();
1813 let right = outputs.get(12).unwrap();
1814 diff += (left - right).abs();
1815 }
1816 assert!(
1817 diff > 1.0,
1818 "phaser left/right should decorrelate with spread; diff = {diff}"
1819 );
1820 }
1821
1822 #[test]
1823 fn test_unit_delay_default_reset_sample_rate() {
1824 let mut delay = UnitDelay::default();
1825 let mut inputs = PortValues::new();
1826 let mut outputs = PortValues::new();
1827 inputs.set(0, 5.0);
1828 delay.tick(&inputs, &mut outputs);
1829
1830 delay.reset();
1831 assert!(delay.buffer == 0.0);
1832
1833 delay.set_sample_rate(48000.0);
1834 assert_eq!(delay.type_id(), "unit_delay");
1835 }
1836 #[test]
1837 fn test_reverb_default_reset_sample_rate() {
1838 let mut reverb = Reverb::default();
1839 assert_eq!(reverb.sample_rate, 44100.0);
1840
1841 let mut inputs = PortValues::new();
1843 let mut outputs = PortValues::new();
1844 inputs.set(0, 0.5);
1845 reverb.tick(&inputs, &mut outputs);
1846
1847 reverb.reset();
1849 assert_eq!(reverb.predelay_pos, 0);
1850 assert_eq!(reverb.comb_pos_l, [0; 8]);
1851 assert_eq!(reverb.comb_pos_r, [0; 8]);
1852 assert_eq!(reverb.allpass_pos_l, [0; 4]);
1853 assert_eq!(reverb.allpass_pos_r, [0; 4]);
1854
1855 reverb.set_sample_rate(48000.0);
1857 assert_eq!(reverb.sample_rate, 48000.0);
1858
1859 assert_eq!(reverb.type_id(), "reverb");
1860 assert_eq!(reverb.port_spec().inputs.len(), 5);
1861 assert_eq!(reverb.port_spec().outputs.len(), 2);
1862 }
1863 #[test]
1864 fn test_reverb_stereo_output() {
1865 let mut reverb = Reverb::new(44100.0);
1866 let mut inputs = PortValues::new();
1867 let mut outputs = PortValues::new();
1868
1869 inputs.set(0, 1.0);
1871 inputs.set(3, 1.0); reverb.tick(&inputs, &mut outputs);
1873
1874 inputs.set(0, 0.0);
1876 let mut total_energy = 0.0;
1877 for _ in 0..3000 {
1878 reverb.tick(&inputs, &mut outputs);
1879 total_energy += outputs.get(10).unwrap().abs();
1880 total_energy += outputs.get(11).unwrap().abs();
1881 }
1882
1883 assert!(
1885 total_energy > 0.01,
1886 "Reverb should produce output after impulse, got total_energy={}",
1887 total_energy
1888 );
1889 }
1890 #[test]
1891 fn test_reverb_dry_signal() {
1892 let mut reverb = Reverb::new(44100.0);
1893 let mut inputs = PortValues::new();
1894 let mut outputs = PortValues::new();
1895
1896 inputs.set(0, 0.75);
1898 inputs.set(3, 0.0); reverb.tick(&inputs, &mut outputs);
1900
1901 let left = outputs.get(10).unwrap();
1902 let right = outputs.get(11).unwrap();
1903
1904 assert!(
1906 (left - 0.75).abs() < 0.001,
1907 "Full dry should pass through: got {}",
1908 left
1909 );
1910 assert!(
1911 (right - 0.75).abs() < 0.001,
1912 "Full dry should pass through: got {}",
1913 right
1914 );
1915 }
1916 #[test]
1917 fn test_reverb_room_size() {
1918 let mut reverb1 = Reverb::new(44100.0);
1919 let mut reverb2 = Reverb::new(44100.0);
1920 let mut inputs1 = PortValues::new();
1921 let mut inputs2 = PortValues::new();
1922 let mut outputs1 = PortValues::new();
1923 let mut outputs2 = PortValues::new();
1924
1925 inputs1.set(0, 1.0);
1927 inputs1.set(1, 0.1); inputs1.set(3, 1.0); reverb1.tick(&inputs1, &mut outputs1);
1930
1931 inputs2.set(0, 1.0);
1932 inputs2.set(1, 0.9); inputs2.set(3, 1.0); reverb2.tick(&inputs2, &mut outputs2);
1935
1936 inputs1.set(0, 0.0);
1938 inputs2.set(0, 0.0);
1939 let mut energy1 = 0.0;
1940 let mut energy2 = 0.0;
1941
1942 for _ in 0..5000 {
1943 reverb1.tick(&inputs1, &mut outputs1);
1944 reverb2.tick(&inputs2, &mut outputs2);
1945 energy1 += outputs1.get(10).unwrap().abs();
1946 energy2 += outputs2.get(10).unwrap().abs();
1947 }
1948
1949 assert!(
1951 energy2 > energy1,
1952 "Larger room should have longer decay: small={}, large={}",
1953 energy1,
1954 energy2
1955 );
1956 }
1957 #[test]
1958 fn test_reverb_predelay() {
1959 let mut reverb = Reverb::new(44100.0);
1960 let mut inputs = PortValues::new();
1961 let mut outputs = PortValues::new();
1962
1963 inputs.set(0, 1.0); inputs.set(3, 1.0); inputs.set(4, 1.0); reverb.tick(&inputs, &mut outputs);
1970
1971 let first_output = outputs.get(10).unwrap();
1973
1974 inputs.set(0, 0.0);
1976 let mut total_energy = 0.0;
1977
1978 for _ in 0..6000 {
1980 reverb.tick(&inputs, &mut outputs);
1981 total_energy += outputs.get(10).unwrap().abs();
1982 }
1983
1984 assert!(
1985 total_energy > 0.01,
1986 "Reverb should appear after predelay period, got energy={}",
1987 total_energy
1988 );
1989 assert!(
1990 first_output.abs() < 0.001,
1991 "First sample should be near zero due to predelay, got {}",
1992 first_output
1993 );
1994 }
1995 #[test]
1996 fn test_reverb_damping() {
1997 let mut reverb_low = Reverb::new(44100.0);
1998 let mut reverb_high = Reverb::new(44100.0);
1999 let mut inputs = PortValues::new();
2000 let mut outputs_low = PortValues::new();
2001 let mut outputs_high = PortValues::new();
2002
2003 inputs.set(0, 1.0);
2005 inputs.set(2, 0.1); inputs.set(3, 1.0);
2007 reverb_low.tick(&inputs, &mut outputs_low);
2008
2009 inputs.set(2, 0.9); reverb_high.tick(&inputs, &mut outputs_high);
2011
2012 inputs.set(0, 0.0);
2014 for _ in 0..3000 {
2015 reverb_low.tick(&inputs, &mut outputs_low);
2016 reverb_high.tick(&inputs, &mut outputs_high);
2017 }
2018
2019 let out_low = outputs_low.get(10).unwrap();
2022 let out_high = outputs_high.get(10).unwrap();
2023
2024 assert!(out_low.is_finite());
2026 assert!(out_high.is_finite());
2027 }
2028 #[test]
2029 fn test_reverb_tunings_scale_with_sample_rate() {
2030 let reverb_44 = Reverb::new(44100.0);
2031 let reverb_48 = Reverb::new(48000.0);
2032
2033 let ratio = 48000.0 / 44100.0;
2035
2036 for i in 0..8 {
2037 let expected = (reverb_44.comb_lengths[i] as f64 * ratio) as usize;
2038 assert!(
2039 (reverb_48.comb_lengths[i] as i64 - expected as i64).abs() < 2,
2040 "Comb filter {} should scale with sample rate",
2041 i
2042 );
2043 }
2044 }
2045
2046 #[cfg(test)]
2049 fn allpass_rms_gain(coef: f64, freq_norm: f64) -> f64 {
2050 let mut x1 = 0.0;
2051 let mut y1 = 0.0;
2052 let n = 40_000;
2053 let warmup = 8_000;
2054 let mut sum_in = 0.0;
2055 let mut sum_out = 0.0;
2056 for i in 0..n {
2057 let x = Libm::<f64>::sin(TAU * freq_norm * i as f64);
2058 let y = Phaser::allpass(x, &mut x1, &mut y1, coef);
2059 if i >= warmup {
2060 sum_in += x * x;
2061 sum_out += y * y;
2062 }
2063 }
2064 Libm::<f64>::sqrt(sum_out / sum_in)
2065 }
2066
2067 #[test]
2068 fn test_phaser_allpass_unit_magnitude() {
2069 for &coef in &[-0.6, -0.2, 0.2, 0.5, 0.8] {
2074 let dc_gain = allpass_rms_gain(coef, 0.001);
2075 let nyq_gain = allpass_rms_gain(coef, 0.499);
2076 assert!(
2077 (dc_gain - 1.0).abs() < 0.01,
2078 "DC gain {} not ~1.0 for coef {}",
2079 dc_gain,
2080 coef
2081 );
2082 assert!(
2083 (nyq_gain - 1.0).abs() < 0.01,
2084 "Nyquist gain {} not ~1.0 for coef {}",
2085 nyq_gain,
2086 coef
2087 );
2088 }
2089 }
2090
2091 #[test]
2092 fn test_chorus_delay_stays_positive() {
2093 let sample_rate = 44100.0;
2097 let base = Chorus::BASE_DELAY_MS * sample_rate / 1000.0;
2098 let mod_depth = Chorus::MAX_MOD_DELAY_MS * sample_rate / 1000.0;
2099
2100 let steps = 2000;
2101 let mut min_delay = f64::INFINITY;
2102 for k in 0..steps {
2103 let lfo = Libm::<f64>::sin((k as f64 / steps as f64) * TAU);
2104 let delay = Chorus::voice_delay_samples(base, mod_depth, lfo);
2105 min_delay = min_delay.min(delay);
2106 }
2107 assert!(
2108 min_delay > 1.0,
2109 "minimum chorus delay {} hit the clamp floor",
2110 min_delay
2111 );
2112 let trough = Chorus::voice_delay_samples(base, mod_depth, -1.0);
2114 assert!(
2115 (trough - base).abs() < 1e-9,
2116 "trough {} != base {}",
2117 trough,
2118 base
2119 );
2120 }
2121
2122 #[test]
2123 fn test_delay_line_time_smoothing() {
2124 let mut delay = DelayLine::new(44100.0);
2127 let mut inputs = PortValues::new();
2128 let mut outputs = PortValues::new();
2129
2130 inputs.set(0, 0.0);
2131 inputs.set(1, 0.0); inputs.set(2, 0.0);
2133 inputs.set(3, 0.5);
2134 delay.tick(&inputs, &mut outputs); let start = delay.smoothed_delay;
2136
2137 inputs.set(1, 1.0);
2139 delay.tick(&inputs, &mut outputs);
2140 let after_one = delay.smoothed_delay;
2141
2142 let full_jump = (delay.buffer.len() - 1) as f64 - start;
2143 let moved = after_one - start;
2144 assert!(moved > 0.0, "smoother did not move toward setpoint");
2145 assert!(
2146 moved < full_jump * 0.05,
2147 "smoother jumped {} of a {}-sample step in one tick",
2148 moved,
2149 full_jump
2150 );
2151
2152 let mut ticks = 1;
2154 while delay.smoothed_delay < start + full_jump * 0.9 && ticks < 100_000 {
2155 delay.tick(&inputs, &mut outputs);
2156 ticks += 1;
2157 }
2158 assert!(
2159 ticks > 100,
2160 "smoother converged too fast in {} ticks",
2161 ticks
2162 );
2163 }
2164
2165 #[test]
2166 fn test_vibrato_exact_delay() {
2167 let sample_rate = 44100.0;
2171 let mut vib = Vibrato::new(sample_rate);
2172 let mut inputs = PortValues::new();
2173 let mut outputs = PortValues::new();
2174
2175 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;
2180
2181 inputs.set(0, 1.0); vib.tick(&inputs, &mut outputs);
2183 let mut peak_idx = if outputs.get(10).unwrap().abs() > 0.5 {
2184 Some(0usize)
2185 } else {
2186 None
2187 };
2188
2189 inputs.set(0, 0.0);
2190 for i in 1..(expected + 50) {
2191 vib.tick(&inputs, &mut outputs);
2192 if peak_idx.is_none() && outputs.get(10).unwrap().abs() > 0.5 {
2193 peak_idx = Some(i);
2194 }
2195 }
2196 assert_eq!(
2197 peak_idx,
2198 Some(expected),
2199 "impulse emerged at {:?}, expected {}",
2200 peak_idx,
2201 expected
2202 );
2203 }
2204
2205 #[test]
2206 fn test_reverb_stereo_spread_scales_with_sample_rate() {
2207 let reverb_44 = Reverb::new(44100.0);
2210 assert_eq!(reverb_44.stereo_spread, STEREO_SPREAD);
2211
2212 let reverb_88 = Reverb::new(88200.0);
2213 assert_eq!(reverb_88.stereo_spread, 46);
2215
2216 for i in 0..8 {
2217 let length_l = reverb_88.comb_lengths[i];
2218 let length_r = (length_l + reverb_88.stereo_spread).min(MAX_COMB_SIZE - 1);
2219 assert_eq!(
2220 length_r - length_l,
2221 46,
2222 "right comb {} should lead left by the scaled spread",
2223 i
2224 );
2225 }
2226 }
2227
2228 #[test]
2231 fn test_tremolo_am_depth() {
2232 let mut trem = Tremolo::new(44100.0);
2236 let mut inputs = PortValues::new();
2237 let mut outputs = PortValues::new();
2238 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);
2244 for _ in 0..44_100 {
2245 trem.tick(&inputs, &mut outputs);
2246 let o = outputs.get(10).unwrap();
2247 assert!(o.is_finite());
2248 lo = lo.min(o);
2249 hi = hi.max(o);
2250 }
2251 assert!(
2252 lo < 0.1 && hi > 0.9,
2253 "full-depth AM must reach near 0 and near the carrier: lo={lo} hi={hi}"
2254 );
2255
2256 trem.reset();
2257 inputs.set(2, 0.0); let (mut lo0, mut hi0) = (f64::INFINITY, f64::NEG_INFINITY);
2259 for _ in 0..4410 {
2260 trem.tick(&inputs, &mut outputs);
2261 let o = outputs.get(10).unwrap();
2262 lo0 = lo0.min(o);
2263 hi0 = hi0.max(o);
2264 }
2265 assert!(
2266 (hi0 - lo0) < 1e-9 && (hi0 - 1.0).abs() < 1e-9,
2267 "zero-depth tremolo must pass the carrier unchanged: span={}",
2268 hi0 - lo0
2269 );
2270 }
2271
2272 #[test]
2273 fn test_tremolo_reset_and_sample_rate() {
2274 let mut trem = Tremolo::default();
2275 assert_eq!(trem.type_id(), "tremolo");
2276 assert_eq!(trem.sample_rate, 44100.0);
2277 let mut inputs = PortValues::new();
2278 let mut outputs = PortValues::new();
2279 inputs.set(0, 1.0);
2280 inputs.set(1, 0.5);
2281 for _ in 0..500 {
2282 trem.tick(&inputs, &mut outputs);
2283 }
2284 assert!(trem.lfo_phase != 0.0);
2285 trem.reset();
2286 assert_eq!(trem.lfo_phase, 0.0);
2287 trem.set_sample_rate(48000.0);
2288 assert_eq!(trem.sample_rate, 48000.0);
2289 trem.tick(&inputs, &mut outputs);
2290 assert!(outputs.get(10).unwrap().is_finite());
2291 }
2292
2293 fn zero_crossing_interval_spread(sig: &[f64]) -> f64 {
2298 let mut crossings = Vec::new();
2299 for i in 1..sig.len() {
2300 if sig[i - 1] <= 0.0 && sig[i] > 0.0 {
2301 crossings.push(i);
2302 }
2303 }
2304 if crossings.len() < 3 {
2305 return 0.0;
2306 }
2307 let mut min_iv = f64::INFINITY;
2308 let mut max_iv = f64::NEG_INFINITY;
2309 for w in crossings.windows(2) {
2310 let iv = (w[1] - w[0]) as f64;
2311 min_iv = min_iv.min(iv);
2312 max_iv = max_iv.max(iv);
2313 }
2314 max_iv - min_iv
2315 }
2316
2317 #[test]
2318 fn test_vibrato_pitch_modulation_depth() {
2319 let run = |depth: f64| -> f64 {
2323 let mut vib = Vibrato::new(44100.0);
2324 let mut inputs = PortValues::new();
2325 let mut outputs = PortValues::new();
2326 inputs.set(1, 0.78); inputs.set(2, depth);
2328 inputs.set(3, 1.0); let mut out = Vec::with_capacity(20_000);
2330 let dt = 500.0 / 44100.0;
2331 let mut phase = 0.0f64;
2332 for _ in 0..20_000 {
2333 let s = Libm::<f64>::sin(TAU * phase);
2334 phase += dt;
2335 if phase >= 1.0 {
2336 phase -= 1.0;
2337 }
2338 inputs.set(0, s);
2339 vib.tick(&inputs, &mut outputs);
2340 out.push(outputs.get(10).unwrap());
2341 }
2342 zero_crossing_interval_spread(&out)
2343 };
2344
2345 let spread_off = run(0.0);
2346 let spread_on = run(0.8);
2347 assert!(
2348 spread_off < 3.0,
2349 "zero-depth vibrato should have near-constant pitch: spread={spread_off}"
2350 );
2351 assert!(
2352 spread_on > spread_off + 10.0,
2353 "depth-0.8 vibrato must wobble the pitch: on={spread_on} off={spread_off}"
2354 );
2355 }
2356
2357 #[test]
2358 fn test_vibrato_reset_and_sample_rate() {
2359 let mut vib = Vibrato::default();
2360 assert_eq!(vib.type_id(), "vibrato");
2361 let mut inputs = PortValues::new();
2362 let mut outputs = PortValues::new();
2363 inputs.set(0, 1.0);
2364 inputs.set(2, 0.5);
2365 for _ in 0..500 {
2366 vib.tick(&inputs, &mut outputs);
2367 }
2368 assert!(vib.lfo_phase != 0.0);
2369 vib.reset();
2370 assert_eq!(vib.lfo_phase, 0.0);
2371 assert_eq!(vib.write_pos, 0);
2372 assert!(vib.buffer.iter().all(|&x| x == 0.0));
2373 vib.set_sample_rate(48000.0);
2374 assert_eq!(vib.sample_rate, 48000.0);
2375 vib.tick(&inputs, &mut outputs);
2376 assert!(outputs.get(10).unwrap().is_finite());
2377 }
2378
2379 #[test]
2380 fn test_vibrato_lowering_sample_rate_does_not_panic() {
2381 let mut vib = Vibrato::new(96000.0);
2387 let mut inputs = PortValues::new();
2388 let mut outputs = PortValues::new();
2389 inputs.set(0, 0.5);
2390 inputs.set(2, 0.5);
2391 for _ in 0..1000 {
2393 vib.tick(&inputs, &mut outputs);
2394 }
2395 vib.set_sample_rate(22050.0);
2397 assert_eq!(vib.write_pos, 0, "write_pos must be reset after resize");
2398 vib.tick(&inputs, &mut outputs);
2400 assert!(outputs.get(10).unwrap().is_finite());
2401 }
2402
2403 #[test]
2412 fn test_phaser_memo_bit_identical() {
2413 let mut memoized = Phaser::new(44100.0);
2414 let mut forced = Phaser::new(44100.0);
2415 let mut inputs = PortValues::new();
2416 let mut out_m = PortValues::new();
2417 let mut out_f = PortValues::new();
2418
2419 for n in 0..20_000u32 {
2420 let t = n as f64;
2421 inputs.set(0, Libm::<f64>::sin(t * 0.043) * 3.0);
2422 inputs.set(3, 0.4); inputs.set(6, 0.5); if n < 10_000 {
2425 inputs.set(1, 0.3);
2427 inputs.set(2, 0.0);
2428 } else {
2429 inputs.set(1, 0.3 + 0.2 * Libm::<f64>::sin(t * 0.001));
2431 inputs.set(2, 0.7);
2432 }
2433
2434 memoized.tick(&inputs, &mut out_m);
2435 forced.rate_memo.invalidate();
2436 forced.coef_memo.invalidate();
2437 forced.tick(&inputs, &mut out_f);
2438
2439 for &id in &[10u32, 11, 12] {
2440 assert_eq!(
2441 out_m.get(id).unwrap().to_bits(),
2442 out_f.get(id).unwrap().to_bits(),
2443 "Phaser output {id} diverged at sample {n}"
2444 );
2445 }
2446 }
2447 assert!(memoized.rate_memo.recompute_count() <= 10_001);
2450 }
2451
2452 #[test]
2455 fn test_delay_line_memo_bit_identical() {
2456 let mut memoized = DelayLine::new(44100.0);
2457 let mut forced = DelayLine::new(44100.0);
2458 let mut inputs = PortValues::new();
2459 let mut out_m = PortValues::new();
2460 let mut out_f = PortValues::new();
2461
2462 for n in 0..20_000u32 {
2463 let t = n as f64;
2464 inputs.set(0, Libm::<f64>::sin(t * 0.029) * 4.0);
2465 inputs.set(2, 0.6); inputs.set(3, 0.5); if n < 10_000 {
2468 inputs.set(1, 0.4);
2469 } else {
2470 inputs.set(1, 0.4 + 0.1 * Libm::<f64>::sin(t * 0.0007));
2472 }
2473
2474 memoized.tick(&inputs, &mut out_m);
2475 forced.delay_ms_memo.invalidate();
2476 forced.tick(&inputs, &mut out_f);
2477
2478 assert_eq!(
2479 out_m.get(10).unwrap().to_bits(),
2480 out_f.get(10).unwrap().to_bits(),
2481 "DelayLine output diverged at sample {n}"
2482 );
2483 }
2484 assert!(memoized.delay_ms_memo.recompute_count() <= 10_001);
2485 }
2486}