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}