1extern crate alloc;
36
37use alloc::vec::Vec;
38
39use crate::device::DeviceSpec;
40use crate::object::{AudioObjectId, ObjectKind};
41use crate::property::PropertyScope;
42use crate::stream::{StreamDirection, StreamSpec};
43
44#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
46pub enum Object {
47 PlugIn,
49 Device,
51 Stream(StreamDirection),
53}
54
55impl Object {
56 #[inline]
59 #[must_use]
60 pub const fn kind(self) -> ObjectKind {
61 match self {
62 Self::PlugIn => ObjectKind::PlugIn,
63 Self::Device => ObjectKind::Device,
64 Self::Stream(_) => ObjectKind::Stream,
65 }
66 }
67}
68
69#[derive(Clone, PartialEq, Debug)]
78pub struct ObjectMap {
79 spec: DeviceSpec,
80 device_id: AudioObjectId,
81 input_stream_id: Option<AudioObjectId>,
82 output_stream_id: Option<AudioObjectId>,
83}
84
85impl ObjectMap {
86 #[must_use]
93 pub fn new(spec: DeviceSpec) -> Self {
94 let mut next = AudioObjectId::FIRST_DYNAMIC.as_u32();
95 let device_id = AudioObjectId::from_u32(next);
96 next += 1;
97
98 let input_stream_id = spec.input().map(|_| {
99 let id = AudioObjectId::from_u32(next);
100 next += 1;
101 id
102 });
103 let output_stream_id = spec.output().map(|_| AudioObjectId::from_u32(next));
104
105 Self {
106 spec,
107 device_id,
108 input_stream_id,
109 output_stream_id,
110 }
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn spec(&self) -> &DeviceSpec {
117 &self.spec
118 }
119
120 #[inline]
122 #[must_use]
123 pub const fn plugin_id(&self) -> AudioObjectId {
124 AudioObjectId::PLUGIN
125 }
126
127 #[inline]
129 #[must_use]
130 pub const fn device_id(&self) -> AudioObjectId {
131 self.device_id
132 }
133
134 #[inline]
137 #[must_use]
138 pub const fn stream_id(&self, direction: StreamDirection) -> Option<AudioObjectId> {
139 match direction {
140 StreamDirection::Input => self.input_stream_id,
141 StreamDirection::Output => self.output_stream_id,
142 }
143 }
144
145 #[inline]
148 #[must_use]
149 pub fn stream_spec(&self, direction: StreamDirection) -> Option<StreamSpec> {
150 self.spec.stream(direction)
151 }
152
153 #[must_use]
156 pub fn resolve(&self, id: AudioObjectId) -> Option<Object> {
157 if id == AudioObjectId::PLUGIN {
158 return Some(Object::PlugIn);
159 }
160 if id == self.device_id {
161 return Some(Object::Device);
162 }
163 if Some(id) == self.input_stream_id {
164 return Some(Object::Stream(StreamDirection::Input));
165 }
166 if Some(id) == self.output_stream_id {
167 return Some(Object::Stream(StreamDirection::Output));
168 }
169 None
170 }
171
172 #[inline]
174 #[must_use]
175 pub fn contains(&self, id: AudioObjectId) -> bool {
176 self.resolve(id).is_some()
177 }
178
179 #[must_use]
182 pub fn all_ids(&self) -> Vec<AudioObjectId> {
183 let mut ids = Vec::with_capacity(4);
184 ids.push(self.plugin_id());
185 ids.push(self.device_id);
186 ids.extend(self.input_stream_id);
187 ids.extend(self.output_stream_id);
188 ids
189 }
190
191 #[must_use]
204 pub fn owned_objects(&self, id: AudioObjectId, scope: PropertyScope) -> Vec<AudioObjectId> {
205 match self.resolve(id) {
206 Some(Object::PlugIn) => alloc::vec![self.device_id],
207 Some(Object::Device) => self.device_streams(scope),
208 Some(Object::Stream(_)) | None => Vec::new(),
209 }
210 }
211
212 #[must_use]
219 pub fn device_streams(&self, scope: PropertyScope) -> Vec<AudioObjectId> {
220 let mut ids = Vec::with_capacity(2);
221 let want_input = scope == PropertyScope::GLOBAL || scope == PropertyScope::INPUT;
222 let want_output = scope == PropertyScope::GLOBAL || scope == PropertyScope::OUTPUT;
223 if want_input {
224 ids.extend(self.input_stream_id);
225 }
226 if want_output {
227 ids.extend(self.output_stream_id);
228 }
229 ids
230 }
231
232 #[must_use]
237 pub fn owner_of(&self, id: AudioObjectId) -> Option<AudioObjectId> {
238 match self.resolve(id)? {
239 Object::PlugIn => Some(AudioObjectId::UNKNOWN),
240 Object::Device => Some(self.plugin_id()),
241 Object::Stream(_) => Some(self.device_id),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::format::StreamFormat;
250
251 fn loopback() -> DeviceSpec {
252 let format = StreamFormat::float32(48_000.0, 2);
253 DeviceSpec::new("com.example.loopback", "Loopback", "tympan-aspl")
254 .with_input(StreamSpec::input(format))
255 .with_output(StreamSpec::output(format))
256 }
257
258 fn output_only() -> DeviceSpec {
259 DeviceSpec::new("com.example.speaker", "Speaker", "tympan-aspl")
260 .with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)))
261 }
262
263 #[test]
264 fn loopback_assigns_ids_in_tree_order() {
265 let map = ObjectMap::new(loopback());
266 assert_eq!(map.plugin_id(), AudioObjectId::PLUGIN);
267 assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
268 assert_eq!(
269 map.stream_id(StreamDirection::Input),
270 Some(AudioObjectId::from_u32(3))
271 );
272 assert_eq!(
273 map.stream_id(StreamDirection::Output),
274 Some(AudioObjectId::from_u32(4))
275 );
276 }
277
278 #[test]
279 fn output_only_device_skips_the_input_id() {
280 let map = ObjectMap::new(output_only());
281 assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
282 assert_eq!(map.stream_id(StreamDirection::Input), None);
283 assert_eq!(
286 map.stream_id(StreamDirection::Output),
287 Some(AudioObjectId::from_u32(3))
288 );
289 }
290
291 #[test]
292 fn streamless_device_has_only_plugin_and_device() {
293 let map = ObjectMap::new(DeviceSpec::new("uid", "name", "maker"));
294 assert_eq!(map.stream_id(StreamDirection::Input), None);
295 assert_eq!(map.stream_id(StreamDirection::Output), None);
296 assert_eq!(
297 map.all_ids(),
298 alloc::vec![AudioObjectId::PLUGIN, AudioObjectId::from_u32(2)]
299 );
300 }
301
302 #[test]
303 fn resolve_maps_ids_back_to_objects() {
304 let map = ObjectMap::new(loopback());
305 assert_eq!(map.resolve(AudioObjectId::PLUGIN), Some(Object::PlugIn));
306 assert_eq!(
307 map.resolve(AudioObjectId::from_u32(2)),
308 Some(Object::Device)
309 );
310 assert_eq!(
311 map.resolve(AudioObjectId::from_u32(3)),
312 Some(Object::Stream(StreamDirection::Input))
313 );
314 assert_eq!(
315 map.resolve(AudioObjectId::from_u32(4)),
316 Some(Object::Stream(StreamDirection::Output))
317 );
318 assert_eq!(map.resolve(AudioObjectId::from_u32(99)), None);
319 assert_eq!(map.resolve(AudioObjectId::UNKNOWN), None);
320 }
321
322 #[test]
323 fn contains_matches_resolve() {
324 let map = ObjectMap::new(loopback());
325 for id in map.all_ids() {
326 assert!(map.contains(id));
327 }
328 assert!(!map.contains(AudioObjectId::from_u32(100)));
329 }
330
331 #[test]
332 fn object_kinds_follow_the_tree() {
333 assert_eq!(Object::PlugIn.kind(), ObjectKind::PlugIn);
334 assert_eq!(Object::Device.kind(), ObjectKind::Device);
335 assert_eq!(
336 Object::Stream(StreamDirection::Input).kind(),
337 ObjectKind::Stream
338 );
339 }
340
341 #[test]
342 fn all_ids_is_tree_ordered() {
343 let map = ObjectMap::new(loopback());
344 assert_eq!(
345 map.all_ids(),
346 alloc::vec![
347 AudioObjectId::PLUGIN,
348 AudioObjectId::from_u32(2),
349 AudioObjectId::from_u32(3),
350 AudioObjectId::from_u32(4),
351 ]
352 );
353 }
354
355 #[test]
356 fn plugin_owns_the_device() {
357 let map = ObjectMap::new(loopback());
358 assert_eq!(
359 map.owned_objects(AudioObjectId::PLUGIN, PropertyScope::GLOBAL),
360 alloc::vec![map.device_id()]
361 );
362 }
363
364 #[test]
365 fn device_owns_streams_filtered_by_scope() {
366 let map = ObjectMap::new(loopback());
367 let dev = map.device_id();
368 assert_eq!(
369 map.owned_objects(dev, PropertyScope::GLOBAL),
370 alloc::vec![AudioObjectId::from_u32(3), AudioObjectId::from_u32(4)]
371 );
372 assert_eq!(
373 map.owned_objects(dev, PropertyScope::INPUT),
374 alloc::vec![AudioObjectId::from_u32(3)]
375 );
376 assert_eq!(
377 map.owned_objects(dev, PropertyScope::OUTPUT),
378 alloc::vec![AudioObjectId::from_u32(4)]
379 );
380 }
381
382 #[test]
383 fn streams_own_nothing() {
384 let map = ObjectMap::new(loopback());
385 assert!(map
386 .owned_objects(AudioObjectId::from_u32(3), PropertyScope::GLOBAL)
387 .is_empty());
388 }
389
390 #[test]
391 fn device_streams_respects_a_one_sided_device() {
392 let map = ObjectMap::new(output_only());
393 assert_eq!(
394 map.device_streams(PropertyScope::GLOBAL),
395 alloc::vec![AudioObjectId::from_u32(3)]
396 );
397 assert!(map.device_streams(PropertyScope::INPUT).is_empty());
398 assert_eq!(
399 map.device_streams(PropertyScope::OUTPUT),
400 alloc::vec![AudioObjectId::from_u32(3)]
401 );
402 }
403
404 #[test]
405 fn owner_walks_up_the_tree() {
406 let map = ObjectMap::new(loopback());
407 assert_eq!(
408 map.owner_of(AudioObjectId::PLUGIN),
409 Some(AudioObjectId::UNKNOWN)
410 );
411 assert_eq!(map.owner_of(map.device_id()), Some(AudioObjectId::PLUGIN));
412 assert_eq!(
413 map.owner_of(AudioObjectId::from_u32(3)),
414 Some(map.device_id())
415 );
416 assert_eq!(map.owner_of(AudioObjectId::from_u32(99)), None);
417 }
418
419 #[test]
420 fn stream_spec_is_forwarded_from_the_device_spec() {
421 let map = ObjectMap::new(loopback());
422 assert_eq!(
423 map.stream_spec(StreamDirection::Input).unwrap().direction(),
424 StreamDirection::Input
425 );
426 assert_eq!(
427 ObjectMap::new(output_only()).stream_spec(StreamDirection::Input),
428 None
429 );
430 }
431}