Skip to main content

voirs_cli/plugins/
effects.rs

1use super::{Plugin, PluginError, PluginResult, PluginType};
2use crate::audio::effects::{AudioEffect, EffectConfig};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::{Arc, Mutex};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct EffectPluginConfig {
9    pub parameters: HashMap<String, f32>,
10    pub enabled: bool,
11    pub bypass: bool,
12    pub wet_mix: f32,
13    pub dry_mix: f32,
14}
15
16impl Default for EffectPluginConfig {
17    fn default() -> Self {
18        Self {
19            parameters: HashMap::new(),
20            enabled: true,
21            bypass: false,
22            wet_mix: 1.0,
23            dry_mix: 0.0,
24        }
25    }
26}
27
28pub trait EffectPlugin: Plugin {
29    fn process_audio(
30        &self,
31        input: &[f32],
32        output: &mut [f32],
33        config: &EffectPluginConfig,
34    ) -> PluginResult<()>;
35    fn get_parameter_info(&self) -> Vec<ParameterInfo>;
36    fn set_parameter(&mut self, name: &str, value: f32) -> PluginResult<()>;
37    fn get_parameter(&self, name: &str) -> PluginResult<f32>;
38    fn reset(&mut self) -> PluginResult<()>;
39    fn get_latency(&self) -> u32;
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ParameterInfo {
44    pub name: String,
45    pub display_name: String,
46    pub min_value: f32,
47    pub max_value: f32,
48    pub default_value: f32,
49    pub step_size: f32,
50    pub unit: String,
51    pub description: String,
52    pub parameter_type: ParameterType,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum ParameterType {
57    Float,
58    Integer,
59    Boolean,
60    Choice(Vec<String>),
61}
62
63pub struct EffectPluginManager {
64    effects: HashMap<String, Arc<dyn EffectPlugin>>,
65    presets: HashMap<String, EffectPluginConfig>,
66}
67
68impl EffectPluginManager {
69    pub fn new() -> Self {
70        Self {
71            effects: HashMap::new(),
72            presets: HashMap::new(),
73        }
74    }
75
76    pub fn register_effect(&mut self, name: String, effect: Arc<dyn EffectPlugin>) {
77        self.effects.insert(name, effect);
78    }
79
80    pub fn unregister_effect(&mut self, name: &str) -> bool {
81        self.effects.remove(name).is_some()
82    }
83
84    pub fn list_effects(&self) -> Vec<String> {
85        self.effects.keys().cloned().collect()
86    }
87
88    pub fn get_effect(&self, name: &str) -> Option<Arc<dyn EffectPlugin>> {
89        self.effects.get(name).cloned()
90    }
91
92    pub fn process_with_effect(
93        &self,
94        effect_name: &str,
95        input: &[f32],
96        output: &mut [f32],
97        config: &EffectPluginConfig,
98    ) -> PluginResult<()> {
99        let effect = self
100            .effects
101            .get(effect_name)
102            .ok_or_else(|| PluginError::NotFound(effect_name.to_string()))?;
103
104        if !config.enabled || config.bypass {
105            output.copy_from_slice(input);
106            return Ok(());
107        }
108
109        effect.process_audio(input, output, config)
110    }
111
112    pub fn create_effect_chain(&self, effect_names: &[String]) -> EffectChain {
113        let mut effects = Vec::new();
114        for name in effect_names {
115            if let Some(effect) = self.effects.get(name) {
116                effects.push((name.clone(), effect.clone()));
117            }
118        }
119        EffectChain::new(effects)
120    }
121
122    pub fn save_preset(&mut self, name: String, config: EffectPluginConfig) {
123        self.presets.insert(name, config);
124    }
125
126    pub fn load_preset(&self, name: &str) -> Option<&EffectPluginConfig> {
127        self.presets.get(name)
128    }
129
130    pub fn delete_preset(&mut self, name: &str) -> bool {
131        self.presets.remove(name).is_some()
132    }
133
134    pub fn list_presets(&self) -> Vec<String> {
135        self.presets.keys().cloned().collect()
136    }
137}
138
139impl Default for EffectPluginManager {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145pub struct EffectChain {
146    effects: Vec<(String, Arc<dyn EffectPlugin>)>,
147    buffer: Vec<f32>,
148}
149
150impl EffectChain {
151    pub fn new(effects: Vec<(String, Arc<dyn EffectPlugin>)>) -> Self {
152        Self {
153            effects,
154            buffer: Vec::new(),
155        }
156    }
157
158    pub fn process(
159        &mut self,
160        input: &[f32],
161        output: &mut [f32],
162        configs: &HashMap<String, EffectPluginConfig>,
163    ) -> PluginResult<()> {
164        if self.effects.is_empty() {
165            output.copy_from_slice(input);
166            return Ok(());
167        }
168
169        // Ensure buffer is large enough
170        if self.buffer.len() < input.len() {
171            self.buffer.resize(input.len(), 0.0);
172        }
173
174        let default_config = EffectPluginConfig::default();
175
176        // First effect processes input to buffer
177        if let Some((name, effect)) = self.effects.first() {
178            let config = configs.get(name).unwrap_or(&default_config);
179            effect.process_audio(input, &mut self.buffer[..input.len()], config)?;
180        }
181
182        // Subsequent effects process buffer to buffer (ping-pong if needed)
183        for i in 1..self.effects.len() {
184            let (name, effect) = &self.effects[i];
185            let config = configs.get(name).unwrap_or(&default_config);
186
187            if i == self.effects.len() - 1 {
188                // Last effect writes to output
189                effect.process_audio(&self.buffer[..input.len()], output, config)?;
190            } else {
191                // Intermediate effects process in-place
192                let mut temp_buffer = vec![0.0; input.len()];
193                effect.process_audio(&self.buffer[..input.len()], &mut temp_buffer, config)?;
194                self.buffer[..input.len()].copy_from_slice(&temp_buffer);
195            }
196        }
197
198        Ok(())
199    }
200
201    pub fn get_total_latency(&self) -> u32 {
202        self.effects
203            .iter()
204            .map(|(_, effect)| effect.get_latency())
205            .sum()
206    }
207
208    pub fn reset_all(&mut self) -> PluginResult<()> {
209        for (_, effect) in &self.effects {
210            // Note: reset requires mutable reference, but we only have immutable
211            // In a real implementation, effects would need interior mutability
212        }
213        Ok(())
214    }
215}
216
217// Example builtin effect plugin implementations
218// Comb filter for reverb
219struct CombFilter {
220    buffer: Vec<f32>,
221    buffer_index: usize,
222    feedback: f32,
223    filter_state: f32,
224    damping: f32,
225}
226
227impl CombFilter {
228    fn new(size: usize) -> Self {
229        Self {
230            buffer: vec![0.0; size],
231            buffer_index: 0,
232            feedback: 0.0,
233            filter_state: 0.0,
234            damping: 0.0,
235        }
236    }
237
238    fn process(&mut self, input: f32) -> f32 {
239        let output = self.buffer[self.buffer_index];
240
241        // One-pole lowpass filter for damping
242        self.filter_state = output * (1.0 - self.damping) + self.filter_state * self.damping;
243
244        self.buffer[self.buffer_index] = input + self.filter_state * self.feedback;
245        self.buffer_index = (self.buffer_index + 1) % self.buffer.len();
246
247        output
248    }
249
250    fn set_damping(&mut self, damping: f32) {
251        self.damping = damping;
252    }
253
254    fn set_feedback(&mut self, feedback: f32) {
255        self.feedback = feedback;
256    }
257
258    fn clear(&mut self) {
259        self.buffer.fill(0.0);
260        self.filter_state = 0.0;
261        self.buffer_index = 0;
262    }
263}
264
265// Allpass filter for reverb
266struct AllpassFilter {
267    buffer: Vec<f32>,
268    buffer_index: usize,
269}
270
271impl AllpassFilter {
272    fn new(size: usize) -> Self {
273        Self {
274            buffer: vec![0.0; size],
275            buffer_index: 0,
276        }
277    }
278
279    fn process(&mut self, input: f32) -> f32 {
280        let buffered = self.buffer[self.buffer_index];
281        let output = -input + buffered;
282
283        self.buffer[self.buffer_index] = input + buffered * 0.5;
284        self.buffer_index = (self.buffer_index + 1) % self.buffer.len();
285
286        output
287    }
288
289    fn clear(&mut self) {
290        self.buffer.fill(0.0);
291        self.buffer_index = 0;
292    }
293}
294
295pub struct ReverbEffectPlugin {
296    name: String,
297    version: String,
298    room_size: f32,
299    damping: f32,
300    wet_level: f32,
301    dry_level: f32,
302    // Freeverb-style filter banks (using Mutex for thread-safe processing state)
303    comb_filters: Mutex<Vec<CombFilter>>,
304    allpass_filters: Mutex<Vec<AllpassFilter>>,
305}
306
307impl Default for ReverbEffectPlugin {
308    fn default() -> Self {
309        Self::new()
310    }
311}
312
313impl ReverbEffectPlugin {
314    pub fn new() -> Self {
315        // Freeverb comb filter delay lengths (at 44.1kHz sample rate)
316        const COMB_DELAYS: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
317        // Freeverb allpass filter delay lengths
318        const ALLPASS_DELAYS: [usize; 4] = [556, 441, 341, 225];
319
320        let comb_filters: Vec<CombFilter> = COMB_DELAYS
321            .iter()
322            .map(|&size| CombFilter::new(size))
323            .collect();
324
325        let allpass_filters: Vec<AllpassFilter> = ALLPASS_DELAYS
326            .iter()
327            .map(|&size| AllpassFilter::new(size))
328            .collect();
329
330        Self {
331            name: "builtin-reverb".to_string(),
332            version: "1.0.0".to_string(),
333            room_size: 0.5,
334            damping: 0.5,
335            wet_level: 0.3,
336            dry_level: 0.7,
337            comb_filters: Mutex::new(comb_filters),
338            allpass_filters: Mutex::new(allpass_filters),
339        }
340    }
341
342    fn update_filters(&self) {
343        // Update comb filter parameters based on room_size and damping
344        let feedback = 0.28 + self.room_size * 0.7;
345
346        let mut comb_filters = self
347            .comb_filters
348            .lock()
349            .expect("Reverb comb_filters mutex poisoned - unrecoverable error");
350        for comb in comb_filters.iter_mut() {
351            comb.set_feedback(feedback);
352            comb.set_damping(self.damping);
353        }
354    }
355}
356
357impl Plugin for ReverbEffectPlugin {
358    fn name(&self) -> &str {
359        &self.name
360    }
361
362    fn version(&self) -> &str {
363        &self.version
364    }
365
366    fn description(&self) -> &str {
367        "Built-in reverb effect plugin"
368    }
369
370    fn plugin_type(&self) -> PluginType {
371        PluginType::Effect
372    }
373
374    fn initialize(&mut self, config: &serde_json::Value) -> PluginResult<()> {
375        if let Some(room_size) = config.get("room_size").and_then(|v| v.as_f64()) {
376            self.room_size = room_size as f32;
377        }
378        if let Some(damping) = config.get("damping").and_then(|v| v.as_f64()) {
379            self.damping = damping as f32;
380        }
381        if let Some(wet_level) = config.get("wet_level").and_then(|v| v.as_f64()) {
382            self.wet_level = wet_level as f32;
383        }
384        if let Some(dry_level) = config.get("dry_level").and_then(|v| v.as_f64()) {
385            self.dry_level = dry_level as f32;
386        }
387        Ok(())
388    }
389
390    fn cleanup(&mut self) -> PluginResult<()> {
391        Ok(())
392    }
393
394    fn get_capabilities(&self) -> Vec<String> {
395        vec![
396            "process_audio".to_string(),
397            "set_parameter".to_string(),
398            "get_parameter".to_string(),
399            "reset".to_string(),
400        ]
401    }
402
403    fn execute(&self, command: &str, args: &serde_json::Value) -> PluginResult<serde_json::Value> {
404        match command {
405            "get_parameters" => Ok(serde_json::json!({
406                "room_size": self.room_size,
407                "damping": self.damping,
408                "wet_level": self.wet_level,
409                "dry_level": self.dry_level
410            })),
411            "set_parameter" => {
412                let param_name = args.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
413                    PluginError::ExecutionFailed("Missing parameter name".to_string())
414                })?;
415                let value = args.get("value").and_then(|v| v.as_f64()).ok_or_else(|| {
416                    PluginError::ExecutionFailed("Missing parameter value".to_string())
417                })? as f32;
418
419                match param_name {
420                    "room_size" => Ok(serde_json::json!({"old_value": self.room_size})),
421                    "damping" => Ok(serde_json::json!({"old_value": self.damping})),
422                    "wet_level" => Ok(serde_json::json!({"old_value": self.wet_level})),
423                    "dry_level" => Ok(serde_json::json!({"old_value": self.dry_level})),
424                    _ => Err(PluginError::ExecutionFailed(format!(
425                        "Unknown parameter: {}",
426                        param_name
427                    ))),
428                }
429            }
430            _ => Err(PluginError::ExecutionFailed(format!(
431                "Unknown command: {}",
432                command
433            ))),
434        }
435    }
436}
437
438impl EffectPlugin for ReverbEffectPlugin {
439    fn process_audio(
440        &self,
441        input: &[f32],
442        output: &mut [f32],
443        config: &EffectPluginConfig,
444    ) -> PluginResult<()> {
445        // Real Freeverb implementation with comb and allpass filters
446        self.update_filters();
447
448        let mut comb_filters = self
449            .comb_filters
450            .lock()
451            .expect("Reverb comb_filters mutex poisoned - unrecoverable error");
452        let mut allpass_filters = self
453            .allpass_filters
454            .lock()
455            .expect("Reverb allpass_filters mutex poisoned - unrecoverable error");
456
457        for (i, &input_sample) in input.iter().enumerate() {
458            // Process through parallel comb filters
459            let mut comb_out = 0.0;
460            for comb in comb_filters.iter_mut() {
461                comb_out += comb.process(input_sample);
462            }
463
464            // Process through series allpass filters
465            let mut allpass_out = comb_out;
466            for allpass in allpass_filters.iter_mut() {
467                allpass_out = allpass.process(allpass_out);
468            }
469
470            // Mix wet and dry signals
471            let wet = allpass_out * self.wet_level * config.wet_mix;
472            let dry = input_sample * self.dry_level * config.dry_mix;
473            output[i] = wet + dry;
474        }
475
476        Ok(())
477    }
478
479    fn get_parameter_info(&self) -> Vec<ParameterInfo> {
480        vec![
481            ParameterInfo {
482                name: "room_size".to_string(),
483                display_name: "Room Size".to_string(),
484                min_value: 0.0,
485                max_value: 1.0,
486                default_value: 0.5,
487                step_size: 0.01,
488                unit: "".to_string(),
489                description: "Size of the reverb room".to_string(),
490                parameter_type: ParameterType::Float,
491            },
492            ParameterInfo {
493                name: "damping".to_string(),
494                display_name: "Damping".to_string(),
495                min_value: 0.0,
496                max_value: 1.0,
497                default_value: 0.5,
498                step_size: 0.01,
499                unit: "".to_string(),
500                description: "Damping factor for high frequencies".to_string(),
501                parameter_type: ParameterType::Float,
502            },
503            ParameterInfo {
504                name: "wet_level".to_string(),
505                display_name: "Wet Level".to_string(),
506                min_value: 0.0,
507                max_value: 1.0,
508                default_value: 0.3,
509                step_size: 0.01,
510                unit: "".to_string(),
511                description: "Level of processed signal".to_string(),
512                parameter_type: ParameterType::Float,
513            },
514            ParameterInfo {
515                name: "dry_level".to_string(),
516                display_name: "Dry Level".to_string(),
517                min_value: 0.0,
518                max_value: 1.0,
519                default_value: 0.7,
520                step_size: 0.01,
521                unit: "".to_string(),
522                description: "Level of original signal".to_string(),
523                parameter_type: ParameterType::Float,
524            },
525        ]
526    }
527
528    fn set_parameter(&mut self, name: &str, value: f32) -> PluginResult<()> {
529        match name {
530            "room_size" => self.room_size = value.clamp(0.0, 1.0),
531            "damping" => self.damping = value.clamp(0.0, 1.0),
532            "wet_level" => self.wet_level = value.clamp(0.0, 1.0),
533            "dry_level" => self.dry_level = value.clamp(0.0, 1.0),
534            _ => {
535                return Err(PluginError::ExecutionFailed(format!(
536                    "Unknown parameter: {}",
537                    name
538                )))
539            }
540        }
541        Ok(())
542    }
543
544    fn get_parameter(&self, name: &str) -> PluginResult<f32> {
545        match name {
546            "room_size" => Ok(self.room_size),
547            "damping" => Ok(self.damping),
548            "wet_level" => Ok(self.wet_level),
549            "dry_level" => Ok(self.dry_level),
550            _ => Err(PluginError::ExecutionFailed(format!(
551                "Unknown parameter: {}",
552                name
553            ))),
554        }
555    }
556
557    fn reset(&mut self) -> PluginResult<()> {
558        // Reset parameters to defaults
559        self.room_size = 0.5;
560        self.damping = 0.5;
561        self.wet_level = 0.3;
562        self.dry_level = 0.7;
563
564        // Clear all filter buffers
565        let mut comb_filters = self
566            .comb_filters
567            .lock()
568            .expect("Reverb comb_filters mutex poisoned - unrecoverable error");
569        for comb in comb_filters.iter_mut() {
570            comb.clear();
571        }
572
573        let mut allpass_filters = self
574            .allpass_filters
575            .lock()
576            .expect("Reverb allpass_filters mutex poisoned - unrecoverable error");
577        for allpass in allpass_filters.iter_mut() {
578            allpass.clear();
579        }
580
581        Ok(())
582    }
583
584    fn get_latency(&self) -> u32 {
585        // Reverb has latency from the longest comb filter delay
586        1617 // Longest comb filter delay in samples (at 44.1kHz)
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use super::*;
593
594    #[test]
595    fn test_effect_plugin_manager() {
596        let mut manager = EffectPluginManager::new();
597        let reverb = Arc::new(ReverbEffectPlugin::new());
598
599        manager.register_effect("reverb".to_string(), reverb);
600
601        let effects = manager.list_effects();
602        assert!(effects.contains(&"reverb".to_string()));
603
604        let effect = manager.get_effect("reverb");
605        assert!(effect.is_some());
606    }
607
608    #[test]
609    fn test_reverb_plugin() {
610        let mut reverb = ReverbEffectPlugin::new();
611        assert_eq!(reverb.name(), "builtin-reverb");
612        assert_eq!(reverb.version(), "1.0.0");
613
614        let config = EffectPluginConfig::default();
615        let input = vec![1.0, 0.5, -0.5, -1.0];
616        let mut output = vec![0.0; 4];
617
618        reverb
619            .process_audio(&input, &mut output, &config)
620            .expect("Failed to process audio with reverb");
621
622        // Output should be processed (not just copied)
623        assert_ne!(input, output);
624    }
625
626    #[test]
627    fn test_parameter_setting() {
628        let mut reverb = ReverbEffectPlugin::new();
629
630        reverb
631            .set_parameter("room_size", 0.8)
632            .expect("Failed to set room_size parameter");
633        assert_eq!(
634            reverb
635                .get_parameter("room_size")
636                .expect("Failed to get room_size parameter"),
637            0.8
638        );
639
640        // Test parameter clamping
641        reverb
642            .set_parameter("room_size", 1.5)
643            .expect("Failed to set room_size parameter");
644        assert_eq!(
645            reverb
646                .get_parameter("room_size")
647                .expect("Failed to get room_size parameter"),
648            1.0
649        );
650
651        // Test invalid parameter
652        assert!(reverb.set_parameter("invalid", 0.5).is_err());
653    }
654
655    #[test]
656    fn test_effect_chain() {
657        let reverb = Arc::new(ReverbEffectPlugin::new());
658        let effects = vec![("reverb".to_string(), reverb as Arc<dyn EffectPlugin>)];
659        let mut chain = EffectChain::new(effects);
660
661        let input = vec![1.0, 0.5, -0.5, -1.0];
662        let mut output = vec![0.0; 4];
663        let configs = HashMap::new();
664
665        chain
666            .process(&input, &mut output, &configs)
667            .expect("Failed to process audio with effect chain");
668
669        // Should process without error
670        assert_eq!(output.len(), input.len());
671    }
672}