Skip to main content

sim_lib_stream_core/
packet.rs

1//! Packet payloads carried by stream envelopes.
2//!
3//! [`StreamPacket`] is the umbrella over the concrete payload kinds an
4//! envelope can hold: [`PcmPacket`] audio frames, [`MidiPacket`] events,
5//! [`StreamDiagnostic`] messages, and opaque [`DataPacket`] values. Each
6//! payload round-trips to and from a self-describing [`Expr`] map tagged with
7//! a `stream/packet/*` symbol, so packets can be serialized, interned as
8//! kernel data ([`StreamPacket::intern_ref`]), and reconstructed.
9//!
10//! The kernel defines the `Expr`/`Datum`/datum-store contract; this module
11//! supplies the concrete streaming-fabric payload model on top of it.
12
13use std::fmt::Display;
14use std::str::FromStr;
15
16use sim_kernel::{Cx, Datum, DatumStore, Error, Expr, Ref, Result, Symbol};
17use sim_value::access;
18
19use crate::buffer::{expr_kind, field, string_field, symbol_field};
20use crate::metadata::StreamMedia;
21
22#[path = "packet/pcm.rs"]
23mod pcm;
24
25pub use pcm::{PcmPacket, PcmSampleFormat};
26
27/// A single timed MIDI event within a [`MidiPacket`].
28///
29/// Holds the event time in ticks, the ticks-per-quarter-note (TPQ) resolution
30/// the ticks are measured against, and the raw MIDI message bytes.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct MidiPacketEvent {
33    ticks: i64,
34    tpq: u16,
35    bytes: Vec<u8>,
36}
37
38impl MidiPacketEvent {
39    /// Builds an event from its tick time, TPQ resolution, and message bytes.
40    ///
41    /// Returns an error when `tpq` is zero, since a zero resolution cannot
42    /// time the event.
43    pub fn new(ticks: i64, tpq: u16, bytes: Vec<u8>) -> Result<Self> {
44        if tpq == 0 {
45            return Err(Error::Eval(
46                "MIDI packet TPQ must be greater than zero".to_owned(),
47            ));
48        }
49        Ok(Self { ticks, tpq, bytes })
50    }
51
52    /// Returns the event time in ticks.
53    pub fn ticks(&self) -> i64 {
54        self.ticks
55    }
56
57    /// Returns the ticks-per-quarter-note resolution the ticks are measured in.
58    pub fn tpq(&self) -> u16 {
59        self.tpq
60    }
61
62    /// Returns the raw MIDI message bytes.
63    pub fn bytes(&self) -> &[u8] {
64        &self.bytes
65    }
66}
67
68/// A MIDI payload: an ordered run of [`MidiPacketEvent`]s sharing one TPQ
69/// resolution.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct MidiPacket {
72    tpq: u16,
73    events: Vec<MidiPacketEvent>,
74}
75
76impl MidiPacket {
77    /// Builds a packet from its events, adopting the TPQ of the first event.
78    ///
79    /// Returns an error when `events` is empty or any event uses a different
80    /// TPQ than the first; a packet carries a single shared resolution.
81    pub fn new(events: Vec<MidiPacketEvent>) -> Result<Self> {
82        let Some(first) = events.first() else {
83            return Err(Error::Eval(
84                "MIDI packet must contain at least one event".to_owned(),
85            ));
86        };
87        let tpq = first.tpq;
88        if events.iter().any(|event| event.tpq != tpq) {
89            return Err(Error::Eval(
90                "MIDI packet events must use one shared TPQ".to_owned(),
91            ));
92        }
93        Ok(Self { tpq, events })
94    }
95
96    /// Returns the shared ticks-per-quarter-note resolution of every event.
97    pub fn tpq(&self) -> u16 {
98        self.tpq
99    }
100
101    /// Returns the packet's events in order.
102    pub fn events(&self) -> &[MidiPacketEvent] {
103        &self.events
104    }
105
106    /// Encodes the packet as a `stream/packet/midi` [`Expr`] map.
107    pub fn to_expr(&self) -> Expr {
108        Expr::Map(vec![
109            (
110                Expr::Symbol(Symbol::new("packet")),
111                Expr::Symbol(Symbol::qualified("stream/packet", "midi")),
112            ),
113            (
114                Expr::Symbol(Symbol::new("tpq")),
115                Expr::String(self.tpq.to_string()),
116            ),
117            (
118                Expr::Symbol(Symbol::new("events")),
119                Expr::List(self.events.iter().map(midi_event_expr).collect()),
120            ),
121        ])
122    }
123}
124
125/// A diagnostic payload: a categorized human-readable message carried
126/// in-band on a stream.
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct StreamDiagnostic {
129    kind: Symbol,
130    message: String,
131}
132
133impl StreamDiagnostic {
134    /// Builds a diagnostic from its kind symbol and message text.
135    pub fn new(kind: Symbol, message: impl Into<String>) -> Self {
136        Self {
137            kind,
138            message: message.into(),
139        }
140    }
141
142    /// Returns the symbol categorizing this diagnostic.
143    pub fn kind(&self) -> &Symbol {
144        &self.kind
145    }
146
147    /// Returns the diagnostic message text.
148    pub fn message(&self) -> &str {
149        &self.message
150    }
151
152    /// Encodes the diagnostic as a `stream/packet/diagnostic` [`Expr`] map.
153    pub fn to_expr(&self) -> Expr {
154        Expr::Map(vec![
155            (
156                Expr::Symbol(Symbol::new("packet")),
157                Expr::Symbol(Symbol::qualified("stream/packet", "diagnostic")),
158            ),
159            (
160                Expr::Symbol(Symbol::new("kind")),
161                Expr::Symbol(self.kind.clone()),
162            ),
163            (
164                Expr::Symbol(Symbol::new("message")),
165                Expr::String(self.message.clone()),
166            ),
167        ])
168    }
169}
170
171/// An opaque structured payload: a kind-tagged arbitrary [`Expr`] value.
172///
173/// Used for application-defined traffic the fabric does not interpret, such as
174/// model events and rank frontiers (see [`StreamPacket::model_event`] and
175/// [`StreamPacket::rank_frontier`]).
176#[derive(Clone, Debug, PartialEq, Eq)]
177pub struct DataPacket {
178    /// Symbol categorizing the payload (for example `stream/data/model-event`).
179    pub kind: Symbol,
180    /// The application-defined payload expression.
181    pub payload: Expr,
182}
183
184impl DataPacket {
185    /// Builds a data packet from its kind symbol and payload expression.
186    pub fn new(kind: Symbol, payload: Expr) -> Self {
187        Self { kind, payload }
188    }
189
190    /// Encodes the packet as a `stream/packet/data` [`Expr`] map.
191    pub fn to_expr(&self) -> Expr {
192        Expr::Map(vec![
193            (
194                Expr::Symbol(Symbol::new("packet")),
195                Expr::Symbol(Symbol::qualified("stream/packet", "data")),
196            ),
197            (
198                Expr::Symbol(Symbol::new("kind")),
199                Expr::Symbol(self.kind.clone()),
200            ),
201            (Expr::Symbol(Symbol::new("payload")), self.payload.clone()),
202        ])
203    }
204}
205
206/// Umbrella over every payload kind a stream envelope can carry.
207///
208/// Each variant maps to a [`StreamMedia`] kind and to a `stream/packet/*`
209/// tagged [`Expr`] map. [`TryFrom<Expr>`](StreamPacket#impl-TryFrom<Expr>-for-StreamPacket)
210/// reconstructs a packet from that encoding.
211#[derive(Clone, Debug, PartialEq, Eq)]
212pub enum StreamPacket {
213    /// Real-time PCM audio frames.
214    Pcm(PcmPacket),
215    /// Timed MIDI events.
216    Midi(MidiPacket),
217    /// In-band diagnostic message.
218    Diagnostic(StreamDiagnostic),
219    /// Opaque application-defined data.
220    Data(DataPacket),
221}
222
223impl StreamPacket {
224    /// Returns the [`StreamMedia`] kind this payload belongs to.
225    pub fn media(&self) -> StreamMedia {
226        match self {
227            Self::Pcm(_) => StreamMedia::Pcm,
228            Self::Midi(_) => StreamMedia::Midi,
229            Self::Diagnostic(_) => StreamMedia::Diagnostic,
230            Self::Data(_) => StreamMedia::Data,
231        }
232    }
233
234    /// Builds a [`StreamPacket::Data`] payload from a kind symbol and payload
235    /// expression.
236    pub fn data(kind: Symbol, payload: Expr) -> Self {
237        Self::Data(DataPacket::new(kind, payload))
238    }
239
240    /// Builds a `stream/data/model-event` data packet around `payload`.
241    pub fn model_event(payload: Expr) -> Self {
242        Self::data(Symbol::qualified("stream/data", "model-event"), payload)
243    }
244
245    /// Builds a `stream/data/rank-frontier` data packet around `payload`.
246    pub fn rank_frontier(payload: Expr) -> Self {
247        Self::data(Symbol::qualified("stream/data", "rank-frontier"), payload)
248    }
249
250    /// Encodes the packet as its variant's `stream/packet/*` [`Expr`] map.
251    pub fn to_expr(&self) -> Expr {
252        match self {
253            Self::Pcm(packet) => packet.to_expr(),
254            Self::Midi(packet) => packet.to_expr(),
255            Self::Diagnostic(packet) => packet.to_expr(),
256            Self::Data(packet) => packet.to_expr(),
257        }
258    }
259
260    /// Interns the packet's encoded form into the runtime datum store and
261    /// returns a content [`Ref`] to it.
262    ///
263    /// The kernel owns the datum store and content-addressing; this turns the
264    /// packet's [`Expr`] encoding into an interned [`Datum`].
265    pub fn intern_ref(&self, cx: &mut Cx) -> Result<Ref> {
266        let datum = Datum::try_from(self.to_expr())?;
267        cx.datum_store_mut().intern(datum).map(Ref::Content)
268    }
269}
270
271impl TryFrom<Expr> for StreamPacket {
272    type Error = Error;
273
274    fn try_from(expr: Expr) -> Result<Self> {
275        let Expr::Map(entries) = &expr else {
276            return Err(Error::TypeMismatch {
277                expected: "stream packet map",
278                found: expr_kind(&expr),
279            });
280        };
281        let packet = packet_symbol(entries)?;
282        match packet.as_qualified_str().as_str() {
283            "stream/packet/pcm" => PcmPacket::from_entries(entries).map(Self::Pcm),
284            "stream/packet/midi" => MidiPacket::from_entries(entries).map(Self::Midi),
285            "stream/packet/diagnostic" => {
286                StreamDiagnostic::from_entries(entries).map(Self::Diagnostic)
287            }
288            "stream/packet/data" => DataPacket::from_entries(entries).map(Self::Data),
289            other => Err(Error::Eval(format!("unknown stream packet kind {other}"))),
290        }
291    }
292}
293
294impl MidiPacket {
295    fn from_entries(entries: &[(Expr, Expr)]) -> Result<Self> {
296        let tpq = parse_string_field::<u16>(entries, "tpq")?;
297        let events = list_field(entries, "events")?
298            .iter()
299            .enumerate()
300            .map(|(index, expr)| {
301                let event = MidiPacketEvent::from_expr(expr)?;
302                if event.tpq() != tpq {
303                    return Err(Error::Eval(format!(
304                        "MIDI packet event {index} TPQ {} does not match packet TPQ {tpq}",
305                        event.tpq()
306                    )));
307                }
308                Ok(event)
309            })
310            .collect::<Result<Vec<_>>>()?;
311        Self::new(events)
312    }
313}
314
315impl MidiPacketEvent {
316    fn from_expr(expr: &Expr) -> Result<Self> {
317        let Expr::Map(entries) = expr else {
318            return Err(Error::TypeMismatch {
319                expected: "MIDI packet event map",
320                found: expr_kind(expr),
321            });
322        };
323        let ticks = parse_string_field::<i64>(entries, "ticks")?;
324        let tpq = parse_string_field::<u16>(entries, "tpq")?;
325        let bytes = bytes_field(entries, "bytes")?.to_vec();
326        Self::new(ticks, tpq, bytes)
327    }
328}
329
330impl StreamDiagnostic {
331    fn from_entries(entries: &[(Expr, Expr)]) -> Result<Self> {
332        Ok(Self::new(
333            symbol_field(entries, "kind")?.clone(),
334            string_field(entries, "message")?.to_owned(),
335        ))
336    }
337}
338
339impl DataPacket {
340    fn from_entries(entries: &[(Expr, Expr)]) -> Result<Self> {
341        ensure_data_fields_closed(entries)?;
342        Ok(Self::new(
343            symbol_field(entries, "kind")?.clone(),
344            field(entries, "payload")?.clone(),
345        ))
346    }
347}
348
349fn packet_symbol(entries: &[(Expr, Expr)]) -> Result<&Symbol> {
350    entries
351        .iter()
352        .find_map(|(key, value)| match (key, value) {
353            (Expr::Symbol(key), Expr::Symbol(value)) if key.name.as_ref() == "packet" => {
354                Some(value)
355            }
356            _ => None,
357        })
358        .ok_or_else(|| Error::Eval("stream packet missing packet symbol".to_owned()))
359}
360
361fn ensure_data_fields_closed(entries: &[(Expr, Expr)]) -> Result<()> {
362    for (key, _) in entries {
363        let Expr::Symbol(symbol) = key else {
364            return Err(Error::TypeMismatch {
365                expected: "symbol data packet field",
366                found: expr_kind(key),
367            });
368        };
369        if symbol.namespace.is_none()
370            && matches!(symbol.name.as_ref(), "packet" | "kind" | "payload")
371        {
372            continue;
373        }
374        return Err(Error::Eval(format!(
375            "unknown data packet field {}",
376            symbol.as_qualified_str()
377        )));
378    }
379    Ok(())
380}
381
382pub(super) fn parse_string_field<T>(entries: &[(Expr, Expr)], name: &str) -> Result<T>
383where
384    T: FromStr,
385    T::Err: Display,
386{
387    string_field(entries, name)?
388        .parse::<T>()
389        .map_err(|err| Error::Eval(format!("invalid stream packet {name}: {err}")))
390}
391
392pub(super) fn parse_string_expr<T>(expr: &Expr, expected: &'static str) -> Result<T>
393where
394    T: FromStr,
395    T::Err: Display,
396{
397    match expr {
398        Expr::String(value) => value
399            .parse::<T>()
400            .map_err(|err| Error::Eval(format!("{expected} parse failed: {err}"))),
401        other => Err(Error::TypeMismatch {
402            expected,
403            found: expr_kind(other),
404        }),
405    }
406}
407
408pub(super) fn list_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
409    access::entry_required_list(entries, name, "list field")
410}
411
412fn bytes_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [u8]> {
413    match field(entries, name)? {
414        Expr::Bytes(bytes) => Ok(bytes),
415        other => Err(Error::TypeMismatch {
416            expected: "bytes field",
417            found: expr_kind(other),
418        }),
419    }
420}
421
422fn midi_event_expr(event: &MidiPacketEvent) -> Expr {
423    Expr::Map(vec![
424        (
425            Expr::Symbol(Symbol::new("ticks")),
426            Expr::String(event.ticks.to_string()),
427        ),
428        (
429            Expr::Symbol(Symbol::new("tpq")),
430            Expr::String(event.tpq.to_string()),
431        ),
432        (
433            Expr::Symbol(Symbol::new("bytes")),
434            Expr::Bytes(event.bytes.clone()),
435        ),
436    ])
437}