Skip to main content

sim_lib_music_core/
lane.rs

1use sim_kernel::Symbol;
2
3use crate::MusicError;
4
5/// Stable string identifier for a lane.
6///
7/// Wraps the raw lane name used to group and order [`PlayEvent`](crate::PlayEvent)s.
8///
9/// # Examples
10///
11/// ```
12/// use sim_lib_music_core::LaneId;
13///
14/// let id = LaneId::new("bass");
15/// assert_eq!(id.as_ref(), "bass");
16/// ```
17#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct LaneId(
19    /// The raw lane name.
20    pub String,
21);
22
23impl LaneId {
24    /// Builds a lane id from any string-like value.
25    pub fn new(value: impl Into<String>) -> Self {
26        Self(value.into())
27    }
28}
29
30impl AsRef<str> for LaneId {
31    fn as_ref(&self) -> &str {
32        &self.0
33    }
34}
35
36/// Category of content carried by a lane.
37///
38/// Each variant maps to a [`PlayEvent`](crate::PlayEvent) family and constrains
39/// which [`LaneTarget`] a lane may bind to.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum LaneKind {
42    /// Pitched note events.
43    Note,
44    /// Percussion / drum-hit events.
45    Drum,
46    /// Scale-degree events resolved against a scale.
47    ScaleDegree,
48    /// Raw MIDI events.
49    Midi,
50    /// Bare pitch events without duration or velocity.
51    Pitch,
52    /// Discrete control-change events.
53    Control,
54    /// Continuous automation events.
55    Automation,
56    /// Audio-frame events.
57    Audio,
58    /// Object-valued events.
59    Object,
60    /// Playable-reference events.
61    Playable,
62    /// Performance-intent events.
63    Performance,
64    /// Diagnostic message events.
65    Diagnostic,
66    /// Trace / debugging step events.
67    Trace,
68}
69
70impl LaneKind {
71    /// Returns the qualified `music/lane-kind` symbol for this kind.
72    pub fn symbol(self) -> Symbol {
73        Symbol::qualified("music/lane-kind", self.wire_label())
74    }
75
76    /// Returns the stable wire label used for serialization.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use sim_lib_music_core::LaneKind;
82    ///
83    /// assert_eq!(LaneKind::ScaleDegree.wire_label(), "scale-degree");
84    /// ```
85    pub fn wire_label(self) -> &'static str {
86        match self {
87            Self::Note => "note",
88            Self::Drum => "drum",
89            Self::ScaleDegree => "scale-degree",
90            Self::Midi => "midi",
91            Self::Pitch => "pitch",
92            Self::Control => "control",
93            Self::Automation => "automation",
94            Self::Audio => "audio",
95            Self::Object => "object",
96            Self::Playable => "playable",
97            Self::Performance => "performance",
98            Self::Diagnostic => "diagnostic",
99            Self::Trace => "trace",
100        }
101    }
102}
103
104/// Destination a lane routes its events to.
105///
106/// The target restricts which [`LaneKind`]s may bind to a lane via
107/// [`LaneDescriptor::new`].
108#[derive(Clone, Debug, PartialEq, Eq)]
109pub enum LaneTarget {
110    /// Routes to a named instrument.
111    Instrument(Symbol),
112    /// Routes to a named stream.
113    Stream(Symbol),
114    /// Routes to a named control surface.
115    Control(Symbol),
116    /// Has no routing target.
117    None,
118}
119
120impl LaneTarget {
121    /// Returns the symbol naming this target, or a sentinel for [`LaneTarget::None`].
122    pub fn symbol(&self) -> Symbol {
123        match self {
124            Self::Instrument(symbol) | Self::Stream(symbol) | Self::Control(symbol) => {
125                symbol.clone()
126            }
127            Self::None => Symbol::qualified("music/lane-target", "none"),
128        }
129    }
130
131    fn accepts(&self, kind: LaneKind) -> bool {
132        match kind {
133            LaneKind::Note
134            | LaneKind::Drum
135            | LaneKind::ScaleDegree
136            | LaneKind::Midi
137            | LaneKind::Pitch
138            | LaneKind::Object
139            | LaneKind::Performance => {
140                matches!(self, Self::Instrument(_) | Self::Stream(_))
141            }
142            LaneKind::Control | LaneKind::Automation => matches!(
143                self,
144                Self::Control(_) | Self::Instrument(_) | Self::Stream(_)
145            ),
146            LaneKind::Audio | LaneKind::Playable => matches!(self, Self::Stream(_)),
147            LaneKind::Diagnostic | LaneKind::Trace => matches!(self, Self::None | Self::Stream(_)),
148        }
149    }
150}
151
152/// Describes a single lane: its identity, content kind, target, and order.
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct LaneDescriptor {
155    /// Stable identifier of the lane.
156    pub id: LaneId,
157    /// Category of content the lane carries.
158    pub kind: LaneKind,
159    /// Destination the lane routes events to.
160    pub target: LaneTarget,
161    /// Sort order of the lane relative to its peers.
162    pub order: u32,
163}
164
165impl LaneDescriptor {
166    /// Builds a descriptor, validating that `target` accepts `kind`.
167    ///
168    /// Returns `MusicError::InvalidLaneTarget` when the target cannot carry the
169    /// given kind.
170    pub fn new(
171        id: LaneId,
172        kind: LaneKind,
173        target: LaneTarget,
174        order: u32,
175    ) -> Result<Self, MusicError> {
176        if !target.accepts(kind) {
177            return Err(MusicError::InvalidLaneTarget {
178                lane: id.0,
179                target: target.symbol().to_string(),
180            });
181        }
182        Ok(Self {
183            id,
184            kind,
185            target,
186            order,
187        })
188    }
189}
190
191/// Sorts lanes deterministically by order, then id, then kind.
192pub fn stable_lane_order(mut lanes: Vec<LaneDescriptor>) -> Vec<LaneDescriptor> {
193    lanes.sort_by(|left, right| {
194        left.order
195            .cmp(&right.order)
196            .then_with(|| left.id.cmp(&right.id))
197            .then_with(|| left.kind.cmp(&right.kind))
198    });
199    lanes
200}