Skip to main content

sim_lib_audio_graph_live/
profile.rs

1use 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/// One logical stream lane carried between the live runner and the host.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum LiveStreamLane {
10    /// Incoming audio (a source into the graph).
11    AudioInput,
12    /// Outgoing audio (a sink from the graph).
13    AudioOutput,
14    /// MIDI control events.
15    Midi,
16    /// Control-rate parameter updates.
17    Parameter,
18    /// Diagnostic packets.
19    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    /// Returns every lane in a stable order.
32    pub fn all() -> &'static [Self] {
33        &Self::ALL
34    }
35
36    /// Returns the stable wire label for this lane.
37    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    /// Returns the stream id symbol for this lane.
48    pub fn stream_id(self) -> Symbol {
49        Symbol::qualified("stream/live", self.wire_label())
50    }
51
52    /// Returns the stream media kind this lane carries.
53    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    /// Returns the stream direction (source or sink) for this lane.
63    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    /// Returns the clock domain this lane runs in.
73    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    /// Builds the stream metadata for this lane with a bounded buffer of
83    /// `capacity` items.
84    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    /// Wraps a packet in an envelope using the realtime local audio profile.
95    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    /// Wraps a packet in an envelope using the buffered PCM preview profile.
111    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    /// Wraps a packet in an envelope using the LAN buffered audio preview
127    /// profile.
128    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
166/// Returns the realtime local audio transport profile.
167pub fn realtime_local_audio_profile() -> TransportProfile {
168    TransportProfile::realtime_local_audio()
169}
170
171/// Returns the buffered PCM preview transport profile.
172pub fn buffered_pcm_preview_profile() -> TransportProfile {
173    TransportProfile::buffered_pcm_preview()
174}
175
176/// Returns the LAN buffered audio preview transport profile.
177pub fn lan_buffered_audio_preview_profile() -> TransportProfile {
178    TransportProfile::lan_buffered_audio_preview()
179}
180
181/// Returns the LAN render-return transport profile.
182pub fn lan_render_return_profile() -> TransportProfile {
183    TransportProfile::lan_render_return()
184}
185
186/// Validates that a profile may enter the realtime local audio callback.
187///
188/// Rejects remote streams and requires realtime, bounded, sample-exact
189/// capabilities and the `realtime-local-audio` profile name.
190pub 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
221/// Refuses tunneling an unbuffered or realtime profile through the audio
222/// callback, directing callers to the LAN buffered audio preview profile.
223pub 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}