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