Skip to main content

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