Skip to main content

sim_lib_topology/
instrument.rs

1//! Instrument-style topology specs (modules and cords) lowered to graph data.
2
3use sim_kernel::{Expr, Symbol};
4
5use crate::{Edge, Graph, Node, NodeId, Port, PortMode, PortRef};
6
7/// Instrument-patch topology spec: a modular-synth style description of modules
8/// wired by cords, lowered to graph data by [`InstrumentTopologyAdapter`].
9#[derive(Clone, Debug)]
10pub struct InstrumentTopologySpec {
11    /// The topology name.
12    pub name: Symbol,
13    /// The modules (lowered to nodes).
14    pub modules: Vec<InstrumentTopologyModule>,
15    /// The cords wiring module jacks (lowered to edges).
16    pub cords: Vec<InstrumentTopologyCord>,
17    /// Graph-level metadata key/value pairs.
18    pub metadata: Vec<(Symbol, Expr)>,
19}
20
21impl InstrumentTopologySpec {
22    /// Starts an empty spec with the given name.
23    pub fn new(name: Symbol) -> Self {
24        Self {
25            name,
26            modules: Vec::new(),
27            cords: Vec::new(),
28            metadata: Vec::new(),
29        }
30    }
31
32    /// Adds a module, returning the updated spec.
33    pub fn with_module(mut self, module: InstrumentTopologyModule) -> Self {
34        self.modules.push(module);
35        self
36    }
37
38    /// Adds a cord, returning the updated spec.
39    pub fn with_cord(mut self, cord: InstrumentTopologyCord) -> Self {
40        self.cords.push(cord);
41        self
42    }
43
44    /// Adds a graph-level metadata entry, returning the updated spec.
45    pub fn with_metadata(mut self, key: Symbol, value: Expr) -> Self {
46        self.metadata.push((key, value));
47        self
48    }
49}
50
51/// One module in an instrument spec: a node kind with input/output jacks,
52/// settings, and an opaque raw view.
53#[derive(Clone, Debug)]
54pub struct InstrumentTopologyModule {
55    /// The module's node id.
56    pub id: NodeId,
57    /// The module kind (becomes the node verb/class symbol).
58    pub kind: Symbol,
59    /// Input jacks (lowered to input ports).
60    pub inputs: Vec<InstrumentTopologyJack>,
61    /// Output jacks (lowered to output ports).
62    pub outputs: Vec<InstrumentTopologyJack>,
63    /// Module settings, lowered to a `settings` node option map.
64    pub settings: Vec<(Symbol, Expr)>,
65    /// Opaque module state, lowered to a `raw-view` node option map.
66    pub raw_view: Vec<(Symbol, Expr)>,
67}
68
69impl InstrumentTopologyModule {
70    /// Starts a module with the given id and kind.
71    pub fn new(id: impl Into<NodeId>, kind: Symbol) -> Self {
72        Self {
73            id: id.into(),
74            kind,
75            inputs: Vec::new(),
76            outputs: Vec::new(),
77            settings: Vec::new(),
78            raw_view: Vec::new(),
79        }
80    }
81
82    /// Adds an input jack, returning the updated module.
83    pub fn with_input(mut self, jack: InstrumentTopologyJack) -> Self {
84        self.inputs.push(jack);
85        self
86    }
87
88    /// Adds an output jack, returning the updated module.
89    pub fn with_output(mut self, jack: InstrumentTopologyJack) -> Self {
90        self.outputs.push(jack);
91        self
92    }
93
94    /// Adds a setting, returning the updated module.
95    pub fn with_setting(mut self, key: Symbol, value: Expr) -> Self {
96        self.settings.push((key, value));
97        self
98    }
99
100    /// Adds a raw-view entry, returning the updated module.
101    pub fn with_raw(mut self, key: Symbol, value: Expr) -> Self {
102        self.raw_view.push((key, value));
103        self
104    }
105}
106
107/// One jack on a module: a named port with a mode, a required flag, and an
108/// optional normalled default supplied when the jack is left unpatched.
109#[derive(Clone, Debug)]
110pub struct InstrumentTopologyJack {
111    /// The jack (port) name.
112    pub name: Symbol,
113    /// Whether the jack carries values or a stream.
114    pub mode: PortMode,
115    /// Whether the jack must be wired (unless a normalled default applies).
116    pub required: bool,
117    /// Value supplied when the jack is left unpatched, if any.
118    pub normalled_default: Option<Expr>,
119}
120
121impl InstrumentTopologyJack {
122    /// Builds a value-mode jack.
123    pub fn value(name: impl Into<String>, required: bool) -> Self {
124        Self::new(Symbol::new(name.into()), PortMode::Value, required)
125    }
126
127    /// Builds a stream-mode jack.
128    pub fn stream(name: impl Into<String>, required: bool) -> Self {
129        Self::new(Symbol::new(name.into()), PortMode::Stream, required)
130    }
131
132    /// Builds a jack with an explicit mode.
133    pub fn new(name: Symbol, mode: PortMode, required: bool) -> Self {
134        Self {
135            name,
136            mode,
137            required,
138            normalled_default: None,
139        }
140    }
141
142    /// Sets the normalled default value, returning the updated jack.
143    pub fn with_normalled_default(mut self, value: Expr) -> Self {
144        self.normalled_default = Some(value);
145        self
146    }
147}
148
149/// A patch cord wiring one jack to another, with an optional visit bound.
150#[derive(Clone, Debug)]
151pub struct InstrumentTopologyCord {
152    /// The source port reference.
153    pub from: PortRef,
154    /// The destination port reference.
155    pub to: PortRef,
156    /// Maximum times the edge may be traversed in a bounded cycle, if limited.
157    pub max_visits: Option<u32>,
158}
159
160impl InstrumentTopologyCord {
161    /// Builds a cord between two ports.
162    pub fn new(from: PortRef, to: PortRef) -> Self {
163        Self {
164            from,
165            to,
166            max_visits: None,
167        }
168    }
169
170    /// Sets the cord's maximum visit count, returning the updated cord.
171    pub fn with_max_visits(mut self, max_visits: u32) -> Self {
172        self.max_visits = Some(max_visits);
173        self
174    }
175}
176
177/// Lowers an [`InstrumentTopologySpec`] into the canonical [`Graph`] model.
178#[derive(Clone, Copy, Debug, Default)]
179pub struct InstrumentTopologyAdapter;
180
181impl InstrumentTopologyAdapter {
182    /// Lowers a spec to a graph: modules become nodes and cords become edges.
183    pub fn graph_from_spec(&self, spec: &InstrumentTopologySpec) -> Graph {
184        let mut graph = Graph::new(spec.name.clone());
185        graph.metadata = spec.metadata.clone();
186        graph.metadata.push((
187            Symbol::qualified("topology", "adapter"),
188            Expr::Symbol(Symbol::qualified("topology/adapter", "instrument-patch")),
189        ));
190        graph.nodes = spec.modules.iter().map(module_to_node).collect();
191        graph.edges = spec
192            .cords
193            .iter()
194            .enumerate()
195            .map(|(index, cord)| cord_to_edge(index as u32, cord))
196            .collect();
197        graph
198    }
199}
200
201fn module_to_node(module: &InstrumentTopologyModule) -> Node {
202    let mut node = Node::with_ports(
203        module.id.clone(),
204        module.kind.clone(),
205        module.inputs.iter().map(jack_to_port).collect(),
206        module.outputs.iter().map(jack_to_port).collect(),
207    );
208    if !module.settings.is_empty() {
209        node.options.push((
210            Symbol::new("settings"),
211            Expr::Map(symbol_expr_entries(&module.settings)),
212        ));
213    }
214    if !module.raw_view.is_empty() {
215        node.options.push((
216            Symbol::new("raw-view"),
217            Expr::Map(symbol_expr_entries(&module.raw_view)),
218        ));
219    }
220    let normalled = normalled_defaults(&module.inputs, &module.outputs);
221    if !normalled.is_empty() {
222        node.options
223            .push((Symbol::new("normalled-defaults"), Expr::Map(normalled)));
224    }
225    node
226}
227
228fn jack_to_port(jack: &InstrumentTopologyJack) -> Port {
229    Port::new(
230        jack.name.clone(),
231        jack.mode,
232        jack.required && jack.normalled_default.is_none(),
233    )
234}
235
236fn cord_to_edge(index: u32, cord: &InstrumentTopologyCord) -> Edge {
237    let mut edge = Edge::new(index, cord.from.clone(), cord.to.clone());
238    edge.max_visits = cord.max_visits;
239    edge
240}
241
242fn normalled_defaults(
243    inputs: &[InstrumentTopologyJack],
244    outputs: &[InstrumentTopologyJack],
245) -> Vec<(Expr, Expr)> {
246    inputs
247        .iter()
248        .chain(outputs)
249        .filter_map(|jack| {
250            jack.normalled_default
251                .as_ref()
252                .map(|value| (Expr::Symbol(jack.name.clone()), value.clone()))
253        })
254        .collect()
255}
256
257fn symbol_expr_entries(entries: &[(Symbol, Expr)]) -> Vec<(Expr, Expr)> {
258    entries
259        .iter()
260        .map(|(key, value)| (Expr::Symbol(key.clone()), value.clone()))
261        .collect()
262}