1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_stream_audio::PcmSpec;
3use sim_lib_stream_core::StreamMedia;
4use sim_lib_stream_host::HostDirection;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct AsioTiming {
9 sample_rate_hz: u32,
10 buffer_frames: usize,
11 input_latency_frames: u32,
12 output_latency_frames: u32,
13}
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct AsioDriver {
18 id: Symbol,
19 name: String,
20 timing: AsioTiming,
21 audio_inputs: usize,
22 audio_outputs: usize,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct AsioPort {
28 id: Symbol,
29 driver: Symbol,
30 name: String,
31 media: StreamMedia,
32 direction: HostDirection,
33 index: usize,
34}
35
36impl AsioTiming {
37 pub fn new(
42 sample_rate_hz: u32,
43 buffer_frames: usize,
44 input_latency_frames: u32,
45 output_latency_frames: u32,
46 ) -> Result<Self> {
47 if sample_rate_hz == 0 {
48 return Err(Error::Eval(
49 "ASIO sample rate must be greater than zero".to_owned(),
50 ));
51 }
52 if buffer_frames == 0 {
53 return Err(Error::Eval(
54 "ASIO buffer size must be greater than zero".to_owned(),
55 ));
56 }
57 Ok(Self {
58 sample_rate_hz,
59 buffer_frames,
60 input_latency_frames,
61 output_latency_frames,
62 })
63 }
64
65 pub fn pro_audio_default() -> Self {
68 Self::new(48_000, 128, 128, 128).expect("valid ASIO timing")
69 }
70
71 pub fn sample_rate_hz(self) -> u32 {
73 self.sample_rate_hz
74 }
75
76 pub fn buffer_frames(self) -> usize {
78 self.buffer_frames
79 }
80
81 pub fn input_latency_frames(self) -> u32 {
83 self.input_latency_frames
84 }
85
86 pub fn output_latency_frames(self) -> u32 {
88 self.output_latency_frames
89 }
90}
91
92impl AsioDriver {
93 pub fn new(
99 name: impl Into<String>,
100 timing: AsioTiming,
101 audio_inputs: usize,
102 audio_outputs: usize,
103 ) -> Result<Self> {
104 let name = name.into();
105 if name.is_empty() {
106 return Err(Error::Eval("ASIO driver name must not be empty".to_owned()));
107 }
108 if audio_inputs == 0 && audio_outputs == 0 {
109 return Err(Error::Eval(
110 "ASIO driver must expose at least one audio channel".to_owned(),
111 ));
112 }
113 Ok(Self {
114 id: Symbol::new(format!("asio/{name}/driver")),
115 name,
116 timing,
117 audio_inputs,
118 audio_outputs,
119 })
120 }
121
122 pub fn sim_default() -> Result<Self> {
125 Self::new("SIM-ASIO", AsioTiming::pro_audio_default(), 2, 2)
126 }
127
128 pub fn id(&self) -> &Symbol {
130 &self.id
131 }
132
133 pub fn name(&self) -> &str {
135 &self.name
136 }
137
138 pub fn timing(&self) -> AsioTiming {
140 self.timing
141 }
142
143 pub fn audio_inputs(&self) -> usize {
145 self.audio_inputs
146 }
147
148 pub fn audio_outputs(&self) -> usize {
150 self.audio_outputs
151 }
152
153 pub fn direction(&self) -> HostDirection {
156 match (self.audio_inputs > 0, self.audio_outputs > 0) {
157 (true, true) => HostDirection::Duplex,
158 (true, false) => HostDirection::Input,
159 (false, true) => HostDirection::Output,
160 (false, false) => HostDirection::Duplex,
161 }
162 }
163
164 pub fn is_compatible_with(&self, requested: HostDirection) -> bool {
167 self.direction() == requested || self.direction() == HostDirection::Duplex
168 }
169
170 pub fn output_spec(&self) -> Result<PcmSpec> {
173 PcmSpec::f32(self.audio_outputs.max(1), self.timing.sample_rate_hz)
174 }
175
176 pub fn ports(&self) -> Vec<AsioPort> {
180 let inputs = (0..self.audio_inputs).map(|index| {
181 AsioPort::new(
182 Symbol::new(format!("{}/input_{index}", self.id)),
183 self.id.clone(),
184 format!("input_{index}"),
185 HostDirection::Input,
186 index,
187 )
188 });
189 let outputs = (0..self.audio_outputs).map(|index| {
190 AsioPort::new(
191 Symbol::new(format!("{}/output_{index}", self.id)),
192 self.id.clone(),
193 format!("output_{index}"),
194 HostDirection::Output,
195 index,
196 )
197 });
198 inputs.chain(outputs).collect()
199 }
200}
201
202impl AsioPort {
203 pub fn new(
206 id: Symbol,
207 driver: Symbol,
208 name: String,
209 direction: HostDirection,
210 index: usize,
211 ) -> Self {
212 Self {
213 id,
214 driver,
215 name,
216 media: StreamMedia::Pcm,
217 direction,
218 index,
219 }
220 }
221
222 pub fn id(&self) -> &Symbol {
224 &self.id
225 }
226
227 pub fn driver(&self) -> &Symbol {
229 &self.driver
230 }
231
232 pub fn name(&self) -> &str {
234 &self.name
235 }
236
237 pub fn media(&self) -> StreamMedia {
239 self.media
240 }
241
242 pub fn direction(&self) -> HostDirection {
244 self.direction
245 }
246
247 pub fn index(&self) -> usize {
249 self.index
250 }
251}
252
253pub fn asio_sdk_build_requirements() -> Vec<&'static str> {
256 vec![
257 "Windows target",
258 "Steinberg ASIO SDK headers supplied outside this repository",
259 "vendor driver import library or COM registration",
260 "SIM stream-asio feature enabled explicitly",
261 ]
262}