sim_lib_audio_graph_core/
patch.rs1use sim_kernel::{Error, Expr, NumberLiteral, Result, Symbol};
2
3use crate::{Cable, PortUri};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct PatchNode {
8 pub id: String,
10 pub in_channels: u16,
12 pub out_channels: u16,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Patch {
19 pub nodes: Vec<PatchNode>,
21 pub cables: Vec<Cable>,
23}
24
25impl Patch {
26 pub fn to_expr(&self) -> Expr {
28 Expr::Map(vec![
29 (
30 field("tag"),
31 Expr::Symbol(Symbol::qualified("audio-graph", "patch")),
32 ),
33 (
34 field("nodes"),
35 Expr::Vector(self.nodes.iter().map(node_to_expr).collect()),
36 ),
37 (
38 field("cables"),
39 Expr::Vector(self.cables.iter().map(cable_to_expr).collect()),
40 ),
41 ])
42 }
43
44 pub fn from_expr(expr: &Expr) -> Result<Self> {
46 let map = expr_map(expr, "patch")?;
47 match lookup(map, "tag") {
48 Some(Expr::Symbol(symbol)) if is_symbol(symbol, "audio-graph", "patch") => {}
49 Some(_) => return Err(Error::Eval("audio graph patch tag is invalid".to_owned())),
50 None => return Err(Error::Eval("audio graph patch tag is missing".to_owned())),
51 }
52 let nodes = expr_vector(
53 lookup(map, "nodes").ok_or_else(|| missing("nodes"))?,
54 "patch nodes",
55 )?
56 .iter()
57 .map(node_from_expr)
58 .collect::<Result<Vec<_>>>()?;
59 let cables = expr_vector(
60 lookup(map, "cables").ok_or_else(|| missing("cables"))?,
61 "patch cables",
62 )?
63 .iter()
64 .map(cable_from_expr)
65 .collect::<Result<Vec<_>>>()?;
66 Ok(Self { nodes, cables })
67 }
68}
69
70fn node_to_expr(node: &PatchNode) -> Expr {
71 Expr::Map(vec![
72 (field("id"), Expr::String(node.id.clone())),
73 (field("in-channels"), number(node.in_channels)),
74 (field("out-channels"), number(node.out_channels)),
75 ])
76}
77
78fn node_from_expr(expr: &Expr) -> Result<PatchNode> {
79 let map = expr_map(expr, "patch node")?;
80 Ok(PatchNode {
81 id: expr_string(
82 lookup(map, "id").ok_or_else(|| missing("node id"))?,
83 "node id",
84 )?
85 .to_owned(),
86 in_channels: expr_u16(
87 lookup(map, "in-channels").ok_or_else(|| missing("node in-channels"))?,
88 "node in-channels",
89 )?,
90 out_channels: expr_u16(
91 lookup(map, "out-channels").ok_or_else(|| missing("node out-channels"))?,
92 "node out-channels",
93 )?,
94 })
95}
96
97fn cable_to_expr(cable: &Cable) -> Expr {
98 Expr::Map(vec![
99 (field("from"), Expr::String(cable.from.to_string())),
100 (field("to"), Expr::String(cable.to.to_string())),
101 ])
102}
103
104fn cable_from_expr(expr: &Expr) -> Result<Cable> {
105 let map = expr_map(expr, "patch cable")?;
106 let from = expr_string(
107 lookup(map, "from").ok_or_else(|| missing("cable from"))?,
108 "cable from",
109 )?
110 .parse::<PortUri>()?;
111 let to = expr_string(
112 lookup(map, "to").ok_or_else(|| missing("cable to"))?,
113 "cable to",
114 )?
115 .parse::<PortUri>()?;
116 Ok(Cable { from, to })
117}
118
119fn field(name: &'static str) -> Expr {
120 sim_value::build::qsym("audio-graph", name)
121}
122
123fn number(value: u16) -> Expr {
124 Expr::Number(NumberLiteral {
125 domain: Symbol::qualified("numbers", "i64"),
126 canonical: value.to_string(),
127 })
128}
129
130fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
131 match expr {
132 Expr::Map(entries) => Ok(entries),
133 other => Err(Error::Eval(format!(
134 "expected {context} map, found {}",
135 expr_kind(other)
136 ))),
137 }
138}
139
140fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
141 match expr {
142 Expr::Vector(items) => Ok(items),
143 other => Err(Error::Eval(format!(
144 "expected {context} vector, found {}",
145 expr_kind(other)
146 ))),
147 }
148}
149
150fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
151 match expr {
152 Expr::String(text) => Ok(text),
153 other => Err(Error::Eval(format!(
154 "expected {context} string, found {}",
155 expr_kind(other)
156 ))),
157 }
158}
159
160fn expr_u16(expr: &Expr, context: &str) -> Result<u16> {
161 match expr {
162 Expr::Number(number) => number.canonical.parse::<u16>().map_err(|_| {
163 Error::Eval(format!(
164 "expected {context} u16 number, found {}",
165 number.canonical
166 ))
167 }),
168 Expr::String(text) => text
169 .parse::<u16>()
170 .map_err(|_| Error::Eval(format!("expected {context} u16 string, found {text}"))),
171 other => Err(Error::Eval(format!(
172 "expected {context} u16, found {}",
173 expr_kind(other)
174 ))),
175 }
176}
177
178fn lookup<'a>(map: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
179 map.iter().find_map(|(key, value)| match key {
180 Expr::Symbol(symbol) if is_symbol(symbol, "audio-graph", name) => Some(value),
181 _ => None,
182 })
183}
184
185fn is_symbol(symbol: &Symbol, namespace: &str, name: &str) -> bool {
186 symbol.namespace.as_deref() == Some(namespace) && symbol.name.as_ref() == name
187}
188
189fn missing(field: &str) -> Error {
190 Error::Eval(format!("audio graph patch field is missing: {field}"))
191}
192
193use sim_value::kind::expr_kind;