#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
extern crate js_sys;
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
extern crate wasm_bindgen;
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
extern crate web_sys;
pub use device_description::{
DeviceDescription, DeviceDescriptionBuilder, DeviceDirection, DeviceType, InterfaceType,
};
pub use error::*;
pub use platform::{
available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream,
SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS,
};
pub use sample_format::{FromSample, Sample, SampleFormat, SizedSample, I24, U24};
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
use wasm_bindgen::prelude::*;
pub mod device_description;
mod error;
mod host;
pub mod platform;
mod sample_format;
mod timestamp;
pub mod traits;
pub type DevicesFiltered<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;
pub type InputDevices<I> = DevicesFiltered<I>;
pub type OutputDevices<I> = DevicesFiltered<I>;
pub type ChannelCount = u16;
pub type SampleRate = u32;
pub const SAMPLE_RATE_CD: SampleRate = 44_100;
pub const SAMPLE_RATE_48K: SampleRate = 48_000;
pub type FrameCount = u32;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DeviceId(crate::platform::HostId, Box<str>);
impl DeviceId {
pub fn new(host: crate::platform::HostId, id: impl AsRef<str>) -> Self {
Self(host, Box::from(id.as_ref()))
}
pub fn host(&self) -> crate::platform::HostId {
self.0
}
pub fn id(&self) -> &str {
&self.1
}
}
impl std::fmt::Display for DeviceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.host(), self.id())
}
}
impl std::str::FromStr for DeviceId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (host_str, device_str) = s.split_once(':').ok_or_else(|| {
Error::with_message(
ErrorKind::InvalidInput,
format!("failed to parse device ID \"{s}\": expected \"host:device_id\" format"),
)
})?;
let host_id = crate::platform::HostId::from_str(host_str)?;
Ok(Self::new(host_id, device_str))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum BufferSize {
#[default]
Default,
Fixed(FrameCount),
}
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
impl wasm_bindgen::describe::WasmDescribe for BufferSize {
fn describe() {
<Option<FrameCount> as wasm_bindgen::describe::WasmDescribe>::describe();
}
}
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
impl wasm_bindgen::convert::IntoWasmAbi for BufferSize {
type Abi = <Option<FrameCount> as wasm_bindgen::convert::IntoWasmAbi>::Abi;
fn into_abi(self) -> Self::Abi {
match self {
Self::Default => None,
Self::Fixed(fc) => Some(fc),
}
.into_abi()
}
}
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
))]
impl wasm_bindgen::convert::FromWasmAbi for BufferSize {
type Abi = <Option<FrameCount> as wasm_bindgen::convert::FromWasmAbi>::Abi;
unsafe fn from_abi(js: Self::Abi) -> Self {
match Option::<FrameCount>::from_abi(js) {
None => Self::Default,
Some(fc) => Self::Fixed(fc),
}
}
}
#[cfg_attr(
all(
target_arch = "wasm32",
target_os = "unknown",
feature = "wasm-bindgen"
),
wasm_bindgen
)]
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
pub struct StreamConfig {
pub channels: ChannelCount,
pub sample_rate: SampleRate,
pub buffer_size: BufferSize,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SupportedBufferSize {
Range {
min: FrameCount,
max: FrameCount,
},
#[default]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedStreamConfigRange {
pub(crate) channels: ChannelCount,
pub(crate) min_sample_rate: SampleRate,
pub(crate) max_sample_rate: SampleRate,
pub(crate) buffer_size: SupportedBufferSize,
pub(crate) sample_format: SampleFormat,
}
#[allow(dead_code)]
pub(crate) mod iter {
use super::SupportedStreamConfigRange;
pub type SupportedInputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedStreamConfig {
channels: ChannelCount,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
}
#[derive(Debug)]
pub struct Data {
data: *mut (),
len: usize,
sample_format: SampleFormat,
}
pub use timestamp::{
InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, OutputStreamTimestamp,
StreamInstant,
};
impl SupportedStreamConfig {
pub fn new(
channels: ChannelCount,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
) -> Self {
Self {
channels,
sample_rate,
buffer_size,
sample_format,
}
}
pub fn channels(&self) -> ChannelCount {
self.channels
}
pub fn sample_rate(&self) -> SampleRate {
self.sample_rate
}
pub fn buffer_size(&self) -> &SupportedBufferSize {
&self.buffer_size
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
pub fn config(&self) -> StreamConfig {
StreamConfig {
channels: self.channels,
sample_rate: self.sample_rate,
buffer_size: BufferSize::Default,
}
}
}
#[allow(clippy::len_without_is_empty)]
impl Data {
pub unsafe fn from_parts(data: *mut (), len: usize, sample_format: SampleFormat) -> Self {
Self {
data,
len,
sample_format,
}
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
pub fn len(&self) -> usize {
self.len
}
pub fn bytes(&self) -> &[u8] {
let len = self.len * self.sample_format.sample_size();
unsafe { std::slice::from_raw_parts(self.data as *const u8, len) }
}
pub fn bytes_mut(&mut self) -> &mut [u8] {
let len = self.len * self.sample_format.sample_size();
unsafe { std::slice::from_raw_parts_mut(self.data as *mut u8, len) }
}
pub fn as_slice<T>(&self) -> Option<&[T]>
where
T: SizedSample,
{
if T::FORMAT == self.sample_format {
unsafe { Some(std::slice::from_raw_parts(self.data as *const T, self.len)) }
} else {
None
}
}
pub fn as_slice_mut<T>(&mut self) -> Option<&mut [T]>
where
T: SizedSample,
{
if T::FORMAT == self.sample_format {
unsafe {
Some(std::slice::from_raw_parts_mut(
self.data as *mut T,
self.len,
))
}
} else {
None
}
}
}
impl SupportedStreamConfigRange {
pub fn new(
channels: ChannelCount,
min_sample_rate: SampleRate,
max_sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
) -> Self {
Self {
channels,
min_sample_rate,
max_sample_rate,
buffer_size,
sample_format,
}
}
pub fn channels(&self) -> ChannelCount {
self.channels
}
pub fn min_sample_rate(&self) -> SampleRate {
self.min_sample_rate
}
pub fn max_sample_rate(&self) -> SampleRate {
self.max_sample_rate
}
pub fn buffer_size(&self) -> &SupportedBufferSize {
&self.buffer_size
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedStreamConfig {
self.try_with_sample_rate(sample_rate)
.expect("sample rate out of range")
}
pub fn try_with_sample_rate(self, sample_rate: SampleRate) -> Option<SupportedStreamConfig> {
if self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate {
Some(SupportedStreamConfig {
channels: self.channels,
sample_rate,
sample_format: self.sample_format,
buffer_size: self.buffer_size,
})
} else {
None
}
}
#[inline]
pub fn with_max_sample_rate(self) -> SupportedStreamConfig {
SupportedStreamConfig {
channels: self.channels,
sample_rate: self.max_sample_rate,
sample_format: self.sample_format,
buffer_size: self.buffer_size,
}
}
pub fn contains_rate(&self, rate: SampleRate) -> bool {
self.min_sample_rate <= rate && rate <= self.max_sample_rate
}
pub fn try_with_standard_sample_rate(self) -> Option<SupportedStreamConfig> {
for rate in [SAMPLE_RATE_48K, SAMPLE_RATE_CD] {
if self.contains_rate(rate) {
return Some(self.with_sample_rate(rate));
}
}
None
}
pub fn with_standard_sample_rate(self) -> SupportedStreamConfig {
self.try_with_standard_sample_rate()
.expect("no standard sample rate (48000 or 44100 Hz) in supported range")
}
pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
let cmp_stereo = (self.channels == 2).cmp(&(other.channels == 2));
if cmp_stereo != Equal {
return cmp_stereo;
}
let cmp_mono = (self.channels == 1).cmp(&(other.channels == 1));
if cmp_mono != Equal {
return cmp_mono;
}
let cmp_channels = self.channels.cmp(&other.channels);
if cmp_channels != Equal {
return cmp_channels;
}
fn format_rank(fmt: SampleFormat) -> u8 {
match fmt {
SampleFormat::DsdU8 => 0,
SampleFormat::DsdU16 => 1,
SampleFormat::DsdU32 => 2,
SampleFormat::U8 => 3,
SampleFormat::I8 => 4,
SampleFormat::U64 => 5,
SampleFormat::I64 => 6,
SampleFormat::U16 => 7,
SampleFormat::I16 => 8,
SampleFormat::U24 => 9,
SampleFormat::I24 => 10,
SampleFormat::U32 => 11,
SampleFormat::I32 => 12,
SampleFormat::F64 => 13,
SampleFormat::F32 => 14,
}
}
let cmp_format = format_rank(self.sample_format).cmp(&format_rank(other.sample_format));
if cmp_format != Equal {
return cmp_format;
}
let cmp_broadcast = self
.contains_rate(SAMPLE_RATE_48K)
.cmp(&other.contains_rate(SAMPLE_RATE_48K));
if cmp_broadcast != Equal {
return cmp_broadcast;
}
let cmp_cd = self
.contains_rate(SAMPLE_RATE_CD)
.cmp(&other.contains_rate(SAMPLE_RATE_CD));
if cmp_cd != Equal {
return cmp_cd;
}
self.max_sample_rate.cmp(&other.max_sample_rate)
}
}
impl From<SupportedStreamConfig> for StreamConfig {
fn from(conf: SupportedStreamConfig) -> Self {
conf.config()
}
}
#[allow(dead_code)]
pub(crate) fn validate_stream_config(config: &StreamConfig) -> Result<(), Error> {
if config.channels == 0 {
return Err(Error::with_message(
ErrorKind::InvalidInput,
"channel count must be at least 1",
));
}
if config.sample_rate == 0 {
return Err(Error::with_message(
ErrorKind::InvalidInput,
"sample rate must be at least 1 Hz",
));
}
if config.buffer_size == BufferSize::Fixed(0) {
return Err(Error::with_message(
ErrorKind::InvalidInput,
"buffer size must be greater than 0",
));
}
Ok(())
}
#[allow(dead_code)]
pub(crate) const COMMON_SAMPLE_RATES: &[SampleRate] = &[
5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 176400,
192000, 352800, 384000, 705600, 768000, 1411200, 1536000, 2822400, 3072000, 5644800, 6144000,
11289600, 12288000, 22579200, 24576000,
];
#[cfg(test)]
mod tests {
use super::*;
fn make_range(
channels: ChannelCount,
format: SampleFormat,
min: SampleRate,
max: SampleRate,
) -> SupportedStreamConfigRange {
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels,
min_sample_rate: min,
max_sample_rate: max,
sample_format: format,
}
}
#[test]
fn with_standard_sample_rate() {
let r = |min, max| make_range(2, SampleFormat::F32, min, max);
assert_eq!(
r(1, 96_000).with_standard_sample_rate().sample_rate(),
SAMPLE_RATE_48K
);
assert_eq!(
r(1, 44_100).with_standard_sample_rate().sample_rate(),
SAMPLE_RATE_CD
);
}
#[test]
#[should_panic(expected = "no standard sample rate")]
fn with_standard_sample_rate_panics_when_no_standard_rate() {
make_range(2, SampleFormat::F32, 8_000, 32_000).with_standard_sample_rate();
}
#[test]
fn try_with_standard_sample_rate() {
let r = |min, max| make_range(2, SampleFormat::F32, min, max);
assert_eq!(
r(1, 96_000)
.try_with_standard_sample_rate()
.map(|c| c.sample_rate()),
Some(SAMPLE_RATE_48K)
);
assert_eq!(
r(1, 44_100)
.try_with_standard_sample_rate()
.map(|c| c.sample_rate()),
Some(SAMPLE_RATE_CD)
);
assert_eq!(r(8_000, 32_000).try_with_standard_sample_rate(), None);
}
#[test]
fn cmp_default_heuristics_format_order() {
use SampleFormat::*;
let unsorted = [
F32, I16, DsdU32, U64, I32, DsdU8, F64, U8, I24, U16, I64, U24, U32, I8, DsdU16,
];
let mut ranges: Vec<_> = unsorted
.iter()
.map(|&fmt| make_range(2, fmt, 1, 96_000))
.collect();
ranges.sort_by(|a, b| a.cmp_default_heuristics(b));
let sorted_formats: Vec<SampleFormat> = ranges.iter().map(|r| r.sample_format()).collect();
assert_eq!(
sorted_formats,
vec![DsdU8, DsdU16, DsdU32, U8, I8, U64, I64, U16, I16, U24, I24, U32, I32, F64, F32,]
);
}
#[test]
fn cmp_default_heuristics() {
let mut configs = [
make_range(1, SampleFormat::F64, 1, 96_000), make_range(2, SampleFormat::DsdU8, 1, 96_000), make_range(2, SampleFormat::U8, 1, 96_000),
make_range(2, SampleFormat::I8, 1, 96_000),
make_range(2, SampleFormat::U16, 1, 96_000),
make_range(2, SampleFormat::I16, 1, 96_000),
make_range(2, SampleFormat::F32, 1, 22_050), make_range(2, SampleFormat::F32, 1, 44_100), make_range(2, SampleFormat::F32, 48_000, 96_000), make_range(2, SampleFormat::F32, 1, 96_000), make_range(2, SampleFormat::F64, 1, 96_000),
];
configs.sort_by(|a, b| a.cmp_default_heuristics(b));
assert_eq!(configs[0].channels(), 1);
assert_eq!(configs[0].sample_format(), SampleFormat::F64);
assert_eq!(configs[1].sample_format(), SampleFormat::DsdU8);
assert_eq!(configs[2].sample_format(), SampleFormat::U8);
assert_eq!(configs[3].sample_format(), SampleFormat::I8);
assert_eq!(configs[4].sample_format(), SampleFormat::U16);
assert_eq!(configs[5].sample_format(), SampleFormat::I16);
assert_eq!(configs[6].sample_format(), SampleFormat::F64);
assert_eq!(configs[6].channels(), 2);
assert_eq!(configs[7].sample_format(), SampleFormat::F32);
assert_eq!(configs[7].max_sample_rate(), 22_050);
assert_eq!(configs[8].sample_format(), SampleFormat::F32);
assert_eq!(configs[8].max_sample_rate(), 44_100);
assert_eq!(configs[9].sample_format(), SampleFormat::F32);
assert_eq!(configs[9].min_sample_rate(), 48_000); assert_eq!(configs[9].max_sample_rate(), 96_000);
assert_eq!(configs[10].sample_format(), SampleFormat::F32);
assert_eq!(configs[10].min_sample_rate(), 1);
assert_eq!(configs[10].max_sample_rate(), 96_000); }
#[test]
fn validate_stream_config_rejects_zero_channels() {
let err = validate_stream_config(&StreamConfig {
channels: 0,
sample_rate: 44100,
buffer_size: BufferSize::Default,
})
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert!(err.message().unwrap().contains("channel"));
}
#[test]
fn validate_stream_config_rejects_zero_sample_rate() {
let err = validate_stream_config(&StreamConfig {
channels: 2,
sample_rate: 0,
buffer_size: BufferSize::Default,
})
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert!(err.message().unwrap().contains("sample rate"));
}
#[test]
fn validate_stream_config_rejects_fixed_buffer_zero() {
let err = validate_stream_config(&StreamConfig {
channels: 2,
sample_rate: 44100,
buffer_size: BufferSize::Fixed(0),
})
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert!(err.message().unwrap().contains("buffer size"));
}
#[test]
fn validate_stream_config_accepts_valid_configs() {
assert!(validate_stream_config(&StreamConfig {
channels: 2,
sample_rate: 44100,
buffer_size: BufferSize::Default,
})
.is_ok());
assert!(validate_stream_config(&StreamConfig {
channels: 1,
sample_rate: 1,
buffer_size: BufferSize::Fixed(1),
})
.is_ok());
}
}