Skip to main content

sim_lib_music_core/
event.rs

1use sim_kernel::{Expr, Result, Symbol};
2use sim_lib_midi_core::MidiEvent;
3use sim_lib_stream_core::{StreamItem, StreamPacket};
4
5use crate::{Channel, LaneId, LaneKind, PerformanceIntent, Pitch, Tick, tick_to_kernel_tick};
6
7/// A single scheduled event on a lane, tagged by its content kind.
8///
9/// Each variant carries a kind-specific payload and corresponds to a
10/// [`LaneKind`](crate::LaneKind).
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum PlayEvent {
13    /// A pitched note event.
14    Note(NoteEvent),
15    /// A raw MIDI event.
16    Midi(MidiPlayEvent),
17    /// A bare pitch event.
18    Pitch(PitchEvent),
19    /// A control-change event.
20    Control(ControlEvent),
21    /// An audio-frame event.
22    Audio(AudioEvent),
23    /// A playable-reference event.
24    Playable(PlayableEvent),
25    /// A performance-intent event.
26    Performance(PerformanceEvent),
27    /// A diagnostic message event.
28    Diagnostic(DiagnosticEvent),
29    /// A trace / debugging step event.
30    Trace(TraceEvent),
31}
32
33impl PlayEvent {
34    /// Returns the [`LaneKind`](crate::LaneKind) matching this event's variant.
35    pub fn kind(&self) -> LaneKind {
36        match self {
37            Self::Note(_) => LaneKind::Note,
38            Self::Midi(_) => LaneKind::Midi,
39            Self::Pitch(_) => LaneKind::Pitch,
40            Self::Control(_) => LaneKind::Control,
41            Self::Audio(_) => LaneKind::Audio,
42            Self::Playable(_) => LaneKind::Playable,
43            Self::Performance(_) => LaneKind::Performance,
44            Self::Diagnostic(_) => LaneKind::Diagnostic,
45            Self::Trace(_) => LaneKind::Trace,
46        }
47    }
48
49    /// Returns the id of the lane this event belongs to.
50    pub fn lane_id(&self) -> &LaneId {
51        match self {
52            Self::Note(event) => &event.lane_id,
53            Self::Midi(event) => &event.lane_id,
54            Self::Pitch(event) => &event.lane_id,
55            Self::Control(event) => &event.lane_id,
56            Self::Audio(event) => &event.lane_id,
57            Self::Playable(event) => &event.lane_id,
58            Self::Performance(event) => &event.lane_id,
59            Self::Diagnostic(event) => &event.lane_id,
60            Self::Trace(event) => &event.lane_id,
61        }
62    }
63
64    /// Returns the start time of this event in ticks.
65    pub fn time(&self) -> Tick {
66        match self {
67            Self::Note(event) => event.time,
68            Self::Midi(event) => event.event.time,
69            Self::Pitch(event) => event.time,
70            Self::Control(event) => event.time,
71            Self::Audio(event) => event.time,
72            Self::Playable(event) => event.time,
73            Self::Performance(event) => event.time,
74            Self::Diagnostic(event) => event.time,
75            Self::Trace(event) => event.time,
76        }
77    }
78
79    /// Encodes this event as a `StreamItem` timestamped against `clock`.
80    pub fn to_stream_item(&self, clock: Symbol) -> Result<StreamItem> {
81        StreamItem::with_ticks(
82            StreamPacket::data(play_event_data_kind(), self.to_expr()),
83            vec![tick_to_kernel_tick(self.time(), clock)],
84        )
85    }
86
87    /// Encodes this event as a kernel `Expr` map.
88    pub fn to_expr(&self) -> Expr {
89        match self {
90            Self::Note(event) => event.to_expr(),
91            Self::Midi(event) => event.to_expr(),
92            Self::Pitch(event) => event.to_expr(),
93            Self::Control(event) => event.to_expr(),
94            Self::Audio(event) => event.to_expr(),
95            Self::Playable(event) => event.to_expr(),
96            Self::Performance(event) => event.to_expr(),
97            Self::Diagnostic(event) => event.to_expr(),
98            Self::Trace(event) => event.to_expr(),
99        }
100    }
101}
102
103/// A pitched note with duration, velocity, and channel.
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct NoteEvent {
106    /// Lane the note plays on.
107    pub lane_id: LaneId,
108    /// Start time in ticks.
109    pub time: Tick,
110    /// Duration in ticks.
111    pub duration: Tick,
112    /// Sounding pitch.
113    pub pitch: Pitch,
114    /// MIDI-style velocity (0-127).
115    pub velocity: u8,
116    /// Output channel.
117    pub channel: Channel,
118}
119
120impl NoteEvent {
121    /// Encodes this note as a kernel `Expr` map.
122    pub fn to_expr(&self) -> Expr {
123        map(vec![
124            ("event", Expr::Symbol(LaneKind::Note.symbol())),
125            ("lane", Expr::String(self.lane_id.0.clone())),
126            ("time", tick_expr(self.time)),
127            ("duration", tick_expr(self.duration)),
128            ("pitch", Expr::String(pitch_label(self.pitch))),
129            ("velocity", Expr::String(self.velocity.to_string())),
130            ("channel", Expr::String(self.channel.0.to_string())),
131        ])
132    }
133}
134
135/// A raw MIDI event bound to a lane.
136#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct MidiPlayEvent {
138    /// Lane the MIDI event plays on.
139    pub lane_id: LaneId,
140    /// The wrapped MIDI event, including its own time and payload.
141    pub event: MidiEvent,
142}
143
144impl MidiPlayEvent {
145    /// Encodes this MIDI event as a kernel `Expr` map.
146    pub fn to_expr(&self) -> Expr {
147        map(vec![
148            ("event", Expr::Symbol(LaneKind::Midi.symbol())),
149            ("lane", Expr::String(self.lane_id.0.clone())),
150            ("time", tick_expr(self.event.time)),
151            ("payload", Expr::String(format!("{:?}", self.event.payload))),
152        ])
153    }
154}
155
156/// A bare pitch event without duration or velocity.
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct PitchEvent {
159    /// Lane the pitch plays on.
160    pub lane_id: LaneId,
161    /// Time in ticks.
162    pub time: Tick,
163    /// Sounding pitch.
164    pub pitch: Pitch,
165}
166
167impl PitchEvent {
168    /// Encodes this pitch event as a kernel `Expr` map.
169    pub fn to_expr(&self) -> Expr {
170        map(vec![
171            ("event", Expr::Symbol(LaneKind::Pitch.symbol())),
172            ("lane", Expr::String(self.lane_id.0.clone())),
173            ("time", tick_expr(self.time)),
174            ("pitch", Expr::String(pitch_label(self.pitch))),
175        ])
176    }
177}
178
179/// A control-change event setting a named control to a value.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct ControlEvent {
182    /// Lane the control change applies to.
183    pub lane_id: LaneId,
184    /// Time in ticks.
185    pub time: Tick,
186    /// Symbol naming the control being changed.
187    pub control: Symbol,
188    /// New control value.
189    pub value: i64,
190}
191
192impl ControlEvent {
193    /// Encodes this control event as a kernel `Expr` map.
194    pub fn to_expr(&self) -> Expr {
195        map(vec![
196            ("event", Expr::Symbol(LaneKind::Control.symbol())),
197            ("lane", Expr::String(self.lane_id.0.clone())),
198            ("time", tick_expr(self.time)),
199            ("control", Expr::Symbol(self.control.clone())),
200            ("value", Expr::String(self.value.to_string())),
201        ])
202    }
203}
204
205/// An audio-frame event covering a span of frames.
206#[derive(Clone, Debug, PartialEq, Eq)]
207pub struct AudioEvent {
208    /// Lane the audio plays on.
209    pub lane_id: LaneId,
210    /// Time in ticks.
211    pub time: Tick,
212    /// Number of audio frames.
213    pub frames: u32,
214}
215
216impl AudioEvent {
217    /// Encodes this audio event as a kernel `Expr` map.
218    pub fn to_expr(&self) -> Expr {
219        timed_count_expr(
220            LaneKind::Audio,
221            &self.lane_id,
222            self.time,
223            "frames",
224            self.frames,
225        )
226    }
227}
228
229/// A reference to a named playable to trigger.
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub struct PlayableEvent {
232    /// Lane the playable triggers on.
233    pub lane_id: LaneId,
234    /// Time in ticks.
235    pub time: Tick,
236    /// Symbol naming the playable.
237    pub playable: Symbol,
238}
239
240impl PlayableEvent {
241    /// Encodes this playable event as a kernel `Expr` map.
242    pub fn to_expr(&self) -> Expr {
243        map(vec![
244            ("event", Expr::Symbol(LaneKind::Playable.symbol())),
245            ("lane", Expr::String(self.lane_id.0.clone())),
246            ("time", tick_expr(self.time)),
247            ("playable", Expr::Symbol(self.playable.clone())),
248        ])
249    }
250}
251
252/// A performance-intent event tying a rendered time back to its input.
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct PerformanceEvent {
255    /// Lane the performance event plays on.
256    pub lane_id: LaneId,
257    /// Symbol identifying the originating source.
258    pub source_id: Symbol,
259    /// Input (pre-performance) time in ticks.
260    pub input_time: Tick,
261    /// Rendered (post-performance) time in ticks.
262    pub time: Tick,
263    /// Performance intent applied to the event.
264    pub intent: PerformanceIntent,
265}
266
267impl PerformanceEvent {
268    /// Encodes this performance event as a kernel `Expr` map.
269    pub fn to_expr(&self) -> Expr {
270        map(vec![
271            ("event", Expr::Symbol(LaneKind::Performance.symbol())),
272            ("lane", Expr::String(self.lane_id.0.clone())),
273            ("source", Expr::Symbol(self.source_id.clone())),
274            ("input-time", tick_expr(self.input_time)),
275            ("time", tick_expr(self.time)),
276            ("intent", self.intent.to_expr()),
277        ])
278    }
279}
280
281/// A diagnostic message emitted on a lane.
282#[derive(Clone, Debug, PartialEq, Eq)]
283pub struct DiagnosticEvent {
284    /// Lane the diagnostic is reported on.
285    pub lane_id: LaneId,
286    /// Time in ticks.
287    pub time: Tick,
288    /// Human-readable diagnostic text.
289    pub message: String,
290}
291
292impl DiagnosticEvent {
293    /// Encodes this diagnostic event as a kernel `Expr` map.
294    pub fn to_expr(&self) -> Expr {
295        map(vec![
296            ("event", Expr::Symbol(LaneKind::Diagnostic.symbol())),
297            ("lane", Expr::String(self.lane_id.0.clone())),
298            ("time", tick_expr(self.time)),
299            ("message", Expr::String(self.message.clone())),
300        ])
301    }
302}
303
304/// A trace / debugging step marker on a lane.
305#[derive(Clone, Debug, PartialEq, Eq)]
306pub struct TraceEvent {
307    /// Lane the trace marker is recorded on.
308    pub lane_id: LaneId,
309    /// Time in ticks.
310    pub time: Tick,
311    /// Monotonic step counter.
312    pub step: u64,
313}
314
315impl TraceEvent {
316    /// Encodes this trace event as a kernel `Expr` map.
317    pub fn to_expr(&self) -> Expr {
318        timed_count_expr(LaneKind::Trace, &self.lane_id, self.time, "step", self.step)
319    }
320}
321
322/// Returns the qualified data-kind symbol used for encoded play events.
323pub fn play_event_data_kind() -> Symbol {
324    Symbol::qualified("music/play", "event")
325}
326
327/// Sorts events in place into a deterministic order.
328///
329/// Orders by time, then lane id, then kind, with the encoded `Expr` as a final
330/// tie-breaker so equal-timed events on the same lane stay stable.
331pub fn stable_event_order(events: &mut [PlayEvent]) {
332    events.sort_by(|left, right| {
333        left.time()
334            .ticks
335            .cmp(&right.time().ticks)
336            .then_with(|| left.lane_id().cmp(right.lane_id()))
337            .then_with(|| left.kind().cmp(&right.kind()))
338            .then_with(|| format!("{:?}", left.to_expr()).cmp(&format!("{:?}", right.to_expr())))
339    });
340}
341
342fn timed_count_expr<T: ToString>(
343    kind: LaneKind,
344    lane_id: &LaneId,
345    time: Tick,
346    field: &'static str,
347    value: T,
348) -> Expr {
349    map(vec![
350        ("event", Expr::Symbol(kind.symbol())),
351        ("lane", Expr::String(lane_id.0.clone())),
352        ("time", tick_expr(time)),
353        (field, Expr::String(value.to_string())),
354    ])
355}
356
357fn tick_expr(tick: Tick) -> Expr {
358    map(vec![
359        ("ticks", Expr::String(tick.ticks.to_string())),
360        ("tpq", Expr::String(tick.tpq.to_string())),
361    ])
362}
363
364fn pitch_label(pitch: Pitch) -> String {
365    pitch
366        .to_midi()
367        .map(|midi| format!("midi:{midi}"))
368        .unwrap_or_else(|| format!("semitone:{}", pitch.semitone()))
369}
370
371fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
372    Expr::Map(
373        entries
374            .into_iter()
375            .map(|(key, value)| (Expr::Symbol(Symbol::new(key)), value))
376            .collect(),
377    )
378}