Initial commit
This commit is contained in:
822
src/effects.rs
Normal file
822
src/effects.rs
Normal file
@ -0,0 +1,822 @@
|
||||
//! Audio effects processing module
|
||||
//!
|
||||
//! This module provides various audio effects that can be applied to synthesized audio,
|
||||
//! including filters, delays, reverbs, distortion, and modulation effects.
|
||||
|
||||
use crate::{SAMPLE_RATE, lerp, low_pass_filter};
|
||||
use std::collections::VecDeque;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// Trait for audio effects that process samples
|
||||
pub trait AudioEffect {
|
||||
/// Process a single audio sample
|
||||
fn process_sample(&mut self, input: f32) -> f32;
|
||||
|
||||
/// Process a buffer of audio samples
|
||||
fn process_buffer(&mut self, buffer: &mut [f32]) {
|
||||
for sample in buffer.iter_mut() {
|
||||
*sample = self.process_sample(*sample);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the effect's internal state
|
||||
fn reset(&mut self);
|
||||
|
||||
/// Set a parameter of the effect
|
||||
fn set_parameter(&mut self, param: &str, value: f32);
|
||||
}
|
||||
|
||||
/// Low-pass filter effect
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LowPassFilter {
|
||||
pub cutoff_frequency: f32,
|
||||
pub resonance: f32,
|
||||
|
||||
// Internal state
|
||||
x1: f32,
|
||||
x2: f32,
|
||||
y1: f32,
|
||||
y2: f32,
|
||||
|
||||
// Filter coefficients
|
||||
a0: f32,
|
||||
a1: f32,
|
||||
a2: f32,
|
||||
b1: f32,
|
||||
b2: f32,
|
||||
}
|
||||
|
||||
impl LowPassFilter {
|
||||
/// Create a new low-pass filter
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cutoff_frequency` - Cutoff frequency in Hz
|
||||
/// * `resonance` - Resonance factor (0.1 to 10.0)
|
||||
pub fn new(cutoff_frequency: f32, resonance: f32) -> Self {
|
||||
let mut filter = Self {
|
||||
cutoff_frequency,
|
||||
resonance: resonance.clamp(0.1, 10.0),
|
||||
x1: 0.0,
|
||||
x2: 0.0,
|
||||
y1: 0.0,
|
||||
y2: 0.0,
|
||||
a0: 1.0,
|
||||
a1: 0.0,
|
||||
a2: 0.0,
|
||||
b1: 0.0,
|
||||
b2: 0.0,
|
||||
};
|
||||
|
||||
filter.update_coefficients();
|
||||
filter
|
||||
}
|
||||
|
||||
fn update_coefficients(&mut self) {
|
||||
let omega = 2.0 * PI * self.cutoff_frequency / SAMPLE_RATE;
|
||||
let sin_omega = omega.sin();
|
||||
let cos_omega = omega.cos();
|
||||
let alpha = sin_omega / (2.0 * self.resonance);
|
||||
|
||||
let b0 = (1.0 - cos_omega) / 2.0;
|
||||
let b1 = 1.0 - cos_omega;
|
||||
let b2 = (1.0 - cos_omega) / 2.0;
|
||||
let a0 = 1.0 + alpha;
|
||||
let a1 = -2.0 * cos_omega;
|
||||
let a2 = 1.0 - alpha;
|
||||
|
||||
// Normalize coefficients
|
||||
self.a0 = b0 / a0;
|
||||
self.a1 = b1 / a0;
|
||||
self.a2 = b2 / a0;
|
||||
self.b1 = a1 / a0;
|
||||
self.b2 = a2 / a0;
|
||||
}
|
||||
|
||||
pub fn set_cutoff(&mut self, frequency: f32) {
|
||||
self.cutoff_frequency = frequency.clamp(20.0, SAMPLE_RATE / 2.0);
|
||||
self.update_coefficients();
|
||||
}
|
||||
|
||||
pub fn set_resonance(&mut self, resonance: f32) {
|
||||
self.resonance = resonance.clamp(0.1, 10.0);
|
||||
self.update_coefficients();
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for LowPassFilter {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let output = self.a0 * input + self.a1 * self.x1 + self.a2 * self.x2
|
||||
- self.b1 * self.y1
|
||||
- self.b2 * self.y2;
|
||||
|
||||
// Update delay line
|
||||
self.x2 = self.x1;
|
||||
self.x1 = input;
|
||||
self.y2 = self.y1;
|
||||
self.y1 = output;
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.x1 = 0.0;
|
||||
self.x2 = 0.0;
|
||||
self.y1 = 0.0;
|
||||
self.y2 = 0.0;
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"cutoff" => self.set_cutoff(value),
|
||||
"resonance" => self.set_resonance(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// High-pass filter effect
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HighPassFilter {
|
||||
pub cutoff_frequency: f32,
|
||||
pub resonance: f32,
|
||||
|
||||
// Internal state (same as low-pass)
|
||||
x1: f32,
|
||||
x2: f32,
|
||||
y1: f32,
|
||||
y2: f32,
|
||||
|
||||
// Filter coefficients
|
||||
a0: f32,
|
||||
a1: f32,
|
||||
a2: f32,
|
||||
b1: f32,
|
||||
b2: f32,
|
||||
}
|
||||
|
||||
impl HighPassFilter {
|
||||
pub fn new(cutoff_frequency: f32, resonance: f32) -> Self {
|
||||
let mut filter = Self {
|
||||
cutoff_frequency,
|
||||
resonance: resonance.clamp(0.1, 10.0),
|
||||
x1: 0.0,
|
||||
x2: 0.0,
|
||||
y1: 0.0,
|
||||
y2: 0.0,
|
||||
a0: 1.0,
|
||||
a1: 0.0,
|
||||
a2: 0.0,
|
||||
b1: 0.0,
|
||||
b2: 0.0,
|
||||
};
|
||||
|
||||
filter.update_coefficients();
|
||||
filter
|
||||
}
|
||||
|
||||
fn update_coefficients(&mut self) {
|
||||
let omega = 2.0 * PI * self.cutoff_frequency / SAMPLE_RATE;
|
||||
let sin_omega = omega.sin();
|
||||
let cos_omega = omega.cos();
|
||||
let alpha = sin_omega / (2.0 * self.resonance);
|
||||
|
||||
let b0 = (1.0 + cos_omega) / 2.0;
|
||||
let b1 = -(1.0 + cos_omega);
|
||||
let b2 = (1.0 + cos_omega) / 2.0;
|
||||
let a0 = 1.0 + alpha;
|
||||
let a1 = -2.0 * cos_omega;
|
||||
let a2 = 1.0 - alpha;
|
||||
|
||||
// Normalize coefficients
|
||||
self.a0 = b0 / a0;
|
||||
self.a1 = b1 / a0;
|
||||
self.a2 = b2 / a0;
|
||||
self.b1 = a1 / a0;
|
||||
self.b2 = a2 / a0;
|
||||
}
|
||||
|
||||
pub fn set_cutoff(&mut self, frequency: f32) {
|
||||
self.cutoff_frequency = frequency.clamp(20.0, SAMPLE_RATE / 2.0);
|
||||
self.update_coefficients();
|
||||
}
|
||||
|
||||
pub fn set_resonance(&mut self, resonance: f32) {
|
||||
self.resonance = resonance.clamp(0.1, 10.0);
|
||||
self.update_coefficients();
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for HighPassFilter {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let output = self.a0 * input + self.a1 * self.x1 + self.a2 * self.x2
|
||||
- self.b1 * self.y1
|
||||
- self.b2 * self.y2;
|
||||
|
||||
self.x2 = self.x1;
|
||||
self.x1 = input;
|
||||
self.y2 = self.y1;
|
||||
self.y1 = output;
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.x1 = 0.0;
|
||||
self.x2 = 0.0;
|
||||
self.y1 = 0.0;
|
||||
self.y2 = 0.0;
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"cutoff" => self.set_cutoff(value),
|
||||
"resonance" => self.set_resonance(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delay effect with feedback
|
||||
#[derive(Debug)]
|
||||
pub struct Delay {
|
||||
pub delay_time: f32, // Delay time in seconds
|
||||
pub feedback: f32, // Feedback amount (0.0 to 0.95)
|
||||
pub mix: f32, // Wet/dry mix (0.0 = dry, 1.0 = wet)
|
||||
|
||||
delay_buffer: VecDeque<f32>,
|
||||
delay_samples: usize,
|
||||
}
|
||||
|
||||
impl Delay {
|
||||
/// Create a new delay effect
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `delay_time` - Delay time in seconds
|
||||
/// * `feedback` - Feedback amount (0.0 to 0.95)
|
||||
/// * `mix` - Wet/dry mix (0.0 to 1.0)
|
||||
pub fn new(delay_time: f32, feedback: f32, mix: f32) -> Self {
|
||||
let delay_samples = (delay_time * SAMPLE_RATE) as usize;
|
||||
let mut delay_buffer = VecDeque::with_capacity(delay_samples);
|
||||
|
||||
// Initialize with zeros
|
||||
for _ in 0..delay_samples {
|
||||
delay_buffer.push_back(0.0);
|
||||
}
|
||||
|
||||
Self {
|
||||
delay_time,
|
||||
feedback: feedback.clamp(0.0, 0.95),
|
||||
mix: mix.clamp(0.0, 1.0),
|
||||
delay_buffer,
|
||||
delay_samples,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_delay_time(&mut self, time: f32) {
|
||||
self.delay_time = time.max(0.001); // Minimum 1ms delay
|
||||
let new_samples = (time * SAMPLE_RATE) as usize;
|
||||
|
||||
if new_samples != self.delay_samples {
|
||||
self.delay_samples = new_samples;
|
||||
self.delay_buffer.clear();
|
||||
|
||||
for _ in 0..new_samples {
|
||||
self.delay_buffer.push_back(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_feedback(&mut self, feedback: f32) {
|
||||
self.feedback = feedback.clamp(0.0, 0.95);
|
||||
}
|
||||
|
||||
pub fn set_mix(&mut self, mix: f32) {
|
||||
self.mix = mix.clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for Delay {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let delayed_sample = self.delay_buffer.front().copied().unwrap_or(0.0);
|
||||
let feedback_sample = input + delayed_sample * self.feedback;
|
||||
|
||||
// Add new sample to delay line
|
||||
self.delay_buffer.pop_front();
|
||||
self.delay_buffer.push_back(feedback_sample);
|
||||
|
||||
// Mix dry and wet signals
|
||||
lerp(input, delayed_sample, self.mix)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
for sample in self.delay_buffer.iter_mut() {
|
||||
*sample = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"delay_time" => self.set_delay_time(value),
|
||||
"feedback" => self.set_feedback(value),
|
||||
"mix" => self.set_mix(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple reverb effect using multiple delay lines
|
||||
#[derive(Debug)]
|
||||
pub struct Reverb {
|
||||
pub room_size: f32,
|
||||
pub damping: f32,
|
||||
pub mix: f32,
|
||||
|
||||
delay_lines: Vec<VecDeque<f32>>,
|
||||
feedback_gains: Vec<f32>,
|
||||
damping_filters: Vec<f32>, // Simple one-pole filters for damping
|
||||
}
|
||||
|
||||
impl Reverb {
|
||||
/// Create a new reverb effect
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `room_size` - Room size factor (0.0 to 1.0)
|
||||
/// * `damping` - High frequency damping (0.0 to 1.0)
|
||||
/// * `mix` - Wet/dry mix (0.0 to 1.0)
|
||||
pub fn new(room_size: f32, damping: f32, mix: f32) -> Self {
|
||||
// Use Schroeder's reverb design with 4 comb filters and 2 allpass filters
|
||||
let base_delays = vec![1116, 1188, 1277, 1356, 225, 556]; // Prime numbers for better diffusion
|
||||
let base_gains = vec![0.7, 0.7, 0.7, 0.7, 0.5, 0.5];
|
||||
|
||||
let mut delay_lines = Vec::new();
|
||||
let mut feedback_gains = Vec::new();
|
||||
|
||||
let room_factor = 1.0 + room_size * 0.5; // Scale delays based on room size
|
||||
|
||||
for (i, &base_delay) in base_delays.iter().enumerate() {
|
||||
let delay_samples = (base_delay as f32 * room_factor) as usize;
|
||||
let mut delay_line = VecDeque::with_capacity(delay_samples);
|
||||
|
||||
for _ in 0..delay_samples {
|
||||
delay_line.push_back(0.0);
|
||||
}
|
||||
|
||||
delay_lines.push(delay_line);
|
||||
feedback_gains.push(base_gains[i] * room_size);
|
||||
}
|
||||
|
||||
Self {
|
||||
room_size: room_size.clamp(0.0, 1.0),
|
||||
damping: damping.clamp(0.0, 1.0),
|
||||
mix: mix.clamp(0.0, 1.0),
|
||||
delay_lines,
|
||||
feedback_gains,
|
||||
damping_filters: vec![0.0; base_delays.len()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_room_size(&mut self, size: f32) {
|
||||
self.room_size = size.clamp(0.0, 1.0);
|
||||
// Update feedback gains
|
||||
for (i, gain) in self.feedback_gains.iter_mut().enumerate() {
|
||||
let base_gains = vec![0.7, 0.7, 0.7, 0.7, 0.5, 0.5];
|
||||
*gain = base_gains[i] * self.room_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_damping(&mut self, damping: f32) {
|
||||
self.damping = damping.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
pub fn set_mix(&mut self, mix: f32) {
|
||||
self.mix = mix.clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for Reverb {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let mut output = 0.0;
|
||||
|
||||
// Process comb filters (first 4 delay lines)
|
||||
for i in 0..4 {
|
||||
if let Some(delayed) = self.delay_lines[i].front().copied() {
|
||||
// Apply damping filter
|
||||
self.damping_filters[i] =
|
||||
low_pass_filter(delayed, self.damping_filters[i], 1.0 - self.damping);
|
||||
|
||||
let feedback_sample = input + self.damping_filters[i] * self.feedback_gains[i];
|
||||
|
||||
self.delay_lines[i].pop_front();
|
||||
self.delay_lines[i].push_back(feedback_sample);
|
||||
|
||||
output += delayed;
|
||||
}
|
||||
}
|
||||
|
||||
// Process allpass filters (last 2 delay lines)
|
||||
let mut allpass_input = output * 0.25; // Scale down comb filter output
|
||||
|
||||
for i in 4..6 {
|
||||
if let Some(delayed) = self.delay_lines[i].front().copied() {
|
||||
let feedback_sample = allpass_input + delayed * self.feedback_gains[i];
|
||||
|
||||
self.delay_lines[i].pop_front();
|
||||
self.delay_lines[i].push_back(feedback_sample);
|
||||
|
||||
allpass_input = delayed - allpass_input * self.feedback_gains[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Mix dry and wet signals
|
||||
lerp(input, allpass_input, self.mix)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
for delay_line in &mut self.delay_lines {
|
||||
for sample in delay_line.iter_mut() {
|
||||
*sample = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
for filter in &mut self.damping_filters {
|
||||
*filter = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"room_size" => self.set_room_size(value),
|
||||
"damping" => self.set_damping(value),
|
||||
"mix" => self.set_mix(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Distortion effect
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Distortion {
|
||||
pub drive: f32, // Distortion amount (1.0 to 10.0)
|
||||
pub tone: f32, // Tone control (0.0 to 1.0)
|
||||
pub output_gain: f32, // Output gain compensation
|
||||
|
||||
pre_filter: LowPassFilter,
|
||||
post_filter: LowPassFilter,
|
||||
}
|
||||
|
||||
impl Distortion {
|
||||
/// Create a new distortion effect
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `drive` - Distortion drive amount (1.0 to 10.0)
|
||||
/// * `tone` - Tone control (0.0 to 1.0)
|
||||
pub fn new(drive: f32, tone: f32) -> Self {
|
||||
Self {
|
||||
drive: drive.clamp(1.0, 10.0),
|
||||
tone: tone.clamp(0.0, 1.0),
|
||||
output_gain: 1.0 / drive.clamp(1.0, 10.0), // Automatic gain compensation
|
||||
pre_filter: LowPassFilter::new(8000.0, 0.7),
|
||||
post_filter: LowPassFilter::new(4000.0 + tone * 4000.0, 0.7),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_drive(&mut self, drive: f32) {
|
||||
self.drive = drive.clamp(1.0, 10.0);
|
||||
self.output_gain = 1.0 / self.drive;
|
||||
}
|
||||
|
||||
pub fn set_tone(&mut self, tone: f32) {
|
||||
self.tone = tone.clamp(0.0, 1.0);
|
||||
self.post_filter.set_cutoff(4000.0 + self.tone * 4000.0);
|
||||
}
|
||||
|
||||
fn waveshaper(&self, input: f32) -> f32 {
|
||||
let driven = input * self.drive;
|
||||
|
||||
// Soft clipping using tanh
|
||||
if driven.abs() <= 1.0 {
|
||||
driven * (1.0 - driven.abs() / 3.0)
|
||||
} else {
|
||||
driven.signum() * (2.0 / 3.0 + (driven.abs() - 1.0) / (driven.abs() + 1.0) / 3.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for Distortion {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
// Pre-filtering to reduce aliasing
|
||||
let filtered_input = self.pre_filter.process_sample(input);
|
||||
|
||||
// Apply waveshaping distortion
|
||||
let distorted = self.waveshaper(filtered_input);
|
||||
|
||||
// Post-filtering for tone shaping
|
||||
let shaped = self.post_filter.process_sample(distorted);
|
||||
|
||||
// Apply output gain compensation
|
||||
shaped * self.output_gain
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.pre_filter.reset();
|
||||
self.post_filter.reset();
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"drive" => self.set_drive(value),
|
||||
"tone" => self.set_tone(value),
|
||||
"output_gain" => self.output_gain = value.clamp(0.0, 2.0),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Chorus effect using multiple delay lines with LFO modulation
|
||||
#[derive(Debug)]
|
||||
pub struct Chorus {
|
||||
pub rate: f32, // LFO rate in Hz
|
||||
pub depth: f32, // Modulation depth (0.0 to 1.0)
|
||||
pub mix: f32, // Wet/dry mix
|
||||
pub layers: usize, // Number of chorus layers
|
||||
|
||||
delay_lines: Vec<VecDeque<f32>>,
|
||||
lfo_phases: Vec<f32>,
|
||||
base_delay_samples: usize,
|
||||
}
|
||||
|
||||
impl Chorus {
|
||||
/// Create a new chorus effect
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `rate` - LFO rate in Hz
|
||||
/// * `depth` - Modulation depth (0.0 to 1.0)
|
||||
/// * `mix` - Wet/dry mix (0.0 to 1.0)
|
||||
/// * `layers` - Number of chorus layers (1 to 4)
|
||||
pub fn new(rate: f32, depth: f32, mix: f32, layers: usize) -> Self {
|
||||
let layers = layers.clamp(1, 4);
|
||||
let base_delay_ms = 15.0; // Base delay in milliseconds
|
||||
let max_delay_ms = base_delay_ms + depth * 10.0; // Max modulation range
|
||||
|
||||
let base_delay_samples = (base_delay_ms * SAMPLE_RATE / 1000.0) as usize;
|
||||
let max_delay_samples = (max_delay_ms * SAMPLE_RATE / 1000.0) as usize;
|
||||
|
||||
let mut delay_lines = Vec::new();
|
||||
let mut lfo_phases = Vec::new();
|
||||
|
||||
for i in 0..layers {
|
||||
let mut delay_line = VecDeque::with_capacity(max_delay_samples);
|
||||
for _ in 0..max_delay_samples {
|
||||
delay_line.push_back(0.0);
|
||||
}
|
||||
delay_lines.push(delay_line);
|
||||
|
||||
// Distribute LFO phases evenly
|
||||
lfo_phases.push(i as f32 * 2.0 * PI / layers as f32);
|
||||
}
|
||||
|
||||
Self {
|
||||
rate: rate.clamp(0.1, 10.0),
|
||||
depth: depth.clamp(0.0, 1.0),
|
||||
mix: mix.clamp(0.0, 1.0),
|
||||
layers,
|
||||
delay_lines,
|
||||
lfo_phases,
|
||||
base_delay_samples,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rate(&mut self, rate: f32) {
|
||||
self.rate = rate.clamp(0.1, 10.0);
|
||||
}
|
||||
|
||||
pub fn set_depth(&mut self, depth: f32) {
|
||||
self.depth = depth.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
fn get_delayed_sample(&self, delay_line: &VecDeque<f32>, delay_samples: f32) -> f32 {
|
||||
let delay_int = delay_samples.floor() as usize;
|
||||
let delay_frac = delay_samples - delay_int as f32;
|
||||
|
||||
if delay_int >= delay_line.len() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let sample1 = delay_line.get(delay_int).copied().unwrap_or(0.0);
|
||||
let sample2 = delay_line.get(delay_int + 1).copied().unwrap_or(0.0);
|
||||
|
||||
// Linear interpolation
|
||||
lerp(sample1, sample2, delay_frac)
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for Chorus {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let mut chorus_output = 0.0;
|
||||
|
||||
for i in 0..self.layers {
|
||||
// Update LFO phase
|
||||
self.lfo_phases[i] += 2.0 * PI * self.rate / SAMPLE_RATE;
|
||||
if self.lfo_phases[i] >= 2.0 * PI {
|
||||
self.lfo_phases[i] -= 2.0 * PI;
|
||||
}
|
||||
|
||||
// Calculate modulated delay
|
||||
let lfo_value = self.lfo_phases[i].sin();
|
||||
let delay_offset = self.depth * 10.0 * SAMPLE_RATE / 1000.0; // Convert to samples
|
||||
let total_delay = self.base_delay_samples as f32 + lfo_value * delay_offset;
|
||||
|
||||
// Get delayed sample with interpolation
|
||||
let delayed_sample = self.get_delayed_sample(&self.delay_lines[i], total_delay);
|
||||
chorus_output += delayed_sample;
|
||||
|
||||
// Add input to delay line
|
||||
self.delay_lines[i].pop_front();
|
||||
self.delay_lines[i].push_back(input);
|
||||
}
|
||||
|
||||
// Average the chorus layers
|
||||
chorus_output /= self.layers as f32;
|
||||
|
||||
// Mix with dry signal
|
||||
lerp(input, chorus_output, self.mix)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
for delay_line in &mut self.delay_lines {
|
||||
for sample in delay_line.iter_mut() {
|
||||
*sample = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
for phase in &mut self.lfo_phases {
|
||||
*phase = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
match param {
|
||||
"rate" => self.set_rate(value),
|
||||
"depth" => self.set_depth(value),
|
||||
"mix" => self.mix = value.clamp(0.0, 1.0),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Effects chain that can combine multiple effects
|
||||
pub struct EffectsChain {
|
||||
effects: Vec<Box<dyn AudioEffect>>,
|
||||
}
|
||||
|
||||
impl EffectsChain {
|
||||
/// Create a new empty effects chain
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
effects: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an effect to the chain
|
||||
pub fn add_effect(&mut self, effect: Box<dyn AudioEffect>) {
|
||||
self.effects.push(effect);
|
||||
}
|
||||
|
||||
/// Remove an effect from the chain
|
||||
pub fn remove_effect(&mut self, index: usize) -> Option<Box<dyn AudioEffect>> {
|
||||
if index < self.effects.len() {
|
||||
Some(self.effects.remove(index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all effects from the chain
|
||||
pub fn clear(&mut self) {
|
||||
self.effects.clear();
|
||||
}
|
||||
|
||||
/// Get the number of effects in the chain
|
||||
pub fn len(&self) -> usize {
|
||||
self.effects.len()
|
||||
}
|
||||
|
||||
/// Check if the chain is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.effects.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEffect for EffectsChain {
|
||||
fn process_sample(&mut self, input: f32) -> f32 {
|
||||
let mut signal = input;
|
||||
|
||||
for effect in &mut self.effects {
|
||||
signal = effect.process_sample(signal);
|
||||
}
|
||||
|
||||
signal
|
||||
}
|
||||
|
||||
fn process_buffer(&mut self, buffer: &mut [f32]) {
|
||||
for effect in &mut self.effects {
|
||||
effect.process_buffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
for effect in &mut self.effects {
|
||||
effect.reset();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parameter(&mut self, param: &str, value: f32) {
|
||||
// This would require a more sophisticated parameter routing system
|
||||
// For now, we'll just ignore it
|
||||
let _ = (param, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EffectsChain {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_low_pass_filter() {
|
||||
let mut filter = LowPassFilter::new(1000.0, 1.0);
|
||||
let output = filter.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_high_pass_filter() {
|
||||
let mut filter = HighPassFilter::new(1000.0, 1.0);
|
||||
let output = filter.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delay() {
|
||||
let mut delay = Delay::new(0.1, 0.5, 0.5);
|
||||
let output = delay.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
|
||||
// After enough samples, we should get some delayed signal
|
||||
for _ in 0..(0.1 * SAMPLE_RATE) as usize {
|
||||
delay.process_sample(0.0);
|
||||
}
|
||||
let delayed_output = delay.process_sample(0.0);
|
||||
assert!(delayed_output.abs() > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reverb() {
|
||||
let mut reverb = Reverb::new(0.5, 0.3, 0.4);
|
||||
let output = reverb.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distortion() {
|
||||
let mut distortion = Distortion::new(3.0, 0.5);
|
||||
let output = distortion.process_sample(0.5);
|
||||
assert!(output.is_finite());
|
||||
assert!(output.abs() <= 1.0); // Should not clip beyond reasonable bounds
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chorus() {
|
||||
let mut chorus = Chorus::new(1.0, 0.5, 0.3, 2);
|
||||
let output = chorus.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effects_chain() {
|
||||
let mut chain = EffectsChain::new();
|
||||
|
||||
chain.add_effect(Box::new(LowPassFilter::new(2000.0, 1.0)));
|
||||
chain.add_effect(Box::new(Delay::new(0.05, 0.3, 0.2)));
|
||||
|
||||
assert_eq!(chain.len(), 2);
|
||||
|
||||
let output = chain.process_sample(1.0);
|
||||
assert!(output.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_parameter_setting() {
|
||||
let mut filter = LowPassFilter::new(1000.0, 1.0);
|
||||
filter.set_parameter("cutoff", 2000.0);
|
||||
assert_eq!(filter.cutoff_frequency, 2000.0);
|
||||
|
||||
filter.set_parameter("resonance", 2.0);
|
||||
assert_eq!(filter.resonance, 2.0);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user