From ac047d27aa087701a61c0b833800a5485b283204 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Fri, 26 Jul 2024 14:45:28 +0200 Subject: [PATCH 1/9] feat(backends): coreaudio initial impl --- Cargo.toml | 3 + build.rs | 3 +- examples/enumerate_coreaudio.rs | 9 ++ examples/util/enumerate.rs | 7 +- src/audio_buffer.rs | 9 +- src/backends/alsa.rs | 8 +- src/backends/coreaudio.rs | 175 ++++++++++++++++++++++++++++++++ src/backends/mod.rs | 3 + src/channel_map.rs | 16 +-- 9 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 examples/enumerate_coreaudio.rs create mode 100644 src/backends/coreaudio.rs diff --git a/Cargo.toml b/Cargo.toml index 175a5d6..3ae5277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ cfg_aliases = "0.2.1" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies] alsa = "0.9.0" +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +coreaudio-rs = "0.12.0" + [[example]] name = "enumerate_alsa" path = "examples/enumerate_alsa.rs" diff --git a/build.rs b/build.rs index abfd10d..c248868 100644 --- a/build.rs +++ b/build.rs @@ -5,6 +5,7 @@ fn main() { cfg_aliases! { wasm: { any(target_os = "wasm32") }, os_alsa: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", - target_os = "netbsd") } + target_os = "netbsd") }, + os_coreaudio: { any (target_os = "macos", target_os = "ios") } } } diff --git a/examples/enumerate_coreaudio.rs b/examples/enumerate_coreaudio.rs new file mode 100644 index 0000000..364dcd9 --- /dev/null +++ b/examples/enumerate_coreaudio.rs @@ -0,0 +1,9 @@ +use interflow::backends::coreaudio::CoreAudioDriver; +use interflow::{AudioDevice, AudioDriver}; +use std::error::Error; + +mod enumerate; + +fn main() -> Result<(), Box> { + enumerate::enumerate_devices(CoreAudioDriver) +} diff --git a/examples/util/enumerate.rs b/examples/util/enumerate.rs index b888927..4805b35 100644 --- a/examples/util/enumerate.rs +++ b/examples/util/enumerate.rs @@ -1,7 +1,10 @@ use interflow::{AudioDevice, AudioDriver, DeviceType}; use std::error::Error; -pub fn enumerate_devices(driver: Driver) -> Result<(), Box> where ::Error: 'static { +pub fn enumerate_devices(driver: Driver) -> Result<(), Box> +where + ::Error: 'static, +{ eprintln!("Driver name : {}", Driver::DISPLAY_NAME); eprintln!("Driver version: {}", driver.version()?); eprintln!("Default device"); @@ -19,4 +22,4 @@ pub fn enumerate_devices(driver: Driver) -> Result<(), Box< eprintln!("\t{} ({:?})", device.name(), device.device_type()); } Ok(()) -} \ No newline at end of file +} diff --git a/src/audio_buffer.rs b/src/audio_buffer.rs index b0228f8..e78d5d7 100644 --- a/src/audio_buffer.rs +++ b/src/audio_buffer.rs @@ -54,7 +54,7 @@ impl Default for AudioBufferBase { } } -impl> PartialEq> for AudioBufferBase +impl> PartialEq> for AudioBufferBase where S::Elem: PartialEq, { @@ -374,11 +374,8 @@ impl<'a, T: Sample> AudioMut<'a, T> { } } - pub fn mix( - &mut self, - other: AudioRef, - other_amplitude: T::Float, - ) where + pub fn mix(&mut self, other: AudioRef, other_amplitude: T::Float) + where T: AddAssign, { for (mut ch_a, ch_b) in self.channels_mut().zip(other.channels()) { diff --git a/src/backends/alsa.rs b/src/backends/alsa.rs index 0c694b8..2fb51e5 100644 --- a/src/backends/alsa.rs +++ b/src/backends/alsa.rs @@ -5,7 +5,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::thread::JoinHandle; use std::time::Duration; -use alsa::{device_name::HintIter, pcm, PCM}; +use alsa::{ + device_name::HintIter, + pcm, + Direction, PCM, +}; use thiserror::Error; use crate::{ @@ -236,7 +240,7 @@ impl AlsaStream { let input = AudioInput { buffer, timestamp }; callback.on_input_data(context, input); timestamp += frames as u64; - + match device.pcm.state() { pcm::State::Suspended => { if hwp.can_resume() { diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs new file mode 100644 index 0000000..2b9bff8 --- /dev/null +++ b/src/backends/coreaudio.rs @@ -0,0 +1,175 @@ +use std::borrow::Cow; + +use coreaudio::audio_unit::audio_format::LinearPcmFlags; +use coreaudio::audio_unit::macos_helpers::{ + audio_unit_from_device_id, find_matching_physical_format, get_audio_device_ids_for_scope, + get_default_device_id, get_device_name, get_supported_physical_stream_formats, +}; +use coreaudio::audio_unit::{AudioUnit, SampleFormat, Scope, StreamFormat}; +use coreaudio::sys::AudioDeviceID; +use thiserror::Error; + +use crate::channel_map::Bitset; +use crate::{AudioDevice, AudioDriver, Channel, DeviceType, StreamConfig}; + +#[derive(Debug, Error)] +#[error("CoreAudio error:")] +pub enum CoreAudioError { + #[error("{0}")] + BackendError(#[from] coreaudio::Error), + #[error("Invalid scope {0:?}")] + InvalidScope(Scope), +} + +#[derive(Debug, Copy, Clone)] +pub struct CoreAudioDriver; + +impl AudioDriver for CoreAudioDriver { + type Error = CoreAudioError; + type Device = CoreAudioDevice; + const DISPLAY_NAME: &'static str = "CoreAudio"; + + fn version(&self) -> Result, Self::Error> { + Ok(Cow::Borrowed("CoreAudio (version unknown)")) + } + + fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { + let (is_input, scope) = match device_type { + DeviceType::Input => (true, Scope::Input), + DeviceType::Output => (false, Scope::Output), + DeviceType::Duplex => { + return Ok(None); + } + }; + let Some(device_id) = get_default_device_id(is_input) else { + return Ok(None); + }; + let audio_unit = audio_unit_from_device_id(device_id, is_input)?; + Ok(Some(CoreAudioDevice { + device_id, + audio_unit, + device_type, + })) + } + + fn list_devices(&self) -> Result, Self::Error> { + let per_scope = [Scope::Input, Scope::Output] + .into_iter() + .map(|scope| { + let audio_ids = get_audio_device_ids_for_scope(scope)?; + Ok::<_, CoreAudioError>( + audio_ids + .into_iter() + .map(|id| CoreAudioDevice::from_id(scope, id)) + .collect::, _>>()?, + ) + }) + .collect::, _>>()?; + Ok(per_scope.into_iter().flatten()) + } +} + +pub struct CoreAudioDevice { + device_id: AudioDeviceID, + audio_unit: AudioUnit, + device_type: DeviceType, +} + +impl CoreAudioDevice { + fn from_id(scope: Scope, device_id: AudioDeviceID) -> Result { + eprintln!("CoreAudioDevice::from_id(scope: {scope:?}, device_id: {device_id})"); + let device_type = + Self::scope_to_valid_device_type(scope).ok_or(CoreAudioError::InvalidScope(scope))?; + let is_input = matches!(scope, Scope::Input); + let audio_unit = audio_unit_from_device_id(device_id, is_input)?; + Ok(Self { + device_id, + audio_unit, + device_type, + }) + } + + fn scope_to_valid_device_type(scope: Scope) -> Option { + match scope { + Scope::Input => Some(DeviceType::Input), + Scope::Output => Some(DeviceType::Output), + _ => None, + } + } +} + +impl AudioDevice for CoreAudioDevice { + type Error = CoreAudioError; + + fn name(&self) -> Cow { + match get_device_name(self.device_id) { + Ok(std) => Cow::Owned(std), + Err(err) => { + eprintln!("Cannot get audio device name: {err}"); + Cow::Borrowed("") + } + } + } + + fn device_type(&self) -> DeviceType { + self.device_type + } + + fn channel_map(&self) -> impl IntoIterator { + let channels = match self.device_type { + DeviceType::Input => self.audio_unit.input_stream_format().unwrap().channels as usize, + DeviceType::Output => self.audio_unit.output_stream_format().unwrap().channels as usize, + _ => 0, + }; + (0..channels).map(|ch| Channel { + index: ch, + name: Cow::Owned(format!("Channel {}", ch)), + }) + } + + fn is_config_supported(&self, config: &StreamConfig) -> bool { + let stream_format = StreamFormat { + sample_rate: config.samplerate, + sample_format: SampleFormat::F32, + flags: LinearPcmFlags::IS_NON_INTERLEAVED + | LinearPcmFlags::IS_PACKED + | LinearPcmFlags::IS_FLOAT, + channels: config.channels.count() as u32, + }; + let Some(asbd) = find_matching_physical_format(self.device_id, stream_format) else { + return false; + }; + if let Some(min_size) = config.buffer_size_range.0 { + if (asbd.mFramesPerPacket as usize) < min_size { + return false; + } + } + if let Some(max_size) = config.buffer_size_range.1 { + if (asbd.mFramesPerPacket as usize) > max_size { + return false; + } + } + return true; + } + + fn enumerate_configurations(&self) -> Option> { + const TYPICAL_SAMPLERATES: [f64; 5] = [44100., 48000., 96000., 128000., 192000.]; + let supported_list = get_supported_physical_stream_formats(self.device_id).ok()?; + Some(supported_list.into_iter().flat_map(|asbd| { + let samplerate_range = asbd.mSampleRateRange.mMinimum..asbd.mSampleRateRange.mMaximum; + TYPICAL_SAMPLERATES + .iter() + .copied() + .filter(move |sr| samplerate_range.contains(sr)) + .map(move |sr| { + let channels = 1 << asbd.mFormat.mChannelsPerFrame as u32 - 1; + let buf_size = asbd.mFormat.mFramesPerPacket as usize; + StreamConfig { + samplerate: sr, + channels, + buffer_size_range: (Some(buf_size), Some(buf_size)), + } + }) + })) + } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 98b7f29..1d1b229 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -3,6 +3,9 @@ use crate::{AudioDevice, AudioDriver, AudioInputDevice, AudioOutputCallback, Aud #[cfg(os_alsa)] pub mod alsa; +#[cfg(os_coreaudio)] +pub mod coreaudio; + pub fn default_driver() -> impl AudioDriver { #[cfg(os_alsa)] alsa::AlsaDriver diff --git a/src/channel_map.rs b/src/channel_map.rs index 0beeadf..d6098fb 100644 --- a/src/channel_map.rs +++ b/src/channel_map.rs @@ -7,10 +7,10 @@ pub trait Bitset: Sized { fn set_index(&mut self, index: usize, value: bool); - fn indices(&self) -> impl IntoIterator { + fn indices(&self) -> impl IntoIterator { (0..self.capacity()).filter_map(|i| self.get_index(i).then_some(i)) } - + fn count(&self) -> usize { self.indices().into_iter().count() } @@ -19,13 +19,17 @@ pub trait Bitset: Sized { self.set_index(index, value); self } - + fn with_indices(mut self, indices: impl IntoIterator) -> Self { for ix in indices { self.set_index(ix, true); } self } + + fn count(&self) -> usize { + self.indices().into_iter().count() + } } #[duplicate::duplicate_item( @@ -65,13 +69,13 @@ fn get_inner_bitset_at(arr: &[T], mut index: usize) -> Option<(usize, let mut acc = 0; move |(i, b)| { return match index.checked_sub(b.capacity()) { - None => Some((i, index)), + None => Some((i, index)), Some(v) => { index = v; acc += b.capacity(); None } - } + }; } }) } @@ -151,4 +155,4 @@ mod test { let result = HashSet::<_, RandomState>::from_iter(bitrate.indices()); assert_eq!(HashSet::from_iter([0, 2, 5, 12, 14, 16]), result); } -} \ No newline at end of file +} From 7e072175fa8d11b3bacea12f6fb50e7e6af27baa Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 27 Jul 2024 00:23:41 +0200 Subject: [PATCH 2/9] chore: rebase coreaudio branch --- .github/workflows/CI.yml | 3 ++- examples/enumerate_coreaudio.rs | 15 +++++++++++---- src/backends/coreaudio.rs | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bd023c4..839a79f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,8 +12,9 @@ env: jobs: test: strategy: + fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/examples/enumerate_coreaudio.rs b/examples/enumerate_coreaudio.rs index 364dcd9..3e9c37f 100644 --- a/examples/enumerate_coreaudio.rs +++ b/examples/enumerate_coreaudio.rs @@ -1,9 +1,16 @@ -use interflow::backends::coreaudio::CoreAudioDriver; -use interflow::{AudioDevice, AudioDriver}; use std::error::Error; -mod enumerate; +mod util; +#[cfg(os_coreaudio)] fn main() -> Result<(), Box> { - enumerate::enumerate_devices(CoreAudioDriver) + use interflow::backends::coreaudio::CoreAudioDriver; + use crate::util::enumerate::enumerate_devices; + + enumerate_devices(CoreAudioDriver) +} + +#[cfg(not(os_coreaudio))] +fn main() { + println!("CoreAudio is not available on this platform"); } diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 2b9bff8..e6e4ff3 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -1,3 +1,7 @@ +//! # CoreAudio backend +//! +//! CoreAudio is the audio backend for macOS and iOS devices. + use std::borrow::Cow; use coreaudio::audio_unit::audio_format::LinearPcmFlags; @@ -12,15 +16,19 @@ use thiserror::Error; use crate::channel_map::Bitset; use crate::{AudioDevice, AudioDriver, Channel, DeviceType, StreamConfig}; +/// Type of errors from the CoreAudio backend #[derive(Debug, Error)] #[error("CoreAudio error:")] pub enum CoreAudioError { + /// Error originating from CoreAudio #[error("{0}")] BackendError(#[from] coreaudio::Error), + /// The scope given to an audio device is invalid. #[error("Invalid scope {0:?}")] InvalidScope(Scope), } +/// The CoreAudio driver. #[derive(Debug, Copy, Clone)] pub struct CoreAudioDriver; @@ -34,13 +42,7 @@ impl AudioDriver for CoreAudioDriver { } fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { - let (is_input, scope) = match device_type { - DeviceType::Input => (true, Scope::Input), - DeviceType::Output => (false, Scope::Output), - DeviceType::Duplex => { - return Ok(None); - } - }; + let is_input = matches!(device_type, DeviceType::Input); let Some(device_id) = get_default_device_id(is_input) else { return Ok(None); }; @@ -69,6 +71,7 @@ impl AudioDriver for CoreAudioDriver { } } +/// Type of devices available from the CoreAudio driver. pub struct CoreAudioDevice { device_id: AudioDeviceID, audio_unit: AudioUnit, @@ -77,7 +80,6 @@ pub struct CoreAudioDevice { impl CoreAudioDevice { fn from_id(scope: Scope, device_id: AudioDeviceID) -> Result { - eprintln!("CoreAudioDevice::from_id(scope: {scope:?}, device_id: {device_id})"); let device_type = Self::scope_to_valid_device_type(scope).ok_or(CoreAudioError::InvalidScope(scope))?; let is_input = matches!(scope, Scope::Input); From caceb2680e349630fa08743fe5066f255b884616 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 27 Jul 2024 21:46:58 +0200 Subject: [PATCH 3/9] feat(coreaudio): audio output streams --- Cargo.lock | 191 +++++++++++++++++++++++++++++++- Cargo.toml | 1 + examples/enumerate_alsa.rs | 1 - examples/enumerate_coreaudio.rs | 4 +- examples/sine_wave.rs | 21 ++-- examples/util/mod.rs | 2 +- src/audio_buffer.rs | 2 +- src/backends/alsa.rs | 16 +-- src/backends/coreaudio.rs | 150 ++++++++++++++++++++++--- src/backends/mod.rs | 16 ++- src/channel_map.rs | 6 +- src/lib.rs | 10 +- src/prelude.rs | 2 +- 13 files changed, 366 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17f4323..6e5a344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "alsa" version = "0.9.0" @@ -9,7 +18,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce" dependencies = [ "alsa-sys", - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -35,12 +44,47 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.72", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -53,6 +97,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "console" version = "0.15.8" @@ -66,6 +121,32 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "coreaudio-rs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb5be6fe37ca53193950415bb49e502c361329ac34f4986d640cf7c3e1e0adb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9" +dependencies = [ + "bindgen", +] + [[package]] name = "duplicate" version = "1.0.0" @@ -76,12 +157,24 @@ dependencies = [ "proc-macro-error", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "heck" version = "0.4.1" @@ -117,24 +210,51 @@ dependencies = [ "alsa", "anyhow", "cfg_aliases", + "coreaudio-rs", "duplicate", "indicatif", "ndarray", + "oneshot", "thiserror", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "matrixmultiply" version = "0.3.8" @@ -145,6 +265,18 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "ndarray" version = "0.15.6" @@ -158,6 +290,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -191,6 +333,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "oneshot" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29" + [[package]] name = "pkg-config" version = "0.3.30" @@ -251,6 +399,47 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 3ae5277..55bf6f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" [dependencies] duplicate = "1.0.0" ndarray = "0.15.6" +oneshot = "0.1.8" thiserror = "1.0.63" [dev-dependencies] diff --git a/examples/enumerate_alsa.rs b/examples/enumerate_alsa.rs index a864085..b6fb0ec 100644 --- a/examples/enumerate_alsa.rs +++ b/examples/enumerate_alsa.rs @@ -13,4 +13,3 @@ fn main() -> Result<(), Box> { fn main() { println!("ALSA driver is not available on this platform"); } - diff --git a/examples/enumerate_coreaudio.rs b/examples/enumerate_coreaudio.rs index 3e9c37f..4110557 100644 --- a/examples/enumerate_coreaudio.rs +++ b/examples/enumerate_coreaudio.rs @@ -4,9 +4,9 @@ mod util; #[cfg(os_coreaudio)] fn main() -> Result<(), Box> { - use interflow::backends::coreaudio::CoreAudioDriver; use crate::util::enumerate::enumerate_devices; - + use interflow::backends::coreaudio::CoreAudioDriver; + enumerate_devices(CoreAudioDriver) } diff --git a/examples/sine_wave.rs b/examples/sine_wave.rs index 25f22d8..153f34a 100644 --- a/examples/sine_wave.rs +++ b/examples/sine_wave.rs @@ -13,13 +13,15 @@ fn main() -> Result<()> { }; assert!(device.is_config_supported(&config)); println!("Using device {}", device.name()); - let stream = device.create_output_stream( - config, - SineWave { - frequency: 440., - phase: 0., - }, - ).unwrap(); + let stream = device + .create_output_stream( + config, + SineWave { + frequency: 440., + phase: 0., + }, + ) + .unwrap(); println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new())?; stream.eject().unwrap(); @@ -33,7 +35,10 @@ struct SineWave { impl AudioOutputCallback for SineWave { fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput) { - eprintln!("Callback called, timestamp: {:2.3} s", context.timestamp.as_seconds()); + eprintln!( + "Callback called, timestamp: {:2.3} s", + context.timestamp.as_seconds() + ); let sr = context.timestamp.samplerate as f32; for i in 0..output.buffer.num_samples() { output.buffer.set_mono(i, self.next_sample(sr)); diff --git a/examples/util/mod.rs b/examples/util/mod.rs index 6ce24a3..3b5980b 100644 --- a/examples/util/mod.rs +++ b/examples/util/mod.rs @@ -1 +1 @@ -pub mod enumerate; \ No newline at end of file +pub mod enumerate; diff --git a/src/audio_buffer.rs b/src/audio_buffer.rs index e78d5d7..6e8ed07 100644 --- a/src/audio_buffer.rs +++ b/src/audio_buffer.rs @@ -178,7 +178,7 @@ impl AudioBufferBase { pub fn channels_mut(&mut self) -> impl '_ + Iterator> { self.storage.rows_mut().into_iter() } - + pub fn as_interleaved_mut(&mut self) -> ArrayViewMut2 { self.storage.view_mut().reversed_axes() } diff --git a/src/backends/alsa.rs b/src/backends/alsa.rs index 2fb51e5..b0c6057 100644 --- a/src/backends/alsa.rs +++ b/src/backends/alsa.rs @@ -1,25 +1,21 @@ use core::fmt; -use std::{borrow::Cow, convert::Infallible, ffi::CStr}; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; +use std::{borrow::Cow, convert::Infallible, ffi::CStr}; -use alsa::{ - device_name::HintIter, - pcm, - Direction, PCM, -}; +use alsa::{device_name::HintIter, pcm, Direction, PCM}; use thiserror::Error; +use crate::audio_buffer::{AudioMut, AudioRef}; +use crate::channel_map::{Bitset, ChannelMap32}; +use crate::timestamp::Timestamp; use crate::{ AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, StreamConfig, }; -use crate::audio_buffer::{AudioMut, AudioRef}; -use crate::channel_map::{Bitset, ChannelMap32}; -use crate::timestamp::Timestamp; #[derive(Debug, Error)] #[error("ALSA error: ")] diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index e6e4ff3..3d00fda 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -1,20 +1,29 @@ //! # CoreAudio backend -//! +//! //! CoreAudio is the audio backend for macOS and iOS devices. use std::borrow::Cow; +use std::convert::Infallible; use coreaudio::audio_unit::audio_format::LinearPcmFlags; use coreaudio::audio_unit::macos_helpers::{ audio_unit_from_device_id, find_matching_physical_format, get_audio_device_ids_for_scope, get_default_device_id, get_device_name, get_supported_physical_stream_formats, + set_device_physical_stream_format, }; -use coreaudio::audio_unit::{AudioUnit, SampleFormat, Scope, StreamFormat}; -use coreaudio::sys::AudioDeviceID; +use coreaudio::audio_unit::render_callback::{data, Args}; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; +use coreaudio::sys::{kAudioUnitProperty_StreamFormat, AudioDeviceID}; use thiserror::Error; +use crate::audio_buffer::AudioBuffer; use crate::channel_map::Bitset; -use crate::{AudioDevice, AudioDriver, Channel, DeviceType, StreamConfig}; +use crate::timestamp::Timestamp; +use crate::{ + AudioCallbackContext, AudioDevice, AudioDriver, AudioInputCallback, AudioInputDevice, + AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, + SendEverywhereButOnWeb, StreamConfig, +}; /// Type of errors from the CoreAudio backend #[derive(Debug, Error)] @@ -46,10 +55,8 @@ impl AudioDriver for CoreAudioDriver { let Some(device_id) = get_default_device_id(is_input) else { return Ok(None); }; - let audio_unit = audio_unit_from_device_id(device_id, is_input)?; Ok(Some(CoreAudioDevice { device_id, - audio_unit, device_type, })) } @@ -72,9 +79,9 @@ impl AudioDriver for CoreAudioDriver { } /// Type of devices available from the CoreAudio driver. +#[derive(Debug, Clone, Copy)] pub struct CoreAudioDevice { device_id: AudioDeviceID, - audio_unit: AudioUnit, device_type: DeviceType, } @@ -82,11 +89,8 @@ impl CoreAudioDevice { fn from_id(scope: Scope, device_id: AudioDeviceID) -> Result { let device_type = Self::scope_to_valid_device_type(scope).ok_or(CoreAudioError::InvalidScope(scope))?; - let is_input = matches!(scope, Scope::Input); - let audio_unit = audio_unit_from_device_id(device_id, is_input)?; Ok(Self { device_id, - audio_unit, device_type, }) } @@ -118,10 +122,20 @@ impl AudioDevice for CoreAudioDevice { } fn channel_map(&self) -> impl IntoIterator { - let channels = match self.device_type { - DeviceType::Input => self.audio_unit.input_stream_format().unwrap().channels as usize, - DeviceType::Output => self.audio_unit.output_stream_format().unwrap().channels as usize, - _ => 0, + let is_input = matches!(self.device_type, DeviceType::Input); + let channels = match audio_unit_from_device_id(self.device_id, is_input) { + Err(err) => { + eprintln!("CoreAudio error getting audio unit: {err}"); + 0 + } + Ok(audio_unit) => { + let stream_format = if is_input { + audio_unit.input_stream_format().unwrap() + } else { + audio_unit.output_stream_format().unwrap() + }; + stream_format.channels as usize + } }; (0..channels).map(|ch| Channel { index: ch, @@ -139,7 +153,7 @@ impl AudioDevice for CoreAudioDevice { channels: config.channels.count() as u32, }; let Some(asbd) = find_matching_physical_format(self.device_id, stream_format) else { - return false; + return true; }; if let Some(min_size) = config.buffer_size_range.0 { if (asbd.mFramesPerPacket as usize) < min_size { @@ -175,3 +189,109 @@ impl AudioDevice for CoreAudioDevice { })) } } + +impl AudioInputDevice for CoreAudioDevice { + type StreamHandle = CoreAudioStream; + + fn create_input_stream( + &self, + stream_config: StreamConfig, + callback: Callback, + ) -> Result, Self::Error> { + todo!() + } +} + +impl AudioOutputDevice for CoreAudioDevice { + type StreamHandle = CoreAudioStream; + + fn create_output_stream( + &self, + stream_config: StreamConfig, + callback: Callback, + ) -> Result, Self::Error> { + CoreAudioStream::new_output(self.device_id, stream_config, callback) + } +} + +pub struct CoreAudioStream { + audio_unit: AudioUnit, + callback_retrieve: oneshot::Sender>, +} + +impl AudioStreamHandle for CoreAudioStream { + type Error = Infallible; + + fn eject(mut self) -> Result { + let (tx, rx) = oneshot::channel(); + self.callback_retrieve.send(tx).unwrap(); + let callback = rx.recv().unwrap(); + self.audio_unit.free_render_callback(); + Ok(callback) + } +} + +impl CoreAudioStream { + fn new_output( + device_id: AudioDeviceID, + stream_config: StreamConfig, + callback: Callback, + ) -> Result { + let mut audio_unit = audio_unit_from_device_id(device_id, false)?; + let hw_stream_format = StreamFormat { + sample_rate: stream_config.samplerate, + sample_format: SampleFormat::F32, + flags: LinearPcmFlags::IS_NON_INTERLEAVED + | LinearPcmFlags::IS_PACKED + | LinearPcmFlags::IS_FLOAT, + channels: stream_config.channels.count() as _, + }; + let hw_asbd = find_matching_physical_format(device_id, hw_stream_format) + .ok_or(coreaudio::Error::UnsupportedStreamFormat)?; + set_device_physical_stream_format(device_id, hw_asbd)?; + let asbd = hw_stream_format.to_asbd(); + audio_unit.set_property( + kAudioUnitProperty_StreamFormat, + Scope::Input, + Element::Output, + Some(&asbd), + )?; + let mut buffer = AudioBuffer::zeroed( + hw_asbd.mChannelsPerFrame as _, + stream_config.samplerate as _, + ); + + // Set up the callback retrieval process, without needing to make the callback `Sync` + let (tx, rx) = oneshot::channel::>(); + let mut callback = Some(callback); + audio_unit.set_render_callback(move |mut args: Args>| { + if let Ok(sender) = rx.try_recv() { + sender.send(callback.take().unwrap()).unwrap(); + return Err(()); + } + let mut buffer = buffer.slice_mut(..args.num_frames); + let timestamp = + Timestamp::from_count(stream_config.samplerate, args.time_stamp.mSampleTime as _); + let output = AudioOutput { buffer: buffer.as_mut(), timestamp }; + if let Some(callback) = &mut callback { + callback.on_output_data( + AudioCallbackContext { + stream_config, + timestamp, + }, + output, + ); + for (output, inner) in args.data.channels_mut().zip(buffer.channels()) { + output.copy_from_slice(inner.as_slice().unwrap()); + } + } + //timestamp += args.num_frames as u64; + Ok(()) + })?; + audio_unit.start()?; + Ok(Self { + audio_unit, + callback_retrieve: tx, + }) + } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 1d1b229..2b503cf 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,4 +1,6 @@ -use crate::{AudioDevice, AudioDriver, AudioInputDevice, AudioOutputCallback, AudioOutputDevice, DeviceType}; +use crate::{ + AudioDevice, AudioDriver, AudioInputDevice, AudioOutputCallback, AudioOutputDevice, DeviceType, +}; #[cfg(os_alsa)] pub mod alsa; @@ -8,7 +10,9 @@ pub mod coreaudio; pub fn default_driver() -> impl AudioDriver { #[cfg(os_alsa)] - alsa::AlsaDriver + return alsa::AlsaDriver; + #[cfg(os_coreaudio)] + return coreaudio::CoreAudioDriver; } pub fn default_input_device_from(driver: &Driver) -> Driver::Device @@ -27,7 +31,9 @@ where pub fn default_input_device() -> impl AudioInputDevice { #[cfg(os_alsa)] - default_input_device_from(&alsa::AlsaDriver) + return default_input_device_from(&alsa::AlsaDriver); + #[cfg(os_coreaudio)] + return default_input_device_from(&coreaudio::CoreAudioDriver); } pub fn default_output_device_from(driver: &Driver) -> Driver::Device @@ -43,5 +49,7 @@ where pub fn default_output_device() -> impl AudioOutputDevice { #[cfg(os_alsa)] - default_output_device_from(&alsa::AlsaDriver) + return default_output_device_from(&alsa::AlsaDriver); + #[cfg(os_coreaudio)] + return default_output_device_from(&coreaudio::CoreAudioDriver); } diff --git a/src/channel_map.rs b/src/channel_map.rs index d6098fb..30d8f73 100644 --- a/src/channel_map.rs +++ b/src/channel_map.rs @@ -11,16 +11,12 @@ pub trait Bitset: Sized { (0..self.capacity()).filter_map(|i| self.get_index(i).then_some(i)) } - fn count(&self) -> usize { - self.indices().into_iter().count() - } - fn with_index(&mut self, index: usize, value: bool) -> &mut Self { self.set_index(index, value); self } - fn with_indices(mut self, indices: impl IntoIterator) -> Self { + fn with_indices(mut self, indices: impl IntoIterator) -> Self { for ix in indices { self.set_index(ix, true); } diff --git a/src/lib.rs b/src/lib.rs index 65e9833..7d41769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ use crate::timestamp::Timestamp; pub mod audio_buffer; pub mod backends; pub mod channel_map; -pub mod timestamp; pub mod prelude; +pub mod timestamp; /// Audio drivers provide access to the inputs and outputs of physical devices. /// Several drivers might provide the same accesses, some sharing it with other applications, @@ -108,9 +108,7 @@ impl SendEverywhereButOnWeb for T {} pub trait AudioInputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; - fn create_input_stream< - Callback: SendEverywhereButOnWeb + AudioInputCallback, - >( + fn create_input_stream( &self, stream_config: StreamConfig, callback: Callback, @@ -120,9 +118,7 @@ pub trait AudioInputDevice: AudioDevice { pub trait AudioOutputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; - fn create_output_stream< - Callback: SendEverywhereButOnWeb + AudioOutputCallback, - >( + fn create_output_stream( &self, stream_config: StreamConfig, callback: Callback, diff --git a/src/prelude.rs b/src/prelude.rs index cf86cd7..bea0c8c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,2 +1,2 @@ +pub use crate::backends::*; pub use crate::*; -pub use crate::backends::*; \ No newline at end of file From 9cea1a10c4b91a94709361f9569ca3a085bf3475 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 27 Jul 2024 23:33:42 +0200 Subject: [PATCH 4/9] wip(coreaudio): audio input streams --- src/backends/coreaudio.rs | 140 ++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 45 deletions(-) diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 3d00fda..0a57fd3 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -5,25 +5,25 @@ use std::borrow::Cow; use std::convert::Infallible; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; use coreaudio::audio_unit::audio_format::LinearPcmFlags; use coreaudio::audio_unit::macos_helpers::{ - audio_unit_from_device_id, find_matching_physical_format, get_audio_device_ids_for_scope, - get_default_device_id, get_device_name, get_supported_physical_stream_formats, - set_device_physical_stream_format, + audio_unit_from_device_id, get_audio_device_ids_for_scope, + get_default_device_id, get_device_name, get_supported_physical_stream_formats + , }; -use coreaudio::audio_unit::render_callback::{data, Args}; -use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; -use coreaudio::sys::{kAudioUnitProperty_StreamFormat, AudioDeviceID}; +use coreaudio::audio_unit::render_callback::{Args, data}; +use coreaudio::sys::{AudioDeviceID, kAudioUnitProperty_StreamFormat}; use thiserror::Error; -use crate::audio_buffer::AudioBuffer; -use crate::channel_map::Bitset; -use crate::timestamp::Timestamp; use crate::{ - AudioCallbackContext, AudioDevice, AudioDriver, AudioInputCallback, AudioInputDevice, - AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, - SendEverywhereButOnWeb, StreamConfig, + AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, + AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, + Channel, DeviceType, SendEverywhereButOnWeb, StreamConfig, }; +use crate::audio_buffer::{AudioBuffer, Sample}; +use crate::channel_map::Bitset; +use crate::timestamp::Timestamp; /// Type of errors from the CoreAudio backend #[derive(Debug, Error)] @@ -143,34 +143,15 @@ impl AudioDevice for CoreAudioDevice { }) } - fn is_config_supported(&self, config: &StreamConfig) -> bool { - let stream_format = StreamFormat { - sample_rate: config.samplerate, - sample_format: SampleFormat::F32, - flags: LinearPcmFlags::IS_NON_INTERLEAVED - | LinearPcmFlags::IS_PACKED - | LinearPcmFlags::IS_FLOAT, - channels: config.channels.count() as u32, - }; - let Some(asbd) = find_matching_physical_format(self.device_id, stream_format) else { - return true; - }; - if let Some(min_size) = config.buffer_size_range.0 { - if (asbd.mFramesPerPacket as usize) < min_size { - return false; - } - } - if let Some(max_size) = config.buffer_size_range.1 { - if (asbd.mFramesPerPacket as usize) > max_size { - return false; - } - } - return true; + fn is_config_supported(&self, _config: &StreamConfig) -> bool { + true } fn enumerate_configurations(&self) -> Option> { const TYPICAL_SAMPLERATES: [f64; 5] = [44100., 48000., 96000., 128000., 192000.]; - let supported_list = get_supported_physical_stream_formats(self.device_id).ok()?; + let supported_list = get_supported_physical_stream_formats(self.device_id) + .inspect_err(|err| eprintln!("Error getting stream formats: {err}")) + .ok()?; Some(supported_list.into_iter().flat_map(|asbd| { let samplerate_range = asbd.mSampleRateRange.mMinimum..asbd.mSampleRateRange.mMaximum; TYPICAL_SAMPLERATES @@ -179,11 +160,10 @@ impl AudioDevice for CoreAudioDevice { .filter(move |sr| samplerate_range.contains(sr)) .map(move |sr| { let channels = 1 << asbd.mFormat.mChannelsPerFrame as u32 - 1; - let buf_size = asbd.mFormat.mFramesPerPacket as usize; StreamConfig { samplerate: sr, channels, - buffer_size_range: (Some(buf_size), Some(buf_size)), + buffer_size_range: (None, None), } }) })) @@ -198,7 +178,7 @@ impl AudioInputDevice for CoreAudioDevice { stream_config: StreamConfig, callback: Callback, ) -> Result, Self::Error> { - todo!() + CoreAudioStream::new_input(self.device_id, stream_config, callback) } } @@ -226,11 +206,82 @@ impl AudioStreamHandle for CoreAudioStream { let (tx, rx) = oneshot::channel(); self.callback_retrieve.send(tx).unwrap(); let callback = rx.recv().unwrap(); + self.audio_unit.free_input_callback(); self.audio_unit.free_render_callback(); Ok(callback) } } +impl CoreAudioStream { + fn new_input( + device_id: AudioDeviceID, + stream_config: StreamConfig, + callback: Callback, + ) -> Result { + let mut audio_unit = audio_unit_from_device_id(device_id, true)?; + let stream_format = StreamFormat { + sample_rate: stream_config.samplerate, + sample_format: SampleFormat::I16, + flags: LinearPcmFlags::IS_NON_INTERLEAVED | LinearPcmFlags::IS_SIGNED_INTEGER, + channels: 1, + }; + let asbd = stream_format.to_asbd(); + audio_unit.set_property( + kAudioUnitProperty_StreamFormat, + Scope::Output, + Element::Input, + Some(&asbd), + )?; + let mut buffer = AudioBuffer::zeroed( + 1, + stream_config.samplerate as _, + ); + + // Set up the callback retrieval process, within needing to make the callback `Sync` + let (tx, rx) = oneshot::channel::>(); + let mut callback = Some(callback); + audio_unit.set_input_callback(move |mut args: Args>| { + eprintln!("[Input callback] block"); + if let Ok(sender) = rx.try_recv() { + sender.send(callback.take().unwrap()).unwrap(); + return Err(()); + } + let mut buffer = buffer.slice_mut(..args.num_frames); + for (input, mut inner) in args.data.channels().zip(buffer.channels_mut()) { + for (s1, s2) in input.into_iter().zip(inner.iter_mut()) { + *s2 = s1.into_float(); + } + } + let timestamp = + Timestamp::from_count(stream_config.samplerate, args.time_stamp.mSampleTime as _); + let input = AudioInput { + buffer: buffer.as_ref(), + timestamp, + }; + if let Some(callback) = &mut callback { + callback.on_input_data( + AudioCallbackContext { + stream_config, + timestamp, + }, + input, + ); + for (input, inner) in args.data.channels_mut().zip(buffer.channels()) { + for (s1, s2) in input.into_iter().zip(inner.iter()) { + *s1 = i16::from_float(*s2); + } + } + } + Ok(()) + })?; + audio_unit.start()?; + Ok(Self { + audio_unit, + callback_retrieve: tx, + }) + } +} + impl CoreAudioStream { fn new_output( device_id: AudioDeviceID, @@ -246,9 +297,6 @@ impl CoreAudioStream { | LinearPcmFlags::IS_FLOAT, channels: stream_config.channels.count() as _, }; - let hw_asbd = find_matching_physical_format(device_id, hw_stream_format) - .ok_or(coreaudio::Error::UnsupportedStreamFormat)?; - set_device_physical_stream_format(device_id, hw_asbd)?; let asbd = hw_stream_format.to_asbd(); audio_unit.set_property( kAudioUnitProperty_StreamFormat, @@ -257,7 +305,7 @@ impl CoreAudioStream { Some(&asbd), )?; let mut buffer = AudioBuffer::zeroed( - hw_asbd.mChannelsPerFrame as _, + stream_config.channels.count(), stream_config.samplerate as _, ); @@ -272,7 +320,10 @@ impl CoreAudioStream { let mut buffer = buffer.slice_mut(..args.num_frames); let timestamp = Timestamp::from_count(stream_config.samplerate, args.time_stamp.mSampleTime as _); - let output = AudioOutput { buffer: buffer.as_mut(), timestamp }; + let output = AudioOutput { + buffer: buffer.as_mut(), + timestamp, + }; if let Some(callback) = &mut callback { callback.on_output_data( AudioCallbackContext { @@ -285,7 +336,6 @@ impl CoreAudioStream { output.copy_from_slice(inner.as_slice().unwrap()); } } - //timestamp += args.num_frames as u64; Ok(()) })?; audio_unit.start()?; From 174013f7014d4d06755e83e730f0dd6a71c7741b Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 28 Jul 2024 10:51:47 +0200 Subject: [PATCH 5/9] fix(coreaudio): remove prints in callback --- examples/input.rs | 2 +- src/backends/coreaudio.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index 921c0bc..4347ae2 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -18,7 +18,7 @@ fn main() -> Result<()> { println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new()).unwrap(); let meter = stream.eject().unwrap(); - meter.progress.finish(); + meter.progress.finish_and_clear(); Ok(()) } diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 0a57fd3..0b39a33 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -237,11 +237,10 @@ impl CoreAudioStream { stream_config.samplerate as _, ); - // Set up the callback retrieval process, within needing to make the callback `Sync` + // Set up the callback retrieval process, without needing to make the callback `Sync` let (tx, rx) = oneshot::channel::>(); let mut callback = Some(callback); audio_unit.set_input_callback(move |mut args: Args>| { - eprintln!("[Input callback] block"); if let Ok(sender) = rx.try_recv() { sender.send(callback.take().unwrap()).unwrap(); return Err(()); From 9560427d40f96ce0393930e60164574cd9c72782 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 28 Jul 2024 11:37:03 +0200 Subject: [PATCH 6/9] feat(coreaudio): add default i/o config and default streams --- examples/input.rs | 2 +- examples/sine_wave.rs | 3 +- src/backends/coreaudio.rs | 61 ++++++++++++++++++++++++++++----------- src/lib.rs | 14 +++++++++ 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index 4347ae2..a9c12b5 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -13,7 +13,7 @@ fn main() -> Result<()> { }; assert!(device.is_config_supported(&config)); let stream = device - .create_input_stream(config, RmsMeter::default()) + .default_input_stream(RmsMeter::default()) .unwrap(); println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new()).unwrap(); diff --git a/examples/sine_wave.rs b/examples/sine_wave.rs index 153f34a..5bba4f2 100644 --- a/examples/sine_wave.rs +++ b/examples/sine_wave.rs @@ -14,8 +14,7 @@ fn main() -> Result<()> { assert!(device.is_config_supported(&config)); println!("Using device {}", device.name()); let stream = device - .create_output_stream( - config, + .default_output_stream( SineWave { frequency: 440., phase: 0., diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 0b39a33..d51a6ed 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -13,7 +13,7 @@ use coreaudio::audio_unit::macos_helpers::{ , }; use coreaudio::audio_unit::render_callback::{Args, data}; -use coreaudio::sys::{AudioDeviceID, kAudioUnitProperty_StreamFormat}; +use coreaudio::sys::{AudioDeviceID, kAudioUnitProperty_SampleRate, kAudioUnitProperty_StreamFormat}; use thiserror::Error; use crate::{ @@ -23,6 +23,7 @@ use crate::{ }; use crate::audio_buffer::{AudioBuffer, Sample}; use crate::channel_map::Bitset; +use crate::prelude::ChannelMap32; use crate::timestamp::Timestamp; /// Type of errors from the CoreAudio backend @@ -170,9 +171,30 @@ impl AudioDevice for CoreAudioDevice { } } +fn input_stream_format(sample_rate: f64) -> StreamFormat { + StreamFormat { + sample_rate, + sample_format: SampleFormat::I16, + flags: LinearPcmFlags::IS_NON_INTERLEAVED | LinearPcmFlags::IS_SIGNED_INTEGER, + channels: 1, + } +} + impl AudioInputDevice for CoreAudioDevice { type StreamHandle = CoreAudioStream; + fn default_input_config(&self) -> Result { + let audio_unit = audio_unit_from_device_id(self.device_id, true)?; + let samplerate = audio_unit.get_property::(kAudioUnitProperty_SampleRate, + Scope::Input, + Element::Input)?; + Ok(StreamConfig { + channels: 0b1, // Hardcoded to mono on non-interleaved inputs + samplerate, + buffer_size_range: (None, None), + }) + } + fn create_input_stream( &self, stream_config: StreamConfig, @@ -182,8 +204,27 @@ impl AudioInputDevice for CoreAudioDevice { } } +fn output_stream_format(sample_rate: f64, channels: ChannelMap32) -> StreamFormat { + StreamFormat { + sample_rate, + sample_format: SampleFormat::F32, + flags: LinearPcmFlags::IS_NON_INTERLEAVED | LinearPcmFlags::IS_FLOAT, + channels, + } +} + impl AudioOutputDevice for CoreAudioDevice { type StreamHandle = CoreAudioStream; + + fn default_output_config(&self) -> Result { + let audio_unit = audio_unit_from_device_id(self.device_id, false)?; + let samplerate = audio_unit.sample_rate()?; + Ok(StreamConfig { + samplerate, + buffer_size_range: (None, None), + channels: 0b11, + }) + } fn create_output_stream( &self, @@ -219,13 +260,7 @@ impl CoreAudioStream { callback: Callback, ) -> Result { let mut audio_unit = audio_unit_from_device_id(device_id, true)?; - let stream_format = StreamFormat { - sample_rate: stream_config.samplerate, - sample_format: SampleFormat::I16, - flags: LinearPcmFlags::IS_NON_INTERLEAVED | LinearPcmFlags::IS_SIGNED_INTEGER, - channels: 1, - }; - let asbd = stream_format.to_asbd(); + let asbd = input_stream_format(stream_config.samplerate).to_asbd(); audio_unit.set_property( kAudioUnitProperty_StreamFormat, Scope::Output, @@ -288,15 +323,7 @@ impl CoreAudioStream { callback: Callback, ) -> Result { let mut audio_unit = audio_unit_from_device_id(device_id, false)?; - let hw_stream_format = StreamFormat { - sample_rate: stream_config.samplerate, - sample_format: SampleFormat::F32, - flags: LinearPcmFlags::IS_NON_INTERLEAVED - | LinearPcmFlags::IS_PACKED - | LinearPcmFlags::IS_FLOAT, - channels: stream_config.channels.count() as _, - }; - let asbd = hw_stream_format.to_asbd(); + let asbd = output_stream_format(stream_config.samplerate, stream_config.channels).to_asbd(); audio_unit.set_property( kAudioUnitProperty_StreamFormat, Scope::Input, diff --git a/src/lib.rs b/src/lib.rs index 7d41769..6dd662a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,22 +107,36 @@ impl SendEverywhereButOnWeb for T {} pub trait AudioInputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; + fn default_input_config(&self) -> Result; fn create_input_stream( &self, stream_config: StreamConfig, callback: Callback, ) -> Result, Self::Error>; + + fn default_input_stream( + &self, callback: Callback, + ) -> Result, Self::Error> { + self.create_input_stream(self.default_input_config()?, callback) + } } pub trait AudioOutputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; + fn default_output_config(&self) -> Result; fn create_output_stream( &self, stream_config: StreamConfig, callback: Callback, ) -> Result, Self::Error>; + + fn default_output_stream( + &self, callback: Callback, + ) -> Result, Self::Error> { + self.create_output_stream(self.default_output_config()?, callback) + } } pub trait AudioStreamHandle { From e8644f22a92cc81eeb330f5d09fcd83fee6852d6 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 28 Jul 2024 11:40:40 +0200 Subject: [PATCH 7/9] fix(examples): remove config literal --- examples/input.rs | 6 ------ examples/sine_wave.rs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index a9c12b5..fddd11a 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -6,12 +6,6 @@ use interflow::timestamp::Timestamp; fn main() -> Result<()> { let device = default_input_device(); - let config = StreamConfig { - samplerate: 48000., - channels: 0b1, - buffer_size_range: (Some(16384), None), - }; - assert!(device.is_config_supported(&config)); let stream = device .default_input_stream(RmsMeter::default()) .unwrap(); diff --git a/examples/sine_wave.rs b/examples/sine_wave.rs index 5bba4f2..fb9c5c3 100644 --- a/examples/sine_wave.rs +++ b/examples/sine_wave.rs @@ -6,12 +6,6 @@ use interflow::prelude::*; fn main() -> Result<()> { let device = default_output_device(); - let config = StreamConfig { - samplerate: 48000., - channels: 0b11, - buffer_size_range: (None, None), - }; - assert!(device.is_config_supported(&config)); println!("Using device {}", device.name()); let stream = device .default_output_stream( From 97a3898f42df653fa8ce723387607b18e741c607 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 28 Jul 2024 13:27:45 +0200 Subject: [PATCH 8/9] feat(alsa): add missing default i/o config methods --- src/backends/alsa.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/backends/alsa.rs b/src/backends/alsa.rs index b0c6057..0b6e507 100644 --- a/src/backends/alsa.rs +++ b/src/backends/alsa.rs @@ -1,21 +1,21 @@ use core::fmt; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::{borrow::Cow, ffi::CStr}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::thread::JoinHandle; use std::time::Duration; -use std::{borrow::Cow, convert::Infallible, ffi::CStr}; -use alsa::{device_name::HintIter, pcm, Direction, PCM}; +use alsa::{device_name::HintIter, pcm, PCM}; use thiserror::Error; -use crate::audio_buffer::{AudioMut, AudioRef}; -use crate::channel_map::{Bitset, ChannelMap32}; -use crate::timestamp::Timestamp; use crate::{ AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, StreamConfig, }; +use crate::audio_buffer::{AudioMut, AudioRef}; +use crate::channel_map::{Bitset, ChannelMap32}; +use crate::timestamp::Timestamp; #[derive(Debug, Error)] #[error("ALSA error: ")] @@ -68,7 +68,7 @@ impl fmt::Debug for AlsaDevice { } impl AudioDevice for AlsaDevice { - type Error = Infallible; + type Error = AlsaError; fn name(&self) -> Cow { Cow::Borrowed(self.name.as_str()) @@ -97,6 +97,10 @@ impl AudioDevice for AlsaDevice { impl AudioInputDevice for AlsaDevice { type StreamHandle = AlsaStream; + fn default_input_config(&self) -> Result { + self.default_config() + } + fn create_input_stream( &self, stream_config: StreamConfig, @@ -113,6 +117,10 @@ impl AudioInputDevice for AlsaDevice { impl AudioOutputDevice for AlsaDevice { type StreamHandle = AlsaStream; + fn default_output_config(&self) -> Result { + self.default_config() + } + fn create_output_stream( &self, stream_config: StreamConfig, @@ -177,6 +185,17 @@ impl AlsaDevice { self.pcm.sw_params(&swp)?; Ok((hwp, swp, io)) } + + fn default_config(&self) -> Result { + let samplerate = 48000.; // Default ALSA sample rate + let channel_count = 2; // Stereo stream + let channels = 1 << channel_count - 1; + Ok(StreamConfig { + samplerate: samplerate as _, + channels, + buffer_size_range: (None, None), + }) + } } pub struct AlsaStream { From 2b9c51334dd40a13e9e66eacecb31bc46c2b0c56 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 10 Nov 2024 13:55:57 +0100 Subject: [PATCH 9/9] chore(all): post-merge fix + formatting --- Cargo.lock | 36 ++++++++++++++++++------------------ examples/enumerate_alsa.rs | 4 ++-- examples/input.rs | 6 ++---- examples/sine_wave.rs | 12 +++++------- src/backends/alsa.rs | 20 ++++++++++---------- src/backends/coreaudio.rs | 36 ++++++++++++++++++------------------ src/backends/mod.rs | 11 +++-------- src/lib.rs | 6 ++++-- src/timestamp.rs | 20 ++++++++++---------- 9 files changed, 72 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6d5f80..b609894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,12 +146,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - [[package]] name = "clang-sys" version = "1.8.1" @@ -163,6 +157,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "console" version = "0.15.8" @@ -304,6 +304,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -313,12 +319,6 @@ dependencies = [ "either", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "lazy_static" version = "1.5.0" @@ -337,12 +337,6 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - [[package]] name = "libloading" version = "0.8.5" @@ -353,6 +347,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "matrixmultiply" version = "0.3.8" diff --git a/examples/enumerate_alsa.rs b/examples/enumerate_alsa.rs index c460a1b..7aaaa26 100644 --- a/examples/enumerate_alsa.rs +++ b/examples/enumerate_alsa.rs @@ -6,9 +6,9 @@ mod util; fn main() -> Result<(), Box> { use crate::util::enumerate::enumerate_devices; use interflow::backends::alsa::AlsaDriver; - + env_logger::init(); - + enumerate_devices(AlsaDriver::default()) } diff --git a/examples/input.rs b/examples/input.rs index 4d0abb8..22b6a2e 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -6,11 +6,9 @@ use interflow::timestamp::Timestamp; fn main() -> Result<()> { env_logger::init(); - + let device = default_input_device(); - let stream = device - .default_input_stream(RmsMeter::default()) - .unwrap(); + let stream = device.default_input_stream(RmsMeter::default()).unwrap(); println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new()).unwrap(); let meter = stream.eject().unwrap(); diff --git a/examples/sine_wave.rs b/examples/sine_wave.rs index 2e24fed..3b4dcd7 100644 --- a/examples/sine_wave.rs +++ b/examples/sine_wave.rs @@ -6,16 +6,14 @@ use interflow::prelude::*; fn main() -> Result<()> { env_logger::init(); - + let device = default_output_device(); println!("Using device {}", device.name()); let stream = device - .default_output_stream( - SineWave { - frequency: 440., - phase: 0., - }, - ) + .default_output_stream(SineWave { + frequency: 440., + phase: 0., + }) .unwrap(); println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new())?; diff --git a/src/backends/alsa.rs b/src/backends/alsa.rs index 9ed1a74..17552f2 100644 --- a/src/backends/alsa.rs +++ b/src/backends/alsa.rs @@ -1,28 +1,28 @@ //! # ALSA backend -//! +//! //! ALSA is a generally available driver for Linux and BSD systems. It is the oldest of the Linux //! drivers supported in this library, and as such makes it a good fallback driver. Newer drivers //! (PulseAudio, PipeWire) offer ALSA-compatible APIs so that older software can still access the //! audio devices through them. use core::fmt; -use std::{borrow::Cow, ffi::CStr}; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; +use std::{borrow::Cow, ffi::CStr}; use alsa::{device_name::HintIter, pcm, PCM}; use thiserror::Error; +use crate::audio_buffer::{AudioMut, AudioRef}; +use crate::channel_map::{Bitset, ChannelMap32}; +use crate::timestamp::Timestamp; use crate::{ AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, StreamConfig, }; -use crate::audio_buffer::{AudioMut, AudioRef}; -use crate::channel_map::{Bitset, ChannelMap32}; -use crate::timestamp::Timestamp; /// Type of errors from using the ALSA backend. #[derive(Debug, Error)] @@ -62,7 +62,7 @@ impl AudioDriver for AlsaDriver { } } -/// Type of ALSA devices. +/// Type of ALSA devices. #[derive(Clone)] pub struct AlsaDevice { pcm: Arc, @@ -220,9 +220,9 @@ impl AlsaDevice { } /// Type of ALSA streams. -/// -/// The audio stream implementation relies on the synchronous API for now, as the [`alsa`] crate -/// does not seem to wrap the asynchronous API as of now. A separate I/O thread is spawned when +/// +/// The audio stream implementation relies on the synchronous API for now, as the [`alsa`] crate +/// does not seem to wrap the asynchronous API as of now. A separate I/O thread is spawned when /// creating a stream, and is stopped when caling [`AudioInputDevice::eject`] / /// [`AudioOutputDevice::eject`]. pub struct AlsaStream { diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index d51a6ed..95263d8 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -5,26 +5,27 @@ use std::borrow::Cow; use std::convert::Infallible; -use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; use coreaudio::audio_unit::audio_format::LinearPcmFlags; use coreaudio::audio_unit::macos_helpers::{ - audio_unit_from_device_id, get_audio_device_ids_for_scope, - get_default_device_id, get_device_name, get_supported_physical_stream_formats - , + audio_unit_from_device_id, get_audio_device_ids_for_scope, get_default_device_id, + get_device_name, get_supported_physical_stream_formats, +}; +use coreaudio::audio_unit::render_callback::{data, Args}; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; +use coreaudio::sys::{ + kAudioUnitProperty_SampleRate, kAudioUnitProperty_StreamFormat, AudioDeviceID, }; -use coreaudio::audio_unit::render_callback::{Args, data}; -use coreaudio::sys::{AudioDeviceID, kAudioUnitProperty_SampleRate, kAudioUnitProperty_StreamFormat}; use thiserror::Error; +use crate::audio_buffer::{AudioBuffer, Sample}; +use crate::channel_map::Bitset; +use crate::prelude::ChannelMap32; +use crate::timestamp::Timestamp; use crate::{ AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, SendEverywhereButOnWeb, StreamConfig, }; -use crate::audio_buffer::{AudioBuffer, Sample}; -use crate::channel_map::Bitset; -use crate::prelude::ChannelMap32; -use crate::timestamp::Timestamp; /// Type of errors from the CoreAudio backend #[derive(Debug, Error)] @@ -185,9 +186,11 @@ impl AudioInputDevice for CoreAudioDevice { fn default_input_config(&self) -> Result { let audio_unit = audio_unit_from_device_id(self.device_id, true)?; - let samplerate = audio_unit.get_property::(kAudioUnitProperty_SampleRate, - Scope::Input, - Element::Input)?; + let samplerate = audio_unit.get_property::( + kAudioUnitProperty_SampleRate, + Scope::Input, + Element::Input, + )?; Ok(StreamConfig { channels: 0b1, // Hardcoded to mono on non-interleaved inputs samplerate, @@ -215,7 +218,7 @@ fn output_stream_format(sample_rate: f64, channels: ChannelMap32) -> StreamForma impl AudioOutputDevice for CoreAudioDevice { type StreamHandle = CoreAudioStream; - + fn default_output_config(&self) -> Result { let audio_unit = audio_unit_from_device_id(self.device_id, false)?; let samplerate = audio_unit.sample_rate()?; @@ -267,10 +270,7 @@ impl CoreAudioStream { Element::Input, Some(&asbd), )?; - let mut buffer = AudioBuffer::zeroed( - 1, - stream_config.samplerate as _, - ); + let mut buffer = AudioBuffer::zeroed(1, stream_config.samplerate as _); // Set up the callback retrieval process, without needing to make the callback `Sync` let (tx, rx) = oneshot::channel::>(); diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 60d8ac3..906acb8 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -3,9 +3,8 @@ //! Home of the various backends supported by the library. //! //! Each backend is provided in its own submodule. Types should be public so that the user isn't -//! limited to going through the main API if they want to choose a specific backend.use crate::{ - AudioDriver, AudioInputDevice, AudioOutputDevice, DeviceType, -}; +//! limited to going through the main API if they want to choose a specific backend. +use crate::{AudioDriver, AudioInputDevice, AudioOutputDevice, DeviceType}; #[cfg(os_alsa)] pub mod alsa; @@ -43,10 +42,7 @@ where driver .default_device(DeviceType::Input) .expect("Audio driver error") - .expect( - "No \ - default device found", - ) + .expect("No default device found") .clone() } @@ -76,7 +72,6 @@ where .clone() } - /// Default output device from the default driver for this platform. /// /// "Default" here means both in terms of platform support but also can include runtime selection. diff --git a/src/lib.rs b/src/lib.rs index 7772199..b034b61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,8 @@ pub trait AudioInputDevice: AudioDevice { ) -> Result, Self::Error>; fn default_input_stream( - &self, callback: Callback, + &self, + callback: Callback, ) -> Result, Self::Error> { self.create_input_stream(self.default_input_config()?, callback) } @@ -178,7 +179,8 @@ pub trait AudioOutputDevice: AudioDevice { ) -> Result, Self::Error>; fn default_output_stream( - &self, callback: Callback, + &self, + callback: Callback, ) -> Result, Self::Error> { self.create_output_stream(self.default_output_config()?, callback) } diff --git a/src/timestamp.rs b/src/timestamp.rs index e550b3b..c652e3e 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -4,9 +4,9 @@ use std::time::Duration; /// Timestamp value, which computes duration information from a provided samplerate and a running /// sample counter. -/// +/// /// You can update the timestamp by add-assigning sample counts to it: -/// +/// /// ```rust /// use std::time::Duration; /// use interflow::timestamp::Timestamp; @@ -15,7 +15,7 @@ use std::time::Duration; /// ts += 48; /// assert_eq!(ts.as_duration(), Duration::from_millis(1)); /// ``` -/// +/// /// Adding also works, returning a new timestamp: /// /// ```rust @@ -27,9 +27,9 @@ use std::time::Duration; /// assert_eq!(ts.as_duration(), Duration::from_millis(0)); /// assert_eq!(ts2.as_duration(), Duration::from_millis(1)); /// ``` -/// +/// /// Similarly, you can compute sample offsets by adding a [`Duration`] to it: -/// +/// /// ```rust /// use std::time::Duration; /// use interflow::timestamp::Timestamp; @@ -38,14 +38,14 @@ use std::time::Duration; /// assert_eq!(ts_off.as_duration(), Duration::from_millis(101)); /// assert_eq!(ts_off.counter, 4848); /// ``` -/// +/// /// Or simply construct a [`Timestamp`] from a specified duration: -/// +/// /// ```rust /// use std::time::Duration; /// use interflow::timestamp::Timestamp; /// let ts = Timestamp::from_duration(44100., Duration::from_millis(1)); -/// assert_eq!(ts.counter, 44); // Note that the conversion is lossy, as only whole samples are +/// assert_eq!(ts.counter, 44); // Note that the conversion is lossy, as only whole samples are /// // stored in the timestamp. /// ``` #[derive(Debug, Copy, Clone, PartialEq)] @@ -98,13 +98,13 @@ impl Timestamp { } } - /// Compute the sample offset that most closely matches the provided duration for the given + /// Compute the sample offset that most closely matches the provided duration for the given /// sample rate. pub fn from_duration(samplerate: f64, duration: Duration) -> Self { Self::from_seconds(samplerate, duration.as_secs_f64()) } - /// Compute the sample offset that most closely matches the provided duration in seconds for + /// Compute the sample offset that most closely matches the provided duration in seconds for /// the given sample rate. pub fn from_seconds(samplerate: f64, seconds: f64) -> Self { let samples = samplerate * seconds;