extern crate alloc;
use crate::fourcc::FourCharCode;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct PropertySelector(pub FourCharCode);
impl PropertySelector {
#[inline]
#[must_use]
pub const fn new(code: [u8; 4]) -> Self {
Self(FourCharCode::new(code))
}
#[inline]
#[must_use]
pub const fn code(self) -> FourCharCode {
self.0
}
pub const BASE_CLASS: Self = Self::new(*b"bcls");
pub const CLASS: Self = Self::new(*b"clas");
pub const OWNER: Self = Self::new(*b"stdv");
pub const NAME: Self = Self::new(*b"lnam");
pub const MANUFACTURER: Self = Self::new(*b"lmak");
pub const OWNED_OBJECTS: Self = Self::new(*b"ownd");
pub const PLUGIN_BOX_LIST: Self = Self::new(*b"box#");
pub const PLUGIN_DEVICE_LIST: Self = Self::new(*b"dev#");
pub const PLUGIN_TRANSLATE_UID_TO_DEVICE: Self = Self::new(*b"uidd");
pub const PLUGIN_RESOURCE_BUNDLE: Self = Self::new(*b"rsrc");
pub const DEVICE_UID: Self = Self::new(*b"uid ");
pub const DEVICE_MODEL_UID: Self = Self::new(*b"muid");
pub const DEVICE_TRANSPORT_TYPE: Self = Self::new(*b"tran");
pub const DEVICE_IS_ALIVE: Self = Self::new(*b"livn");
pub const DEVICE_IS_RUNNING: Self = Self::new(*b"goin");
pub const DEVICE_STREAMS: Self = Self::new(*b"stm#");
pub const DEVICE_NOMINAL_SAMPLE_RATE: Self = Self::new(*b"nsrt");
pub const DEVICE_AVAILABLE_SAMPLE_RATES: Self = Self::new(*b"nsr#");
pub const DEVICE_LATENCY: Self = Self::new(*b"ltnc");
pub const DEVICE_SAFETY_OFFSET: Self = Self::new(*b"saft");
pub const DEVICE_ZERO_TIMESTAMP_PERIOD: Self = Self::new(*b"ring");
pub const STREAM_DIRECTION: Self = Self::new(*b"sdir");
pub const STREAM_TERMINAL_TYPE: Self = Self::new(*b"term");
pub const STREAM_STARTING_CHANNEL: Self = Self::new(*b"schn");
pub const STREAM_VIRTUAL_FORMAT: Self = Self::new(*b"sfmt");
pub const STREAM_PHYSICAL_FORMAT: Self = Self::new(*b"pft ");
pub const STREAM_AVAILABLE_VIRTUAL_FORMATS: Self = Self::new(*b"sfma");
}
impl From<FourCharCode> for PropertySelector {
#[inline]
fn from(code: FourCharCode) -> Self {
Self(code)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct PropertyScope(pub FourCharCode);
impl PropertyScope {
pub const GLOBAL: Self = Self(FourCharCode::new(*b"glob"));
pub const INPUT: Self = Self(FourCharCode::new(*b"inpt"));
pub const OUTPUT: Self = Self(FourCharCode::new(*b"outp"));
pub const PLAY_THROUGH: Self = Self(FourCharCode::new(*b"ptru"));
#[inline]
#[must_use]
pub const fn code(self) -> FourCharCode {
self.0
}
}
impl From<FourCharCode> for PropertyScope {
#[inline]
fn from(code: FourCharCode) -> Self {
Self(code)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[repr(transparent)]
pub struct PropertyElement(pub u32);
impl PropertyElement {
pub const MAIN: Self = Self(0);
#[inline]
#[must_use]
pub const fn channel(n: u32) -> Self {
Self(n)
}
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0
}
#[inline]
#[must_use]
pub const fn is_main(self) -> bool {
self.0 == 0
}
}
impl From<u32> for PropertyElement {
#[inline]
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct PropertyAddress {
pub selector: PropertySelector,
pub scope: PropertyScope,
pub element: PropertyElement,
}
impl PropertyAddress {
#[inline]
#[must_use]
pub const fn new(
selector: PropertySelector,
scope: PropertyScope,
element: PropertyElement,
) -> Self {
Self {
selector,
scope,
element,
}
}
#[inline]
#[must_use]
pub const fn global(selector: PropertySelector) -> Self {
Self::new(selector, PropertyScope::GLOBAL, PropertyElement::MAIN)
}
#[inline]
#[must_use]
pub fn is_object_wide(&self) -> bool {
self.scope == PropertyScope::GLOBAL && self.element.is_main()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ValueRange {
pub min: f64,
pub max: f64,
}
impl ValueRange {
pub const SIZE: usize = 16;
#[inline]
#[must_use]
pub const fn point(v: f64) -> Self {
Self { min: v, max: v }
}
#[inline]
#[must_use]
pub const fn new(min: f64, max: f64) -> Self {
Self { min, max }
}
#[inline]
#[must_use]
pub fn contains(&self, value: f64) -> bool {
value >= self.min && value <= self.max
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum PropertyValue {
U32(u32),
ObjectId(crate::object::AudioObjectId),
F64(f64),
Text(alloc::string::String),
ObjectList(alloc::vec::Vec<crate::object::AudioObjectId>),
Format(crate::format::StreamFormat),
RangeList(alloc::vec::Vec<ValueRange>),
}
impl PropertyValue {
#[must_use]
pub fn byte_size(&self) -> usize {
match self {
Self::U32(_) | Self::ObjectId(_) => core::mem::size_of::<u32>(),
Self::F64(_) => core::mem::size_of::<f64>(),
Self::Text(_) => core::mem::size_of::<*const ()>(),
Self::ObjectList(ids) => ids.len() * core::mem::size_of::<u32>(),
Self::Format(_) => crate::format::ASBD_SIZE,
Self::RangeList(ranges) => ranges.len() * ValueRange::SIZE,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::ObjectList(ids) => ids.is_empty(),
Self::RangeList(ranges) => ranges.is_empty(),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selector_constants_match_core_audio_codes() {
assert_eq!(
PropertySelector::DEVICE_UID.code(),
FourCharCode::new(*b"uid ")
);
assert_eq!(
PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE.code(),
FourCharCode::new(*b"nsrt")
);
assert_eq!(
PropertySelector::PLUGIN_DEVICE_LIST.code(),
FourCharCode::new(*b"dev#")
);
assert_eq!(
PropertySelector::STREAM_VIRTUAL_FORMAT.code(),
FourCharCode::new(*b"sfmt")
);
}
#[test]
fn scope_constants_match_core_audio_codes() {
assert_eq!(PropertyScope::GLOBAL.code(), FourCharCode::new(*b"glob"));
assert_eq!(PropertyScope::INPUT.code(), FourCharCode::new(*b"inpt"));
assert_eq!(PropertyScope::OUTPUT.code(), FourCharCode::new(*b"outp"));
}
#[test]
fn element_main_is_zero() {
assert_eq!(PropertyElement::MAIN.as_u32(), 0);
assert!(PropertyElement::MAIN.is_main());
assert!(!PropertyElement::channel(1).is_main());
assert_eq!(PropertyElement::channel(3).as_u32(), 3);
}
#[test]
fn global_address_is_object_wide() {
let addr = PropertyAddress::global(PropertySelector::NAME);
assert_eq!(addr.selector, PropertySelector::NAME);
assert_eq!(addr.scope, PropertyScope::GLOBAL);
assert_eq!(addr.element, PropertyElement::MAIN);
assert!(addr.is_object_wide());
}
#[test]
fn scoped_address_is_not_object_wide() {
let input = PropertyAddress::new(
PropertySelector::DEVICE_STREAMS,
PropertyScope::INPUT,
PropertyElement::MAIN,
);
assert!(!input.is_object_wide());
let channel = PropertyAddress::new(
PropertySelector::NAME,
PropertyScope::GLOBAL,
PropertyElement::channel(2),
);
assert!(!channel.is_object_wide());
}
#[test]
fn address_equality_is_structural() {
let a = PropertyAddress::global(PropertySelector::DEVICE_UID);
let b = PropertyAddress::new(
PropertySelector::DEVICE_UID,
PropertyScope::GLOBAL,
PropertyElement::MAIN,
);
assert_eq!(a, b);
let c = PropertyAddress::new(
PropertySelector::DEVICE_UID,
PropertyScope::INPUT,
PropertyElement::MAIN,
);
assert_ne!(a, c);
}
#[test]
fn selectors_built_from_fourcc_round_trip() {
let code = FourCharCode::new(*b"nsrt");
assert_eq!(
PropertySelector::from(code),
PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE
);
}
#[test]
fn newtype_layouts_are_transparent() {
use core::mem::size_of;
assert_eq!(size_of::<PropertySelector>(), size_of::<u32>());
assert_eq!(size_of::<PropertyScope>(), size_of::<u32>());
assert_eq!(size_of::<PropertyElement>(), size_of::<u32>());
}
#[test]
fn value_range_point_has_equal_bounds() {
let r = ValueRange::point(48_000.0);
assert_eq!(r.min, 48_000.0);
assert_eq!(r.max, 48_000.0);
assert!(r.contains(48_000.0));
assert!(!r.contains(44_100.0));
}
#[test]
fn value_range_contains_is_inclusive() {
let r = ValueRange::new(44_100.0, 96_000.0);
assert!(r.contains(44_100.0));
assert!(r.contains(96_000.0));
assert!(r.contains(48_000.0));
assert!(!r.contains(44_099.0));
assert!(!r.contains(96_001.0));
}
#[test]
fn property_value_byte_sizes_match_the_c_types() {
use crate::format::StreamFormat;
use crate::object::AudioObjectId;
assert_eq!(PropertyValue::U32(0).byte_size(), 4);
assert_eq!(
PropertyValue::ObjectId(AudioObjectId::PLUGIN).byte_size(),
4
);
assert_eq!(PropertyValue::F64(0.0).byte_size(), 8);
assert_eq!(
PropertyValue::Text("x".into()).byte_size(),
PropertyValue::Text("a much longer string".into()).byte_size()
);
assert_eq!(
PropertyValue::Text(alloc::string::String::new()).byte_size(),
core::mem::size_of::<*const ()>()
);
assert_eq!(
PropertyValue::ObjectList(alloc::vec![
AudioObjectId::from_u32(2),
AudioObjectId::from_u32(3)
])
.byte_size(),
8
);
assert_eq!(
PropertyValue::Format(StreamFormat::float32(48_000.0, 2)).byte_size(),
40
);
assert_eq!(
PropertyValue::RangeList(alloc::vec![ValueRange::point(48_000.0)]).byte_size(),
16
);
}
#[test]
fn property_value_is_empty_only_for_empty_lists() {
use crate::object::AudioObjectId;
assert!(PropertyValue::ObjectList(alloc::vec![]).is_empty());
assert!(PropertyValue::RangeList(alloc::vec![]).is_empty());
assert!(!PropertyValue::ObjectList(alloc::vec![AudioObjectId::PLUGIN]).is_empty());
assert!(!PropertyValue::U32(0).is_empty());
assert!(!PropertyValue::F64(0.0).is_empty());
assert!(!PropertyValue::Text(alloc::string::String::new()).is_empty());
}
}