#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(all(
target_arch = "wasm32",
any(target_os = "emscripten", feature = "wasm-bindgen")
))]
extern crate js_sys;
#[cfg(all(
target_arch = "wasm32",
any(target_os = "emscripten", feature = "wasm-bindgen")
))]
extern crate wasm_bindgen;
#[cfg(all(
target_arch = "wasm32",
any(target_os = "emscripten", feature = "wasm-bindgen")
))]
extern crate web_sys;
#[cfg(all(
target_arch = "wasm32",
any(target_os = "emscripten", feature = "wasm-bindgen")
))]
use wasm_bindgen::prelude::*;
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 samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24};
use std::convert::TryInto;
use std::time::Duration;
pub mod device_description;
mod error;
mod host;
pub mod platform;
mod samples_formats;
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 type FrameCount = u32;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DeviceId(pub crate::platform::HostId, pub String);
impl std::fmt::Display for DeviceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.0, self.1)
}
}
impl std::str::FromStr for DeviceId {
type Err = DeviceIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (host_str, device_str) = s.split_once(':').ok_or(DeviceIdError::BackendSpecific {
err: BackendSpecificError {
description: format!(
"Failed to parse device ID from: {s}\nCheck if format matches \"host:device_id\""
),
},
})?;
let host_id = crate::platform::HostId::from_str(host_str)
.map_err(|_| DeviceIdError::UnsupportedPlatform)?;
Ok(DeviceId(host_id, device_str.to_string()))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BufferSize {
Default,
Fixed(FrameCount),
}
#[cfg(all(
target_arch = "wasm32",
any(target_os = "emscripten", 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",
any(target_os = "emscripten", 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",
any(target_os = "emscripten", 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",
any(target_os = "emscripten", feature = "wasm-bindgen")
),
wasm_bindgen
)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StreamConfig {
pub channels: ChannelCount,
pub sample_rate: SampleRate,
pub buffer_size: BufferSize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SupportedBufferSize {
Range {
min: FrameCount,
max: FrameCount,
},
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, PartialEq, Eq)]
pub struct SupportedStreamConfig {
channels: ChannelCount,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
}
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Debug)]
pub struct Data {
data: *mut (),
len: usize,
sample_format: SampleFormat,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct StreamInstant {
secs: i64,
nanos: u32,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct InputStreamTimestamp {
pub callback: StreamInstant,
pub capture: StreamInstant,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct OutputStreamTimestamp {
pub callback: StreamInstant,
pub playback: StreamInstant,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct InputCallbackInfo {
timestamp: InputStreamTimestamp,
}
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct OutputCallbackInfo {
timestamp: OutputStreamTimestamp,
}
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,
}
}
}
impl StreamInstant {
pub fn duration_since(&self, earlier: &Self) -> Option<Duration> {
if self < earlier {
None
} else {
(self.as_nanos() - earlier.as_nanos())
.try_into()
.ok()
.map(Duration::from_nanos)
}
}
pub fn add(&self, duration: Duration) -> Option<Self> {
self.as_nanos()
.checked_add(duration.as_nanos() as i128)
.and_then(Self::from_nanos_i128)
}
pub fn sub(&self, duration: Duration) -> Option<Self> {
self.as_nanos()
.checked_sub(duration.as_nanos() as i128)
.and_then(Self::from_nanos_i128)
}
fn as_nanos(&self) -> i128 {
(self.secs as i128 * 1_000_000_000) + self.nanos as i128
}
#[allow(dead_code)]
fn from_nanos(nanos: i64) -> Self {
let secs = nanos / 1_000_000_000;
let subsec_nanos = nanos - secs * 1_000_000_000;
Self::new(secs, subsec_nanos as u32)
}
#[allow(dead_code)]
fn from_nanos_i128(nanos: i128) -> Option<Self> {
let secs = nanos / 1_000_000_000;
if secs > i64::MAX as i128 || secs < i64::MIN as i128 {
None
} else {
let subsec_nanos = nanos - secs * 1_000_000_000;
debug_assert!(subsec_nanos < u32::MAX as i128);
Some(Self::new(secs as i64, subsec_nanos as u32))
}
}
#[allow(dead_code)]
fn from_secs_f64(secs: f64) -> crate::StreamInstant {
let s = secs.floor() as i64;
let ns = ((secs - s as f64) * 1_000_000_000.0) as u32;
Self::new(s, ns)
}
pub fn new(secs: i64, nanos: u32) -> Self {
StreamInstant { secs, nanos }
}
}
impl InputCallbackInfo {
pub fn new(timestamp: InputStreamTimestamp) -> Self {
Self { timestamp }
}
pub fn timestamp(&self) -> InputStreamTimestamp {
self.timestamp
}
}
impl OutputCallbackInfo {
pub fn new(timestamp: OutputStreamTimestamp) -> Self {
Self { timestamp }
}
pub fn timestamp(&self) -> OutputStreamTimestamp {
self.timestamp
}
}
#[allow(clippy::len_without_is_empty)]
impl Data {
pub unsafe fn from_parts(data: *mut (), len: usize, sample_format: SampleFormat) -> Self {
Data {
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 cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
use SampleFormat::{F32, I16, I24, I32, U16, U24, U32};
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;
}
let cmp_f32 = (self.sample_format == F32).cmp(&(other.sample_format == F32));
if cmp_f32 != Equal {
return cmp_f32;
}
let cmp_i32 = (self.sample_format == I32).cmp(&(other.sample_format == I32));
if cmp_i32 != Equal {
return cmp_i32;
}
let cmp_u32 = (self.sample_format == U32).cmp(&(other.sample_format == U32));
if cmp_u32 != Equal {
return cmp_u32;
}
let cmp_i24 = (self.sample_format == I24).cmp(&(other.sample_format == I24));
if cmp_i24 != Equal {
return cmp_i24;
}
let cmp_u24 = (self.sample_format == U24).cmp(&(other.sample_format == U24));
if cmp_u24 != Equal {
return cmp_u24;
}
let cmp_i16 = (self.sample_format == I16).cmp(&(other.sample_format == I16));
if cmp_i16 != Equal {
return cmp_i16;
}
let cmp_u16 = (self.sample_format == U16).cmp(&(other.sample_format == U16));
if cmp_u16 != Equal {
return cmp_u16;
}
const HZ_44100: SampleRate = 44_100;
let r44100_in_self = self.min_sample_rate <= HZ_44100 && HZ_44100 <= self.max_sample_rate;
let r44100_in_other =
other.min_sample_rate <= HZ_44100 && HZ_44100 <= other.max_sample_rate;
let cmp_r44100 = r44100_in_self.cmp(&r44100_in_other);
if cmp_r44100 != Equal {
return cmp_r44100;
}
self.max_sample_rate.cmp(&other.max_sample_rate)
}
}
#[test]
fn test_cmp_default_heuristics() {
let mut formats = [
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: 1,
max_sample_rate: 96000,
sample_format: SampleFormat::F32,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 1,
min_sample_rate: 1,
max_sample_rate: 96000,
sample_format: SampleFormat::F32,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: 1,
max_sample_rate: 96000,
sample_format: SampleFormat::I16,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: 1,
max_sample_rate: 96000,
sample_format: SampleFormat::U16,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: 1,
max_sample_rate: 22050,
sample_format: SampleFormat::F32,
},
];
formats.sort_by(|a, b| a.cmp_default_heuristics(b));
assert_eq!(formats[0].sample_format(), SampleFormat::F32);
assert_eq!(formats[0].min_sample_rate(), 1);
assert_eq!(formats[0].max_sample_rate(), 96000);
assert_eq!(formats[0].channels(), 1);
assert_eq!(formats[1].sample_format(), SampleFormat::U16);
assert_eq!(formats[1].min_sample_rate(), 1);
assert_eq!(formats[1].max_sample_rate(), 96000);
assert_eq!(formats[1].channels(), 2);
assert_eq!(formats[2].sample_format(), SampleFormat::I16);
assert_eq!(formats[2].min_sample_rate(), 1);
assert_eq!(formats[2].max_sample_rate(), 96000);
assert_eq!(formats[2].channels(), 2);
assert_eq!(formats[3].sample_format(), SampleFormat::F32);
assert_eq!(formats[3].min_sample_rate(), 1);
assert_eq!(formats[3].max_sample_rate(), 22050);
assert_eq!(formats[3].channels(), 2);
assert_eq!(formats[4].sample_format(), SampleFormat::F32);
assert_eq!(formats[4].min_sample_rate(), 1);
assert_eq!(formats[4].max_sample_rate(), 96000);
assert_eq!(formats[4].channels(), 2);
}
impl From<SupportedStreamConfig> for StreamConfig {
fn from(conf: SupportedStreamConfig) -> Self {
conf.config()
}
}
#[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,
];
#[test]
fn test_stream_instant() {
let a = StreamInstant::new(2, 0);
let b = StreamInstant::new(-2, 0);
let min = StreamInstant::new(i64::MIN, 0);
let max = StreamInstant::new(i64::MAX, 0);
assert_eq!(
a.sub(Duration::from_secs(1)),
Some(StreamInstant::new(1, 0))
);
assert_eq!(
a.sub(Duration::from_secs(2)),
Some(StreamInstant::new(0, 0))
);
assert_eq!(
a.sub(Duration::from_secs(3)),
Some(StreamInstant::new(-1, 0))
);
assert_eq!(min.sub(Duration::from_secs(1)), None);
assert_eq!(
b.add(Duration::from_secs(1)),
Some(StreamInstant::new(-1, 0))
);
assert_eq!(
b.add(Duration::from_secs(2)),
Some(StreamInstant::new(0, 0))
);
assert_eq!(
b.add(Duration::from_secs(3)),
Some(StreamInstant::new(1, 0))
);
assert_eq!(max.add(Duration::from_secs(1)), None);
}