use crate::object::AudioObjectId;
use crate::stream::{StreamDirection, StreamSpec};
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[repr(transparent)]
pub struct DeviceId(pub AudioObjectId);
impl DeviceId {
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Self {
Self(AudioObjectId::from_u32(value))
}
#[inline]
#[must_use]
pub const fn object_id(self) -> AudioObjectId {
self.0
}
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0.as_u32()
}
}
impl From<AudioObjectId> for DeviceId {
#[inline]
fn from(value: AudioObjectId) -> Self {
Self(value)
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct DeviceSpec {
uid: &'static str,
name: &'static str,
manufacturer: &'static str,
sample_rate: f64,
input: Option<StreamSpec>,
output: Option<StreamSpec>,
}
impl DeviceSpec {
#[inline]
#[must_use]
pub const fn new(uid: &'static str, name: &'static str, manufacturer: &'static str) -> Self {
Self {
uid,
name,
manufacturer,
sample_rate: 48_000.0,
input: None,
output: None,
}
}
#[inline]
#[must_use]
pub const fn with_sample_rate(mut self, sample_rate: f64) -> Self {
self.sample_rate = sample_rate;
self
}
#[inline]
#[must_use]
pub const fn with_input(mut self, stream: StreamSpec) -> Self {
self.input = Some(stream);
self
}
#[inline]
#[must_use]
pub const fn with_output(mut self, stream: StreamSpec) -> Self {
self.output = Some(stream);
self
}
#[inline]
#[must_use]
pub const fn uid(&self) -> &'static str {
self.uid
}
#[inline]
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[inline]
#[must_use]
pub const fn manufacturer(&self) -> &'static str {
self.manufacturer
}
#[inline]
#[must_use]
pub const fn sample_rate(&self) -> f64 {
self.sample_rate
}
#[inline]
#[must_use]
pub const fn input(&self) -> Option<StreamSpec> {
self.input
}
#[inline]
#[must_use]
pub const fn output(&self) -> Option<StreamSpec> {
self.output
}
#[inline]
#[must_use]
pub const fn stream(&self, direction: StreamDirection) -> Option<StreamSpec> {
match direction {
StreamDirection::Input => self.input,
StreamDirection::Output => self.output,
}
}
#[inline]
#[must_use]
pub const fn stream_count(&self) -> usize {
self.input.is_some() as usize + self.output.is_some() as usize
}
#[inline]
#[must_use]
pub const fn is_loopback(&self) -> bool {
self.input.is_some() && self.output.is_some()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Device {
id: DeviceId,
spec: DeviceSpec,
}
impl Device {
#[inline]
#[must_use]
pub const fn new(id: DeviceId, spec: DeviceSpec) -> Self {
Self { id, spec }
}
#[inline]
#[must_use]
pub const fn id(&self) -> DeviceId {
self.id
}
#[inline]
#[must_use]
pub const fn spec(&self) -> &DeviceSpec {
&self.spec
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::StreamFormat;
fn loopback_spec() -> DeviceSpec {
DeviceSpec::new("com.example.loopback", "Example Loopback", "tympan-aspl")
.with_sample_rate(48_000.0)
.with_input(StreamSpec::input(StreamFormat::float32(48_000.0, 2)))
.with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)))
}
#[test]
fn device_id_round_trips() {
let id = DeviceId::from_u32(7);
assert_eq!(id.as_u32(), 7);
assert_eq!(id.object_id(), AudioObjectId::from_u32(7));
assert_eq!(DeviceId::from(AudioObjectId::from_u32(7)), id);
}
#[test]
fn new_device_starts_streamless_at_48k() {
let spec = DeviceSpec::new("uid", "name", "maker");
assert_eq!(spec.sample_rate(), 48_000.0);
assert_eq!(spec.stream_count(), 0);
assert!(spec.input().is_none());
assert!(spec.output().is_none());
assert!(!spec.is_loopback());
}
#[test]
fn identity_fields_round_trip() {
let spec = loopback_spec();
assert_eq!(spec.uid(), "com.example.loopback");
assert_eq!(spec.name(), "Example Loopback");
assert_eq!(spec.manufacturer(), "tympan-aspl");
}
#[test]
fn loopback_has_both_streams() {
let spec = loopback_spec();
assert_eq!(spec.stream_count(), 2);
assert!(spec.is_loopback());
assert_eq!(
spec.stream(StreamDirection::Input).unwrap().direction(),
StreamDirection::Input
);
assert_eq!(
spec.stream(StreamDirection::Output).unwrap().direction(),
StreamDirection::Output
);
}
#[test]
fn output_only_device_is_not_loopback() {
let spec = DeviceSpec::new("uid", "Speaker", "maker")
.with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)));
assert_eq!(spec.stream_count(), 1);
assert!(!spec.is_loopback());
assert!(spec.input().is_none());
assert!(spec.output().is_some());
}
#[test]
fn device_pairs_id_and_spec() {
let spec = loopback_spec();
let device = Device::new(DeviceId::from_u32(2), spec);
assert_eq!(device.id(), DeviceId::from_u32(2));
assert_eq!(device.spec().uid(), "com.example.loopback");
}
#[test]
fn with_setters_replace_streams() {
let spec = DeviceSpec::new("uid", "name", "maker")
.with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 1)))
.with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)));
assert_eq!(spec.output().unwrap().channels(), 2);
}
}