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