elemaudio_rs/
core.rs

1use crate::graph::Node;
2
3/// Node-or-number input accepted by core utilities.
4#[derive(Debug, Clone)]
5pub enum ElemNode {
6    Node(Node),
7    Number(f64),
8}
9
10impl From<Node> for ElemNode {
11    fn from(node: Node) -> Self {
12        Self::Node(node)
13    }
14}
15
16impl From<f64> for ElemNode {
17    fn from(value: f64) -> Self {
18        Self::Number(value)
19    }
20}
21
22/// Creates a node from a kind, props, and child inputs.
23pub fn create_node(
24    kind: impl Into<String>,
25    props: serde_json::Value,
26    children: impl IntoIterator<Item = ElemNode>,
27) -> Node {
28    Node::new(kind, props, children.into_iter().map(resolve).collect())
29}
30
31/// Returns whether the value is already a node.
32pub fn is_node(value: &ElemNode) -> bool {
33    matches!(value, ElemNode::Node(_))
34}
35
36/// Resolves a value into a node.
37pub fn resolve(value: impl Into<ElemNode>) -> Node {
38    match value.into() {
39        ElemNode::Node(node) => node,
40        ElemNode::Number(value) => {
41            Node::new("const", serde_json::json!({ "value": value }), vec![])
42        }
43    }
44}
45
46/// Expands a node into one node per output channel.
47pub fn unpack(node: Node, num_channels: usize) -> Vec<Node> {
48    (0..num_channels)
49        .map(|output_channel| node.clone().with_output_channel(output_channel))
50        .collect()
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn resolve_turns_numbers_into_const_nodes() {
59        let node = resolve(220.0);
60
61        assert_eq!(node.kind(), "const");
62        assert_eq!(node.props()["value"], 220.0);
63        assert!(node.children().is_empty());
64        assert_eq!(node.output_channel(), 0);
65    }
66
67    #[test]
68    fn resolve_leaves_nodes_unchanged() {
69        let node = Node::new("sin", serde_json::Value::Null, vec![]).with_output_channel(2);
70
71        match resolve(node.clone()) {
72            resolved if resolved.kind() == node.kind() => {
73                assert_eq!(resolved.output_channel(), 2);
74            }
75            _ => panic!("expected resolve to keep the node as-is"),
76        }
77    }
78
79    #[test]
80    fn is_node_detects_node_values() {
81        assert!(is_node(&ElemNode::Node(Node::new(
82            "sr",
83            serde_json::Value::Null,
84            vec![],
85        ))));
86        assert!(!is_node(&ElemNode::Number(1.0)));
87    }
88
89    #[test]
90    fn create_node_resolves_children() {
91        let node = create_node(
92            "add",
93            serde_json::Value::Null,
94            [ElemNode::from(1.0), ElemNode::from(2.0)],
95        );
96
97        assert_eq!(node.kind(), "add");
98        assert_eq!(node.children().len(), 2);
99        assert_eq!(node.children()[0].kind(), "const");
100        assert_eq!(node.children()[0].props()["value"], 1.0);
101        assert_eq!(node.children()[1].props()["value"], 2.0);
102        assert_eq!(node.output_channel(), 0);
103    }
104
105    #[test]
106    fn unpack_clones_the_node_for_each_channel() {
107        let node = Node::new("meter", serde_json::json!({ "name": "out" }), vec![]);
108
109        let unpacked = unpack(node.clone(), 3);
110
111        assert_eq!(unpacked.len(), 3);
112        for (index, child) in unpacked.into_iter().enumerate() {
113            assert_eq!(child.kind(), node.kind());
114            assert_eq!(child.props(), node.props());
115            assert_eq!(child.output_channel(), index);
116        }
117    }
118}