sim_lib_stream_asio/
backend.rs1use 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
11pub fn asio_backend_symbol() -> Symbol {
13 Symbol::qualified("stream/host", "asio")
14}
15
16pub fn asio_transport_symbol() -> Symbol {
19 Symbol::qualified("stream/transport", "asio")
20}
21
22pub fn asio_clock_symbol() -> Symbol {
24 Symbol::qualified("clock", "asio")
25}
26
27#[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 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 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 pub fn list_drivers(&self) -> &[AsioDriver] {
75 &self.drivers
76 }
77
78 pub fn sim_driver(&self) -> Option<&AsioDriver> {
80 self.drivers
81 .iter()
82 .find(|driver| driver.name() == "SIM-ASIO")
83 }
84
85 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}