Skip to main content

sim_lib_plugin_core/
cookbook.rs

1//! Deterministic cookbook builders for plugin-core recipes.
2
3use sim_kernel::{Expr, NumberLiteral, Symbol};
4use sim_lib_audio_graph_core::{PortDir, PortMedia};
5
6use crate::{ParameterDescriptor, PluginDescriptor, PluginFormat};
7
8/// Build the modeled gain plugin descriptor used by the cookbook recipe.
9pub fn gain_plugin_demo() -> Expr {
10    let descriptor =
11        PluginDescriptor::audio_effect(PluginFormat::Sim, "org.sim.gain", "SIM Gain", 2)
12            .expect("valid gain plugin descriptor")
13            .with_parameter(
14                ParameterDescriptor::new(0, "gain", "Gain", 0.0, 2.0, 1.0)
15                    .expect("valid gain parameter"),
16            );
17    plugin_descriptor_expr(&descriptor)
18}
19
20fn plugin_descriptor_expr(descriptor: &PluginDescriptor) -> Expr {
21    Expr::Map(vec![
22        (field("kind"), sym("plugin-core", "descriptor")),
23        (
24            field("format"),
25            sym("plugin-format", descriptor.id.format.as_str()),
26        ),
27        (field("id"), Expr::String(descriptor.id.stable_id.clone())),
28        (field("name"), Expr::String(descriptor.name.clone())),
29        (
30            field("ports"),
31            Expr::Vector(descriptor.ports.iter().map(port_expr).collect()),
32        ),
33        (
34            field("parameters"),
35            Expr::Vector(
36                descriptor
37                    .parameters
38                    .iter()
39                    .map(|parameter| {
40                        Expr::Map(vec![
41                            (field("id"), number(parameter.id)),
42                            (
43                                field("stable-id"),
44                                Expr::String(parameter.stable_id.clone()),
45                            ),
46                            (field("default"), number_f64(parameter.default)),
47                        ])
48                    })
49                    .collect(),
50            ),
51        ),
52    ])
53}
54
55fn port_expr(port: &sim_lib_audio_graph_core::PortDecl) -> Expr {
56    Expr::Map(vec![
57        (field("name"), Expr::String(port.name.clone())),
58        (field("media"), sym("audio-port", port_media(port.media))),
59        (field("dir"), sym("audio-port", port_dir(port.dir))),
60        (field("channels"), number(port.channels)),
61    ])
62}
63
64fn port_media(media: PortMedia) -> &'static str {
65    match media {
66        PortMedia::Audio => "audio",
67        PortMedia::Control => "control",
68        PortMedia::Event => "event",
69    }
70}
71
72fn port_dir(dir: PortDir) -> &'static str {
73    match dir {
74        PortDir::In => "in",
75        PortDir::Out => "out",
76    }
77}
78
79fn field(name: &str) -> Expr {
80    Expr::Symbol(Symbol::qualified("plugin-core", name))
81}
82
83fn sym(namespace: &str, name: &str) -> Expr {
84    Expr::Symbol(Symbol::qualified(namespace, name))
85}
86
87fn number(value: impl ToString) -> Expr {
88    Expr::Number(NumberLiteral {
89        domain: Symbol::qualified("numbers", "i64"),
90        canonical: value.to_string(),
91    })
92}
93
94fn number_f64(value: f64) -> Expr {
95    Expr::Number(NumberLiteral {
96        domain: Symbol::qualified("numbers", "f64"),
97        canonical: value.to_string(),
98    })
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn gain_plugin_demo_carries_stereo_ports_and_gain_parameter() {
107        let Expr::Map(entries) = gain_plugin_demo() else {
108            panic!("gain plugin demo is a map")
109        };
110        let rendered = format!("{entries:?}");
111        assert!(rendered.contains("audio-in"));
112        assert!(rendered.contains("audio-out"));
113        assert!(rendered.contains("gain"));
114    }
115}