Skip to main content

sim_lib_stream_asio/
backend.rs

1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_stream_core::{BufferPolicy, StreamMedia};
3use sim_lib_stream_host::{
4    HostBackend, HostBackendCapability, HostBackendInfo, HostClockInfo, HostDeviceInventory,
5    HostDeviceSpec, HostDirection, HostLatencyInfo, HostOpenStream, HostPortSpec, HostStreamConfig,
6    HostStreamConfigRequest,
7};
8
9use crate::{AsioDriver, AsioTiming};
10
11/// Returns the stream-host backend identifier for ASIO (`stream/host:asio`).
12pub fn asio_backend_symbol() -> Symbol {
13    Symbol::qualified("stream/host", "asio")
14}
15
16/// Returns the transport identifier carried by ASIO host streams
17/// (`stream/transport:asio`).
18pub fn asio_transport_symbol() -> Symbol {
19    Symbol::qualified("stream/transport", "asio")
20}
21
22/// Returns the clock identifier reported by ASIO host streams (`clock:asio`).
23pub fn asio_clock_symbol() -> Symbol {
24    Symbol::qualified("clock", "asio")
25}
26
27/// ASIO host backend with provider-supplied deterministic driver data.
28#[derive(Clone, Debug)]
29pub struct AsioBackend {
30    info: HostBackendInfo,
31    drivers: Vec<AsioDriver>,
32}
33
34impl Default for AsioBackend {
35    fn default() -> Self {
36        Self::new(Vec::new())
37    }
38}
39
40impl AsioBackend {
41    /// Builds a backend over the provider-reported `drivers`, marking it as a
42    /// native (non-fake) backend and deriving its capabilities from the driver
43    /// set.
44    pub fn new(drivers: Vec<AsioDriver>) -> Self {
45        Self {
46            info: HostBackendInfo::new(
47                asio_backend_symbol(),
48                asio_transport_symbol(),
49                StreamMedia::Pcm,
50                true,
51            )
52            .with_capabilities(capabilities_for(&drivers, false)),
53            drivers,
54        }
55    }
56
57    /// Builds an offline backend exposing the single `SIM-ASIO` driver, marked
58    /// as fake so it can run under CI without the ASIO SDK or audio hardware.
59    pub fn fake() -> Self {
60        let drivers = vec![AsioDriver::sim_default().expect("valid fake ASIO driver")];
61        Self {
62            info: HostBackendInfo::new(
63                asio_backend_symbol(),
64                asio_transport_symbol(),
65                StreamMedia::Pcm,
66                false,
67            )
68            .with_capabilities(capabilities_for(&drivers, true)),
69            drivers,
70        }
71    }
72
73    /// Returns the drivers this backend was constructed with.
74    pub fn list_drivers(&self) -> &[AsioDriver] {
75        &self.drivers
76    }
77
78    /// Returns the bundled `SIM-ASIO` driver if it is present in the backend.
79    pub fn sim_driver(&self) -> Option<&AsioDriver> {
80        self.drivers
81            .iter()
82            .find(|driver| driver.name() == "SIM-ASIO")
83    }
84
85    /// Opens a duplex stream on the bundled `SIM-ASIO` driver with a bounded
86    /// buffer of `capacity` packets.
87    ///
88    /// Returns an error if the `SIM-ASIO` driver is not present in this backend.
89    pub fn open_sim_driver(&self, capacity: usize) -> Result<HostOpenStream> {
90        let driver = self
91            .sim_driver()
92            .ok_or_else(|| Error::Eval("SIM ASIO driver was not found".to_owned()))?;
93        self.open(request(driver, HostDirection::Duplex, capacity)?)
94    }
95
96    fn require_driver(&self, driver_id: &Symbol, direction: HostDirection) -> Result<&AsioDriver> {
97        let Some(driver) = self
98            .drivers
99            .iter()
100            .find(|candidate| candidate.id() == driver_id)
101        else {
102            return Err(Error::Eval(format!(
103                "ASIO driver {driver_id} was not found"
104            )));
105        };
106        if !driver.is_compatible_with(direction) {
107            return Err(Error::TypeMismatch {
108                expected: "ASIO driver with requested audio direction",
109                found: "ASIO driver with another audio direction",
110            });
111        }
112        Ok(driver)
113    }
114}
115
116impl HostBackend for AsioBackend {
117    fn info(&self) -> &HostBackendInfo {
118        &self.info
119    }
120
121    fn enumerate(&self) -> Result<HostDeviceInventory> {
122        let devices = self
123            .drivers
124            .iter()
125            .map(|driver| {
126                Ok(HostDeviceSpec::new(
127                    driver.id().clone(),
128                    asio_backend_symbol(),
129                    StreamMedia::Pcm,
130                    driver.direction(),
131                    asio_clock_symbol(),
132                    BufferPolicy::bounded(driver.timing().buffer_frames())?,
133                ))
134            })
135            .collect::<Result<Vec<_>>>()?;
136        let ports = self
137            .drivers
138            .iter()
139            .flat_map(AsioDriver::ports)
140            .map(|port| {
141                HostPortSpec::new(
142                    port.id().clone(),
143                    port.driver().clone(),
144                    asio_backend_symbol(),
145                    port.media(),
146                    port.direction(),
147                )
148            })
149            .collect();
150        Ok(HostDeviceInventory::new(asio_backend_symbol())
151            .with_devices(devices)
152            .with_ports(ports))
153    }
154
155    fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
156        if request.backend() != self.info.id() {
157            return Err(Error::Eval(format!(
158                "ASIO backend cannot open {} requests",
159                request.backend()
160            )));
161        }
162        if request.media() != StreamMedia::Pcm {
163            return Err(Error::TypeMismatch {
164                expected: "PCM stream request",
165                found: "non-PCM stream request",
166            });
167        }
168        let direction = request.direction();
169        let driver = self.require_driver(request.device(), direction)?;
170        let config = HostStreamConfig::from_request(
171            request,
172            latency_for(direction, driver.timing()),
173            HostClockInfo::new(
174                asio_clock_symbol(),
175                Some(driver.timing().sample_rate_hz()),
176                true,
177            ),
178        );
179        Ok(HostOpenStream::new(config))
180    }
181}
182
183fn request(
184    driver: &AsioDriver,
185    direction: HostDirection,
186    capacity: usize,
187) -> Result<HostStreamConfigRequest> {
188    Ok(HostStreamConfigRequest::new(
189        asio_backend_symbol(),
190        driver.id().clone(),
191        StreamMedia::Pcm,
192        direction,
193        BufferPolicy::bounded(capacity)?,
194    ))
195}
196
197fn capabilities_for(drivers: &[AsioDriver], fake: bool) -> Vec<HostBackendCapability> {
198    let mut capabilities = Vec::new();
199    if drivers.iter().any(|driver| driver.audio_inputs() > 0) {
200        capabilities.push(HostBackendCapability::AudioInput);
201    }
202    if drivers.iter().any(|driver| driver.audio_outputs() > 0) {
203        capabilities.push(HostBackendCapability::AudioOutput);
204    }
205    if drivers
206        .iter()
207        .any(|driver| driver.direction() == HostDirection::Duplex)
208    {
209        capabilities.push(HostBackendCapability::Duplex);
210    }
211    capabilities.push(HostBackendCapability::Reconnect);
212    if fake {
213        capabilities.push(HostBackendCapability::Offline);
214        capabilities.push(HostBackendCapability::Fake);
215    }
216    capabilities
217}
218
219fn latency_for(direction: HostDirection, timing: AsioTiming) -> HostLatencyInfo {
220    match direction {
221        HostDirection::Input => HostLatencyInfo::new(timing.input_latency_frames(), 0),
222        HostDirection::Output => HostLatencyInfo::new(0, timing.output_latency_frames()),
223        HostDirection::Duplex => HostLatencyInfo::new(
224            timing.input_latency_frames(),
225            timing.output_latency_frames(),
226        ),
227    }
228}