Skip to main content

voirs_cli/audio/
effects.rs

1//! Audio effects processing implementation.
2//!
3//! This module provides professional-grade audio effects for TTS post-processing:
4//!
5//! ## Basic Effects
6//! - **VolumeEffect**: Simple gain adjustment
7//! - **LowPassFilter**: Frequency filtering for bandwidth control
8//! - **ReverbEffect**: Spatial audio processing with delay-based reverb
9//!
10//! ## Dynamic Processing
11//! - **CompressorEffect**: Dynamic range compression with envelope follower
12//!   - Reduces volume differences between loud and quiet parts
13//!   - Essential for consistent podcast/broadcast audio
14//!   - Configurable threshold, ratio, attack/release times, and makeup gain
15//!
16//! - **NoiseGateEffect**: Removes low-level noise and background sounds
17//!   - Opens/closes based on signal threshold
18//!   - Configurable attack, hold, and release times
19//!   - Perfect for cleaning up TTS output
20//!
21//! ## Frequency Shaping
22//! - **EqualizerEffect**: 3-band EQ for tonal shaping
23//!   - Independent control of low, mid, and high frequencies
24//!   - Adjustable crossover frequencies
25//!   - ±12dB gain range per band
26//!
27//! ## Effect Chaining
28//! Use `EffectChain` to combine multiple effects in series:
29//! ```rust,ignore
30//! let mut chain = EffectChain::new();
31//! chain.add_effect(Box::new(NoiseGateEffect::new(-45.0, 2.0, 50.0, 100.0)));
32//! chain.add_effect(Box::new(CompressorEffect::new(-18.0, 3.0, 5.0, 50.0)));
33//! chain.add_effect(Box::new(EqualizerEffect::new(0.0, 2.0, 1.0)));
34//! ```
35//!
36//! ## Presets
37//! Available presets via `create_preset_effect_chain()`:
38//! - `vocal_enhancement`: Professional vocal processing
39//! - `podcast`: Optimized for speech with clarity
40//! - `broadcast`: Broadcast-quality compression and EQ
41//! - `audiobook`: Natural narration sound
42//! - `radio`: Heavy compression for radio-style sound
43//! - `warm`: Gentle processing with warm tone
44//! - `clean`: Minimal processing, noise gate only
45//! - `telephone`: Lo-fi telephone simulation
46
47use super::AudioData;
48use std::collections::HashMap;
49use voirs_sdk::{Result, VoirsError};
50
51/// Audio effect configuration
52#[derive(Debug, Clone)]
53pub struct EffectConfig {
54    /// Effect type identifier
55    pub effect_type: String,
56    /// Effect parameters
57    pub parameters: HashMap<String, f32>,
58    /// Whether the effect is enabled
59    pub enabled: bool,
60    /// Effect processing order priority
61    pub priority: i32,
62}
63
64impl EffectConfig {
65    /// Create a new effect configuration
66    pub fn new(effect_type: &str) -> Self {
67        Self {
68            effect_type: effect_type.to_string(),
69            parameters: HashMap::new(),
70            enabled: true,
71            priority: 0,
72        }
73    }
74
75    /// Set a parameter value
76    pub fn with_parameter(mut self, name: &str, value: f32) -> Self {
77        self.parameters.insert(name.to_string(), value);
78        self
79    }
80
81    /// Set enabled state
82    pub fn with_enabled(mut self, enabled: bool) -> Self {
83        self.enabled = enabled;
84        self
85    }
86
87    /// Set priority
88    pub fn with_priority(mut self, priority: i32) -> Self {
89        self.priority = priority;
90        self
91    }
92
93    /// Get parameter value
94    pub fn get_parameter(&self, name: &str) -> Option<f32> {
95        self.parameters.get(name).copied()
96    }
97}
98
99/// Audio effect trait
100pub trait AudioEffect: Send + Sync {
101    /// Get effect name
102    fn name(&self) -> &str;
103
104    /// Process audio samples
105    fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()>;
106
107    /// Reset effect state
108    fn reset(&mut self) -> Result<()>;
109
110    /// Check if effect is enabled
111    fn is_enabled(&self) -> bool;
112
113    /// Set enabled state
114    fn set_enabled(&mut self, enabled: bool);
115
116    /// Get effect parameters
117    fn get_parameters(&self) -> HashMap<String, f32>;
118
119    /// Set effect parameter
120    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()>;
121}
122
123/// Volume effect for adjusting audio level
124pub struct VolumeEffect {
125    gain: f32,
126    enabled: bool,
127}
128
129impl VolumeEffect {
130    /// Create a new volume effect
131    pub fn new(gain: f32) -> Self {
132        Self {
133            gain: gain.clamp(0.0, 2.0), // Limit to reasonable range
134            enabled: true,
135        }
136    }
137}
138
139impl AudioEffect for VolumeEffect {
140    fn name(&self) -> &str {
141        "volume"
142    }
143
144    fn process(&mut self, samples: &mut [f32], _sample_rate: u32) -> Result<()> {
145        if !self.enabled {
146            return Ok(());
147        }
148
149        for sample in samples.iter_mut() {
150            *sample *= self.gain;
151        }
152
153        Ok(())
154    }
155
156    fn reset(&mut self) -> Result<()> {
157        // Volume effect has no state to reset
158        Ok(())
159    }
160
161    fn is_enabled(&self) -> bool {
162        self.enabled
163    }
164
165    fn set_enabled(&mut self, enabled: bool) {
166        self.enabled = enabled;
167    }
168
169    fn get_parameters(&self) -> HashMap<String, f32> {
170        let mut params = HashMap::new();
171        params.insert("gain".to_string(), self.gain);
172        params
173    }
174
175    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
176        match name {
177            "gain" => {
178                self.gain = value.clamp(0.0, 2.0);
179                Ok(())
180            }
181            _ => Err(VoirsError::config_error(format!(
182                "Unknown parameter: {name}"
183            ))),
184        }
185    }
186}
187
188/// Low-pass filter effect
189pub struct LowPassFilter {
190    cutoff_freq: f32,
191    resonance: f32,
192    enabled: bool,
193    // Filter state
194    prev_input: f32,
195    prev_output: f32,
196}
197
198impl LowPassFilter {
199    /// Create a new low-pass filter
200    pub fn new(cutoff_freq: f32, resonance: f32) -> Self {
201        Self {
202            cutoff_freq: cutoff_freq.clamp(20.0, 20000.0),
203            resonance: resonance.clamp(0.1, 10.0),
204            enabled: true,
205            prev_input: 0.0,
206            prev_output: 0.0,
207        }
208    }
209}
210
211impl AudioEffect for LowPassFilter {
212    fn name(&self) -> &str {
213        "lowpass"
214    }
215
216    fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
217        if !self.enabled {
218            return Ok(());
219        }
220
221        // Simple first-order low-pass filter
222        let dt = 1.0 / sample_rate as f32;
223        let rc = 1.0 / (2.0 * std::f32::consts::PI * self.cutoff_freq);
224        let alpha = dt / (rc + dt);
225
226        for sample in samples.iter_mut() {
227            let output = alpha * *sample + (1.0 - alpha) * self.prev_output;
228            self.prev_output = output;
229            *sample = output;
230        }
231
232        Ok(())
233    }
234
235    fn reset(&mut self) -> Result<()> {
236        self.prev_input = 0.0;
237        self.prev_output = 0.0;
238        Ok(())
239    }
240
241    fn is_enabled(&self) -> bool {
242        self.enabled
243    }
244
245    fn set_enabled(&mut self, enabled: bool) {
246        self.enabled = enabled;
247    }
248
249    fn get_parameters(&self) -> HashMap<String, f32> {
250        let mut params = HashMap::new();
251        params.insert("cutoff_freq".to_string(), self.cutoff_freq);
252        params.insert("resonance".to_string(), self.resonance);
253        params
254    }
255
256    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
257        match name {
258            "cutoff_freq" => {
259                self.cutoff_freq = value.clamp(20.0, 20000.0);
260                Ok(())
261            }
262            "resonance" => {
263                self.resonance = value.clamp(0.1, 10.0);
264                Ok(())
265            }
266            _ => Err(VoirsError::config_error(format!(
267                "Unknown parameter: {name}"
268            ))),
269        }
270    }
271}
272
273/// Reverb effect for spatial audio processing
274pub struct ReverbEffect {
275    room_size: f32,
276    damping: f32,
277    wet_level: f32,
278    dry_level: f32,
279    enabled: bool,
280    // Delay lines for reverb (simplified)
281    delay_buffer: Vec<f32>,
282    delay_index: usize,
283}
284
285impl ReverbEffect {
286    /// Create a new reverb effect
287    pub fn new(room_size: f32, damping: f32, wet_level: f32) -> Self {
288        let delay_samples = (room_size * 22050.0) as usize; // Assume 22kHz for buffer size
289        let clamped_wet = wet_level.clamp(0.0, 1.0);
290
291        Self {
292            room_size: room_size.clamp(0.0, 1.0),
293            damping: damping.clamp(0.0, 1.0),
294            wet_level: clamped_wet,
295            dry_level: 1.0 - clamped_wet,
296            enabled: true,
297            delay_buffer: vec![0.0; delay_samples.max(1024)],
298            delay_index: 0,
299        }
300    }
301}
302
303impl AudioEffect for ReverbEffect {
304    fn name(&self) -> &str {
305        "reverb"
306    }
307
308    fn process(&mut self, samples: &mut [f32], _sample_rate: u32) -> Result<()> {
309        if !self.enabled {
310            return Ok(());
311        }
312
313        for sample in samples.iter_mut() {
314            // Get delayed sample
315            let delayed = self.delay_buffer[self.delay_index];
316
317            // Apply damping to the delayed signal
318            let reverb_sample = delayed * (1.0 - self.damping);
319
320            // Mix dry and wet signals
321            let output = (*sample * self.dry_level) + (reverb_sample * self.wet_level);
322
323            // Write input + feedback to delay buffer
324            self.delay_buffer[self.delay_index] = *sample + (reverb_sample * self.room_size * 0.5);
325
326            // Update delay index
327            self.delay_index = (self.delay_index + 1) % self.delay_buffer.len();
328
329            *sample = output;
330        }
331
332        Ok(())
333    }
334
335    fn reset(&mut self) -> Result<()> {
336        self.delay_buffer.fill(0.0);
337        self.delay_index = 0;
338        Ok(())
339    }
340
341    fn is_enabled(&self) -> bool {
342        self.enabled
343    }
344
345    fn set_enabled(&mut self, enabled: bool) {
346        self.enabled = enabled;
347    }
348
349    fn get_parameters(&self) -> HashMap<String, f32> {
350        let mut params = HashMap::new();
351        params.insert("room_size".to_string(), self.room_size);
352        params.insert("damping".to_string(), self.damping);
353        params.insert("wet_level".to_string(), self.wet_level);
354        params.insert("dry_level".to_string(), self.dry_level);
355        params
356    }
357
358    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
359        match name {
360            "room_size" => {
361                self.room_size = value.clamp(0.0, 1.0);
362                Ok(())
363            }
364            "damping" => {
365                self.damping = value.clamp(0.0, 1.0);
366                Ok(())
367            }
368            "wet_level" => {
369                self.wet_level = value.clamp(0.0, 1.0);
370                self.dry_level = 1.0 - self.wet_level;
371                Ok(())
372            }
373            _ => Err(VoirsError::config_error(format!(
374                "Unknown parameter: {name}"
375            ))),
376        }
377    }
378}
379
380/// Compressor effect for dynamic range control
381pub struct CompressorEffect {
382    threshold: f32,    // dB threshold for compression
383    ratio: f32,        // Compression ratio (e.g., 4.0 means 4:1)
384    attack_time: f32,  // Attack time in ms
385    release_time: f32, // Release time in ms
386    makeup_gain: f32,  // Post-compression gain in dB
387    enabled: bool,
388    envelope: f32, // Current envelope follower value
389}
390
391impl CompressorEffect {
392    /// Create a new compressor effect
393    pub fn new(threshold: f32, ratio: f32, attack_ms: f32, release_ms: f32) -> Self {
394        Self {
395            threshold: threshold.clamp(-60.0, 0.0),
396            ratio: ratio.clamp(1.0, 20.0),
397            attack_time: attack_ms.clamp(0.1, 100.0),
398            release_time: release_ms.clamp(10.0, 1000.0),
399            makeup_gain: 0.0,
400            enabled: true,
401            envelope: 0.0,
402        }
403    }
404
405    /// Set makeup gain
406    pub fn with_makeup_gain(mut self, gain_db: f32) -> Self {
407        self.makeup_gain = gain_db.clamp(0.0, 20.0);
408        self
409    }
410
411    /// Convert dB to linear amplitude
412    fn db_to_linear(db: f32) -> f32 {
413        10.0_f32.powf(db / 20.0)
414    }
415
416    /// Convert linear amplitude to dB
417    fn linear_to_db(linear: f32) -> f32 {
418        20.0 * linear.abs().max(1e-10).log10()
419    }
420}
421
422impl AudioEffect for CompressorEffect {
423    fn name(&self) -> &str {
424        "compressor"
425    }
426
427    fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
428        if !self.enabled {
429            return Ok(());
430        }
431
432        let attack_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.attack_time / 1000.0)).exp();
433        let release_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.release_time / 1000.0)).exp();
434
435        for sample in samples.iter_mut() {
436            let input_level_db = Self::linear_to_db(*sample);
437
438            // Envelope follower
439            let coef = if input_level_db > Self::linear_to_db(self.envelope) {
440                attack_coef
441            } else {
442                release_coef
443            };
444            self.envelope = self.envelope + coef * (sample.abs() - self.envelope);
445
446            // Calculate gain reduction
447            let envelope_db = Self::linear_to_db(self.envelope);
448            let gain_reduction = if envelope_db > self.threshold {
449                (envelope_db - self.threshold) * (1.0 - 1.0 / self.ratio)
450            } else {
451                0.0
452            };
453
454            // Apply compression and makeup gain
455            let total_gain_db = -gain_reduction + self.makeup_gain;
456            let gain = Self::db_to_linear(total_gain_db);
457
458            *sample *= gain;
459        }
460
461        Ok(())
462    }
463
464    fn reset(&mut self) -> Result<()> {
465        self.envelope = 0.0;
466        Ok(())
467    }
468
469    fn is_enabled(&self) -> bool {
470        self.enabled
471    }
472
473    fn set_enabled(&mut self, enabled: bool) {
474        self.enabled = enabled;
475    }
476
477    fn get_parameters(&self) -> HashMap<String, f32> {
478        let mut params = HashMap::new();
479        params.insert("threshold".to_string(), self.threshold);
480        params.insert("ratio".to_string(), self.ratio);
481        params.insert("attack_time".to_string(), self.attack_time);
482        params.insert("release_time".to_string(), self.release_time);
483        params.insert("makeup_gain".to_string(), self.makeup_gain);
484        params
485    }
486
487    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
488        match name {
489            "threshold" => {
490                self.threshold = value.clamp(-60.0, 0.0);
491                Ok(())
492            }
493            "ratio" => {
494                self.ratio = value.clamp(1.0, 20.0);
495                Ok(())
496            }
497            "attack_time" => {
498                self.attack_time = value.clamp(0.1, 100.0);
499                Ok(())
500            }
501            "release_time" => {
502                self.release_time = value.clamp(10.0, 1000.0);
503                Ok(())
504            }
505            "makeup_gain" => {
506                self.makeup_gain = value.clamp(0.0, 20.0);
507                Ok(())
508            }
509            _ => Err(VoirsError::config_error(format!(
510                "Unknown parameter: {name}"
511            ))),
512        }
513    }
514}
515
516/// Noise gate effect for removing low-level noise
517pub struct NoiseGateEffect {
518    threshold: f32,    // dB threshold below which audio is muted
519    attack_time: f32,  // Attack time in ms
520    release_time: f32, // Release time in ms
521    hold_time: f32,    // Hold time in ms
522    enabled: bool,
523    envelope: f32,       // Current envelope follower value
524    hold_counter: usize, // Samples remaining in hold phase
525}
526
527impl NoiseGateEffect {
528    /// Create a new noise gate effect
529    pub fn new(threshold: f32, attack_ms: f32, hold_ms: f32, release_ms: f32) -> Self {
530        Self {
531            threshold: threshold.clamp(-80.0, -10.0),
532            attack_time: attack_ms.clamp(0.1, 50.0),
533            hold_time: hold_ms.clamp(0.0, 500.0),
534            release_time: release_ms.clamp(10.0, 1000.0),
535            enabled: true,
536            envelope: 0.0,
537            hold_counter: 0,
538        }
539    }
540
541    /// Convert dB to linear amplitude
542    fn db_to_linear(db: f32) -> f32 {
543        10.0_f32.powf(db / 20.0)
544    }
545
546    /// Convert linear amplitude to dB
547    fn linear_to_db(linear: f32) -> f32 {
548        20.0 * linear.abs().max(1e-10).log10()
549    }
550}
551
552impl AudioEffect for NoiseGateEffect {
553    fn name(&self) -> &str {
554        "noise_gate"
555    }
556
557    fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
558        if !self.enabled {
559            return Ok(());
560        }
561
562        let threshold_linear = Self::db_to_linear(self.threshold);
563        let attack_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.attack_time / 1000.0)).exp();
564        let release_coef = 1.0 - (-1.0 / (sample_rate as f32 * self.release_time / 1000.0)).exp();
565        let hold_samples = (sample_rate as f32 * self.hold_time / 1000.0) as usize;
566
567        for sample in samples.iter_mut() {
568            let input_level = sample.abs();
569
570            // Update envelope
571            if input_level > threshold_linear {
572                // Above threshold - open gate quickly
573                self.envelope = self.envelope + attack_coef * (1.0 - self.envelope);
574                self.hold_counter = hold_samples;
575            } else if self.hold_counter > 0 {
576                // In hold phase - keep gate open
577                self.hold_counter -= 1;
578                self.envelope = 1.0;
579            } else {
580                // Below threshold - close gate slowly
581                self.envelope = self.envelope + release_coef * (0.0 - self.envelope);
582            }
583
584            // Apply gate
585            *sample *= self.envelope;
586        }
587
588        Ok(())
589    }
590
591    fn reset(&mut self) -> Result<()> {
592        self.envelope = 0.0;
593        self.hold_counter = 0;
594        Ok(())
595    }
596
597    fn is_enabled(&self) -> bool {
598        self.enabled
599    }
600
601    fn set_enabled(&mut self, enabled: bool) {
602        self.enabled = enabled;
603    }
604
605    fn get_parameters(&self) -> HashMap<String, f32> {
606        let mut params = HashMap::new();
607        params.insert("threshold".to_string(), self.threshold);
608        params.insert("attack_time".to_string(), self.attack_time);
609        params.insert("hold_time".to_string(), self.hold_time);
610        params.insert("release_time".to_string(), self.release_time);
611        params
612    }
613
614    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
615        match name {
616            "threshold" => {
617                self.threshold = value.clamp(-80.0, -10.0);
618                Ok(())
619            }
620            "attack_time" => {
621                self.attack_time = value.clamp(0.1, 50.0);
622                Ok(())
623            }
624            "hold_time" => {
625                self.hold_time = value.clamp(0.0, 500.0);
626                Ok(())
627            }
628            "release_time" => {
629                self.release_time = value.clamp(10.0, 1000.0);
630                Ok(())
631            }
632            _ => Err(VoirsError::config_error(format!(
633                "Unknown parameter: {name}"
634            ))),
635        }
636    }
637}
638
639/// Three-band equalizer effect for frequency shaping
640pub struct EqualizerEffect {
641    low_gain: f32,  // Low frequency gain in dB (-12 to +12)
642    mid_gain: f32,  // Mid frequency gain in dB
643    high_gain: f32, // High frequency gain in dB
644    low_freq: f32,  // Low band center frequency (Hz)
645    high_freq: f32, // High band center frequency (Hz)
646    enabled: bool,
647    // Filter state variables
648    low_filter_state: [f32; 2],
649    high_filter_state: [f32; 2],
650}
651
652impl EqualizerEffect {
653    /// Create a new 3-band equalizer
654    pub fn new(low_gain_db: f32, mid_gain_db: f32, high_gain_db: f32) -> Self {
655        Self {
656            low_gain: low_gain_db.clamp(-12.0, 12.0),
657            mid_gain: mid_gain_db.clamp(-12.0, 12.0),
658            high_gain: high_gain_db.clamp(-12.0, 12.0),
659            low_freq: 250.0,   // Default low band at 250Hz
660            high_freq: 4000.0, // Default high band at 4kHz
661            enabled: true,
662            low_filter_state: [0.0; 2],
663            high_filter_state: [0.0; 2],
664        }
665    }
666
667    /// Convert dB to linear gain
668    fn db_to_linear(db: f32) -> f32 {
669        10.0_f32.powf(db / 20.0)
670    }
671}
672
673impl AudioEffect for EqualizerEffect {
674    fn name(&self) -> &str {
675        "equalizer"
676    }
677
678    fn process(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
679        if !self.enabled {
680            return Ok(());
681        }
682
683        let low_gain_linear = Self::db_to_linear(self.low_gain);
684        let mid_gain_linear = Self::db_to_linear(self.mid_gain);
685        let high_gain_linear = Self::db_to_linear(self.high_gain);
686
687        // Simple first-order filter coefficients
688        let low_omega = 2.0 * std::f32::consts::PI * self.low_freq / sample_rate as f32;
689        let high_omega = 2.0 * std::f32::consts::PI * self.high_freq / sample_rate as f32;
690
691        let low_alpha = low_omega / (1.0 + low_omega);
692        let high_alpha = high_omega / (1.0 + high_omega);
693
694        for sample in samples.iter_mut() {
695            let input = *sample;
696
697            // Low-pass filter for low band
698            self.low_filter_state[1] =
699                self.low_filter_state[1] + low_alpha * (input - self.low_filter_state[1]);
700            let low_band = self.low_filter_state[1];
701
702            // High-pass filter for high band
703            self.high_filter_state[0] =
704                low_alpha * (self.high_filter_state[0] + input - self.high_filter_state[1]);
705            self.high_filter_state[1] = input;
706            let high_band = self.high_filter_state[0];
707
708            // Mid band is what's left
709            let mid_band = input - low_band - high_band;
710
711            // Apply gains and mix
712            *sample = (low_band * low_gain_linear)
713                + (mid_band * mid_gain_linear)
714                + (high_band * high_gain_linear);
715        }
716
717        Ok(())
718    }
719
720    fn reset(&mut self) -> Result<()> {
721        self.low_filter_state = [0.0; 2];
722        self.high_filter_state = [0.0; 2];
723        Ok(())
724    }
725
726    fn is_enabled(&self) -> bool {
727        self.enabled
728    }
729
730    fn set_enabled(&mut self, enabled: bool) {
731        self.enabled = enabled;
732    }
733
734    fn get_parameters(&self) -> HashMap<String, f32> {
735        let mut params = HashMap::new();
736        params.insert("low_gain".to_string(), self.low_gain);
737        params.insert("mid_gain".to_string(), self.mid_gain);
738        params.insert("high_gain".to_string(), self.high_gain);
739        params.insert("low_freq".to_string(), self.low_freq);
740        params.insert("high_freq".to_string(), self.high_freq);
741        params
742    }
743
744    fn set_parameter(&mut self, name: &str, value: f32) -> Result<()> {
745        match name {
746            "low_gain" => {
747                self.low_gain = value.clamp(-12.0, 12.0);
748                Ok(())
749            }
750            "mid_gain" => {
751                self.mid_gain = value.clamp(-12.0, 12.0);
752                Ok(())
753            }
754            "high_gain" => {
755                self.high_gain = value.clamp(-12.0, 12.0);
756                Ok(())
757            }
758            "low_freq" => {
759                self.low_freq = value.clamp(50.0, 500.0);
760                Ok(())
761            }
762            "high_freq" => {
763                self.high_freq = value.clamp(2000.0, 10000.0);
764                Ok(())
765            }
766            _ => Err(VoirsError::config_error(format!(
767                "Unknown parameter: {name}"
768            ))),
769        }
770    }
771}
772
773/// Effect chain for processing multiple effects in sequence
774pub struct EffectChain {
775    effects: Vec<Box<dyn AudioEffect>>,
776    enabled: bool,
777}
778
779impl EffectChain {
780    /// Create a new empty effect chain
781    pub fn new() -> Self {
782        Self {
783            effects: Vec::new(),
784            enabled: true,
785        }
786    }
787
788    /// Add an effect to the chain
789    pub fn add_effect(&mut self, effect: Box<dyn AudioEffect>) {
790        self.effects.push(effect);
791    }
792
793    /// Remove an effect by name
794    pub fn remove_effect(&mut self, name: &str) -> Result<()> {
795        let initial_len = self.effects.len();
796        self.effects.retain(|effect| effect.name() != name);
797
798        if self.effects.len() == initial_len {
799            return Err(VoirsError::config_error(format!(
800                "Effect '{}' not found",
801                name
802            )));
803        }
804
805        Ok(())
806    }
807
808    /// Get effect by name
809    pub fn get_effect_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioEffect>> {
810        self.effects.iter_mut().find(|effect| effect.name() == name)
811    }
812
813    /// Process audio through the entire effect chain
814    pub fn process(&mut self, audio_data: &mut AudioData) -> Result<()> {
815        if !self.enabled {
816            return Ok(());
817        }
818
819        let mut samples: Vec<f32> = audio_data
820            .samples
821            .iter()
822            .map(|&s| s as f32 / i16::MAX as f32)
823            .collect();
824
825        // Process through each effect in order
826        for effect in &mut self.effects {
827            if effect.is_enabled() {
828                effect.process(&mut samples, audio_data.sample_rate)?;
829            }
830        }
831
832        // Convert back to i16
833        audio_data.samples = samples
834            .iter()
835            .map(|&s| (s * i16::MAX as f32) as i16)
836            .collect();
837
838        Ok(())
839    }
840
841    /// Process raw f32 samples
842    pub fn process_samples(&mut self, samples: &mut [f32], sample_rate: u32) -> Result<()> {
843        if !self.enabled {
844            return Ok(());
845        }
846
847        for effect in &mut self.effects {
848            if effect.is_enabled() {
849                effect.process(samples, sample_rate)?;
850            }
851        }
852
853        Ok(())
854    }
855
856    /// Reset all effects in the chain
857    pub fn reset(&mut self) -> Result<()> {
858        for effect in &mut self.effects {
859            effect.reset()?;
860        }
861        Ok(())
862    }
863
864    /// Enable or disable the entire effect chain
865    pub fn set_enabled(&mut self, enabled: bool) {
866        self.enabled = enabled;
867    }
868
869    /// Check if the effect chain is enabled
870    pub fn is_enabled(&self) -> bool {
871        self.enabled
872    }
873
874    /// Get list of effect names in the chain
875    pub fn get_effect_names(&self) -> Vec<String> {
876        self.effects
877            .iter()
878            .map(|effect| effect.name().to_string())
879            .collect()
880    }
881
882    /// Get number of effects in the chain
883    pub fn len(&self) -> usize {
884        self.effects.len()
885    }
886
887    /// Check if the chain is empty
888    pub fn is_empty(&self) -> bool {
889        self.effects.is_empty()
890    }
891}
892
893impl Default for EffectChain {
894    fn default() -> Self {
895        Self::new()
896    }
897}
898
899/// Create a preset effect chain for common use cases
900pub fn create_preset_effect_chain(preset_name: &str) -> Result<EffectChain> {
901    let mut chain = EffectChain::new();
902
903    match preset_name {
904        "vocal_enhancement" => {
905            // Professional vocal enhancement with compression and EQ
906            chain.add_effect(Box::new(NoiseGateEffect::new(-45.0, 2.0, 50.0, 100.0)));
907            chain.add_effect(Box::new(EqualizerEffect::new(0.0, 2.0, 3.0))); // Boost mids and highs
908            chain.add_effect(Box::new(
909                CompressorEffect::new(-18.0, 3.0, 5.0, 50.0).with_makeup_gain(4.0),
910            ));
911            chain.add_effect(Box::new(LowPassFilter::new(8000.0, 0.7)));
912            chain.add_effect(Box::new(ReverbEffect::new(0.3, 0.4, 0.2)));
913        }
914        "podcast" => {
915            // Optimized for speech/podcast with professional dynamics
916            chain.add_effect(Box::new(NoiseGateEffect::new(-50.0, 3.0, 100.0, 200.0)));
917            chain.add_effect(Box::new(EqualizerEffect::new(-1.0, 3.0, 1.0))); // Boost mid-range clarity
918            chain.add_effect(Box::new(
919                CompressorEffect::new(-20.0, 4.0, 10.0, 100.0).with_makeup_gain(6.0),
920            ));
921            chain.add_effect(Box::new(LowPassFilter::new(7000.0, 0.8)));
922        }
923        "radio" => {
924            // Radio-style processing with heavy compression
925            chain.add_effect(Box::new(NoiseGateEffect::new(-40.0, 1.0, 20.0, 80.0)));
926            chain.add_effect(Box::new(EqualizerEffect::new(3.0, 4.0, 2.0))); // Pumped sound
927            chain.add_effect(Box::new(
928                CompressorEffect::new(-15.0, 6.0, 3.0, 30.0).with_makeup_gain(8.0),
929            ));
930            chain.add_effect(Box::new(LowPassFilter::new(6000.0, 0.9)));
931            chain.add_effect(Box::new(ReverbEffect::new(0.1, 0.8, 0.1)));
932        }
933        "warm" => {
934            // Warm, pleasant sound with gentle processing
935            chain.add_effect(Box::new(NoiseGateEffect::new(-55.0, 5.0, 150.0, 300.0)));
936            chain.add_effect(Box::new(EqualizerEffect::new(2.0, 0.0, -2.0))); // Warm low-end
937            chain.add_effect(Box::new(
938                CompressorEffect::new(-24.0, 2.5, 15.0, 150.0).with_makeup_gain(3.0),
939            ));
940            chain.add_effect(Box::new(LowPassFilter::new(5000.0, 0.6)));
941            chain.add_effect(Box::new(ReverbEffect::new(0.4, 0.3, 0.3)));
942        }
943        "clean" => {
944            // Clean, minimal processing with only noise gate
945            chain.add_effect(Box::new(NoiseGateEffect::new(-60.0, 5.0, 100.0, 250.0)));
946            chain.add_effect(Box::new(VolumeEffect::new(1.0)));
947        }
948        "broadcast" => {
949            // Professional broadcast-quality processing
950            chain.add_effect(Box::new(NoiseGateEffect::new(-42.0, 2.0, 75.0, 150.0)));
951            chain.add_effect(Box::new(EqualizerEffect::new(0.0, 4.0, 2.0))); // Clear and present
952            chain.add_effect(Box::new(
953                CompressorEffect::new(-16.0, 5.0, 5.0, 50.0).with_makeup_gain(7.0),
954            ));
955            chain.add_effect(Box::new(LowPassFilter::new(7500.0, 0.75)));
956        }
957        "audiobook" => {
958            // Optimized for audiobook narration
959            chain.add_effect(Box::new(NoiseGateEffect::new(-48.0, 4.0, 120.0, 250.0)));
960            chain.add_effect(Box::new(EqualizerEffect::new(1.0, 2.0, 0.0))); // Natural warmth
961            chain.add_effect(Box::new(
962                CompressorEffect::new(-22.0, 3.5, 12.0, 120.0).with_makeup_gain(5.0),
963            ));
964            chain.add_effect(Box::new(LowPassFilter::new(6500.0, 0.7)));
965        }
966        "telephone" => {
967            // Telephone/lo-fi sound simulation
968            chain.add_effect(Box::new(EqualizerEffect::new(-6.0, 8.0, -10.0))); // Narrow mid-range
969            chain.add_effect(Box::new(
970                CompressorEffect::new(-12.0, 8.0, 2.0, 20.0).with_makeup_gain(10.0),
971            ));
972            chain.add_effect(Box::new(LowPassFilter::new(3400.0, 1.2)));
973        }
974        _ => {
975            return Err(VoirsError::config_error(format!(
976                "Unknown preset: {}. Available presets: vocal_enhancement, podcast, radio, warm, clean, broadcast, audiobook, telephone",
977                preset_name
978            )));
979        }
980    }
981
982    Ok(chain)
983}
984
985#[cfg(test)]
986mod tests {
987    use super::AudioData;
988    use super::*;
989
990    #[test]
991    fn test_effect_config() {
992        let config = EffectConfig::new("volume")
993            .with_parameter("gain", 1.5)
994            .with_enabled(true)
995            .with_priority(10);
996
997        assert_eq!(config.effect_type, "volume");
998        assert_eq!(config.get_parameter("gain"), Some(1.5));
999        assert!(config.enabled);
1000        assert_eq!(config.priority, 10);
1001    }
1002
1003    #[test]
1004    fn test_volume_effect() {
1005        let mut effect = VolumeEffect::new(2.0);
1006        assert_eq!(effect.name(), "volume");
1007        assert!(effect.is_enabled());
1008
1009        let mut samples = vec![0.1, 0.2, 0.3, 0.4];
1010        effect.process(&mut samples, 22050).unwrap();
1011
1012        // Volume should be doubled
1013        assert_eq!(samples, vec![0.2, 0.4, 0.6, 0.8]);
1014
1015        // Test parameter setting
1016        effect.set_parameter("gain", 0.5).unwrap();
1017        let params = effect.get_parameters();
1018        assert_eq!(params.get("gain"), Some(&0.5));
1019    }
1020
1021    #[test]
1022    fn test_lowpass_filter() {
1023        let mut filter = LowPassFilter::new(1000.0, 0.7);
1024        assert_eq!(filter.name(), "lowpass");
1025
1026        let mut samples = vec![1.0, -1.0, 1.0, -1.0]; // High-frequency signal
1027        filter.process(&mut samples, 22050).unwrap();
1028
1029        // High-frequency content should be attenuated
1030        assert!(samples.iter().all(|&s| s.abs() < 1.0));
1031
1032        // Test reset
1033        filter.reset().unwrap();
1034    }
1035
1036    #[test]
1037    fn test_reverb_effect() {
1038        let mut reverb = ReverbEffect::new(0.1, 0.2, 0.6); // Smaller room, more wet signal
1039        assert_eq!(reverb.name(), "reverb");
1040
1041        // Process a longer sequence to allow reverb tail to develop
1042        let mut samples = vec![1.0; 10]; // Start with impulse followed by zeros
1043        samples.extend(vec![0.0; 100]); // Add many zeros for reverb tail
1044
1045        let original_samples = samples.clone();
1046        reverb.process(&mut samples, 22050).unwrap();
1047
1048        // After processing, samples should be different due to reverb processing
1049        assert_ne!(samples, original_samples);
1050
1051        // The effect should mix dry and wet signal, so the first sample should change
1052        assert_ne!(samples[0], 1.0);
1053    }
1054
1055    #[test]
1056    fn test_effect_chain() {
1057        let mut chain = EffectChain::new();
1058        assert!(chain.is_empty());
1059
1060        // Add effects
1061        chain.add_effect(Box::new(VolumeEffect::new(2.0)));
1062        chain.add_effect(Box::new(LowPassFilter::new(5000.0, 0.7)));
1063
1064        assert_eq!(chain.len(), 2);
1065        assert!(!chain.is_empty());
1066
1067        let effect_names = chain.get_effect_names();
1068        assert!(effect_names.contains(&"volume".to_string()));
1069        assert!(effect_names.contains(&"lowpass".to_string()));
1070
1071        // Test processing
1072        let mut audio_data = AudioData {
1073            samples: vec![1000, 2000, 3000, 4000],
1074            sample_rate: 22050,
1075            channels: 1,
1076        };
1077
1078        chain.process(&mut audio_data).unwrap();
1079
1080        // Samples should be modified by the effects
1081        assert_ne!(audio_data.samples, vec![1000, 2000, 3000, 4000]);
1082
1083        // Test removal
1084        chain.remove_effect("volume").unwrap();
1085        assert_eq!(chain.len(), 1);
1086    }
1087
1088    #[test]
1089    fn test_preset_effect_chains() {
1090        let presets = vec!["vocal_enhancement", "podcast", "radio", "warm", "clean"];
1091
1092        for preset in presets {
1093            let chain = create_preset_effect_chain(preset).unwrap();
1094            assert!(!chain.is_empty());
1095        }
1096
1097        // Test unknown preset
1098        assert!(create_preset_effect_chain("unknown").is_err());
1099    }
1100
1101    #[test]
1102    fn test_effect_chain_enable_disable() {
1103        let mut chain = EffectChain::new();
1104        chain.add_effect(Box::new(VolumeEffect::new(2.0)));
1105
1106        let mut samples = vec![0.1, 0.2, 0.3, 0.4];
1107        let original_samples = samples.clone();
1108
1109        // Process with enabled chain
1110        chain.process_samples(&mut samples, 22050).unwrap();
1111        assert_ne!(samples, original_samples);
1112
1113        // Disable chain and process again
1114        samples = original_samples.clone();
1115        chain.set_enabled(false);
1116        chain.process_samples(&mut samples, 22050).unwrap();
1117        assert_eq!(samples, original_samples); // Should be unchanged
1118    }
1119}