Skip to main content

sim_lib_stream_core/envelope/
profile.rs

1//! Transport capability and latency model carried by a stream envelope.
2//!
3//! A [`TransportProfile`] names what a transport is allowed to do with a stream
4//! ([`StreamCapability`]) together with the real-time [`LatencyClass`] it
5//! promises. The kernel owns the capability/latency contract vocabulary as
6//! [`Symbol`]s; this module supplies the concrete profile set, the
7//! capability/latency consistency rules, and the named profile presets used
8//! across the fabric (in-memory, real-time audio, buffered preview, and the LAN
9//! and remote variants).
10
11use sim_kernel::{Error, Expr, Result, Symbol};
12
13use crate::buffer::{expr_kind, field, symbol_field};
14
15/// Real-time latency promise a transport profile makes.
16///
17/// Ordered loosely from most relaxed to most demanding; the kernel defines the
18/// latency contract as [`Symbol`]s and this enum is the concrete set the fabric
19/// recognizes. [`LatencyClass::symbol`] and [`LatencyClass::from_symbol`] map to
20/// and from the kernel symbol under the `stream/latency` namespace.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum LatencyClass {
23    /// Offline (faster- or slower-than-real-time) rendering; no timing promise.
24    OfflineRender,
25    /// Block-local processing latency within a single host.
26    BlockLocal,
27    /// Interactive latency suitable for responsive control.
28    Interactive,
29    /// Sample-exact timing (the tightest real-time class).
30    SampleExact,
31    /// Buffered preview latency, trading immediacy for smoothness.
32    BufferedPreview,
33    /// Collaboration latency tolerating up to a musical bar of delay.
34    CollabBarDelay,
35    /// Remote-collaboration latency across a network link.
36    RemoteCollaboration,
37}
38
39impl LatencyClass {
40    /// Returns the stable wire label for this class (for example
41    /// `"sample-exact"`).
42    pub fn wire_label(self) -> &'static str {
43        match self {
44            Self::OfflineRender => "offline-render",
45            Self::BlockLocal => "block-local",
46            Self::Interactive => "interactive",
47            Self::SampleExact => "sample-exact",
48            Self::BufferedPreview => "buffered-preview",
49            Self::CollabBarDelay => "collab-bardelay",
50            Self::RemoteCollaboration => "remote-collaboration",
51        }
52    }
53
54    /// Returns the kernel [`Symbol`] for this class under the `stream/latency`
55    /// namespace.
56    pub fn symbol(self) -> Symbol {
57        Symbol::qualified("stream/latency", self.wire_label())
58    }
59
60    /// Parses a [`LatencyClass`] from a kernel [`Symbol`].
61    ///
62    /// Accepts the bare label and the fully qualified `stream/latency/<label>`
63    /// form, erroring on any unrecognized latency class.
64    pub fn from_symbol(symbol: &Symbol) -> Result<Self> {
65        match symbol.as_qualified_str().as_str() {
66            "offline-render" | "stream/latency/offline-render" => Ok(Self::OfflineRender),
67            "block-local" | "stream/latency/block-local" => Ok(Self::BlockLocal),
68            "interactive" | "stream/latency/interactive" => Ok(Self::Interactive),
69            "sample-exact" | "stream/latency/sample-exact" => Ok(Self::SampleExact),
70            "buffered-preview" | "stream/latency/buffered-preview" => Ok(Self::BufferedPreview),
71            "collab-bardelay" | "stream/latency/collab-bardelay" => Ok(Self::CollabBarDelay),
72            "remote-collaboration" | "stream/latency/remote-collaboration" => {
73                Ok(Self::RemoteCollaboration)
74            }
75            other => Err(Error::Eval(format!("unknown stream latency class {other}"))),
76        }
77    }
78}
79
80/// One thing a transport is permitted to do with a stream.
81///
82/// A [`TransportProfile`] carries a set of these. The kernel defines the
83/// capability vocabulary as [`Symbol`]s; this enum is the concrete set the
84/// fabric recognizes, mapped under the `stream/capability` namespace.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum StreamCapability {
87    /// Sample-exact delivery with no approximation.
88    Exact,
89    /// Deterministic, reproducible output for identical input.
90    Deterministic,
91    /// Real-time delivery.
92    Realtime,
93    /// Bounded buffering and latency.
94    Bounded,
95    /// Delivery across a remote (networked) link.
96    Remote,
97    /// Output that can be replayed from a recorded source.
98    Replayable,
99    /// Lower-fidelity preview output.
100    Preview,
101    /// Output backed by persistent storage.
102    Persistent,
103    /// Delivery that can resume after interruption.
104    Resumable,
105    /// Lossy delivery that may drop or approximate data.
106    Lossy,
107}
108
109impl StreamCapability {
110    /// Returns the stable wire label for this capability (for example
111    /// `"realtime"`).
112    pub fn wire_label(self) -> &'static str {
113        match self {
114            Self::Exact => "exact",
115            Self::Deterministic => "deterministic",
116            Self::Realtime => "realtime",
117            Self::Bounded => "bounded",
118            Self::Remote => "remote",
119            Self::Replayable => "replayable",
120            Self::Preview => "preview",
121            Self::Persistent => "persistent",
122            Self::Resumable => "resumable",
123            Self::Lossy => "lossy",
124        }
125    }
126
127    /// Returns the kernel [`Symbol`] for this capability under the
128    /// `stream/capability` namespace.
129    pub fn symbol(self) -> Symbol {
130        Symbol::qualified("stream/capability", self.wire_label())
131    }
132
133    /// Parses a [`StreamCapability`] from a kernel [`Symbol`].
134    ///
135    /// Accepts the bare label and the fully qualified
136    /// `stream/capability/<label>` form, erroring on any unrecognized
137    /// capability.
138    pub fn from_symbol(symbol: &Symbol) -> Result<Self> {
139        match symbol.as_qualified_str().as_str() {
140            "exact" | "stream/capability/exact" => Ok(Self::Exact),
141            "deterministic" | "stream/capability/deterministic" => Ok(Self::Deterministic),
142            "realtime" | "stream/capability/realtime" => Ok(Self::Realtime),
143            "bounded" | "stream/capability/bounded" => Ok(Self::Bounded),
144            "remote" | "stream/capability/remote" => Ok(Self::Remote),
145            "replayable" | "stream/capability/replayable" => Ok(Self::Replayable),
146            "preview" | "stream/capability/preview" => Ok(Self::Preview),
147            "persistent" | "stream/capability/persistent" => Ok(Self::Persistent),
148            "resumable" | "stream/capability/resumable" => Ok(Self::Resumable),
149            "lossy" | "stream/capability/lossy" => Ok(Self::Lossy),
150            other => Err(Error::Eval(format!("unknown stream capability {other}"))),
151        }
152    }
153
154    /// Returns the latency class this capability implies on its own.
155    ///
156    /// Used as a default when reasoning about a lone capability; an assembled
157    /// [`TransportProfile`] carries its own [`LatencyClass`] independently.
158    pub fn latency_class(self) -> LatencyClass {
159        match self {
160            Self::Exact => LatencyClass::SampleExact,
161            Self::Deterministic => LatencyClass::OfflineRender,
162            Self::Realtime => LatencyClass::SampleExact,
163            Self::Bounded => LatencyClass::BlockLocal,
164            Self::Remote => LatencyClass::RemoteCollaboration,
165            Self::Replayable => LatencyClass::OfflineRender,
166            Self::Preview => LatencyClass::BufferedPreview,
167            Self::Persistent => LatencyClass::RemoteCollaboration,
168            Self::Resumable => LatencyClass::RemoteCollaboration,
169            Self::Lossy => LatencyClass::BufferedPreview,
170        }
171    }
172}
173
174/// The capability and latency contract a transport offers for a stream.
175///
176/// A profile pairs a name with a [`LatencyClass`] and a set of
177/// [`StreamCapability`] values, validated for mutual consistency at
178/// construction. The named constructors provide the standard fabric presets;
179/// fields are private and read through the accessor methods.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct TransportProfile {
182    name: Symbol,
183    latency_class: LatencyClass,
184    capabilities: Vec<StreamCapability>,
185}
186
187impl TransportProfile {
188    /// Builds a profile from a name, latency class, and capability set.
189    ///
190    /// Rejects inconsistent combinations -- for example `Exact` together with
191    /// `Lossy`, or `Realtime` under a non-real-time latency class -- returning
192    /// an error rather than a profile.
193    pub fn new(
194        name: Symbol,
195        latency_class: LatencyClass,
196        capabilities: Vec<StreamCapability>,
197    ) -> Result<Self> {
198        validate_capabilities(latency_class, &capabilities)?;
199        Ok(Self {
200            name,
201            latency_class,
202            capabilities,
203        })
204    }
205
206    /// Preset profile for in-process, in-memory streaming: block-local latency
207    /// with exact, deterministic, bounded, replayable delivery.
208    pub fn memory_local() -> Self {
209        Self::new(
210            Symbol::qualified("stream/profile", "memory-local"),
211            LatencyClass::BlockLocal,
212            vec![
213                StreamCapability::Exact,
214                StreamCapability::Deterministic,
215                StreamCapability::Bounded,
216                StreamCapability::Replayable,
217            ],
218        )
219        .expect("memory-local stream profile is valid")
220    }
221
222    /// Preset profile for local real-time audio: sample-exact latency with
223    /// exact, real-time, bounded delivery.
224    pub fn realtime_local_audio() -> Self {
225        Self::new(
226            Symbol::qualified("stream/profile", "realtime-local-audio"),
227            LatencyClass::SampleExact,
228            vec![
229                StreamCapability::Exact,
230                StreamCapability::Realtime,
231                StreamCapability::Bounded,
232            ],
233        )
234        .expect("realtime-local-audio stream profile is valid")
235    }
236
237    /// Preset profile for buffered PCM preview: buffered-preview latency with
238    /// bounded, preview, lossy delivery.
239    pub fn buffered_pcm_preview() -> Self {
240        Self::new(
241            Symbol::qualified("stream/profile", "buffered-pcm-preview"),
242            LatencyClass::BufferedPreview,
243            vec![
244                StreamCapability::Bounded,
245                StreamCapability::Preview,
246                StreamCapability::Lossy,
247            ],
248        )
249        .expect("buffered-pcm-preview stream profile is valid")
250    }
251
252    /// Preset profile for the remote stream fabric: remote-collaboration
253    /// latency with remote, bounded, replayable, resumable delivery.
254    pub fn remote_stream_fabric() -> Self {
255        Self::new(
256            Symbol::qualified("stream/profile", "remote-stream-fabric"),
257            LatencyClass::RemoteCollaboration,
258            vec![
259                StreamCapability::Remote,
260                StreamCapability::Bounded,
261                StreamCapability::Replayable,
262                StreamCapability::Resumable,
263            ],
264        )
265        .expect("remote-stream-fabric stream profile is valid")
266    }
267
268    /// Preset profile for LAN MIDI control: interactive latency with remote,
269    /// bounded, replayable delivery.
270    pub fn lan_midi_control() -> Self {
271        Self::new(
272            Symbol::qualified("stream/profile", "lan-midi-control"),
273            LatencyClass::Interactive,
274            vec![
275                StreamCapability::Remote,
276                StreamCapability::Bounded,
277                StreamCapability::Replayable,
278            ],
279        )
280        .expect("lan-midi-control stream profile is valid")
281    }
282
283    /// Preset profile for LAN buffered audio preview: buffered-preview latency
284    /// with remote, bounded, preview, lossy delivery.
285    pub fn lan_buffered_audio_preview() -> Self {
286        Self::new(
287            Symbol::qualified("stream/profile", "lan-buffered-audio-preview"),
288            LatencyClass::BufferedPreview,
289            vec![
290                StreamCapability::Remote,
291                StreamCapability::Bounded,
292                StreamCapability::Preview,
293                StreamCapability::Lossy,
294            ],
295        )
296        .expect("lan-buffered-audio-preview stream profile is valid")
297    }
298
299    /// Preset profile for LAN render return: offline-render latency with remote,
300    /// bounded, deterministic, replayable, resumable delivery.
301    pub fn lan_render_return() -> Self {
302        Self::new(
303            Symbol::qualified("stream/profile", "lan-render-return"),
304            LatencyClass::OfflineRender,
305            vec![
306                StreamCapability::Remote,
307                StreamCapability::Bounded,
308                StreamCapability::Deterministic,
309                StreamCapability::Replayable,
310                StreamCapability::Resumable,
311            ],
312        )
313        .expect("lan-render-return stream profile is valid")
314    }
315
316    /// Returns the profile's name symbol.
317    pub fn name(&self) -> &Symbol {
318        &self.name
319    }
320
321    /// Returns the latency class this profile promises.
322    pub fn latency_class(&self) -> LatencyClass {
323        self.latency_class
324    }
325
326    /// Returns the capabilities this profile grants.
327    pub fn capabilities(&self) -> &[StreamCapability] {
328        &self.capabilities
329    }
330
331    /// Returns whether this profile grants `capability`.
332    pub fn has_capability(&self, capability: StreamCapability) -> bool {
333        self.capabilities.contains(&capability)
334    }
335
336    /// Encodes this profile into its [`Expr`] map wire form.
337    ///
338    /// Round-trips back through [`TransportProfile::from_expr`].
339    pub fn to_expr(&self) -> Expr {
340        Expr::Map(vec![
341            (
342                Expr::Symbol(Symbol::new("name")),
343                Expr::Symbol(self.name.clone()),
344            ),
345            (
346                Expr::Symbol(Symbol::new("latency-class")),
347                Expr::Symbol(self.latency_class.symbol()),
348            ),
349            (
350                Expr::Symbol(Symbol::new("capabilities")),
351                Expr::List(
352                    self.capabilities
353                        .iter()
354                        .map(|capability| Expr::Symbol(capability.symbol()))
355                        .collect(),
356                ),
357            ),
358        ])
359    }
360
361    /// Decodes a profile from its [`Expr`] map wire form.
362    ///
363    /// Requires exactly the `name`, `latency-class`, and `capabilities` fields,
364    /// and re-applies the capability/latency consistency checks via
365    /// [`TransportProfile::new`].
366    pub fn from_expr(expr: &Expr) -> Result<Self> {
367        let Expr::Map(entries) = expr else {
368            return Err(Error::TypeMismatch {
369                expected: "stream transport profile map",
370                found: expr_kind(expr),
371            });
372        };
373        ensure_fields(entries, &["name", "latency-class", "capabilities"])?;
374        Self::new(
375            symbol_field(entries, "name")?.clone(),
376            LatencyClass::from_symbol(symbol_field(entries, "latency-class")?)?,
377            symbol_list(entries, "capabilities")?
378                .iter()
379                .map(StreamCapability::from_symbol)
380                .collect::<Result<Vec<_>>>()?,
381        )
382    }
383}
384
385fn validate_capabilities(
386    latency_class: LatencyClass,
387    capabilities: &[StreamCapability],
388) -> Result<()> {
389    let has = |needle| capabilities.contains(&needle);
390    if has(StreamCapability::Exact) && has(StreamCapability::Lossy) {
391        return Err(Error::Eval(
392            "stream capabilities exact and lossy cannot be combined".to_owned(),
393        ));
394    }
395    if latency_class == LatencyClass::SampleExact && has(StreamCapability::Remote) {
396        return Err(Error::Eval(
397            "remote streams cannot claim sample-exact latency".to_owned(),
398        ));
399    }
400    if latency_class == LatencyClass::RemoteCollaboration && has(StreamCapability::Realtime) {
401        return Err(Error::Eval(
402            "remote-collaboration streams cannot claim realtime capability".to_owned(),
403        ));
404    }
405    if latency_class == LatencyClass::OfflineRender && has(StreamCapability::Realtime) {
406        return Err(Error::Eval(
407            "offline-render streams cannot claim realtime capability".to_owned(),
408        ));
409    }
410    Ok(())
411}
412
413fn symbol_list(entries: &[(Expr, Expr)], name: &str) -> Result<Vec<Symbol>> {
414    list_field(entries, name)?
415        .iter()
416        .map(|expr| match expr {
417            Expr::Symbol(symbol) => Ok(symbol.clone()),
418            other => Err(Error::TypeMismatch {
419                expected: "symbol list item",
420                found: expr_kind(other),
421            }),
422        })
423        .collect()
424}
425
426fn list_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
427    match field(entries, name)? {
428        Expr::List(items) => Ok(items),
429        other => Err(Error::TypeMismatch {
430            expected: "list field",
431            found: expr_kind(other),
432        }),
433    }
434}
435
436fn ensure_fields(entries: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
437    for (key, _) in entries {
438        let Expr::Symbol(symbol) = key else {
439            return Err(Error::TypeMismatch {
440                expected: "symbol stream profile field",
441                found: expr_kind(key),
442            });
443        };
444        if symbol.namespace.is_none() && allowed.contains(&symbol.name.as_ref()) {
445            continue;
446        }
447        return Err(Error::Eval(format!(
448            "unknown stream profile field {}",
449            symbol.as_qualified_str()
450        )));
451    }
452    Ok(())
453}