1use sim_kernel::{Error, Expr, Result, Symbol};
12
13use crate::buffer::{expr_kind, field, symbol_field};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum LatencyClass {
23 OfflineRender,
25 BlockLocal,
27 Interactive,
29 SampleExact,
31 BufferedPreview,
33 CollabBarDelay,
35 RemoteCollaboration,
37}
38
39impl LatencyClass {
40 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 pub fn symbol(self) -> Symbol {
57 Symbol::qualified("stream/latency", self.wire_label())
58 }
59
60 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum StreamCapability {
87 Exact,
89 Deterministic,
91 Realtime,
93 Bounded,
95 Remote,
97 Replayable,
99 Preview,
101 Persistent,
103 Resumable,
105 Lossy,
107}
108
109impl StreamCapability {
110 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 pub fn symbol(self) -> Symbol {
130 Symbol::qualified("stream/capability", self.wire_label())
131 }
132
133 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 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#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct TransportProfile {
182 name: Symbol,
183 latency_class: LatencyClass,
184 capabilities: Vec<StreamCapability>,
185}
186
187impl TransportProfile {
188 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 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 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 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 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 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 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 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 pub fn name(&self) -> &Symbol {
318 &self.name
319 }
320
321 pub fn latency_class(&self) -> LatencyClass {
323 self.latency_class
324 }
325
326 pub fn capabilities(&self) -> &[StreamCapability] {
328 &self.capabilities
329 }
330
331 pub fn has_capability(&self, capability: StreamCapability) -> bool {
333 self.capabilities.contains(&capability)
334 }
335
336 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 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}