Skip to main content

alpha2_render/
builtins.rs

1use alpha2_gpu::CapabilityTier;
2use alpha2_shaders::{
3    ANGULAR, CHROMA_KEY_BODY, CIRCLE, CIRCLE_CROP, CIRCLE_OPEN, COLOR_CONTROLS, COLOR_PHASE,
4    COLOUR_DISTANCE, CROP, CROSS_WARP, DIRECTIONAL, DIRECTIONAL_WARP, DIRECTIONAL_WIPE, DREAMY,
5    EXPANDED_EFFECT, FADE, FADE_COLOR, FADE_GRAYSCALE, FILM_GRAIN, GAUSSIAN_BLUR, GLOW_COMBINE,
6    GLOW_THRESHOLD, HEART, LUT_3D_BODY, MIRROR, MORPH, PINWHEEL, PIXELATE_EFFECT, PIXELIZE,
7    POLKA_DOTS_CURTAIN, RADIAL, RIPPLE, SHARPEN, SIMPLE_ZOOM, SQUEEZE, TRANSITION_HELPERS,
8    VIGNETTE, WIND, WINDOW_BLINDS, WINDOW_SLICE, WIPE_DOWN, WIPE_LEFT, WIPE_RIGHT, WIPE_UP,
9};
10
11use crate::{
12    EffectDef, EffectId, EffectKind, EffectResourceKind, ParamSpec, ParamType, ParamUnit,
13    ParamValue, Renderer, ResourceSpec, ShaderDiagnostics,
14};
15
16/// Stable identifier of the built-in chroma-key effect.
17pub const CHROMA_KEY_ID: &str = "alpha2.effect.chroma-key";
18
19/// Stable identifier of the atomic Gaussian-blur effect.
20pub const GAUSSIAN_BLUR_ID: &str = "alpha2.effect.gaussian-blur";
21
22/// Internal stable identifier of one separable Gaussian-blur pass.
23pub(crate) const GAUSSIAN_BLUR_PASS_ID: &str = "alpha2.internal.gaussian-blur-pass";
24
25/// Stable identifier of the built-in glow effect.
26pub const GLOW_ID: &str = "alpha2.effect.glow";
27
28/// Internal stable identifier of glow's threshold pass.
29pub(crate) const GLOW_THRESHOLD_ID: &str = "alpha2.internal.glow-threshold";
30
31/// Internal stable identifier of glow's two-input combining pass.
32pub(crate) const GLOW_COMBINE_ID: &str = "alpha2.internal.glow-combine";
33
34/// `EffectDef::license` of the effects Alpha2 itself wrote.
35pub(crate) const ALPHA2_EFFECT_LICENSE: &str = "MIT; Copyright Alpha2 contributors";
36
37/// Stable identifier of the resource-backed 3D LUT effect.
38pub const LUT_3D_ID: &str = "alpha2.effect.lut-3d";
39
40/// Returns fresh definitions for every public built-in clip effect.
41#[must_use]
42pub fn builtin_effect_definitions() -> Vec<EffectDef> {
43    vec![
44        gaussian_blur_definition(),
45        chroma_key_definition(),
46        lut_3d_definition(),
47        simple_effect(
48            "color-controls",
49            COLOR_CONTROLS,
50            vec![
51                float("exposure", 0.0, -3.0, 3.0),
52                float("contrast", 1.0, 0.0, 2.0),
53                float("saturation", 1.0, 0.0, 2.0),
54                float("temperature", 0.0, -1.0, 1.0),
55                float("tint", 0.0, -1.0, 1.0),
56            ],
57        ),
58        simple_effect(
59            "vignette",
60            VIGNETTE,
61            vec![
62                float("amount", 0.45, 0.0, 1.0),
63                float("radius", 0.72, 0.0, 1.5),
64                float("softness", 0.45, 0.01, 1.0),
65            ],
66        ),
67        simple_effect("sharpen", SHARPEN, vec![float("amount", 0.6, 0.0, 3.0)]),
68        simple_effect(
69            "pixelate",
70            PIXELATE_EFFECT,
71            vec![float("blocks", 48.0, 2.0, 400.0)],
72        ),
73        glow_definition(),
74        simple_effect(
75            "film-grain",
76            FILM_GRAIN,
77            vec![
78                float("amount", 0.18, 0.0, 1.0),
79                float("size", 1.4, 0.5, 6.0),
80            ],
81        ),
82        simple_effect(
83            "crop",
84            CROP,
85            vec![
86                vec2_range("top_left", [0.0, 0.0], 0.0, 1.0, 0.005),
87                vec2_range("bottom_right", [0.0, 0.0], 0.0, 1.0, 0.005),
88                float_step("feather", 0.004, 0.0, 0.2, 0.002),
89            ],
90        ),
91        simple_effect(
92            "mirror",
93            MIRROR,
94            vec![
95                choice("axis", 0, &["horizontal", "vertical"]),
96                boolean("keep_second_half", false),
97            ],
98        ),
99    ]
100}
101
102const GL_TRANSITIONS_BASE: &str = "https://github.com/gl-transitions/gl-transitions/blob/902218a1b63773ac0d0d9f491951da3392365bfe/transitions/";
103
104/// Returns fresh, data-only definitions for all built-in transitions.
105#[must_use]
106pub fn builtin_transition_definitions() -> Vec<EffectDef> {
107    let mut definitions = classic_transition_definitions();
108    definitions.extend(wipe_transition_definitions());
109    definitions.extend(stylized_transition_definitions());
110    definitions
111}
112
113fn classic_transition_definitions() -> Vec<EffectDef> {
114    vec![
115        transition("fade", FADE, "fade.glsl", "gre", Vec::new()),
116        transition(
117            "directional",
118            DIRECTIONAL,
119            "Directional.glsl",
120            "Gaëtan Renaudeau",
121            vec![vec2_range("direction", [0.0, 1.0], -1.0, 1.0, 0.1)],
122        ),
123        transition(
124            "circle-open",
125            CIRCLE_OPEN,
126            "circleopen.glsl",
127            "gre",
128            vec![float("smoothness", 0.3, 0.0, 1.0), boolean("opening", true)],
129        ),
130        transition(
131            "angular",
132            ANGULAR,
133            "angular.glsl",
134            "Fernando Kuteken",
135            vec![degrees("starting_angle", 90.0, -360.0, 360.0)],
136        ),
137        transition(
138            "pixelize",
139            PIXELIZE,
140            "pixelize.glsl",
141            "gre",
142            vec![
143                vec2_range("squares_min", [20.0, 20.0], 1.0, 100.0, 1.0),
144                float("steps", 50.0, 0.0, 200.0),
145            ],
146        ),
147        transition(
148            "directional-wipe",
149            DIRECTIONAL_WIPE,
150            "directionalwipe.glsl",
151            "gre",
152            vec![
153                vec2_range("direction", [1.0, -1.0], -1.0, 1.0, 0.1),
154                float("smoothness", 0.5, 0.0, 1.0),
155            ],
156        ),
157        transition(
158            "circle",
159            CIRCLE,
160            "circle.glsl",
161            "Fernando Kuteken",
162            vec![
163                vec2_range("center", [0.5, 0.5], 0.0, 1.0, 0.01),
164                color("back_color", [0.1, 0.1, 0.1, 1.0]),
165            ],
166        ),
167        transition(
168            "cross-warp",
169            CROSS_WARP,
170            "crosswarp.glsl",
171            "Eke Péter",
172            Vec::new(),
173        ),
174        transition("dreamy", DREAMY, "Dreamy.glsl", "mikolalysenko", Vec::new()),
175        transition(
176            "radial",
177            RADIAL,
178            "Radial.glsl",
179            "Xaychru",
180            vec![float("smoothness", 1.0, 0.0001, 2.0)],
181        ),
182    ]
183}
184
185fn wipe_transition_definitions() -> Vec<EffectDef> {
186    vec![
187        transition("wipe-up", WIPE_UP, "wipeUp.glsl", "Jake Nelson", Vec::new()),
188        transition(
189            "wipe-down",
190            WIPE_DOWN,
191            "wipeDown.glsl",
192            "Jake Nelson",
193            Vec::new(),
194        ),
195        transition(
196            "wipe-left",
197            WIPE_LEFT,
198            "wipeLeft.glsl",
199            "Jake Nelson",
200            Vec::new(),
201        ),
202        transition(
203            "wipe-right",
204            WIPE_RIGHT,
205            "wipeRight.glsl",
206            "Jake Nelson",
207            Vec::new(),
208        ),
209        transition(
210            "window-blinds",
211            WINDOW_BLINDS,
212            "windowblinds.glsl",
213            "Fabien Benetou",
214            Vec::new(),
215        ),
216        transition(
217            "window-slice",
218            WINDOW_SLICE,
219            "windowslice.glsl",
220            "gre",
221            vec![
222                float("count", 10.0, 1.0, 100.0),
223                float("smoothness", 0.5, 0.0, 1.0),
224            ],
225        ),
226        transition(
227            "wind",
228            WIND,
229            "wind.glsl",
230            "gre",
231            vec![float("size", 0.2, 0.0001, 1.0), boolean("reversed", false)],
232        ),
233        transition(
234            "pinwheel",
235            PINWHEEL,
236            "pinwheel.glsl",
237            "Mr Speaker",
238            vec![float("speed", 2.0, 0.0, 20.0)],
239        ),
240        transition("heart", HEART, "heart.glsl", "gre", Vec::new()),
241    ]
242}
243
244fn stylized_transition_definitions() -> Vec<EffectDef> {
245    vec![
246        transition(
247            "color-phase",
248            COLOR_PHASE,
249            "colorphase.glsl",
250            "gre",
251            vec![
252                thresholds("from_step", [0.0, 0.2, 0.4, 0.0]),
253                thresholds("to_step", [0.6, 0.8, 1.0, 1.0]),
254            ],
255        ),
256        transition(
257            "fade-color",
258            FADE_COLOR,
259            "fadecolor.glsl",
260            "gre",
261            vec![
262                color("color", [0.0, 0.0, 0.0, 1.0]),
263                float("color_phase", 0.4, 0.0, 1.0),
264            ],
265        ),
266        transition(
267            "fade-grayscale",
268            FADE_GRAYSCALE,
269            "fadegrayscale.glsl",
270            "gre",
271            vec![float("intensity", 0.3, 0.0, 1.0)],
272        ),
273        transition(
274            "morph",
275            MORPH,
276            "morph.glsl",
277            "paniq",
278            vec![float("strength", 0.1, 0.0, 1.0)],
279        ),
280        transition(
281            "ripple",
282            RIPPLE,
283            "ripple.glsl",
284            "gre",
285            vec![
286                float("amplitude", 100.0, 0.0, 200.0),
287                float("speed", 50.0, 0.0, 100.0),
288            ],
289        ),
290        transition(
291            "squeeze",
292            SQUEEZE,
293            "squeeze.glsl",
294            "gre",
295            vec![float("color_separation", 0.04, 0.0, 0.5)],
296        ),
297        transition(
298            "simple-zoom",
299            SIMPLE_ZOOM,
300            "SimpleZoom.glsl",
301            "0gust1",
302            vec![float("zoom_quickness", 0.8, 0.2, 1.0)],
303        ),
304        transition(
305            "circle-crop",
306            CIRCLE_CROP,
307            "CircleCrop.glsl",
308            "fkuteken",
309            vec![color("back_color", [0.0, 0.0, 0.0, 1.0])],
310        ),
311        transition(
312            "colour-distance",
313            COLOUR_DISTANCE,
314            "ColourDistance.glsl",
315            "P-Seebauer",
316            vec![float("power", 5.0, 0.0, 20.0)],
317        ),
318        transition(
319            "polka-dots-curtain",
320            POLKA_DOTS_CURTAIN,
321            "PolkaDotsCurtain.glsl",
322            "bobylito",
323            vec![
324                float("dots", 20.0, 1.0, 100.0),
325                vec2_range("center", [0.0, 0.0], 0.0, 1.0, 0.01),
326            ],
327        ),
328        transition(
329            "directional-warp",
330            DIRECTIONAL_WARP,
331            "directionalwarp.glsl",
332            "pschroen",
333            vec![
334                float("smoothness", 0.1, 0.0001, 1.0),
335                vec2_range("direction", [-1.0, 1.0], -1.0, 1.0, 0.1),
336            ],
337        ),
338    ]
339}
340
341/// Returns the data-only definition of the built-in chroma-key effect.
342///
343/// `key_color` is compared with each pixel's straight color as the renderer
344/// stores it: sRGB-encoded, not linear light. Pick the key as a color picker
345/// shows the screen. With `distance_mode` at `rgb` a pixel within `threshold`
346/// of the key in that RGB space is removed, `softness` wide edges are partly
347/// transparent and `spill_suppression` desaturates them; `chroma` compares
348/// only the chroma plane, so a key survives shadows on a screen.
349#[must_use]
350pub fn chroma_key_definition() -> EffectDef {
351    EffectDef {
352        id: CHROMA_KEY_ID.into(),
353        version: 1,
354        kind: EffectKind::Effect,
355        inputs: Vec::new(),
356        params: vec![
357            color("key_color", [0.0, 1.0, 0.0, 1.0]),
358            float("threshold", 0.35, 0.0, 3.0_f32.sqrt()),
359            float("softness", 0.1, f32::EPSILON, 1.0),
360            float("spill_suppression", 0.2, 0.0, 1.0),
361            choice("distance_mode", 0, &["rgb", "chroma"]),
362        ],
363        resources: Vec::new(),
364        wgsl_body: CHROMA_KEY_BODY.into(),
365        tier_min: CapabilityTier::TierB,
366        license: Some(ALPHA2_EFFECT_LICENSE.into()),
367    }
368}
369
370/// Returns the data-only definition of the built-in Gaussian blur.
371///
372/// Its standard deviation is half its `radius`, a fraction of the output
373/// height. A render graph draws it as a cascade of separable passes
374/// ([`crate::RenderGraph::add_effect`]); its own body is never drawn.
375#[must_use]
376pub fn gaussian_blur_definition() -> EffectDef {
377    EffectDef {
378        id: GAUSSIAN_BLUR_ID.into(),
379        version: 1,
380        kind: EffectKind::Effect,
381        inputs: Vec::new(),
382        params: vec![float_step("radius", 0.012, 0.0, 1.0, 0.001)],
383        resources: Vec::new(),
384        wgsl_body: EXPANDED_EFFECT.into(),
385        tier_min: CapabilityTier::TierB,
386        license: Some(ALPHA2_EFFECT_LICENSE.into()),
387    }
388}
389
390pub(crate) fn gaussian_blur_pass_definition() -> EffectDef {
391    EffectDef {
392        id: GAUSSIAN_BLUR_PASS_ID.into(),
393        version: 1,
394        kind: EffectKind::Effect,
395        inputs: Vec::new(),
396        params: vec![
397            vec2("step", [0.0, 0.0]),
398            float_step(
399                "sigma",
400                1.0,
401                0.0,
402                crate::gaussian_blur::MAX_PASS_SIGMA_TAPS,
403                0.001,
404            ),
405        ],
406        resources: Vec::new(),
407        wgsl_body: GAUSSIAN_BLUR.into(),
408        tier_min: CapabilityTier::TierB,
409        license: Some(ALPHA2_EFFECT_LICENSE.into()),
410    }
411}
412
413/// Returns the data-only definition of the built-in glow.
414///
415/// The straight color above `threshold` is blurred like
416/// [`gaussian_blur_definition`] with this `radius` and added back to the
417/// source, scaled by `intensity`. A render graph draws it as internal passes;
418/// its own body is never drawn.
419#[must_use]
420pub fn glow_definition() -> EffectDef {
421    EffectDef {
422        id: GLOW_ID.into(),
423        version: 1,
424        kind: EffectKind::Effect,
425        inputs: Vec::new(),
426        params: vec![
427            float("threshold", 0.6, 0.0, 1.0),
428            float("intensity", 0.8, 0.0, 3.0),
429            float("radius", 0.012, 0.0, 0.06),
430        ],
431        resources: Vec::new(),
432        wgsl_body: EXPANDED_EFFECT.into(),
433        tier_min: CapabilityTier::TierB,
434        license: Some(ALPHA2_EFFECT_LICENSE.into()),
435    }
436}
437
438fn glow_threshold_definition() -> EffectDef {
439    EffectDef {
440        id: GLOW_THRESHOLD_ID.into(),
441        ..simple_effect(
442            "glow-threshold",
443            GLOW_THRESHOLD,
444            vec![float("threshold", 0.6, 0.0, 1.0)],
445        )
446    }
447}
448
449// Two inputs make it a transition to the registry; the graph draws it at progress 0.5, and
450// its body reads no progress.
451fn glow_combine_definition() -> EffectDef {
452    EffectDef {
453        id: GLOW_COMBINE_ID.into(),
454        version: 1,
455        kind: EffectKind::Transition,
456        inputs: Vec::new(),
457        params: vec![float("intensity", 0.8, 0.0, 3.0)],
458        resources: Vec::new(),
459        wgsl_body: GLOW_COMBINE.into(),
460        tier_min: CapabilityTier::TierB,
461        license: Some(ALPHA2_EFFECT_LICENSE.into()),
462    }
463}
464
465/// Returns the data-only definition of the resource-backed 3D LUT effect.
466#[must_use]
467pub fn lut_3d_definition() -> EffectDef {
468    EffectDef {
469        id: LUT_3D_ID.into(),
470        version: 1,
471        kind: EffectKind::Effect,
472        inputs: Vec::new(),
473        params: vec![float("strength", 1.0, 0.0, 1.0)],
474        resources: vec![ResourceSpec {
475            name: "lut".into(),
476            kind: EffectResourceKind::Lut3d,
477        }],
478        wgsl_body: LUT_3D_BODY.into(),
479        tier_min: CapabilityTier::TierB,
480        license: Some(ALPHA2_EFFECT_LICENSE.into()),
481    }
482}
483
484/// The passes the engine inserts itself, hidden from the effect catalog.
485pub(crate) fn internal_effect_definitions() -> Vec<EffectDef> {
486    let mut definitions = vec![
487        gaussian_blur_pass_definition(),
488        glow_threshold_definition(),
489        glow_combine_definition(),
490        crate::masking::track_matte_definition(),
491        crate::generator::generator_definition(),
492    ];
493    definitions.extend(crate::masking::mask_combine_definitions());
494    definitions
495}
496
497impl Renderer {
498    async fn register_builtin_definition(
499        &mut self,
500        definition: EffectDef,
501    ) -> Result<EffectId, ShaderDiagnostics> {
502        let id = self.register_effect(definition).await?;
503        self.effects.mark_builtin(id.clone());
504        Ok(id)
505    }
506
507    /// Compiles and registers the complete built-in effect and transition catalog.
508    ///
509    /// Built-ins are engine-owned and cannot be removed through the host-effect
510    /// lifecycle methods.
511    ///
512    /// # Errors
513    /// Returns diagnostics from the first definition that fails.
514    pub async fn register_builtins(&mut self) -> Result<Vec<EffectId>, ShaderDiagnostics> {
515        for definition in internal_effect_definitions() {
516            let internal = self.register_builtin_definition(definition).await?;
517            self.effects.mark_internal(internal);
518        }
519        let mut definitions = builtin_effect_definitions();
520        definitions.extend(builtin_transition_definitions());
521        let mut ids = Vec::with_capacity(definitions.len());
522        for definition in definitions {
523            ids.push(self.register_builtin_definition(definition).await?);
524        }
525        Ok(ids)
526    }
527
528    /// Compiles and registers the built-in chroma-key effect.
529    ///
530    /// # Errors
531    /// Returns schema or compiler diagnostics when registration fails.
532    pub async fn register_builtin_chroma_key(&mut self) -> Result<EffectId, ShaderDiagnostics> {
533        self.register_builtin_definition(chroma_key_definition())
534            .await
535    }
536
537    /// Compiles and registers the public Gaussian blur and its internal pass.
538    ///
539    /// # Errors
540    /// Returns schema or compiler diagnostics when registration fails.
541    pub async fn register_builtin_gaussian_blur(&mut self) -> Result<EffectId, ShaderDiagnostics> {
542        let internal = self
543            .register_builtin_definition(gaussian_blur_pass_definition())
544            .await?;
545        self.effects.mark_internal(internal);
546        self.register_builtin_definition(gaussian_blur_definition())
547            .await
548    }
549
550    /// Compiles and registers the built-in 3D LUT effect.
551    ///
552    /// # Errors
553    /// Returns schema or compiler diagnostics when registration fails.
554    pub async fn register_builtin_lut_3d(&mut self) -> Result<EffectId, ShaderDiagnostics> {
555        self.register_builtin_definition(lut_3d_definition()).await
556    }
557
558    /// Compiles and registers all built-in transition definitions.
559    ///
560    /// # Errors
561    /// Returns diagnostics from the first definition that fails. Definitions
562    /// compiled before that point remain registered.
563    pub async fn register_builtin_transitions(
564        &mut self,
565    ) -> Result<Vec<EffectId>, ShaderDiagnostics> {
566        let definitions = builtin_transition_definitions();
567        let mut ids = Vec::with_capacity(definitions.len());
568        for definition in definitions {
569            ids.push(self.register_builtin_definition(definition).await?);
570        }
571        Ok(ids)
572    }
573}
574
575fn simple_effect(name: &str, body: &str, params: Vec<ParamSpec>) -> EffectDef {
576    EffectDef {
577        id: format!("alpha2.effect.{name}"),
578        version: 1,
579        kind: EffectKind::Effect,
580        inputs: Vec::new(),
581        params,
582        resources: Vec::new(),
583        wgsl_body: body.into(),
584        tier_min: CapabilityTier::TierB,
585        license: Some(ALPHA2_EFFECT_LICENSE.into()),
586    }
587}
588
589fn transition(
590    name: &str,
591    body: &str,
592    source: &str,
593    author: &str,
594    params: Vec<ParamSpec>,
595) -> EffectDef {
596    EffectDef {
597        id: format!("alpha2.transition.{name}"),
598        version: 1,
599        kind: EffectKind::Transition,
600        inputs: Vec::new(),
601        params,
602        resources: Vec::new(),
603        wgsl_body: format!("{TRANSITION_HELPERS}\n{body}"),
604        tier_min: CapabilityTier::TierB,
605        license: Some(format!(
606            "MIT; Author: {author}; Source: {GL_TRANSITIONS_BASE}{source}"
607        )),
608    }
609}
610
611fn float(name: &str, default: f32, min: f32, max: f32) -> ParamSpec {
612    let span = max - min;
613    let step = if span > 50.0 {
614        1.0
615    } else if span <= 0.1 {
616        0.001
617    } else {
618        0.01
619    };
620    float_step(name, default, min, max, step)
621}
622
623fn float_step(name: &str, default: f32, min: f32, max: f32, step: f32) -> ParamSpec {
624    ParamSpec {
625        name: name.into(),
626        param_type: ParamType::Float,
627        default: ParamValue::Float(default),
628        min: Some(min),
629        max: Some(max),
630        step: Some(step),
631        unit: ParamUnit::Normalized,
632        enum_values: Vec::new(),
633    }
634}
635
636fn degrees(name: &str, default: f32, min: f32, max: f32) -> ParamSpec {
637    ParamSpec {
638        unit: ParamUnit::Degrees,
639        step: Some(1.0),
640        ..float(name, default, min, max)
641    }
642}
643
644fn vec2(name: &str, default: [f32; 2]) -> ParamSpec {
645    ParamSpec {
646        name: name.into(),
647        param_type: ParamType::Vec2,
648        default: ParamValue::Vec2(default),
649        min: None,
650        max: None,
651        step: None,
652        unit: ParamUnit::Normalized,
653        enum_values: Vec::new(),
654    }
655}
656
657fn vec2_range(name: &str, default: [f32; 2], min: f32, max: f32, step: f32) -> ParamSpec {
658    ParamSpec {
659        min: Some(min),
660        max: Some(max),
661        step: Some(step),
662        ..vec2(name, default)
663    }
664}
665
666fn color(name: &str, default: [f32; 4]) -> ParamSpec {
667    ParamSpec {
668        name: name.into(),
669        param_type: ParamType::Color,
670        default: ParamValue::Color(default),
671        min: None,
672        max: None,
673        step: None,
674        unit: ParamUnit::Color,
675        enum_values: Vec::new(),
676    }
677}
678
679/// Four independent progress thresholds, one per channel, in `0..=1`.
680fn thresholds(name: &str, default: [f32; 4]) -> ParamSpec {
681    ParamSpec {
682        name: name.into(),
683        param_type: ParamType::Vec4,
684        default: ParamValue::Vec4(default),
685        min: Some(0.0),
686        max: Some(1.0),
687        step: Some(0.01),
688        unit: ParamUnit::Normalized,
689        enum_values: Vec::new(),
690    }
691}
692
693fn choice(name: &str, default: u32, values: &[&str]) -> ParamSpec {
694    ParamSpec {
695        name: name.into(),
696        param_type: ParamType::Enum,
697        default: ParamValue::Enum(default),
698        min: None,
699        max: None,
700        step: None,
701        unit: ParamUnit::Enum,
702        enum_values: values.iter().map(|value| (*value).into()).collect(),
703    }
704}
705
706fn boolean(name: &str, default: bool) -> ParamSpec {
707    ParamSpec {
708        name: name.into(),
709        param_type: ParamType::Bool,
710        default: ParamValue::Bool(default),
711        min: None,
712        max: None,
713        step: None,
714        unit: ParamUnit::Bool,
715        enum_values: Vec::new(),
716    }
717}