use super::AudioData;
use std::collections::HashMap;
use voirs_sdk::{Result, VoirsError};
#[derive(Debug, Clone)]
pub struct EffectConfig {
pub effect_type: String,
pub parameters: HashMap<String, f32>,
pub enabled: bool,
pub priority: i32,
}
impl EffectConfig {
pub fn new(effect_type: &str) -> Self {
Self {
effect_type: effect_type.to_string(),
parameters: HashMap::new(),
enabled: true,
priority: 0,
}
}
pub fn with_parameter(mut self, name: &str, value: f32) -> Self {
self.parameters.insert(name.to_string(), value);
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn get_parameter(&self, name: &str) -> Option<f32> {
self.parameters.get(name).copied()
}
}
pub trait AudioEffect: Send + Sync {
fn name(&self) -> &str;
fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()>;
fn reset(&mut self) -> Result<()>;
fn is_enabled(&self) -> bool;
fn set_enabled(&mut self, enabled: bool);
fn get_parameters(&self) -> HashMap<String, f32>;
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()>;
}
pub struct VolumeEffect {
gain: f32,
enabled: bool,
}
impl VolumeEffect {
pub fn new(gain: f32) -> Self {
Self {
gain: gain.clamp(0.0, 2.0), enabled: true,
}
}
}
impl AudioEffect for VolumeEffect {
fn name(&self) -> &str {
"volume"
}
fn process(&mut self, samples: &mut [f32], _sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
for sample in samples.iter_mut() {
*sample *= self.gain;
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("gain".to_string(), self.gain);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"gain" => {
self.gain = value.clamp(0.0, 2.0);
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct LowPassFilter {
cutoff_freq: f32,
resonance: f32,
enabled: bool,
prev_input: f32,
prev_output: f32,
}
impl LowPassFilter {
pub fn new(cutoff_freq: f32, resonance: f32) -> Self {
Self {
cutoff_freq: cutoff_freq.clamp(20.0, 20000.0),
resonance: resonance.clamp(0.1, 10.0),
enabled: true,
prev_input: 0.0,
prev_output: 0.0,
}
}
}
impl AudioEffect for LowPassFilter {
fn name(&self) -> &str {
"lowpass"
}
fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
let dt = 1.0 / sample_rate as f32;
let rc = 1.0 / (2.0 * std::f32::consts::PI * self.cutoff_freq);
let alpha = dt / (rc + dt);
for sample in samples.iter_mut() {
let output = alpha * *sample + (1.0 - alpha) * self.prev_output;
self.prev_output = output;
*sample = output;
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.prev_input = 0.0;
self.prev_output = 0.0;
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("cutoff_freq".to_string(), self.cutoff_freq);
params.insert("resonance".to_string(), self.resonance);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"cutoff_freq" => {
self.cutoff_freq = value.clamp(20.0, 20000.0);
Ok(())
}
"resonance" => {
self.resonance = value.clamp(0.1, 10.0);
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct ReverbEffect {
room_size: f32,
damping: f32,
wet_level: f32,
dry_level: f32,
enabled: bool,
delay_buffer: Vec<f32>,
delay_index: usize,
}
impl ReverbEffect {
pub fn new(room_size: f32, damping: f32, wet_level: f32) -> Self {
let delay_samples = (room_size * 22050.0) as usize; let clamped_wet = wet_level.clamp(0.0, 1.0);
Self {
room_size: room_size.clamp(0.0, 1.0),
damping: damping.clamp(0.0, 1.0),
wet_level: clamped_wet,
dry_level: 1.0 - clamped_wet,
enabled: true,
delay_buffer: vec![0.0; delay_samples.max(1024)],
delay_index: 0,
}
}
}
impl AudioEffect for ReverbEffect {
fn name(&self) -> &str {
"reverb"
}
fn process(&mut self, samples: &mut [f32], _sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
for sample in samples.iter_mut() {
let delayed = self.delay_buffer[self.delay_index];
let reverb_sample = delayed * (1.0 - self.damping);
let output = (*sample * self.dry_level) + (reverb_sample * self.wet_level);
self.delay_buffer[self.delay_index] = *sample + (reverb_sample * self.room_size * 0.5);
self.delay_index = (self.delay_index + 1) % self.delay_buffer.len();
*sample = output;
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.delay_buffer.fill(0.0);
self.delay_index = 0;
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("room_size".to_string(), self.room_size);
params.insert("damping".to_string(), self.damping);
params.insert("wet_level".to_string(), self.wet_level);
params.insert("dry_level".to_string(), self.dry_level);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"room_size" => {
self.room_size = value.clamp(0.0, 1.0);
Ok(())
}
"damping" => {
self.damping = value.clamp(0.0, 1.0);
Ok(())
}
"wet_level" => {
self.wet_level = value.clamp(0.0, 1.0);
self.dry_level = 1.0 - self.wet_level;
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct CompressorEffect {
threshold: f32, ratio: f32, attack_time: f32, release_time: f32, makeup_gain: f32, enabled: bool,
envelope: f32, }
impl CompressorEffect {
pub fn new(threshold: f32, ratio: f32, attack_ms: f32, release_ms: f32) -> Self {
Self {
threshold: threshold.clamp(-60.0, 0.0),
ratio: ratio.clamp(1.0, 20.0),
attack_time: attack_ms.clamp(0.1, 100.0),
release_time: release_ms.clamp(10.0, 1000.0),
makeup_gain: 0.0,
enabled: true,
envelope: 0.0,
}
}
pub fn with_makeup_gain(mut self, gain_db: f32) -> Self {
self.makeup_gain = gain_db.clamp(0.0, 20.0);
self
}
fn db_to_linear(db: f32) -> f32 {
10.0_f32.powf(db / 20.0)
}
fn linear_to_db(linear: f32) -> f32 {
20.0 * linear.abs().max(1e-10).log10()
}
}
impl AudioEffect for CompressorEffect {
fn name(&self) -> &str {
"compressor"
}
fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
let attack_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.attack_time / 1000.0)).exp();
let release_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.release_time / 1000.0)).exp();
for sample in samples.iter_mut() {
let input_level_db = Self::linear_to_db(*sample);
let coef = if input_level_db > Self::linear_to_db(self.envelope) {
attack_coef
} else {
release_coef
};
self.envelope = self.envelope + coef * (sample.abs() - self.envelope);
let envelope_db = Self::linear_to_db(self.envelope);
let gain_reduction = if envelope_db > self.threshold {
(envelope_db - self.threshold) * (1.0 - 1.0 / self.ratio)
} else {
0.0
};
let total_gain_db = -gain_reduction + self.makeup_gain;
let gain = Self::db_to_linear(total_gain_db);
*sample *= gain;
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.envelope = 0.0;
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("threshold".to_string(), self.threshold);
params.insert("ratio".to_string(), self.ratio);
params.insert("attack_time".to_string(), self.attack_time);
params.insert("release_time".to_string(), self.release_time);
params.insert("makeup_gain".to_string(), self.makeup_gain);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"threshold" => {
self.threshold = value.clamp(-60.0, 0.0);
Ok(())
}
"ratio" => {
self.ratio = value.clamp(1.0, 20.0);
Ok(())
}
"attack_time" => {
self.attack_time = value.clamp(0.1, 100.0);
Ok(())
}
"release_time" => {
self.release_time = value.clamp(10.0, 1000.0);
Ok(())
}
"makeup_gain" => {
self.makeup_gain = value.clamp(0.0, 20.0);
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct NoiseGateEffect {
threshold: f32, attack_time: f32, release_time: f32, hold_time: f32, enabled: bool,
envelope: f32, hold_counter: usize, }
impl NoiseGateEffect {
pub fn new(threshold: f32, attack_ms: f32, hold_ms: f32, release_ms: f32) -> Self {
Self {
threshold: threshold.clamp(-80.0, -10.0),
attack_time: attack_ms.clamp(0.1, 50.0),
hold_time: hold_ms.clamp(0.0, 500.0),
release_time: release_ms.clamp(10.0, 1000.0),
enabled: true,
envelope: 0.0,
hold_counter: 0,
}
}
fn db_to_linear(db: f32) -> f32 {
10.0_f32.powf(db / 20.0)
}
fn linear_to_db(linear: f32) -> f32 {
20.0 * linear.abs().max(1e-10).log10()
}
}
impl AudioEffect for NoiseGateEffect {
fn name(&self) -> &str {
"noise_gate"
}
fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
let threshold_linear = Self::db_to_linear(self.threshold);
let attack_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.attack_time / 1000.0)).exp();
let release_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.release_time / 1000.0)).exp();
let hold_samples = (sample_rate as f32 * self.hold_time / 1000.0) as usize;
for sample in samples.iter_mut() {
let input_level = sample.abs();
if input_level > threshold_linear {
self.envelope = self.envelope + attack_coef * (1.0 - self.envelope);
self.hold_counter = hold_samples;
} else if self.hold_counter > 0 {
self.hold_counter -= 1;
self.envelope = 1.0;
} else {
self.envelope = self.envelope + release_coef * (0.0 - self.envelope);
}
*sample *= self.envelope;
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.envelope = 0.0;
self.hold_counter = 0;
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("threshold".to_string(), self.threshold);
params.insert("attack_time".to_string(), self.attack_time);
params.insert("hold_time".to_string(), self.hold_time);
params.insert("release_time".to_string(), self.release_time);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"threshold" => {
self.threshold = value.clamp(-80.0, -10.0);
Ok(())
}
"attack_time" => {
self.attack_time = value.clamp(0.1, 50.0);
Ok(())
}
"hold_time" => {
self.hold_time = value.clamp(0.0, 500.0);
Ok(())
}
"release_time" => {
self.release_time = value.clamp(10.0, 1000.0);
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct EqualizerEffect {
low_gain: f32, mid_gain: f32, high_gain: f32, low_freq: f32, high_freq: f32, enabled: bool,
low_filter_state: [f32; 2],
high_filter_state: [f32; 2],
}
impl EqualizerEffect {
pub fn new(low_gain_db: f32, mid_gain_db: f32, high_gain_db: f32) -> Self {
Self {
low_gain: low_gain_db.clamp(-12.0, 12.0),
mid_gain: mid_gain_db.clamp(-12.0, 12.0),
high_gain: high_gain_db.clamp(-12.0, 12.0),
low_freq: 250.0, high_freq: 4000.0, enabled: true,
low_filter_state: [0.0; 2],
high_filter_state: [0.0; 2],
}
}
fn db_to_linear(db: f32) -> f32 {
10.0_f32.powf(db / 20.0)
}
}
impl AudioEffect for EqualizerEffect {
fn name(&self) -> &str {
"equalizer"
}
fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
let low_gain_linear = Self::db_to_linear(self.low_gain);
let mid_gain_linear = Self::db_to_linear(self.mid_gain);
let high_gain_linear = Self::db_to_linear(self.high_gain);
let low_omega = 2.0 * std::f32::consts::PI * self.low_freq / sample_rate as f32;
let high_omega = 2.0 * std::f32::consts::PI * self.high_freq / sample_rate as f32;
let low_alpha = low_omega / (1.0 + low_omega);
let high_alpha = high_omega / (1.0 + high_omega);
for sample in samples.iter_mut() {
let input = *sample;
self.low_filter_state[1] =
self.low_filter_state[1] + low_alpha * (input - self.low_filter_state[1]);
let low_band = self.low_filter_state[1];
self.high_filter_state[0] =
low_alpha * (self.high_filter_state[0] + input - self.high_filter_state[1]);
self.high_filter_state[1] = input;
let high_band = self.high_filter_state[0];
let mid_band = input - low_band - high_band;
*sample = (low_band * low_gain_linear)
+ (mid_band * mid_gain_linear)
+ (high_band * high_gain_linear);
}
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.low_filter_state = [0.0; 2];
self.high_filter_state = [0.0; 2];
Ok(())
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_parameters(&self) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("low_gain".to_string(), self.low_gain);
params.insert("mid_gain".to_string(), self.mid_gain);
params.insert("high_gain".to_string(), self.high_gain);
params.insert("low_freq".to_string(), self.low_freq);
params.insert("high_freq".to_string(), self.high_freq);
params
}
fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
match name {
"low_gain" => {
self.low_gain = value.clamp(-12.0, 12.0);
Ok(())
}
"mid_gain" => {
self.mid_gain = value.clamp(-12.0, 12.0);
Ok(())
}
"high_gain" => {
self.high_gain = value.clamp(-12.0, 12.0);
Ok(())
}
"low_freq" => {
self.low_freq = value.clamp(50.0, 500.0);
Ok(())
}
"high_freq" => {
self.high_freq = value.clamp(2000.0, 10000.0);
Ok(())
}
_ => Err(VoirsError::config_error(format!(
"Unknown parameter: {name}"
))),
}
}
}
pub struct EffectChain {
effects: Vec<Box<dyn AudioEffect>>,
enabled: bool,
}
impl EffectChain {
pub fn new() -> Self {
Self {
effects: Vec::new(),
enabled: true,
}
}
pub fn add_effect(&mut self, effect: Box<dyn AudioEffect>) {
self.effects.push(effect);
}
pub fn remove_effect(&mut self, name: &str) -> Result<()> {
let initial_len = self.effects.len();
self.effects.retain(|effect| effect.name() != name);
if self.effects.len() == initial_len {
return Err(VoirsError::config_error(format!(
"Effect '{}' not found",
name
)));
}
Ok(())
}
pub fn get_effect_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioEffect>> {
self.effects.iter_mut().find(|effect| effect.name() == name)
}
pub fn process(&mut self, audio_data: &mut AudioData) -> Result<()> {
if !self.enabled {
return Ok(());
}
let mut samples: Vec<f32> = audio_data
.samples
.iter()
.map(|&s| s as f32 / i16::MAX as f32)
.collect();
for effect in &mut self.effects {
if effect.is_enabled() {
effect.process(&mut samples, audio_data.sample_rate)?;
}
}
audio_data.samples = samples
.iter()
.map(|&s| (s * i16::MAX as f32) as i16)
.collect();
Ok(())
}
pub fn process_samples(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
if !self.enabled {
return Ok(());
}
for effect in &mut self.effects {
if effect.is_enabled() {
effect.process(samples, sample_rate)?;
}
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
for effect in &mut self.effects {
effect.reset()?;
}
Ok(())
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn get_effect_names(&self) -> Vec<String> {
self.effects
.iter()
.map(|effect| effect.name().to_string())
.collect()
}
pub fn len(&self) -> usize {
self.effects.len()
}
pub fn is_empty(&self) -> bool {
self.effects.is_empty()
}
}
impl Default for EffectChain {
fn default() -> Self {
Self::new()
}
}
pub fn create_preset_effect_chain(preset_name: &str) -> Result<EffectChain> {
let mut chain = EffectChain::new();
match preset_name {
"vocal_enhancement" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-45.0, 2.0, 50.0, 100.0)));
chain.add_effect(Box::new(EqualizerEffect::new(0.0, 2.0, 3.0))); chain.add_effect(Box::new(
CompressorEffect::new(-18.0, 3.0, 5.0, 50.0).with_makeup_gain(4.0),
));
chain.add_effect(Box::new(LowPassFilter::new(8000.0, 0.7)));
chain.add_effect(Box::new(ReverbEffect::new(0.3, 0.4, 0.2)));
}
"podcast" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-50.0, 3.0, 100.0, 200.0)));
chain.add_effect(Box::new(EqualizerEffect::new(-1.0, 3.0, 1.0))); chain.add_effect(Box::new(
CompressorEffect::new(-20.0, 4.0, 10.0, 100.0).with_makeup_gain(6.0),
));
chain.add_effect(Box::new(LowPassFilter::new(7000.0, 0.8)));
}
"radio" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-40.0, 1.0, 20.0, 80.0)));
chain.add_effect(Box::new(EqualizerEffect::new(3.0, 4.0, 2.0))); chain.add_effect(Box::new(
CompressorEffect::new(-15.0, 6.0, 3.0, 30.0).with_makeup_gain(8.0),
));
chain.add_effect(Box::new(LowPassFilter::new(6000.0, 0.9)));
chain.add_effect(Box::new(ReverbEffect::new(0.1, 0.8, 0.1)));
}
"warm" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-55.0, 5.0, 150.0, 300.0)));
chain.add_effect(Box::new(EqualizerEffect::new(2.0, 0.0, -2.0))); chain.add_effect(Box::new(
CompressorEffect::new(-24.0, 2.5, 15.0, 150.0).with_makeup_gain(3.0),
));
chain.add_effect(Box::new(LowPassFilter::new(5000.0, 0.6)));
chain.add_effect(Box::new(ReverbEffect::new(0.4, 0.3, 0.3)));
}
"clean" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-60.0, 5.0, 100.0, 250.0)));
chain.add_effect(Box::new(VolumeEffect::new(1.0)));
}
"broadcast" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-42.0, 2.0, 75.0, 150.0)));
chain.add_effect(Box::new(EqualizerEffect::new(0.0, 4.0, 2.0))); chain.add_effect(Box::new(
CompressorEffect::new(-16.0, 5.0, 5.0, 50.0).with_makeup_gain(7.0),
));
chain.add_effect(Box::new(LowPassFilter::new(7500.0, 0.75)));
}
"audiobook" => {
chain.add_effect(Box::new(NoiseGateEffect::new(-48.0, 4.0, 120.0, 250.0)));
chain.add_effect(Box::new(EqualizerEffect::new(1.0, 2.0, 0.0))); chain.add_effect(Box::new(
CompressorEffect::new(-22.0, 3.5, 12.0, 120.0).with_makeup_gain(5.0),
));
chain.add_effect(Box::new(LowPassFilter::new(6500.0, 0.7)));
}
"telephone" => {
chain.add_effect(Box::new(EqualizerEffect::new(-6.0, 8.0, -10.0))); chain.add_effect(Box::new(
CompressorEffect::new(-12.0, 8.0, 2.0, 20.0).with_makeup_gain(10.0),
));
chain.add_effect(Box::new(LowPassFilter::new(3400.0, 1.2)));
}
_ => {
return Err(VoirsError::config_error(format!(
"Unknown preset: {}. Available presets: vocal_enhancement, podcast, radio, warm, clean, broadcast, audiobook, telephone",
preset_name
)));
}
}
Ok(chain)
}
#[cfg(test)]
mod tests {
use super::AudioData;
use super::*;
#[test]
fn test_effect_config() {
let config = EffectConfig::new("volume")
.with_parameter("gain", 1.5)
.with_enabled(true)
.with_priority(10);
assert_eq!(config.effect_type, "volume");
assert_eq!(config.get_parameter("gain"), Some(1.5));
assert!(config.enabled);
assert_eq!(config.priority, 10);
}
#[test]
fn test_volume_effect() {
let mut effect = VolumeEffect::new(2.0);
assert_eq!(effect.name(), "volume");
assert!(effect.is_enabled());
let mut samples = vec![0.1, 0.2, 0.3, 0.4];
effect.process(&mut samples, 22050).unwrap();
assert_eq!(samples, vec![0.2, 0.4, 0.6, 0.8]);
effect.set_parameter("gain", 0.5).unwrap();
let params = effect.get_parameters();
assert_eq!(params.get("gain"), Some(&0.5));
}
#[test]
fn test_lowpass_filter() {
let mut filter = LowPassFilter::new(1000.0, 0.7);
assert_eq!(filter.name(), "lowpass");
let mut samples = vec![1.0, -1.0, 1.0, -1.0]; filter.process(&mut samples, 22050).unwrap();
assert!(samples.iter().all(|&s| s.abs() < 1.0));
filter.reset().unwrap();
}
#[test]
fn test_reverb_effect() {
let mut reverb = ReverbEffect::new(0.1, 0.2, 0.6); assert_eq!(reverb.name(), "reverb");
let mut samples = vec![1.0; 10]; samples.extend(vec![0.0; 100]);
let original_samples = samples.clone();
reverb.process(&mut samples, 22050).unwrap();
assert_ne!(samples, original_samples);
assert_ne!(samples[0], 1.0);
}
#[test]
fn test_effect_chain() {
let mut chain = EffectChain::new();
assert!(chain.is_empty());
chain.add_effect(Box::new(VolumeEffect::new(2.0)));
chain.add_effect(Box::new(LowPassFilter::new(5000.0, 0.7)));
assert_eq!(chain.len(), 2);
assert!(!chain.is_empty());
let effect_names = chain.get_effect_names();
assert!(effect_names.contains(&"volume".to_string()));
assert!(effect_names.contains(&"lowpass".to_string()));
let mut audio_data = AudioData {
samples: vec![1000, 2000, 3000, 4000],
sample_rate: 22050,
channels: 1,
};
chain.process(&mut audio_data).unwrap();
assert_ne!(audio_data.samples, vec![1000, 2000, 3000, 4000]);
chain.remove_effect("volume").unwrap();
assert_eq!(chain.len(), 1);
}
#[test]
fn test_preset_effect_chains() {
let presets = vec!["vocal_enhancement", "podcast", "radio", "warm", "clean"];
for preset in presets {
let chain = create_preset_effect_chain(preset).unwrap();
assert!(!chain.is_empty());
}
assert!(create_preset_effect_chain("unknown").is_err());
}
#[test]
fn test_effect_chain_enable_disable() {
let mut chain = EffectChain::new();
chain.add_effect(Box::new(VolumeEffect::new(2.0)));
let mut samples = vec![0.1, 0.2, 0.3, 0.4];
let original_samples = samples.clone();
chain.process_samples(&mut samples, 22050).unwrap();
assert_ne!(samples, original_samples);
samples = original_samples.clone();
chain.set_enabled(false);
chain.process_samples(&mut samples, 22050).unwrap();
assert_eq!(samples, original_samples); }
}