1use sim_kernel::{Error, Expr, Result, Symbol};
12use sim_value::access;
13
14use crate::buffer::{expr_kind, symbol_field};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum LatencyClass {
24 OfflineRender,
26 BlockLocal,
28 Interactive,
30 SampleExact,
32 BufferedPreview,
34 CollabBarDelay,
36 RemoteCollaboration,
38}
39
40impl LatencyClass {
41 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 pub fn symbol(self) -> Symbol {
58 Symbol::qualified("stream/latency", self.wire_label())
59 }
60
61 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum StreamCapability {
88 Exact,
90 Deterministic,
92 Realtime,
94 Bounded,
96 Remote,
98 Replayable,
100 Preview,
102 Persistent,
104 Resumable,
106 Lossy,
108}
109
110impl StreamCapability {
111 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 pub fn symbol(self) -> Symbol {
131 Symbol::qualified("stream/capability", self.wire_label())
132 }
133
134 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 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#[derive(Clone, Debug, PartialEq, Eq)]
182pub struct TransportProfile {
183 name: Symbol,
184 latency_class: LatencyClass,
185 capabilities: Vec<StreamCapability>,
186}
187
188impl TransportProfile {
189 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 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 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 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 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 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 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 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 pub fn name(&self) -> &Symbol {
319 &self.name
320 }
321
322 pub fn latency_class(&self) -> LatencyClass {
324 self.latency_class
325 }
326
327 pub fn capabilities(&self) -> &[StreamCapability] {
329 &self.capabilities
330 }
331
332 pub fn has_capability(&self, capability: StreamCapability) -> bool {
334 self.capabilities.contains(&capability)
335 }
336
337 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 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}