devalang_wasm/engine/audio/effects/
registry.rs

1use super::EffectAvailability;
2use crate::engine::audio::effects::processors::EffectProcessor;
3use crate::engine::audio::effects::processors::{
4    BandpassProcessor, BitcrushProcessor, FreezeProcessor, HighpassProcessor, LfoProcessor,
5    LowpassProcessor, MonoizerProcessor, ReverseProcessor, RollProcessor, SliceProcessor,
6    SpeedProcessor, StereoProcessor, StretchProcessor, TremoloProcessor, VibratoProcessor,
7};
8use crate::engine::audio::effects::processors::{
9    ChorusProcessor, CompressorProcessor, DelayProcessor, DistortionProcessor, DriveProcessor,
10    FlangerProcessor, GateProcessor, PhaserProcessor, ReverbProcessor,
11};
12use std::collections::HashMap;
13
14/// Effect registry - stores available effects and their processors
15#[derive(Debug)]
16pub struct EffectRegistry {
17    effects: HashMap<&'static str, (EffectAvailability, Box<dyn CloneableEffect>)>,
18}
19
20impl EffectRegistry {
21    pub fn new() -> Self {
22        let mut registry = Self {
23            effects: HashMap::new(),
24        };
25
26        // Common effects
27        registry.register_effect(
28            "gain",
29            EffectAvailability::Both,
30            Box::new(DriveProcessor::default()),
31        );
32        registry.register_effect(
33            "volume",
34            EffectAvailability::Both,
35            Box::new(DriveProcessor::default()),
36        );
37        registry.register_effect(
38            "pan",
39            EffectAvailability::Both,
40            Box::new(DriveProcessor::default()),
41        );
42        registry.register_effect(
43            "fadeIn",
44            EffectAvailability::Both,
45            Box::new(DriveProcessor::default()),
46        );
47        registry.register_effect(
48            "fadeOut",
49            EffectAvailability::Both,
50            Box::new(DriveProcessor::default()),
51        );
52        registry.register_effect(
53            "pitch",
54            EffectAvailability::Both,
55            Box::new(DriveProcessor::default()),
56        );
57        registry.register_effect(
58            "chorus",
59            EffectAvailability::Both,
60            Box::new(ChorusProcessor::default()),
61        );
62        registry.register_effect(
63            "flanger",
64            EffectAvailability::Both,
65            Box::new(FlangerProcessor::default()),
66        );
67        registry.register_effect(
68            "phaser",
69            EffectAvailability::Both,
70            Box::new(PhaserProcessor::default()),
71        );
72        registry.register_effect(
73            "compressor",
74            EffectAvailability::Both,
75            Box::new(CompressorProcessor::default()),
76        );
77        registry.register_effect(
78            "gate",
79            EffectAvailability::Both,
80            Box::new(GateProcessor::default()),
81        );
82        registry.register_effect(
83            "drive",
84            EffectAvailability::Both,
85            Box::new(DriveProcessor::default()),
86        );
87        registry.register_effect(
88            "reverb",
89            EffectAvailability::Both,
90            Box::new(ReverbProcessor::default()),
91        );
92        registry.register_effect(
93            "delay",
94            EffectAvailability::Both,
95            Box::new(DelayProcessor::default()),
96        );
97        registry.register_effect(
98            "bitcrush",
99            EffectAvailability::Both,
100            Box::new(BitcrushProcessor::default()),
101        );
102        registry.register_effect(
103            "lowpass",
104            EffectAvailability::Both,
105            Box::new(LowpassProcessor::default()),
106        );
107        registry.register_effect(
108            "highpass",
109            EffectAvailability::Both,
110            Box::new(HighpassProcessor::default()),
111        );
112        registry.register_effect(
113            "bandpass",
114            EffectAvailability::Both,
115            Box::new(BandpassProcessor::default()),
116        );
117        registry.register_effect(
118            "tremolo",
119            EffectAvailability::Both,
120            Box::new(TremoloProcessor::default()),
121        );
122        registry.register_effect(
123            "vibrato",
124            EffectAvailability::Both,
125            Box::new(VibratoProcessor::default()),
126        );
127        registry.register_effect(
128            "mono",
129            EffectAvailability::Both,
130            Box::new(MonoizerProcessor::default()),
131        );
132        registry.register_effect(
133            "monoizer",
134            EffectAvailability::Both,
135            Box::new(MonoizerProcessor::default()),
136        );
137        registry.register_effect(
138            "stereo",
139            EffectAvailability::Both,
140            Box::new(StereoProcessor::default()),
141        );
142        registry.register_effect(
143            "freeze",
144            EffectAvailability::Both,
145            Box::new(FreezeProcessor::default()),
146        );
147        registry.register_effect(
148            "distortion",
149            EffectAvailability::Both,
150            Box::new(DistortionProcessor::default()),
151        );
152        registry.register_effect(
153            "dist",
154            EffectAvailability::Both,
155            Box::new(DistortionProcessor::default()),
156        );
157        registry.register_effect(
158            "lfo",
159            EffectAvailability::Both,
160            Box::new(LfoProcessor::default()),
161        );
162
163        // Trigger-only effects
164        registry.register_effect(
165            "reverse",
166            EffectAvailability::TriggerOnly,
167            Box::new(ReverseProcessor::default()),
168        );
169        registry.register_effect(
170            "speed",
171            EffectAvailability::TriggerOnly,
172            Box::new(SpeedProcessor::default()),
173        );
174        registry.register_effect(
175            "slice",
176            EffectAvailability::TriggerOnly,
177            Box::new(SliceProcessor::default()),
178        );
179        registry.register_effect(
180            "stretch",
181            EffectAvailability::TriggerOnly,
182            Box::new(StretchProcessor::default()),
183        );
184        registry.register_effect(
185            "roll",
186            EffectAvailability::TriggerOnly,
187            Box::new(RollProcessor::default()),
188        );
189
190        // Aliases
191        registry.register_effect(
192            "dist",
193            EffectAvailability::Both,
194            Box::new(DistortionProcessor::default()),
195        );
196        registry.register_effect(
197            "comp",
198            EffectAvailability::Both,
199            Box::new(CompressorProcessor::default()),
200        );
201        registry.register_effect(
202            "lpf",
203            EffectAvailability::Both,
204            Box::new(LowpassProcessor::default()),
205        );
206        registry.register_effect(
207            "hpf",
208            EffectAvailability::Both,
209            Box::new(HighpassProcessor::default()),
210        );
211        registry.register_effect(
212            "bpf",
213            EffectAvailability::Both,
214            Box::new(BandpassProcessor::default()),
215        );
216
217        // Synth-only effects (could add more specific synth effects here)
218
219        registry
220    }
221
222    /// Register a new effect with its availability and processor
223    pub fn register_effect(
224        &mut self,
225        name: &'static str,
226        availability: EffectAvailability,
227        processor: Box<dyn CloneableEffect>,
228    ) {
229        self.effects.insert(name, (availability, processor));
230    }
231
232    /// Get an effect processor by name if it's available for the given context
233    pub fn get_effect(&self, name: &str, synth_context: bool) -> Option<Box<dyn CloneableEffect>> {
234        self.effects
235            .get(name)
236            .and_then(|(availability, processor)| {
237                match (availability, synth_context) {
238                    // In synth context, allow SynthOnly and Both
239                    (EffectAvailability::SynthOnly, true) | (EffectAvailability::Both, true) => {
240                        Some(processor.clone_box())
241                    }
242                    // In trigger context, allow TriggerOnly and Both
243                    (EffectAvailability::TriggerOnly, false)
244                    | (EffectAvailability::Both, false) => Some(processor.clone_box()),
245                    _ => None, // Effect not available in this context
246                }
247            })
248    }
249
250    /// Check if an effect exists and is available in the given context
251    pub fn is_effect_available(&self, name: &str, synth_context: bool) -> bool {
252        self.effects.get(name).map_or(false, |(availability, _)| {
253            match (availability, synth_context) {
254                (EffectAvailability::SynthOnly, true)
255                | (EffectAvailability::Both, true)
256                | (EffectAvailability::TriggerOnly, false)
257                | (EffectAvailability::Both, false) => true,
258                _ => false,
259            }
260        })
261    }
262
263    /// List all available effects for a given context
264    pub fn list_available_effects(&self, synth_context: bool) -> Vec<&'static str> {
265        self.effects
266            .iter()
267            .filter_map(
268                |(&name, (availability, _))| match (availability, synth_context) {
269                    (EffectAvailability::SynthOnly, true)
270                    | (EffectAvailability::Both, true)
271                    | (EffectAvailability::TriggerOnly, false)
272                    | (EffectAvailability::Both, false) => Some(name),
273                    _ => None,
274                },
275            )
276            .collect()
277    }
278}
279
280/// Trait object that is cloneable: used to store prototype processors in the registry.
281/// This is a super-trait combining `EffectProcessor` behaviour and a `clone_box` method
282/// returning a boxed clone of the same dyn trait.
283pub trait CloneableEffect: EffectProcessor {
284    fn clone_box(&self) -> Box<dyn CloneableEffect>;
285}
286
287impl<T> CloneableEffect for T
288where
289    T: EffectProcessor + Clone + 'static,
290{
291    fn clone_box(&self) -> Box<dyn CloneableEffect> {
292        Box::new(self.clone())
293    }
294}
295
296impl dyn EffectProcessor {
297    /// Get description of effect parameters
298    pub fn get_parameters_description(&self) -> HashMap<&'static str, String> {
299        let mut params = HashMap::new();
300        match self.name() {
301            "Chorus" => {
302                params.insert("depth", "Modulation depth (0.0 to 1.0)".to_string());
303                params.insert("rate", "Modulation rate (0.1 to 10.0 Hz)".to_string());
304                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
305            }
306            "Flanger" => {
307                params.insert("depth", "Modulation depth (0.0 to 1.0)".to_string());
308                params.insert("rate", "Modulation rate (0.1 to 10.0 Hz)".to_string());
309                params.insert("feedback", "Feedback amount (0.0 to 0.95)".to_string());
310                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
311            }
312            "Phaser" => {
313                params.insert("stages", "Number of allpass stages (2 to 12)".to_string());
314                params.insert("rate", "Modulation rate (0.1 to 10.0 Hz)".to_string());
315                params.insert("depth", "Modulation depth (0.0 to 1.0)".to_string());
316                params.insert("feedback", "Feedback amount (0.0 to 0.95)".to_string());
317                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
318            }
319            "Compressor" => {
320                params.insert(
321                    "threshold",
322                    "Threshold level in dB (-60.0 to 0.0)".to_string(),
323                );
324                params.insert("ratio", "Compression ratio (1.0 to 20.0)".to_string());
325                params.insert(
326                    "attack",
327                    "Attack time in seconds (0.001 to 1.0)".to_string(),
328                );
329                params.insert(
330                    "release",
331                    "Release time in seconds (0.001 to 2.0)".to_string(),
332                );
333            }
334            "Drive" => {
335                params.insert("amount", "Drive amount (0.0 to 1.0)".to_string());
336                params.insert("tone", "Tone control (0.0 to 1.0)".to_string());
337                params.insert(
338                    "color",
339                    "Color/timbre control (0.0 bright to 1.0 dark)".to_string(),
340                );
341                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
342            }
343            "Distortion" => {
344                params.insert("amount", "Distortion amount (0.0 to 1.0)".to_string());
345                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
346            }
347            "Bitcrush" => {
348                params.insert("depth", "Bit depth (1..16)".to_string());
349                params.insert(
350                    "sample_rate",
351                    "Target sample rate for downsampling (Hz)".to_string(),
352                );
353                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
354            }
355            "Lowpass" => {
356                params.insert("cutoff", "Cutoff frequency (20.0 to 20000.0)".to_string());
357                params.insert("resonance", "Resonance/Q (0.0 to 1.0)".to_string());
358            }
359            "Highpass" => {
360                params.insert("cutoff", "Cutoff frequency (20.0 to 20000.0)".to_string());
361                params.insert("resonance", "Resonance/Q (0.0 to 1.0)".to_string());
362            }
363            "Bandpass" => {
364                params.insert("cutoff", "Center frequency (20.0 to 20000.0)".to_string());
365                params.insert("resonance", "Bandwidth/Resonance (0.0 to 1.0)".to_string());
366            }
367            "Tremolo" => {
368                params.insert("rate", "LFO rate (0.1 to 20.0 Hz)".to_string());
369                params.insert("depth", "Depth (0.0 to 1.0)".to_string());
370                params.insert("sync", "Sync to tempo (true/false)".to_string());
371            }
372            "Vibrato" => {
373                params.insert("rate", "LFO rate (0.1 to 10.0 Hz)".to_string());
374                params.insert(
375                    "depth",
376                    "Delay depth in seconds (small, e.g. 0.003)".to_string(),
377                );
378                params.insert("sync", "Sync to tempo (true/false)".to_string());
379            }
380            "Monoizer" => {
381                params.insert("enabled", "Enable monoizer (true/false)".to_string());
382                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
383            }
384            "Stereo" => {
385                params.insert("width", "Stereo width (0.0 to 2.0)".to_string());
386            }
387            "Freeze" => {
388                params.insert("enabled", "Enable freeze (true/false)".to_string());
389                params.insert(
390                    "fade",
391                    "Fade-in amount when freezing (0.0 to 1.0)".to_string(),
392                );
393                params.insert("hold", "Hold time in seconds (0.05 to 5.0)".to_string());
394            }
395            "Slice" => {
396                params.insert("segments", "Number of segments (1 to 16)".to_string());
397                params.insert("mode", "Mode: sequential | random".to_string());
398                params.insert(
399                    "crossfade",
400                    "Crossfade between slices (0.0 to 1.0)".to_string(),
401                );
402            }
403            "Stretch" => {
404                params.insert("factor", "Time stretch factor (0.25 to 4.0)".to_string());
405                params.insert(
406                    "pitch",
407                    "Pitch shift in semitones (-48.0 to 48.0)".to_string(),
408                );
409                params.insert("formant", "Preserve formants (true/false)".to_string());
410            }
411            "Roll" => {
412                params.insert("duration_ms", "Roll segment duration in ms".to_string());
413                params.insert("sync", "Sync to tempo (true/false)".to_string());
414                params.insert("repeats", "Number of repeats (1 to 16)".to_string());
415                params.insert("fade", "Crossfade between repeats (0.0 to 1.0)".to_string());
416            }
417            "Reverb" => {
418                params.insert("size", "Room size (0.0 to 1.0)".to_string());
419                params.insert(
420                    "decay",
421                    "Decay/time multiplier (0.0 short to 2.0 long)".to_string(),
422                );
423                params.insert("damping", "High frequency damping (0.0 to 1.0)".to_string());
424                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
425            }
426            "Delay" => {
427                params.insert(
428                    "time",
429                    "Delay time in milliseconds (1.0 to 2000.0)".to_string(),
430                );
431                params.insert("feedback", "Feedback amount (0.0 to 0.95)".to_string());
432                params.insert("mix", "Wet/dry mix (0.0 to 1.0)".to_string());
433            }
434            "Reverse" => {
435                params.insert(
436                    "enabled",
437                    "Enable/disable reverse effect (true/false)".to_string(),
438                );
439            }
440            "Speed" => {
441                params.insert(
442                    "speed",
443                    "Playback speed multiplier (0.1 to 4.0)".to_string(),
444                );
445            }
446            _ => {}
447        }
448        params
449    }
450}
451
452// trigger-specific processors moved to effects::processors module
453
454#[cfg(test)]
455#[path = "test_registry.rs"]
456mod tests;