1#![forbid(unsafe_code)]
79#![warn(missing_docs)]
80
81pub mod analog_delay;
82pub mod auto_pan;
83pub mod barrel_lens;
84pub mod bass_enhancer;
85pub mod bitcrusher;
86pub mod blend;
87pub mod chorus;
88pub mod chorus_flanger;
89pub mod color_grade;
90pub mod composite;
91pub mod compressor;
92pub mod compressor_look;
93pub mod deesser;
94pub mod delay;
95pub mod delay_line;
96pub mod distort;
97pub mod distortion;
98pub mod ducking;
99pub mod dynamics;
100pub mod eq;
101pub mod filter;
102pub mod filter_bank;
103pub mod flanger;
104pub mod fundsp_adapter;
105pub mod glitch;
106pub mod harmonic_exciter;
107pub mod keying;
108pub mod lookahead_limiter;
109pub mod lufs_meter;
110pub mod luma_key;
111pub mod mix;
112pub mod modulation;
113pub mod multiband_compressor;
114pub mod parametric_eq;
115pub mod pitch;
116pub mod reverb;
117pub mod reverb_hall;
118pub mod ring_mod;
119pub mod room_reverb;
120pub mod saturation;
121pub mod spatial_audio;
122pub mod stereo_upmix;
123pub mod stereo_widener;
124pub mod stereo_wider;
125pub mod tape_echo;
126pub mod tape_sat;
127pub mod time_stretch;
128pub mod transient_shaper;
129pub mod tremolo;
130pub mod utils;
131pub mod vibrato;
132pub mod video;
133pub mod vocoder;
134pub mod warp;
135pub mod waveshaper;
136pub mod wet_dry;
137
138use thiserror::Error;
139
140#[derive(Debug, Error)]
142pub enum EffectError {
143 #[error("Invalid parameter: {0}")]
145 InvalidParameter(String),
146
147 #[error("Invalid sample rate: {0}")]
149 InvalidSampleRate(f32),
150
151 #[error("Buffer size mismatch: expected {expected}, got {actual}")]
153 BufferSizeMismatch {
154 expected: usize,
156 actual: usize,
158 },
159
160 #[error("Insufficient buffer size: need at least {required}, got {actual}")]
162 InsufficientBuffer {
163 required: usize,
165 actual: usize,
167 },
168
169 #[error("Effect not initialized")]
171 NotInitialized,
172
173 #[error("Processing error: {0}")]
175 ProcessingError(String),
176}
177
178pub type Result<T> = std::result::Result<T, EffectError>;
180
181pub trait AudioEffect {
194 const EFFECT_ID: &'static str;
199
200 fn process_sample(&mut self, input: f32) -> f32;
202
203 fn process(&mut self, buffer: &mut [f32]) {
205 for sample in buffer {
206 *sample = self.process_sample(*sample);
207 }
208 }
209
210 fn process_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
212 let len = left.len().min(right.len());
213 for i in 0..len {
214 let (l, r) = self.process_sample_stereo(left[i], right[i]);
215 left[i] = l;
216 right[i] = r;
217 }
218 }
219
220 fn process_sample_stereo(&mut self, left: f32, right: f32) -> (f32, f32) {
222 (self.process_sample(left), self.process_sample(right))
223 }
224
225 fn reset(&mut self);
227
228 fn latency_samples(&self) -> usize {
230 0
231 }
232
233 fn set_sample_rate(&mut self, _sample_rate: f32) {}
235
236 fn set_wet_dry(&mut self, _wet: f32) {}
245
246 fn wet_dry(&self) -> f32 {
251 1.0
252 }
253
254 fn set_wet_mix(&mut self, wet: f32) {
261 self.set_wet_dry(wet);
262 }
263
264 fn wet_mix(&self) -> f32 {
270 self.wet_dry()
271 }
272
273 fn process_with_wet_dry(&mut self, input: &[f32], output: &mut [f32], wet: f32) {
283 let wet = wet.clamp(0.0, 1.0);
284 let dry = 1.0 - wet;
285 let len = input.len().min(output.len());
286 for i in 0..len {
287 let processed = self.process_sample(input[i]);
288 output[i] = processed * wet + input[i] * dry;
289 }
290 }
291}
292
293pub struct WetDryWrapper<E: AudioEffect> {
307 inner: E,
308 wet: f32,
309 dry: f32,
310}
311
312impl<E: AudioEffect> WetDryWrapper<E> {
313 #[must_use]
315 pub fn new(inner: E, wet: f32) -> Self {
316 let wet = wet.clamp(0.0, 1.0);
317 Self {
318 inner,
319 wet,
320 dry: 1.0 - wet,
321 }
322 }
323
324 #[must_use]
326 pub fn inner(&self) -> &E {
327 &self.inner
328 }
329
330 pub fn inner_mut(&mut self) -> &mut E {
332 &mut self.inner
333 }
334
335 #[must_use]
337 pub fn into_inner(self) -> E {
338 self.inner
339 }
340}
341
342impl<E: AudioEffect> AudioEffect for WetDryWrapper<E> {
343 const EFFECT_ID: &'static str = E::EFFECT_ID;
344
345 fn process_sample(&mut self, input: f32) -> f32 {
346 let wet_out = self.inner.process_sample(input);
347 wet_out * self.wet + input * self.dry
348 }
349
350 fn process_sample_stereo(&mut self, left: f32, right: f32) -> (f32, f32) {
351 let (wl, wr) = self.inner.process_sample_stereo(left, right);
352 (
353 wl * self.wet + left * self.dry,
354 wr * self.wet + right * self.dry,
355 )
356 }
357
358 fn reset(&mut self) {
359 self.inner.reset();
360 }
361
362 fn latency_samples(&self) -> usize {
363 self.inner.latency_samples()
364 }
365
366 fn set_sample_rate(&mut self, sample_rate: f32) {
367 self.inner.set_sample_rate(sample_rate);
368 }
369
370 fn set_wet_dry(&mut self, wet: f32) {
371 self.wet = wet.clamp(0.0, 1.0);
372 self.dry = 1.0 - self.wet;
373 }
374
375 fn wet_dry(&self) -> f32 {
376 self.wet
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct ReverbConfig {
383 pub room_size: f32,
385 pub damping: f32,
387 pub wet: f32,
389 pub dry: f32,
391 pub width: f32,
393 pub predelay_ms: f32,
395}
396
397impl Default for ReverbConfig {
398 fn default() -> Self {
399 Self {
400 room_size: 0.5,
401 damping: 0.5,
402 wet: 0.33,
403 dry: 0.67,
404 width: 1.0,
405 predelay_ms: 0.0,
406 }
407 }
408}
409
410impl ReverbConfig {
411 #[must_use]
413 pub fn new(room_size: f32, damping: f32, wet: f32) -> Self {
414 Self {
415 room_size: room_size.clamp(0.0, 1.0),
416 damping: damping.clamp(0.0, 1.0),
417 wet: wet.clamp(0.0, 1.0),
418 dry: (1.0 - wet).clamp(0.0, 1.0),
419 width: 1.0,
420 predelay_ms: 0.0,
421 }
422 }
423
424 #[must_use]
426 pub fn with_room_size(mut self, room_size: f32) -> Self {
427 self.room_size = room_size.clamp(0.0, 1.0);
428 self
429 }
430
431 #[must_use]
433 pub fn with_damping(mut self, damping: f32) -> Self {
434 self.damping = damping.clamp(0.0, 1.0);
435 self
436 }
437
438 #[must_use]
440 pub fn with_wet(mut self, wet: f32) -> Self {
441 self.wet = wet.clamp(0.0, 1.0);
442 self
443 }
444
445 #[must_use]
447 pub fn with_dry(mut self, dry: f32) -> Self {
448 self.dry = dry.clamp(0.0, 1.0);
449 self
450 }
451
452 #[must_use]
454 pub fn with_width(mut self, width: f32) -> Self {
455 self.width = width.clamp(0.0, 1.0);
456 self
457 }
458
459 #[must_use]
461 pub fn with_predelay(mut self, predelay_ms: f32) -> Self {
462 self.predelay_ms = predelay_ms.max(0.0);
463 self
464 }
465
466 #[must_use]
468 pub fn small_room() -> Self {
469 Self::new(0.3, 0.4, 0.2)
470 }
471
472 #[must_use]
474 pub fn medium_room() -> Self {
475 Self::new(0.5, 0.5, 0.3)
476 }
477
478 #[must_use]
480 pub fn hall() -> Self {
481 Self::new(0.8, 0.6, 0.4).with_predelay(20.0)
482 }
483
484 #[must_use]
486 pub fn cathedral() -> Self {
487 Self::new(0.95, 0.7, 0.5).with_predelay(40.0)
488 }
489
490 #[must_use]
492 pub fn chamber() -> Self {
493 Self::new(0.6, 0.4, 0.35).with_predelay(10.0)
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500
501 #[test]
502 fn test_reverb_config_defaults() {
503 let config = ReverbConfig::default();
504 assert_eq!(config.room_size, 0.5);
505 assert_eq!(config.damping, 0.5);
506 assert_eq!(config.wet, 0.33);
507 }
508
509 #[test]
510 fn test_reverb_config_builder() {
511 let config = ReverbConfig::default()
512 .with_room_size(0.8)
513 .with_damping(0.6)
514 .with_wet(0.4);
515 assert_eq!(config.room_size, 0.8);
516 assert_eq!(config.damping, 0.6);
517 assert_eq!(config.wet, 0.4);
518 }
519
520 #[test]
521 fn test_reverb_config_clamping() {
522 let config = ReverbConfig::new(1.5, -0.5, 2.0);
523 assert_eq!(config.room_size, 1.0);
524 assert_eq!(config.damping, 0.0);
525 assert_eq!(config.wet, 1.0);
526 }
527
528 #[test]
529 fn test_reverb_presets() {
530 let small = ReverbConfig::small_room();
531 assert!(small.room_size < 0.5);
532
533 let hall = ReverbConfig::hall();
534 assert!(hall.room_size > 0.7);
535 assert!(hall.predelay_ms > 0.0);
536 }
537}
538
539#[cfg(test)]
540mod wet_dry_tests {
541 use super::*;
543 use crate::chorus::{ChorusParams, ChorusProcessor};
544 use crate::distortion::fuzz::{Fuzz, FuzzConfig};
545 use crate::distortion::overdrive::{Overdrive, OverdriveConfig};
546 use crate::flanger::{Flanger, FlangerConfig};
547 use crate::reverb::Freeverb;
548
549 #[test]
552 fn test_wrapper_wet_zero_returns_dry() {
553 let mut wrapped = WetDryWrapper::new(Fuzz::new(FuzzConfig::default()), 0.0);
554 let out = wrapped.process_sample(0.5);
555 assert!(
556 (out - 0.5).abs() < 1e-5,
557 "wet=0 should return dry signal, got {out}"
558 );
559 }
560
561 #[test]
562 fn test_wrapper_wet_one_returns_processed() {
563 let inner = Fuzz::new(FuzzConfig {
565 fuzz: 1.0,
566 level: 1.0,
567 });
568 let mut wrapped = WetDryWrapper::new(inner, 1.0);
569 let out = wrapped.process_sample(0.5);
571 assert!(out.is_finite());
572 }
573
574 #[test]
575 fn test_wrapper_wet_half_blends() {
576 let inner = Fuzz::new(FuzzConfig {
579 fuzz: 100.0,
580 level: 1.0,
581 });
582 let mut wrapped = WetDryWrapper::new(inner, 0.5);
583 let out = wrapped.process_sample(0.5);
584 assert!((out - 0.75).abs() < 1e-5, "blend mismatch: got {out}");
586 }
587
588 #[test]
589 fn test_wrapper_set_wet_dry_updates() {
590 let inner = Fuzz::new(FuzzConfig::default());
591 let mut wrapped = WetDryWrapper::new(inner, 0.3);
592 assert!((wrapped.wet_dry() - 0.3).abs() < 1e-5);
593 wrapped.set_wet_dry(0.8);
594 assert!((wrapped.wet_dry() - 0.8).abs() < 1e-5);
595 }
596
597 #[test]
598 fn test_wrapper_set_wet_dry_clamps() {
599 let inner = Fuzz::new(FuzzConfig::default());
600 let mut wrapped = WetDryWrapper::new(inner, 0.5);
601 wrapped.set_wet_dry(2.0);
602 assert!((wrapped.wet_dry() - 1.0).abs() < 1e-5);
603 wrapped.set_wet_dry(-1.0);
604 assert!((wrapped.wet_dry() - 0.0).abs() < 1e-5);
605 }
606
607 #[test]
610 fn test_process_with_wet_dry_zero_equals_input() {
611 let mut fuzz = Fuzz::new(FuzzConfig {
612 fuzz: 100.0,
613 level: 1.0,
614 });
615 let input = vec![0.3_f32, -0.5, 0.7];
616 let mut output = vec![0.0_f32; 3];
617 fuzz.process_with_wet_dry(&input, &mut output, 0.0);
618 for (i, (&inp, &out)) in input.iter().zip(output.iter()).enumerate() {
619 assert!(
620 (out - inp).abs() < 1e-5,
621 "output[{i}]={out} should equal input {inp}"
622 );
623 }
624 }
625
626 #[test]
627 fn test_process_with_wet_dry_one_equals_processed() {
628 let mut fuzz = Fuzz::new(FuzzConfig {
630 fuzz: 1.0,
631 level: 1.0,
632 });
633 let input = vec![0.3_f32, -0.4, 0.2];
634 let mut output = vec![0.0_f32; 3];
635 fuzz.process_with_wet_dry(&input, &mut output, 1.0);
636 for &s in &output {
639 assert!(s.is_finite());
640 }
641 }
642
643 #[test]
644 fn test_process_with_wet_dry_half_blends() {
645 let mut fuzz = Fuzz::new(FuzzConfig {
646 fuzz: 100.0,
647 level: 1.0,
648 });
649 let input = vec![0.5_f32];
650 let mut output = vec![0.0_f32; 1];
651 fuzz.process_with_wet_dry(&input, &mut output, 0.5);
652 assert!((output[0] - 0.75).abs() < 0.01, "blend={}", output[0]);
655 }
656
657 #[test]
660 fn test_overdrive_wet_dry_default_is_one() {
661 let od = Overdrive::new(OverdriveConfig::default());
662 assert!((od.wet_dry() - 1.0).abs() < f32::EPSILON);
663 }
664
665 #[test]
666 fn test_overdrive_wet_zero_passes_dry() {
667 let mut od = Overdrive::new(OverdriveConfig::default());
668 od.set_wet_dry(0.0);
669 let out = od.process_sample(0.4);
670 assert!((out - 0.4).abs() < 1e-5, "dry pass-through failed: {out}");
671 }
672
673 #[test]
674 fn test_overdrive_set_wet_dry_clamps() {
675 let mut od = Overdrive::new(OverdriveConfig::default());
676 od.set_wet_dry(5.0);
677 assert!((od.wet_dry() - 1.0).abs() < f32::EPSILON);
678 od.set_wet_dry(-2.0);
679 assert!((od.wet_dry() - 0.0).abs() < f32::EPSILON);
680 }
681
682 #[test]
685 fn test_fuzz_wet_zero_passes_dry() {
686 let mut f = Fuzz::new(FuzzConfig::default());
687 f.set_wet_dry(0.0);
688 let out = f.process_sample(0.6);
689 assert!((out - 0.6).abs() < 1e-5, "fuzz dry failed: {out}");
690 }
691
692 #[test]
693 fn test_fuzz_wet_one_full_effect() {
694 let mut f = Fuzz::new(FuzzConfig {
695 fuzz: 50.0,
696 level: 1.0,
697 });
698 f.set_wet_dry(1.0);
699 let out = f.process_sample(0.5);
700 assert!((out - 1.0).abs() < 1e-5, "full wet fuzz: {out}");
702 }
703
704 #[test]
707 fn test_flanger_wet_dry_stores() {
708 let mut fl = Flanger::new(FlangerConfig::default(), 48_000.0);
709 fl.set_wet_dry(0.3);
710 assert!((fl.wet_dry() - 0.3).abs() < 1e-5);
711 }
712
713 #[test]
714 fn test_flanger_wet_zero_bypasses() {
715 let mut fl = Flanger::new(
716 FlangerConfig {
717 feedback: 0.0,
718 ..FlangerConfig::default()
719 },
720 48_000.0,
721 );
722 fl.set_wet_dry(0.0);
723 let out = fl.process_sample(0.5);
724 assert!((out - 0.5).abs() < 1e-5, "flanger dry bypass: {out}");
725 }
726
727 #[test]
730 fn test_chorus_wet_zero_passes_dry() {
731 let mut cp = ChorusProcessor::new(48_000.0, ChorusParams::default());
732 cp.set_wet_dry(0.0);
733 let out: f32 = crate::AudioEffect::process_sample(&mut cp, 0.7);
735 assert!((out - 0.7).abs() < 1e-4, "chorus dry: {out}");
736 }
737
738 #[test]
739 fn test_chorus_wet_dry_stores() {
740 let mut cp = ChorusProcessor::new(48_000.0, ChorusParams::default());
741 cp.set_wet_dry(0.6);
742 assert!((cp.wet_dry() - 0.6).abs() < 1e-5);
743 }
744
745 #[test]
748 fn test_freeverb_wet_dry_stores() {
749 let mut rv = Freeverb::new(ReverbConfig::default(), 48_000.0);
750 rv.set_wet_dry(0.5);
751 assert!((rv.wet_dry() - 0.5).abs() < 1e-5);
752 }
753
754 #[test]
755 fn test_freeverb_wet_dry_clamps() {
756 let mut rv = Freeverb::new(ReverbConfig::default(), 48_000.0);
757 rv.set_wet_dry(2.0);
758 assert!((rv.wet_dry() - 1.0).abs() < f32::EPSILON);
759 rv.set_wet_dry(-1.0);
760 assert!((rv.wet_dry() - 0.0).abs() < f32::EPSILON);
761 }
762
763 #[test]
764 fn test_freeverb_wet_zero_output_is_dry() {
765 let mut rv = Freeverb::new(ReverbConfig::default(), 48_000.0);
767 rv.set_wet_dry(0.0);
768 let out = rv.process_sample(1.0);
770 assert!(
772 out.is_finite(),
773 "freeverb wet=0 should produce finite output"
774 );
775 assert!(
778 (out - 1.0).abs() < 0.05,
779 "freeverb wet=0: expected ~1.0, got {out}"
780 );
781 }
782}