sim_lib_audio_graph_live/
profile.rs1use sim_kernel::{Error, Result, Symbol, Tick};
2use sim_lib_stream_core::{
3 BufferPolicy, ClockDomain, LatencyClass, StreamCapability, StreamDirection, StreamEnvelope,
4 StreamMedia, StreamMetadata, StreamPacket, TransportProfile,
5};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum LiveStreamLane {
10 AudioInput,
12 AudioOutput,
14 Midi,
16 Parameter,
18 Diagnostic,
20}
21
22impl LiveStreamLane {
23 const ALL: [Self; 5] = [
24 Self::AudioInput,
25 Self::AudioOutput,
26 Self::Midi,
27 Self::Parameter,
28 Self::Diagnostic,
29 ];
30
31 pub fn all() -> &'static [Self] {
33 &Self::ALL
34 }
35
36 pub fn wire_label(self) -> &'static str {
38 match self {
39 Self::AudioInput => "audio-input",
40 Self::AudioOutput => "audio-output",
41 Self::Midi => "midi",
42 Self::Parameter => "parameter",
43 Self::Diagnostic => "diagnostic",
44 }
45 }
46
47 pub fn stream_id(self) -> Symbol {
49 Symbol::qualified("stream/live", self.wire_label())
50 }
51
52 pub fn media(self) -> StreamMedia {
54 match self {
55 Self::AudioInput | Self::AudioOutput => StreamMedia::Pcm,
56 Self::Midi => StreamMedia::Midi,
57 Self::Parameter => StreamMedia::Data,
58 Self::Diagnostic => StreamMedia::Diagnostic,
59 }
60 }
61
62 pub fn direction(self) -> StreamDirection {
64 match self {
65 Self::AudioOutput => StreamDirection::Sink,
66 Self::AudioInput | Self::Midi | Self::Parameter | Self::Diagnostic => {
67 StreamDirection::Source
68 }
69 }
70 }
71
72 pub fn clock_domain(self) -> ClockDomain {
74 match self {
75 Self::AudioInput | Self::AudioOutput => ClockDomain::Sample,
76 Self::Midi => ClockDomain::MidiTick,
77 Self::Parameter => ClockDomain::Control,
78 Self::Diagnostic => ClockDomain::Block,
79 }
80 }
81
82 pub fn metadata(self, capacity: usize) -> Result<StreamMetadata> {
85 Ok(StreamMetadata::new(
86 self.stream_id(),
87 self.media(),
88 self.direction(),
89 self.clock_domain().symbol(),
90 BufferPolicy::bounded(capacity)?,
91 ))
92 }
93
94 pub fn realtime_envelope(
96 self,
97 sequence: u64,
98 ticks: Vec<Tick>,
99 packet: StreamPacket,
100 ) -> Result<StreamEnvelope> {
101 self.envelope(
102 sequence,
103 ticks,
104 realtime_local_audio_profile(),
105 Vec::new(),
106 packet,
107 )
108 }
109
110 pub fn buffered_preview_envelope(
112 self,
113 sequence: u64,
114 ticks: Vec<Tick>,
115 packet: StreamPacket,
116 ) -> Result<StreamEnvelope> {
117 self.envelope(
118 sequence,
119 ticks,
120 buffered_pcm_preview_profile(),
121 Vec::new(),
122 packet,
123 )
124 }
125
126 pub fn lan_buffered_preview_envelope(
129 self,
130 sequence: u64,
131 ticks: Vec<Tick>,
132 packet: StreamPacket,
133 ) -> Result<StreamEnvelope> {
134 self.envelope(
135 sequence,
136 ticks,
137 lan_buffered_audio_preview_profile(),
138 Vec::new(),
139 packet,
140 )
141 }
142
143 fn envelope(
144 self,
145 sequence: u64,
146 ticks: Vec<Tick>,
147 profile: TransportProfile,
148 diagnostics: Vec<Symbol>,
149 packet: StreamPacket,
150 ) -> Result<StreamEnvelope> {
151 StreamEnvelope::new(
152 self.stream_id(),
153 packet_id(self, sequence),
154 self.media(),
155 self.direction(),
156 sequence,
157 ticks,
158 self.clock_domain(),
159 profile,
160 diagnostics,
161 packet,
162 )
163 }
164}
165
166pub fn realtime_local_audio_profile() -> TransportProfile {
168 TransportProfile::realtime_local_audio()
169}
170
171pub fn buffered_pcm_preview_profile() -> TransportProfile {
173 TransportProfile::buffered_pcm_preview()
174}
175
176pub fn lan_buffered_audio_preview_profile() -> TransportProfile {
178 TransportProfile::lan_buffered_audio_preview()
179}
180
181pub fn lan_render_return_profile() -> TransportProfile {
183 TransportProfile::lan_render_return()
184}
185
186pub fn validate_realtime_local_audio_profile(profile: &TransportProfile) -> Result<()> {
191 if profile.has_capability(StreamCapability::Remote)
192 || profile.latency_class() == LatencyClass::RemoteCollaboration
193 {
194 return Err(Error::Eval(
195 "remote streams cannot enter the realtime local audio callback".to_owned(),
196 ));
197 }
198 if !profile.has_capability(StreamCapability::Realtime) {
199 return Err(Error::Eval(
200 "realtime local audio requires realtime transport capability".to_owned(),
201 ));
202 }
203 if !profile.has_capability(StreamCapability::Bounded) {
204 return Err(Error::Eval(
205 "realtime local audio requires bounded transport capability".to_owned(),
206 ));
207 }
208 if profile.latency_class() != LatencyClass::SampleExact {
209 return Err(Error::Eval(
210 "realtime local audio requires sample-exact latency".to_owned(),
211 ));
212 }
213 if profile.name() != &Symbol::qualified("stream/profile", "realtime-local-audio") {
214 return Err(Error::Eval(
215 "callback entry requires the realtime-local-audio profile".to_owned(),
216 ));
217 }
218 Ok(())
219}
220
221pub fn refuse_unbuffered_audio_callback_tunnel(profile: &TransportProfile) -> Result<()> {
224 if profile.name() == &Symbol::qualified("stream/profile", "realtime-local-audio")
225 || profile.has_capability(StreamCapability::Realtime)
226 {
227 return Err(Error::Eval(format!(
228 "unbuffered audio callback tunneling is refused by default for {}; use stream/profile/lan-buffered-audio-preview",
229 profile.name().as_qualified_str()
230 )));
231 }
232 Ok(())
233}
234
235fn packet_id(lane: LiveStreamLane, sequence: u64) -> Symbol {
236 Symbol::qualified(
237 "stream/live-packet",
238 format!("{}#{sequence}", lane.wire_label()),
239 )
240}