Skip to main content

sim_lib_music_core/
descriptor.rs

1use std::collections::BTreeSet;
2
3use sim_kernel::{Expr, Symbol};
4use sim_lib_stream_core::RateContract;
5
6use crate::{LaneDescriptor, LaneKind};
7
8const DESCRIPTOR_NS: &str = "music/component-descriptor";
9
10/// A capability a music component advertises to the runtime.
11///
12/// Capabilities describe what a component can do (be played, drive other
13/// components, render, and so on) and are carried in a
14/// [`MusicComponentDescriptor`].
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum MusicCapability {
17    /// The component can be played as part of a performance.
18    Playable,
19    /// The component plays incoming material (a player-family component).
20    Player,
21    /// The component produces a control/modulation signal.
22    Modulator,
23    /// The component generates an oscillating signal.
24    Oscillator,
25    /// The component is a source of live performance events.
26    PerformanceSource,
27    /// The component can be rendered to output.
28    Renderable,
29}
30
31impl MusicCapability {
32    /// Returns the stable wire label for this capability.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use sim_lib_music_core::MusicCapability;
38    ///
39    /// assert_eq!(MusicCapability::Playable.wire_label(), "playable");
40    /// assert_eq!(MusicCapability::PerformanceSource.wire_label(), "performance-source");
41    /// ```
42    pub fn wire_label(self) -> &'static str {
43        match self {
44            Self::Playable => "playable",
45            Self::Player => "player",
46            Self::Modulator => "modulator",
47            Self::Oscillator => "oscillator",
48            Self::PerformanceSource => "performance-source",
49            Self::Renderable => "renderable",
50        }
51    }
52
53    /// Returns the qualified `music/capability` symbol for this capability.
54    pub fn symbol(self) -> Symbol {
55        Symbol::qualified("music/capability", self.wire_label())
56    }
57}
58
59/// The broad category a music component belongs to.
60///
61/// Categories group descriptors for browsing and routing; they are carried in
62/// a [`MusicComponentDescriptor`].
63#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub enum MusicComponentCategory {
65    /// A player-family component that interprets incoming material.
66    PlayerFamily,
67    /// An instrument that renders events to audio.
68    Instrument,
69    /// A control component such as a modulator or performance source.
70    Control,
71}
72
73impl MusicComponentCategory {
74    /// Returns the stable wire label for this category.
75    pub fn wire_label(self) -> &'static str {
76        match self {
77            Self::PlayerFamily => "player-family",
78            Self::Instrument => "instrument",
79            Self::Control => "control",
80        }
81    }
82
83    /// Returns the qualified `music/component-category` symbol for this category.
84    pub fn symbol(self) -> Symbol {
85        Symbol::qualified("music/component-category", self.wire_label())
86    }
87}
88
89/// The direction of a component port relative to the component.
90///
91/// Carried in a [`MusicPortDescriptor`] to mark whether a port consumes or
92/// produces material.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
94pub enum MusicPortDirection {
95    /// The port receives material into the component.
96    Input,
97    /// The port emits material from the component.
98    Output,
99    /// The port receives auxiliary side-chain material.
100    Sidechain,
101}
102
103impl MusicPortDirection {
104    /// Returns the stable wire label for this direction.
105    pub fn wire_label(self) -> &'static str {
106        match self {
107            Self::Input => "input",
108            Self::Output => "output",
109            Self::Sidechain => "sidechain",
110        }
111    }
112
113    /// Returns the qualified `music/port-direction` symbol for this direction.
114    pub fn symbol(self) -> Symbol {
115        Symbol::qualified("music/port-direction", self.wire_label())
116    }
117}
118
119/// The unit a parameter value is measured in.
120///
121/// Carried in a [`MusicParamDescriptor`] to describe how a parameter's value
122/// should be interpreted.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub enum MusicUnit {
125    /// No unit; the value is dimensionless or categorical.
126    None,
127    /// Measured in beats.
128    Beats,
129    /// Measured in MIDI ticks.
130    Ticks,
131    /// Measured as a percentage.
132    Percent,
133    /// Measured in semitones.
134    Semitone,
135    /// Measured in hertz.
136    Hertz,
137}
138
139impl MusicUnit {
140    /// Returns the stable wire label for this unit.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use sim_lib_music_core::MusicUnit;
146    ///
147    /// assert_eq!(MusicUnit::None.wire_label(), "none");
148    /// assert_eq!(MusicUnit::Hertz.wire_label(), "hertz");
149    /// ```
150    pub fn wire_label(self) -> &'static str {
151        match self {
152            Self::None => "none",
153            Self::Beats => "beats",
154            Self::Ticks => "ticks",
155            Self::Percent => "percent",
156            Self::Semitone => "semitone",
157            Self::Hertz => "hertz",
158        }
159    }
160
161    /// Returns the qualified `music/unit` symbol for this unit.
162    pub fn symbol(self) -> Symbol {
163        Symbol::qualified("music/unit", self.wire_label())
164    }
165}
166
167/// How reproducible a component's output is.
168///
169/// Carried in a [`MusicComponentDescriptor`] to declare whether output is
170/// fixed, seed-driven, or dependent on live input.
171#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub enum DeterminismPolicy {
173    /// Output is fully determined by the inputs.
174    Deterministic,
175    /// Output is reproducible given a seed.
176    Seeded,
177    /// Output depends on live input and is not reproducible.
178    LiveInput,
179}
180
181impl DeterminismPolicy {
182    /// Returns the stable wire label for this policy.
183    pub fn wire_label(self) -> &'static str {
184        match self {
185            Self::Deterministic => "deterministic",
186            Self::Seeded => "seeded",
187            Self::LiveInput => "live-input",
188        }
189    }
190
191    /// Returns the qualified `music/determinism` symbol for this policy.
192    pub fn symbol(self) -> Symbol {
193        Symbol::qualified("music/determinism", self.wire_label())
194    }
195}
196
197/// Describes a single input or output port of a music component.
198#[derive(Clone, Debug, PartialEq, Eq)]
199pub struct MusicPortDescriptor {
200    /// Stable identifier for the port.
201    pub id: Symbol,
202    /// Human-readable label.
203    pub label: String,
204    /// Whether the port is an input, output, or side-chain.
205    pub direction: MusicPortDirection,
206    /// The rate contract the port operates at.
207    pub rate: RateContract,
208    /// Lane kinds the port accepts as input.
209    pub accepted_event_families: Vec<LaneKind>,
210    /// Lane kinds the port emits as output.
211    pub output_families: Vec<LaneKind>,
212}
213
214impl MusicPortDescriptor {
215    /// Creates a port descriptor with no accepted or output families.
216    pub fn new(
217        id: Symbol,
218        label: impl Into<String>,
219        direction: MusicPortDirection,
220        rate: RateContract,
221    ) -> Self {
222        Self {
223            id,
224            label: label.into(),
225            direction,
226            rate,
227            accepted_event_families: Vec::new(),
228            output_families: Vec::new(),
229        }
230    }
231
232    /// Sets the accepted and output lane kinds, sorting and deduplicating each.
233    pub fn with_events(mut self, accepted: Vec<LaneKind>, output: Vec<LaneKind>) -> Self {
234        self.accepted_event_families = stable_lane_kinds(accepted);
235        self.output_families = stable_lane_kinds(output);
236        self
237    }
238
239    /// Renders the port descriptor as an `Expr` map for wire transport.
240    pub fn to_expr(&self) -> Expr {
241        Expr::Map(vec![
242            field("id", Expr::Symbol(self.id.clone())),
243            field("label", Expr::String(self.label.clone())),
244            field("direction", Expr::Symbol(self.direction.symbol())),
245            field("rate", rate_expr(self.rate)),
246            field("accepted", lane_kind_list(&self.accepted_event_families)),
247            field("output", lane_kind_list(&self.output_families)),
248        ])
249    }
250}
251
252/// Describes a single configurable parameter of a music component.
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct MusicParamDescriptor {
255    /// Stable identifier for the parameter.
256    pub id: Symbol,
257    /// Human-readable label.
258    pub label: String,
259    /// The unit the parameter value is measured in.
260    pub unit: MusicUnit,
261    /// The rate contract the parameter is updated at.
262    pub rate: RateContract,
263    /// The default value for the parameter.
264    pub default: Expr,
265}
266
267impl MusicParamDescriptor {
268    /// Creates a parameter descriptor from its fields.
269    pub fn new(
270        id: Symbol,
271        label: impl Into<String>,
272        unit: MusicUnit,
273        rate: RateContract,
274        default: Expr,
275    ) -> Self {
276        Self {
277            id,
278            label: label.into(),
279            unit,
280            rate,
281            default,
282        }
283    }
284
285    /// Renders the parameter descriptor as an `Expr` map for wire transport.
286    pub fn to_expr(&self) -> Expr {
287        Expr::Map(vec![
288            field("id", Expr::Symbol(self.id.clone())),
289            field("label", Expr::String(self.label.clone())),
290            field("unit", Expr::Symbol(self.unit.symbol())),
291            field("rate", rate_expr(self.rate)),
292            field("default", self.default.clone()),
293        ])
294    }
295}
296
297/// Full description of a music component: its identity, category,
298/// capabilities, ports, lanes, parameters, and behavioral contracts.
299///
300/// Built fluently via [`MusicComponentDescriptor::new`] and the `with_*`
301/// methods, then rendered for transport with
302/// [`MusicComponentDescriptor::to_expr`].
303#[derive(Clone, Debug, PartialEq, Eq)]
304pub struct MusicComponentDescriptor {
305    /// Stable identifier for the component.
306    pub id: Symbol,
307    /// Human-readable label.
308    pub label: String,
309    /// The category the component belongs to.
310    pub category: MusicComponentCategory,
311    /// Capabilities the component advertises.
312    pub capabilities: BTreeSet<MusicCapability>,
313    /// Input and output ports, ordered by id.
314    pub ports: Vec<MusicPortDescriptor>,
315    /// Lane descriptors produced by the component, in stable order.
316    pub lanes: Vec<LaneDescriptor>,
317    /// Configurable parameters, ordered by id.
318    pub params: Vec<MusicParamDescriptor>,
319    /// The rate contract the component runs at.
320    pub rate: RateContract,
321    /// How reproducible the component's output is.
322    pub determinism: DeterminismPolicy,
323    /// Lane kinds the component accepts as input.
324    pub accepted_event_families: Vec<LaneKind>,
325    /// Lane kinds the component emits as output.
326    pub output_families: Vec<LaneKind>,
327    /// The latency class symbol derived from the rate contract.
328    pub latency: Symbol,
329    /// Whether the component has a working implementation.
330    pub implemented: bool,
331}
332
333impl MusicComponentDescriptor {
334    /// Creates a descriptor with default capabilities, ports, lanes, and params.
335    ///
336    /// The latency symbol is derived from `rate` and `implemented` defaults to
337    /// `true`.
338    pub fn new(
339        id: Symbol,
340        label: impl Into<String>,
341        category: MusicComponentCategory,
342        rate: RateContract,
343    ) -> Self {
344        let latency = rate.latency_class().symbol();
345        Self {
346            id,
347            label: label.into(),
348            category,
349            capabilities: BTreeSet::new(),
350            ports: Vec::new(),
351            lanes: Vec::new(),
352            params: Vec::new(),
353            rate,
354            determinism: DeterminismPolicy::Deterministic,
355            accepted_event_families: Vec::new(),
356            output_families: Vec::new(),
357            latency,
358            implemented: true,
359        }
360    }
361
362    /// Adds a capability and returns the updated descriptor.
363    pub fn with_capability(mut self, capability: MusicCapability) -> Self {
364        self.capabilities.insert(capability);
365        self
366    }
367
368    /// Adds a port and re-sorts the port list by id.
369    pub fn with_port(mut self, port: MusicPortDescriptor) -> Self {
370        self.ports.push(port);
371        self.ports.sort_by(|left, right| left.id.cmp(&right.id));
372        self
373    }
374
375    /// Adds a lane and restores the stable lane order.
376    pub fn with_lane(mut self, lane: LaneDescriptor) -> Self {
377        self.lanes.push(lane);
378        self.lanes = crate::stable_lane_order(self.lanes);
379        self
380    }
381
382    /// Adds a parameter and re-sorts the parameter list by id.
383    pub fn with_param(mut self, param: MusicParamDescriptor) -> Self {
384        self.params.push(param);
385        self.params.sort_by(|left, right| left.id.cmp(&right.id));
386        self
387    }
388
389    /// Sets the accepted and output lane kinds, sorting and deduplicating each.
390    pub fn with_events(mut self, accepted: Vec<LaneKind>, output: Vec<LaneKind>) -> Self {
391        self.accepted_event_families = stable_lane_kinds(accepted);
392        self.output_families = stable_lane_kinds(output);
393        self
394    }
395
396    /// Sets the determinism policy and returns the updated descriptor.
397    pub fn with_determinism(mut self, determinism: DeterminismPolicy) -> Self {
398        self.determinism = determinism;
399        self
400    }
401
402    /// Sets the implemented flag and returns the updated descriptor.
403    pub fn with_implemented(mut self, implemented: bool) -> Self {
404        self.implemented = implemented;
405        self
406    }
407
408    /// Returns `true` if the descriptor advertises `capability`.
409    pub fn has_capability(&self, capability: MusicCapability) -> bool {
410        self.capabilities.contains(&capability)
411    }
412
413    /// Renders the full descriptor as an `Expr` map for wire transport.
414    pub fn to_expr(&self) -> Expr {
415        Expr::Map(vec![
416            field("id", Expr::Symbol(self.id.clone())),
417            field("label", Expr::String(self.label.clone())),
418            field("category", Expr::Symbol(self.category.symbol())),
419            field(
420                "capabilities",
421                Expr::Vector(
422                    self.capabilities
423                        .iter()
424                        .map(|capability| Expr::Symbol(capability.symbol()))
425                        .collect(),
426                ),
427            ),
428            field(
429                "ports",
430                Expr::Vector(
431                    self.ports
432                        .iter()
433                        .map(MusicPortDescriptor::to_expr)
434                        .collect(),
435                ),
436            ),
437            field(
438                "lanes",
439                Expr::Vector(self.lanes.iter().map(lane_expr).collect()),
440            ),
441            field(
442                "params",
443                Expr::Vector(
444                    self.params
445                        .iter()
446                        .map(MusicParamDescriptor::to_expr)
447                        .collect(),
448                ),
449            ),
450            field("rate", rate_expr(self.rate)),
451            field("determinism", Expr::Symbol(self.determinism.symbol())),
452            field("accepted", lane_kind_list(&self.accepted_event_families)),
453            field("output", lane_kind_list(&self.output_families)),
454            field("latency", Expr::Symbol(self.latency.clone())),
455            field("implemented", Expr::Bool(self.implemented)),
456        ])
457    }
458}
459
460fn stable_lane_kinds(mut kinds: Vec<LaneKind>) -> Vec<LaneKind> {
461    kinds.sort();
462    kinds.dedup();
463    kinds
464}
465
466fn lane_kind_list(kinds: &[LaneKind]) -> Expr {
467    Expr::Vector(
468        kinds
469            .iter()
470            .map(|kind| Expr::Symbol(kind.symbol()))
471            .collect(),
472    )
473}
474
475fn lane_expr(lane: &LaneDescriptor) -> Expr {
476    Expr::Map(vec![
477        field("id", Expr::String(lane.id.0.clone())),
478        field("kind", Expr::Symbol(lane.kind.symbol())),
479        field("target", Expr::Symbol(lane.target.symbol())),
480        field("order", Expr::String(lane.order.to_string())),
481    ])
482}
483
484pub(crate) fn rate_expr(rate: RateContract) -> Expr {
485    Expr::Map(vec![
486        field("clock-domain", Expr::Symbol(rate.clock_domain().symbol())),
487        field("latency-class", Expr::Symbol(rate.latency_class().symbol())),
488        field(
489            "nominal-rate-hz",
490            Expr::String(
491                rate.nominal_rate_hz()
492                    .map(|rate| rate.to_string())
493                    .unwrap_or_else(|| "none".to_owned()),
494            ),
495        ),
496    ])
497}
498
499fn field(name: &'static str, value: Expr) -> (Expr, Expr) {
500    (Expr::Symbol(Symbol::qualified(DESCRIPTOR_NS, name)), value)
501}