use crate::AudioEffect;
#[derive(Debug, Clone)]
pub struct FuzzConfig {
pub fuzz: f32,
pub level: f32,
}
impl Default for FuzzConfig {
fn default() -> Self {
Self {
fuzz: 10.0,
level: 0.3,
}
}
}
pub struct Fuzz {
config: FuzzConfig,
wet_mix: f32,
}
impl Fuzz {
#[must_use]
pub fn new(config: FuzzConfig) -> Self {
Self {
config,
wet_mix: 1.0,
}
}
fn hard_clip(x: f32) -> f32 {
x.clamp(-1.0, 1.0)
}
}
impl AudioEffect for Fuzz {
const EFFECT_ID: &'static str = "fuzz";
fn process_sample(&mut self, input: f32) -> f32 {
let wet_out = Self::hard_clip(input * self.config.fuzz) * self.config.level;
wet_out * self.wet_mix + input * (1.0 - self.wet_mix)
}
fn reset(&mut self) {
}
fn set_wet_dry(&mut self, wet: f32) {
self.wet_mix = wet.clamp(0.0, 1.0);
}
fn wet_dry(&self) -> f32 {
self.wet_mix
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuzz() {
let config = FuzzConfig::default();
let mut fuzz = Fuzz::new(config);
let output = fuzz.process_sample(0.5);
assert!(output.is_finite());
assert!(output.abs() <= 1.0);
}
#[test]
fn test_fuzz_wet_dry_default_is_one() {
let f = Fuzz::new(FuzzConfig::default());
assert!((f.wet_dry() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_fuzz_dry_only_passes_input() {
let mut f = Fuzz::new(FuzzConfig::default());
f.set_wet_dry(0.0);
let out = f.process_sample(0.5);
assert!((out - 0.5).abs() < 1e-5, "dry-only output={out}, want 0.5");
}
}