Skip to main content

sim_lib_stream_host/
config.rs

1//! Host stream configuration records.
2
3use sim_kernel::{Error, Result, Symbol};
4use sim_lib_stream_core::{BufferPolicy, ClockDomain, StreamMedia, StreamMetadata};
5
6use crate::model::HostDirection;
7
8/// Request supplied to a host backend when opening a stream.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct HostStreamConfigRequest {
11    backend: Symbol,
12    device: Symbol,
13    media: StreamMedia,
14    direction: HostDirection,
15    buffer: BufferPolicy,
16    clock: Symbol,
17    reconnect: HostReconnectPolicy,
18}
19
20/// Accepted stream configuration returned by a host backend.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct HostStreamConfig {
23    backend: Symbol,
24    device: Symbol,
25    media: StreamMedia,
26    direction: HostDirection,
27    buffer: BufferPolicy,
28    clock: HostClockInfo,
29    latency: HostLatencyInfo,
30    reconnect: HostReconnectPolicy,
31}
32
33/// Latency estimate for an opened host stream.
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub struct HostLatencyInfo {
36    input_frames: u32,
37    output_frames: u32,
38}
39
40/// Clock metadata for an opened host stream.
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct HostClockInfo {
43    clock: Symbol,
44    sample_rate_hz: Option<u32>,
45    stable: bool,
46}
47
48/// Reconnect behavior accepted by a host backend.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct HostReconnectPolicy {
51    enabled: bool,
52    max_attempts: u32,
53    backoff_ms: u32,
54}
55
56impl HostStreamConfigRequest {
57    /// Builds an open request for `device` on `backend` with a server-frame
58    /// clock and reconnect disabled.
59    pub fn new(
60        backend: Symbol,
61        device: Symbol,
62        media: StreamMedia,
63        direction: HostDirection,
64        buffer: BufferPolicy,
65    ) -> Self {
66        Self {
67            backend,
68            device,
69            media,
70            direction,
71            buffer,
72            clock: ClockDomain::ServerFrame.symbol(),
73            reconnect: HostReconnectPolicy::disabled(),
74        }
75    }
76
77    /// Overrides the requested clock-domain symbol.
78    pub fn with_clock(mut self, clock: Symbol) -> Self {
79        self.clock = clock;
80        self
81    }
82
83    /// Overrides the requested reconnect policy.
84    pub fn with_reconnect(mut self, reconnect: HostReconnectPolicy) -> Self {
85        self.reconnect = reconnect;
86        self
87    }
88
89    /// Returns the target backend symbol.
90    pub fn backend(&self) -> &Symbol {
91        &self.backend
92    }
93
94    /// Returns the target device symbol.
95    pub fn device(&self) -> &Symbol {
96        &self.device
97    }
98
99    /// Returns the requested stream media.
100    pub fn media(&self) -> StreamMedia {
101        self.media
102    }
103
104    /// Returns the requested stream direction.
105    pub fn direction(&self) -> HostDirection {
106        self.direction
107    }
108
109    /// Returns the requested buffer policy.
110    pub fn buffer(&self) -> &BufferPolicy {
111        &self.buffer
112    }
113
114    /// Returns the requested clock-domain symbol.
115    pub fn clock(&self) -> &Symbol {
116        &self.clock
117    }
118
119    /// Returns the requested reconnect policy.
120    pub fn reconnect(&self) -> &HostReconnectPolicy {
121        &self.reconnect
122    }
123}
124
125impl HostStreamConfig {
126    /// Builds an accepted configuration from a request plus the backend's
127    /// resolved latency and clock metadata.
128    pub fn from_request(
129        request: HostStreamConfigRequest,
130        latency: HostLatencyInfo,
131        clock: HostClockInfo,
132    ) -> Self {
133        Self {
134            backend: request.backend,
135            device: request.device,
136            media: request.media,
137            direction: request.direction,
138            buffer: request.buffer,
139            clock,
140            latency,
141            reconnect: request.reconnect,
142        }
143    }
144
145    /// Returns the backend that accepted the stream.
146    pub fn backend(&self) -> &Symbol {
147        &self.backend
148    }
149
150    /// Returns the opened device symbol.
151    pub fn device(&self) -> &Symbol {
152        &self.device
153    }
154
155    /// Returns the accepted stream media.
156    pub fn media(&self) -> StreamMedia {
157        self.media
158    }
159
160    /// Returns the accepted stream direction.
161    pub fn direction(&self) -> HostDirection {
162        self.direction
163    }
164
165    /// Returns the accepted buffer policy.
166    pub fn buffer(&self) -> &BufferPolicy {
167        &self.buffer
168    }
169
170    /// Returns the resolved clock metadata.
171    pub fn clock(&self) -> &HostClockInfo {
172        &self.clock
173    }
174
175    /// Returns the resolved latency estimate.
176    pub fn latency(&self) -> HostLatencyInfo {
177        self.latency
178    }
179
180    /// Returns the accepted reconnect policy.
181    pub fn reconnect(&self) -> &HostReconnectPolicy {
182        &self.reconnect
183    }
184
185    /// Builds the [`StreamMetadata`] describing the opened stream.
186    pub fn metadata(&self) -> StreamMetadata {
187        StreamMetadata::new(
188            self.device.clone(),
189            self.media,
190            self.direction.stream_direction(),
191            self.clock.clock.clone(),
192            self.buffer.clone(),
193        )
194    }
195
196    /// Checks that this configuration is a valid realtime local audio stream.
197    ///
198    /// Requires PCM media, the sample clock domain, and a bounded buffer;
199    /// returns an evaluation error otherwise.
200    pub fn validate_realtime_local_audio(&self) -> Result<()> {
201        if self.media != StreamMedia::Pcm {
202            return Err(Error::Eval(
203                "realtime local audio host streams require PCM media".to_owned(),
204            ));
205        }
206        if ClockDomain::from_symbol(self.clock.clock())? != ClockDomain::Sample {
207            return Err(Error::Eval(
208                "realtime local audio host streams require the sample clock domain".to_owned(),
209            ));
210        }
211        if self.buffer.capacity() == 0 {
212            return Err(Error::Eval(
213                "realtime local audio host streams require a bounded buffer".to_owned(),
214            ));
215        }
216        Ok(())
217    }
218
219    /// Checks that this configuration is a valid LAN MIDI/control stream.
220    ///
221    /// Requires MIDI media, a MIDI-tick or control clock domain, and a bounded
222    /// buffer; returns an evaluation error otherwise.
223    pub fn validate_lan_midi_control(&self) -> Result<()> {
224        if self.media != StreamMedia::Midi {
225            return Err(Error::Eval(
226                "LAN MIDI/control host streams require MIDI media".to_owned(),
227            ));
228        }
229        match ClockDomain::from_symbol(self.clock.clock())? {
230            ClockDomain::MidiTick | ClockDomain::Control => {}
231            _ => {
232                return Err(Error::Eval(
233                    "LAN MIDI/control host streams require a MIDI tick or control clock domain"
234                        .to_owned(),
235                ));
236            }
237        }
238        if self.buffer.capacity() == 0 {
239            return Err(Error::Eval(
240                "LAN MIDI/control host streams require a bounded buffer".to_owned(),
241            ));
242        }
243        Ok(())
244    }
245
246    /// Checks that this configuration is a valid LAN buffered audio preview
247    /// stream.
248    ///
249    /// Requires PCM media and a bounded buffer; returns an evaluation error
250    /// otherwise.
251    pub fn validate_lan_buffered_audio_preview(&self) -> Result<()> {
252        if self.media != StreamMedia::Pcm {
253            return Err(Error::Eval(
254                "LAN buffered audio preview host streams require PCM media".to_owned(),
255            ));
256        }
257        if self.buffer.capacity() == 0 {
258            return Err(Error::Eval(
259                "LAN buffered audio preview host streams require a bounded buffer".to_owned(),
260            ));
261        }
262        Ok(())
263    }
264}
265
266impl HostLatencyInfo {
267    /// Builds a latency estimate from input and output frame counts.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// use sim_lib_stream_host::HostLatencyInfo;
273    ///
274    /// let latency = HostLatencyInfo::new(64, 128);
275    /// assert_eq!(latency.input_frames(), 64);
276    /// assert_eq!(latency.output_frames(), 128);
277    /// ```
278    pub fn new(input_frames: u32, output_frames: u32) -> Self {
279        Self {
280            input_frames,
281            output_frames,
282        }
283    }
284
285    /// Returns the estimated input latency in frames.
286    pub fn input_frames(self) -> u32 {
287        self.input_frames
288    }
289
290    /// Returns the estimated output latency in frames.
291    pub fn output_frames(self) -> u32 {
292        self.output_frames
293    }
294}
295
296impl HostClockInfo {
297    /// Builds clock metadata from a clock-domain symbol, optional sample rate,
298    /// and a stability flag.
299    pub fn new(clock: Symbol, sample_rate_hz: Option<u32>, stable: bool) -> Self {
300        Self {
301            clock,
302            sample_rate_hz,
303            stable,
304        }
305    }
306
307    /// Returns the clock-domain symbol.
308    pub fn clock(&self) -> &Symbol {
309        &self.clock
310    }
311
312    /// Returns the sample rate in hertz when known.
313    pub fn sample_rate_hz(&self) -> Option<u32> {
314        self.sample_rate_hz
315    }
316
317    /// Returns whether the clock is reported as stable.
318    pub fn stable(&self) -> bool {
319        self.stable
320    }
321}
322
323impl HostReconnectPolicy {
324    /// Returns a policy that never reconnects.
325    pub fn disabled() -> Self {
326        Self {
327            enabled: false,
328            max_attempts: 0,
329            backoff_ms: 0,
330        }
331    }
332
333    /// Returns a policy that reconnects up to `max_attempts` times with a fixed
334    /// `backoff_ms` delay between attempts.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use sim_lib_stream_host::HostReconnectPolicy;
340    ///
341    /// let policy = HostReconnectPolicy::bounded(3, 250);
342    /// assert!(policy.enabled());
343    /// assert_eq!(policy.max_attempts(), 3);
344    /// assert_eq!(policy.backoff_ms(), 250);
345    /// ```
346    pub fn bounded(max_attempts: u32, backoff_ms: u32) -> Self {
347        Self {
348            enabled: true,
349            max_attempts,
350            backoff_ms,
351        }
352    }
353
354    /// Returns whether reconnection is enabled.
355    pub fn enabled(&self) -> bool {
356        self.enabled
357    }
358
359    /// Returns the maximum number of reconnect attempts.
360    pub fn max_attempts(&self) -> u32 {
361        self.max_attempts
362    }
363
364    /// Returns the backoff delay between attempts in milliseconds.
365    pub fn backoff_ms(&self) -> u32 {
366        self.backoff_ms
367    }
368}