extern crate alloc;
use alloc::vec::Vec;
use crate::device::DeviceSpec;
use crate::object::{AudioObjectId, ObjectKind};
use crate::property::PropertyScope;
use crate::stream::{StreamDirection, StreamSpec};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Object {
PlugIn,
Device,
Stream(StreamDirection),
}
impl Object {
#[inline]
#[must_use]
pub const fn kind(self) -> ObjectKind {
match self {
Self::PlugIn => ObjectKind::PlugIn,
Self::Device => ObjectKind::Device,
Self::Stream(_) => ObjectKind::Stream,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct ObjectMap {
spec: DeviceSpec,
device_id: AudioObjectId,
input_stream_id: Option<AudioObjectId>,
output_stream_id: Option<AudioObjectId>,
}
impl ObjectMap {
#[must_use]
pub fn new(spec: DeviceSpec) -> Self {
let mut next = AudioObjectId::FIRST_DYNAMIC.as_u32();
let device_id = AudioObjectId::from_u32(next);
next += 1;
let input_stream_id = spec.input().map(|_| {
let id = AudioObjectId::from_u32(next);
next += 1;
id
});
let output_stream_id = spec.output().map(|_| AudioObjectId::from_u32(next));
Self {
spec,
device_id,
input_stream_id,
output_stream_id,
}
}
#[inline]
#[must_use]
pub fn spec(&self) -> &DeviceSpec {
&self.spec
}
#[inline]
#[must_use]
pub const fn plugin_id(&self) -> AudioObjectId {
AudioObjectId::PLUGIN
}
#[inline]
#[must_use]
pub const fn device_id(&self) -> AudioObjectId {
self.device_id
}
#[inline]
#[must_use]
pub const fn stream_id(&self, direction: StreamDirection) -> Option<AudioObjectId> {
match direction {
StreamDirection::Input => self.input_stream_id,
StreamDirection::Output => self.output_stream_id,
}
}
#[inline]
#[must_use]
pub fn stream_spec(&self, direction: StreamDirection) -> Option<StreamSpec> {
self.spec.stream(direction)
}
#[must_use]
pub fn resolve(&self, id: AudioObjectId) -> Option<Object> {
if id == AudioObjectId::PLUGIN {
return Some(Object::PlugIn);
}
if id == self.device_id {
return Some(Object::Device);
}
if Some(id) == self.input_stream_id {
return Some(Object::Stream(StreamDirection::Input));
}
if Some(id) == self.output_stream_id {
return Some(Object::Stream(StreamDirection::Output));
}
None
}
#[inline]
#[must_use]
pub fn contains(&self, id: AudioObjectId) -> bool {
self.resolve(id).is_some()
}
#[must_use]
pub fn all_ids(&self) -> Vec<AudioObjectId> {
let mut ids = Vec::with_capacity(4);
ids.push(self.plugin_id());
ids.push(self.device_id);
ids.extend(self.input_stream_id);
ids.extend(self.output_stream_id);
ids
}
#[must_use]
pub fn owned_objects(&self, id: AudioObjectId, scope: PropertyScope) -> Vec<AudioObjectId> {
match self.resolve(id) {
Some(Object::PlugIn) => alloc::vec![self.device_id],
Some(Object::Device) => self.device_streams(scope),
Some(Object::Stream(_)) | None => Vec::new(),
}
}
#[must_use]
pub fn device_streams(&self, scope: PropertyScope) -> Vec<AudioObjectId> {
let mut ids = Vec::with_capacity(2);
let want_input = scope == PropertyScope::GLOBAL || scope == PropertyScope::INPUT;
let want_output = scope == PropertyScope::GLOBAL || scope == PropertyScope::OUTPUT;
if want_input {
ids.extend(self.input_stream_id);
}
if want_output {
ids.extend(self.output_stream_id);
}
ids
}
#[must_use]
pub fn owner_of(&self, id: AudioObjectId) -> Option<AudioObjectId> {
match self.resolve(id)? {
Object::PlugIn => Some(AudioObjectId::UNKNOWN),
Object::Device => Some(self.plugin_id()),
Object::Stream(_) => Some(self.device_id),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::StreamFormat;
fn loopback() -> DeviceSpec {
let format = StreamFormat::float32(48_000.0, 2);
DeviceSpec::new("com.example.loopback", "Loopback", "tympan-aspl")
.with_input(StreamSpec::input(format))
.with_output(StreamSpec::output(format))
}
fn output_only() -> DeviceSpec {
DeviceSpec::new("com.example.speaker", "Speaker", "tympan-aspl")
.with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)))
}
#[test]
fn loopback_assigns_ids_in_tree_order() {
let map = ObjectMap::new(loopback());
assert_eq!(map.plugin_id(), AudioObjectId::PLUGIN);
assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
assert_eq!(
map.stream_id(StreamDirection::Input),
Some(AudioObjectId::from_u32(3))
);
assert_eq!(
map.stream_id(StreamDirection::Output),
Some(AudioObjectId::from_u32(4))
);
}
#[test]
fn output_only_device_skips_the_input_id() {
let map = ObjectMap::new(output_only());
assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
assert_eq!(map.stream_id(StreamDirection::Input), None);
assert_eq!(
map.stream_id(StreamDirection::Output),
Some(AudioObjectId::from_u32(3))
);
}
#[test]
fn streamless_device_has_only_plugin_and_device() {
let map = ObjectMap::new(DeviceSpec::new("uid", "name", "maker"));
assert_eq!(map.stream_id(StreamDirection::Input), None);
assert_eq!(map.stream_id(StreamDirection::Output), None);
assert_eq!(
map.all_ids(),
alloc::vec![AudioObjectId::PLUGIN, AudioObjectId::from_u32(2)]
);
}
#[test]
fn resolve_maps_ids_back_to_objects() {
let map = ObjectMap::new(loopback());
assert_eq!(map.resolve(AudioObjectId::PLUGIN), Some(Object::PlugIn));
assert_eq!(
map.resolve(AudioObjectId::from_u32(2)),
Some(Object::Device)
);
assert_eq!(
map.resolve(AudioObjectId::from_u32(3)),
Some(Object::Stream(StreamDirection::Input))
);
assert_eq!(
map.resolve(AudioObjectId::from_u32(4)),
Some(Object::Stream(StreamDirection::Output))
);
assert_eq!(map.resolve(AudioObjectId::from_u32(99)), None);
assert_eq!(map.resolve(AudioObjectId::UNKNOWN), None);
}
#[test]
fn contains_matches_resolve() {
let map = ObjectMap::new(loopback());
for id in map.all_ids() {
assert!(map.contains(id));
}
assert!(!map.contains(AudioObjectId::from_u32(100)));
}
#[test]
fn object_kinds_follow_the_tree() {
assert_eq!(Object::PlugIn.kind(), ObjectKind::PlugIn);
assert_eq!(Object::Device.kind(), ObjectKind::Device);
assert_eq!(
Object::Stream(StreamDirection::Input).kind(),
ObjectKind::Stream
);
}
#[test]
fn all_ids_is_tree_ordered() {
let map = ObjectMap::new(loopback());
assert_eq!(
map.all_ids(),
alloc::vec![
AudioObjectId::PLUGIN,
AudioObjectId::from_u32(2),
AudioObjectId::from_u32(3),
AudioObjectId::from_u32(4),
]
);
}
#[test]
fn plugin_owns_the_device() {
let map = ObjectMap::new(loopback());
assert_eq!(
map.owned_objects(AudioObjectId::PLUGIN, PropertyScope::GLOBAL),
alloc::vec![map.device_id()]
);
}
#[test]
fn device_owns_streams_filtered_by_scope() {
let map = ObjectMap::new(loopback());
let dev = map.device_id();
assert_eq!(
map.owned_objects(dev, PropertyScope::GLOBAL),
alloc::vec![AudioObjectId::from_u32(3), AudioObjectId::from_u32(4)]
);
assert_eq!(
map.owned_objects(dev, PropertyScope::INPUT),
alloc::vec![AudioObjectId::from_u32(3)]
);
assert_eq!(
map.owned_objects(dev, PropertyScope::OUTPUT),
alloc::vec![AudioObjectId::from_u32(4)]
);
}
#[test]
fn streams_own_nothing() {
let map = ObjectMap::new(loopback());
assert!(map
.owned_objects(AudioObjectId::from_u32(3), PropertyScope::GLOBAL)
.is_empty());
}
#[test]
fn device_streams_respects_a_one_sided_device() {
let map = ObjectMap::new(output_only());
assert_eq!(
map.device_streams(PropertyScope::GLOBAL),
alloc::vec![AudioObjectId::from_u32(3)]
);
assert!(map.device_streams(PropertyScope::INPUT).is_empty());
assert_eq!(
map.device_streams(PropertyScope::OUTPUT),
alloc::vec![AudioObjectId::from_u32(3)]
);
}
#[test]
fn owner_walks_up_the_tree() {
let map = ObjectMap::new(loopback());
assert_eq!(
map.owner_of(AudioObjectId::PLUGIN),
Some(AudioObjectId::UNKNOWN)
);
assert_eq!(map.owner_of(map.device_id()), Some(AudioObjectId::PLUGIN));
assert_eq!(
map.owner_of(AudioObjectId::from_u32(3)),
Some(map.device_id())
);
assert_eq!(map.owner_of(AudioObjectId::from_u32(99)), None);
}
#[test]
fn stream_spec_is_forwarded_from_the_device_spec() {
let map = ObjectMap::new(loopback());
assert_eq!(
map.stream_spec(StreamDirection::Input).unwrap().direction(),
StreamDirection::Input
);
assert_eq!(
ObjectMap::new(output_only()).stream_spec(StreamDirection::Input),
None
);
}
}