azo 0.3.0

Library for interacting with ASIO drivers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
use std::ffi::{CString, c_long, c_void};
use std::num::NonZeroI32;
use std::{mem, ptr};
use crate::{WinResult, dto, sys};
use crate::dto::Granularity;
use crate::future::AsioFuture;
use crate::utils::{cast_decoupled, create_result, cstring_from_bytes_until_nul};
use crate::win::{CLSCTX_SERVER, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize, HWND, S_FALSE};
use sys::IIASIORedecl;
use tap::Pipe;
use windows_core::{GUID, HSTRING, IUnknown};

#[cfg(feature = "host")]
pub use crate::host::{Proxy, ExfiltratedHandle};

pub trait Driver {
	/// The spec unfortunately does not elaborate on the purpose of the parameter.
	#[must_use]
	fn init(&self, window_handle: Option<HWND>) -> bool;

	/// Usually (but not necessarily) the same as [`DriverMetadata::description`].
	#[must_use]
	fn name(&self) -> CString;
	
	/// Intended to be the major ASIO version (`2` since the release of ASIO 2.0 in 1999),
	/// but technically allowed to be higher by spec, and many report their own (independent) version this way.
	#[must_use]
	fn version(&self) -> sys::DriverVersion;
	
	/// Retrieves a message associated with the recentmost error.
	#[must_use]
	fn last_error(&self) -> CString;
	
	/// Drivers typically invoke the [`buffer_switch`](sys::Callbacks::buffer_switch) / [`buffer_switch_time_info`](sys::Callbacks::buffer_switch_time_info)
	/// callback 1+ times during (or immediately after) this function call to prime the output buffer(s).
	fn start(&self) -> crate::Result<()>;
	
	/// Halts the streaming.<br>
	/// The driver remains ready to resume via [`.start()`](Self::start).
	fn stop(&self) -> crate::Result<()>;
	
	/// Returns the number of channels in each direction.
	fn channel_counts(&self) -> crate::Result<dto::ChannelCounts>;
	
	/// Accounts for buffer size, assuming [`BufferSize::preferred`](dto::BufferSize::preferred)
	/// when called before [`.create_buffers()`](Self::create_buffers).
	fn latencies(&self) -> crate::Result<dto::Latencies>;
	
	/// Retrieves buffer size(s) supported by the driver.<br>
	/// These can depend on the current sample rate.
	fn buffer_size(&self) -> crate::Result<dto::BufferSize>;
	
	/// Checks whether the specified `sample_rate` is supported.
	fn can_sample_rate(&self, sample_rate: sys::SampleRate) -> crate::Result<()>;
	
	/// Returns the current sample rate.
	fn get_sample_rate(&self) -> crate::Result<sys::SampleRate>;
		
	/// 0 = external sync
	fn set_sample_rate(&self, sample_rate: sys::SampleRate) -> crate::Result<()>;
	
	/// Retrieves a list of all clock sources available to this driver.
	fn clock_sources(&self) -> crate::Result<Vec<sys::ClockSource>>;
	
	/// Selects a [`ClockSource`](sys::ClockSource), as enumerated via [`.clock_sources()`](Self::clock_sources)
	fn set_clock_source(&self, clock_source: sys::ClockSourceIndex) -> crate::Result<()>;
	
	/// Tells the driver to open its GUI
	fn sample_position(&self) -> crate::Result<dto::SamplePosition>;

	fn channel_info(&self, channel_id: dto::ChannelId) -> crate::Result<dto::ChannelInfoResponse>;

	fn dispose_buffers(&self) -> crate::Result<()>;
	
	/// Tells the driver to open its GUI
	fn open_control_panel(&self) -> crate::Result<()>;
	
	/// Tells the driver that the host is done processing output buffers.
	/// 
	/// This is *not* implicitly inferred from the return of [`Callbacks::buffer_switch`] / [`Callbacks::buffer_switch_time_info`],
	/// because it might have been called by a thread that doesn't allow processing within the callback.
	/// 
	/// # Caveats
	/// Devices without hardware DSP and no further internal buffering
	/// have no use for this signal, so their drivers might not support it,
	/// and instead return [`ResultCode::NOT_PRESENT`].
	/// This is not fatal, it just means that calls to this function can (and should) be skipped.
	/// Take care not to "error out" unnecessarily in this case.
	fn output_ready(&self) -> crate::Result<()>;
	
	/// # Safety
	/// * `callbacks` must outlive the created buffers.
	/// * Derefs of the returned buffer pointers must not.
	/// # Remarks
	/// This function is kept C-style because providing safe abstractions for it
	/// is very difficult to do without getting highly opinionated. (Help wanted!)
	unsafe fn create_buffers(
		&self,
		channels: impl IntoIterator<Item=dto::ChannelId>,
		buffer_size: c_long,
		callbacks: *const sys::Callbacks
	) -> crate::Result<impl Iterator<Item=[*mut c_void; 2]>>;
	
	/// A very unfortunate name. 
	/// This function actually has nothing to do with async code,
	/// it merely provides a mechanism for extending ASIO in the future.
	fn future<T: AsioFuture>(&self, param: &mut T::Param) -> crate::Result<()>;
}

/// Metadata of an ASIO driver, retrieved from the system registry via [`get_drivers`]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Metadata {
	pub clsid: GUID,
	pub description: HSTRING,
}

impl Metadata {
	/// Gathers the metadata of all ASIO drivers currently registered in the system.
	/// Malformed keys are skipped.
	/// 
	/// This is the "starting point" of this library.
	pub fn enumerate() -> WinResult<Vec<Self>> {
		let software_key = windows_registry::LOCAL_MACHINE.open("SOFTWARE\\ASIO")?;
			
		let drivers =
			software_key
			.keys()?
			.filter_map(|driver_key_name| {
				let driver_key = software_key.open(&driver_key_name).ok()?;
				Self::from_registry(&driver_key).ok()
			})
			.collect();
		
		Ok(drivers)
	}
	
	fn from_registry(key: &windows_registry::Key) -> WinResult<Self> {
		let clsid =
			key
			.get_string("clsid")?
			.trim_matches(['{', '}'])
			.try_into()?;
		
		let description =
			key
			.get_hstring("description")?;
		
		Ok(Self { clsid, description })
	}
}

/// A safe, [`Clone`]able handle to a driver instance.
/// This type is ! [`Send`] because the driver instance lives in a single-threaded COM apartment.
/// [`crate::utils::Host`] provides the necessary machinery to get around this limitation.
#[derive(Debug, PartialEq, Eq)]
pub struct SafeHandle(UnsafeHandle);

impl Clone for SafeHandle {
	fn clone(&self) -> Self {
		// increment the ref count
		let hresult = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED as _) };
		assert_eq!(hresult, S_FALSE, "COM should be initialized as STA");
		
		Self(self.0.clone())
	}
}

impl SafeHandle {
	pub fn new(clsid: &GUID) -> WinResult<Self> {
		let hresult = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED as _) };
		if !hresult.is_ok() {
			return Err(hresult.into());
		}
		
		// SAFETY:
		// COM is now initialized, and gets uninitialized in the `Drop` implementation of `Self`
		unsafe { UnsafeHandle::new(clsid) }?
    	.pipe(Self)
		.pipe(Ok)
	}
	
	/// # Safety
	/// The caller must ensure that the returned handle and derivatives do not outlive the driver instance.
	#[must_use]
	pub const unsafe fn as_unsafe(&self) -> &UnsafeHandle {
		&self.0
	}
}

impl Drop for SafeHandle {
	fn drop(&mut self) {
		unsafe { CoUninitialize(); }
	}
}

// can't use Deref as that would provide access to its `Clone` implementation without ever having to commit to the API contract
impl Driver for SafeHandle {
	fn init              (&self, window_handle: Option<HWND>        ) -> bool                                    { self.0.init              (window_handle) }
	fn name              (&self                                     ) -> CString                                 { self.0.name              (             ) }
	fn version           (&self                                     ) -> sys::DriverVersion                      { self.0.version           (             ) }
	fn last_error        (&self                                     ) -> CString                                 { self.0.last_error        (             ) }
	fn start             (&self                                     ) -> crate::Result<()>                       { self.0.start             (             ) }
	fn stop              (&self                                     ) -> crate::Result<()>                       { self.0.stop              (             ) }
	fn channel_counts    (&self                                     ) -> crate::Result<dto::ChannelCounts>       { self.0.channel_counts    (             ) }
	fn latencies         (&self                                     ) -> crate::Result<dto::Latencies>           { self.0.latencies         (             ) }
	fn buffer_size       (&self                                     ) -> crate::Result<dto::BufferSize>          { self.0.buffer_size       (             ) }
	fn can_sample_rate   (&self, sample_rate: sys::SampleRate       ) -> crate::Result<()>                       { self.0.can_sample_rate   (sample_rate  ) }
	fn get_sample_rate   (&self                                     ) -> crate::Result<sys::SampleRate>          { self.0.get_sample_rate   (             ) }
	fn set_sample_rate   (&self, sample_rate: sys::SampleRate       ) -> crate::Result<()>                       { self.0.set_sample_rate   (sample_rate  ) }
	fn clock_sources     (&self                                     ) -> crate::Result<Vec<sys::ClockSource>>    { self.0.clock_sources     (             ) }
	fn set_clock_source  (&self, clock_source: sys::ClockSourceIndex) -> crate::Result<()>                       { self.0.set_clock_source  (clock_source ) }
	fn sample_position   (&self                                     ) -> crate::Result<dto::SamplePosition>      { self.0.sample_position   (             ) }
	fn channel_info      (&self, channel_id: dto::ChannelId         ) -> crate::Result<dto::ChannelInfoResponse> { self.0.channel_info      (channel_id   ) }
	fn dispose_buffers   (&self                                     ) -> crate::Result<()>                       { self.0.dispose_buffers   (             ) }
	fn open_control_panel(&self                                     ) -> crate::Result<()>                       { self.0.open_control_panel(             ) }
	fn output_ready      (&self                                     ) -> crate::Result<()>                       { self.0.output_ready      (             ) }
	
	unsafe fn create_buffers(
		&self,
		channels   : impl IntoIterator<Item=dto::ChannelId>,
		buffer_size: c_long,
		callbacks  : *const azo_sys::Callbacks
	) -> crate::Result<impl Iterator<Item=[*mut c_void; 2]>> {
		unsafe { self.0.create_buffers(channels, buffer_size, callbacks) }
	}
	
	fn future<T: AsioFuture>(&self, param: &mut T::Param) -> crate::Result<()> {
		self.0.future::<T>(param)
	}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnsafeHandle(pub IIASIORedecl);

impl UnsafeHandle {
	/// # Safety
	/// The caller must ensure that COM
	/// * is initialized on this thread
	/// * stays that way until this handle and all its clones got dropped
	pub unsafe fn new(guid: &GUID) -> WinResult<Self> {
		// Created as `IUnknown` because windows-rs binds this function in
		// a way where the IID is acquired from a trait-associated constant,
		// which is impossible to implement for `IIASIORedecl` (see its doc comment)
		let i_unknown: IUnknown = unsafe { CoCreateInstance(guid, None, CLSCTX_SERVER as _) }?;

		// The aforementioned binding limitation also applies to `.cast()`.
		// Luckily, the underlying `.query()` is public, which enables the following work-around:
		unsafe { cast_decoupled::<IIASIORedecl>(&i_unknown, guid) }
		.map(Self)
	}

	#[must_use]
	pub const fn from_raw(raw: IIASIORedecl) -> Self {
		Self(raw)
	}
}

impl Driver for UnsafeHandle {    
	fn init(&self, main_window_handle: Option<HWND>) -> bool {
		let sys_ref = main_window_handle.unwrap_or_default(); 

		unsafe { self.0.init(sys_ref.0) }
		.try_into()
		.unwrap_or(false)
	}

	fn name(&self) -> CString {
		let mut buf = [0_u8; 32];
		unsafe { self.0.get_driver_name(buf.as_mut_ptr()); }
		cstring_from_bytes_until_nul(&buf)
	}

	fn version(&self) -> sys::DriverVersion {
		unsafe { self.0.get_driver_version() }
	}

	fn last_error(&self) -> CString {
		let mut buf = [0_u8; 124];
		unsafe { self.0.get_error_message(buf.as_mut_ptr()); }
		cstring_from_bytes_until_nul(&buf)
	}
	
	fn start(&self) -> crate::Result<()> {
		let code = unsafe { self.0.start() };
		create_result((), code)
	}
	fn stop(&self) -> crate::Result<()> {
		let code = unsafe { self.0.stop() };
		create_result((), code)
	}

	fn channel_counts(&self) -> crate::Result<dto::ChannelCounts> {
		let mut counts = dto::ChannelCounts { in_: 0, out: 0 };
		let code = unsafe { self.0.get_channels(&raw mut counts.in_, &raw mut counts.out) };
		create_result(counts, code)
	}

	fn latencies(&self) -> crate::Result<dto::Latencies> {
		let mut latencies = dto::Latencies { in_: 0, out: 0 };
		let code = unsafe { self.0.get_latencies(&raw mut latencies.in_, &raw mut latencies.out) };
		create_result(latencies, code)
	}

	fn buffer_size(&self) -> crate::Result<dto::BufferSize> {
		let mut min         = -1;
		let mut max         = -2;
		let mut preferred   = -3;
		let mut granularity = -4;
		let code = unsafe { self.0.get_buffer_size(&raw mut min, &raw mut max, &raw mut preferred, &raw mut granularity) };
		create_result((), code)?;

		let buffer_size =
			dto::BufferSize {
				min,
				max,
				preferred,
				granularity: NonZeroI32::new(granularity).map(Granularity::from)
			};

		Ok(buffer_size)
	}

	fn can_sample_rate(&self, sample_rate: sys::SampleRate) -> crate::Result<()> {
		let code = unsafe { self.0.can_sample_rate(sample_rate) };
		create_result((), code)
	}
	
	fn get_sample_rate(&self) -> crate::Result<sys::SampleRate> {
		let mut sample_rate = f64::NAN;
		let code = unsafe { self.0.get_sample_rate(&raw mut sample_rate) };
		create_result(sample_rate, code)
	}

	fn set_sample_rate(&self, sample_rate: sys::SampleRate) -> crate::Result<()> {
		let code = unsafe { self.0.set_sample_rate(sample_rate) };
		create_result((), code)
	}

	#[expect(clippy::panic_in_result_fn, reason = "invalid driver behaviour")]
	fn clock_sources(&self) -> crate::Result<Vec<sys::ClockSource>> {
		let mut count = 1;
		let mut first = unsafe { mem::zeroed() };
		
		let code = unsafe { self.0.get_clock_sources(&raw mut first, &raw mut count) };
		create_result((), code)?;
	
		match count {
			0   => Ok(vec![]),
			1   => Ok(vec![first]),
			2.. => {
				let mut all = vec![unsafe { mem::zeroed() }; count as _];
				let code2 = unsafe { self.0.get_clock_sources(all.as_mut_ptr(), &raw mut count) };
				create_result(all, code2)
			}
			neg => panic!("driver reported negative number of clock sources ({neg})")
		}
	}

	/// Selects a [`ClockSource`](sys::ClockSource), as enumerated via [`.clock_sources()`](Self::clock_sources)
	fn set_clock_source(&self, clock_source: sys::ClockSourceIndex) -> crate::Result<()> {
		let code = unsafe { self.0.set_clock_source(clock_source) };
		create_result((), code)
	}

	fn sample_position(&self) -> crate::Result<dto::SamplePosition> {
		let mut position   = sys::Samples  ::default();
		let mut time_stamp = sys::TimeStamp::default();
		let code = unsafe { self.0.get_sample_position(&raw mut position, &raw mut time_stamp) };
		
		let out = dto::SamplePosition {
			position  : position  .into(),
			time_stamp: time_stamp.into()
		};

		create_result(out, code)
	}

	fn channel_info(&self, channel_id: dto::ChannelId) -> crate::Result<dto::ChannelInfoResponse> {
		let mut info =
			sys::ChannelInfo {
				channel: channel_id.index,
				is_input: channel_id.input.into(),
				..unsafe { mem::zeroed() }
			};
		let code = unsafe { self.0.get_channel_info(&raw mut info) };
		create_result(info.into(), code)
	}

	unsafe fn create_buffers(
		&self,
		channels: impl IntoIterator<Item=dto::ChannelId>,
		buffer_size: c_long,
		callbacks: *const sys::Callbacks
	) -> crate::Result<impl Iterator<Item=[*mut c_void; 2]>> {
		let mut infos =
			channels
			.into_iter()
			.map(|dto::ChannelId { input, index }|
				sys::BufferInfo {
				    is_input: input.into(),
				    channel_num: index,
				    buffers: [ptr::null_mut(); 2]
				}
			)
			.collect::<Vec<_>>();
		
		let code = unsafe { self.0.create_buffers(infos.as_mut_ptr(), infos.len() as _, buffer_size, callbacks.cast_mut()) };
		let buffers =
			infos
			.into_iter()
			.map(|info| info.buffers);

		create_result(buffers, code)
	}

	fn dispose_buffers(&self) -> crate::Result<()> {
		let code = unsafe { self.0.dispose_buffers() };
		create_result((), code)
	}

	fn open_control_panel(&self) -> crate::Result<()> {
		let code = unsafe { self.0.control_panel() };
		create_result((), code)
	}

	fn future<T: AsioFuture>(&self, param: &mut T::Param) -> crate::Result<()> {
		let selector = T::SELECTOR;
		let opt = ptr::from_mut(param).cast();
		
		let code = unsafe { self.0.future(selector, opt) };
		create_result((), code)
	}
	
	fn output_ready(&self) -> crate::Result<()> {
		let code = unsafe { self.0.output_ready() };
		create_result((), code)
	}
}