Skip to main content

sim_lib_view_daw/
poly.rs

1//! Polyphonic section view for component builders.
2
3use sim_kernel::{Expr, Symbol};
4use sim_lib_scene::node;
5use sim_value::build::{int, map, sym, text, vector};
6
7/// Stable lens id for the polyphonic section view.
8pub const POLY_SECTION_VIEW_ID: &str = "view:component-poly-sections";
9
10/// Builds the polyphonic section table Scene from a sections descriptor.
11pub fn poly_section_view(sections: &Expr) -> Expr {
12    let rows = sequence(field_named(sections, "sections").unwrap_or(sections))
13        .into_iter()
14        .enumerate()
15        .map(|(index, section)| section_row(&section, index))
16        .collect();
17    node(
18        "table",
19        vec![
20            ("lens", sym(POLY_SECTION_VIEW_ID)),
21            ("role", sym("poly-section-view")),
22            ("rows", vector(rows)),
23            (
24                "actions",
25                vector(vec![
26                    sym("enable-section"),
27                    sym("disable-section"),
28                    sym("inspect"),
29                ]),
30            ),
31        ],
32    )
33}
34
35fn section_row(section: &Expr, index: usize) -> Expr {
36    let id = field_named(section, "id")
37        .cloned()
38        .unwrap_or_else(|| Expr::Symbol(Symbol::new(format!("section-{index}"))));
39    let label = field_str_named(section, "label")
40        .map(text)
41        .unwrap_or_else(|| text(expr_label(&id)));
42    map(vec![
43        ("id", id),
44        ("label", label),
45        (
46            "enabled",
47            field_named(section, "enabled")
48                .cloned()
49                .unwrap_or(Expr::Bool(true)),
50        ),
51        (
52            "voices",
53            field_named(section, "voices")
54                .cloned()
55                .unwrap_or_else(|| int(1)),
56        ),
57        (
58            "clock",
59            field_named(section, "clock")
60                .cloned()
61                .unwrap_or_else(|| sym("sample-clock")),
62        ),
63        (
64            "rate",
65            field_named(section, "rate")
66                .cloned()
67                .unwrap_or_else(|| sym("audio-rate")),
68        ),
69        (
70            "actions",
71            vector(vec![sym("enable-section"), sym("disable-section")]),
72        ),
73    ])
74}
75
76fn sequence(value: &Expr) -> Vec<Expr> {
77    match value {
78        Expr::List(items) | Expr::Vector(items) => items.clone(),
79        Expr::Nil => Vec::new(),
80        other => vec![other.clone()],
81    }
82}
83
84fn field_named<'a>(expr: &'a Expr, name: &str) -> Option<&'a Expr> {
85    let Expr::Map(entries) = expr else {
86        return None;
87    };
88    entries
89        .iter()
90        .find_map(|(key, value)| (key_name(key) == Some(name)).then_some(value))
91}
92
93fn field_str_named<'a>(expr: &'a Expr, name: &str) -> Option<&'a str> {
94    match field_named(expr, name) {
95        Some(Expr::String(text)) => Some(text),
96        _ => None,
97    }
98}
99
100fn key_name(key: &Expr) -> Option<&str> {
101    match key {
102        Expr::Symbol(symbol) => Some(symbol.name.as_ref()),
103        Expr::String(text) => Some(text),
104        _ => None,
105    }
106}
107
108fn expr_label(expr: &Expr) -> String {
109    match expr {
110        Expr::Symbol(symbol) => symbol.as_qualified_str(),
111        Expr::String(text) => text.clone(),
112        other => format!("{other:?}"),
113    }
114}