From 342f64b2286dc008660b84a78b185c8b119516d3 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Wed, 10 Mar 2021 04:28:52 +0200 Subject: [PATCH 1/9] Initial duplex API Stubbing hosts is up next. --- src/lib.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++ src/platform/mod.rs | 148 ++++++++++++++++++++++++++++++++++---- src/traits.rs | 87 +++++++++++++++++++++-- 3 files changed, 386 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8454d7078..8308e0439 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,9 @@ pub type InputDevices = std::iter::Filter::Item) -> bo /// A host's device iterator yielding only *output* devices. pub type OutputDevices = std::iter::Filter::Item) -> bool>; +/// A host's device iterator yielding only *duplex* devices. +pub type DuplexDevices = std::iter::Filter::Item) -> bool>; + /// Number of channels. pub type ChannelCount = u16; @@ -227,6 +230,17 @@ pub struct StreamConfig { pub buffer_size: BufferSize, } +/// The set of parameters used to describe how to open a *duplex* stream. +/// +/// The sample format is omitted in favour of using a sample type. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DuplexStreamConfig { + pub input_channels: ChannelCount, + pub output_channels: ChannelCount, + pub sample_rate: SampleRate, + pub buffer_size: BufferSize, +} + /// Describes the minimum and maximum supported buffer size for the device #[derive(Clone, Debug, Eq, PartialEq)] pub enum SupportedBufferSize { @@ -254,6 +268,22 @@ pub struct SupportedStreamConfigRange { pub(crate) sample_format: SampleFormat, } +/// Describes a range of supported *duplex* stream configurations, retrieved via the +/// `Device::supported_duplex_configs` method. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SupportedDuplexStreamConfigRange { + pub(crate) input_channels: ChannelCount, + pub(crate) output_channels: ChannelCount, + /// Minimum value for the samples rate of the supported formats. + pub(crate) min_sample_rate: SampleRate, + /// Maximum value for the samples rate of the supported formats. + pub(crate) max_sample_rate: SampleRate, + /// Buffersize ranges supported by the device + pub(crate) buffer_size: SupportedBufferSize, + /// Type of data expected by the device. + pub(crate) sample_format: SampleFormat, +} + /// Describes a single supported stream configuration, retrieved via either a /// `SupportedStreamConfigRange` instance or one of the `Device::default_input/output_config` methods. #[derive(Debug, Clone, PartialEq, Eq)] @@ -264,6 +294,17 @@ pub struct SupportedStreamConfig { sample_format: SampleFormat, } +/// Describes a single supported *duplex* stream configuration, retrieved via either a +/// `SupportedDuplexStreamConfigRange` instance or the `Device::default_duplex_config` method. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SupportedDuplexStreamConfig { + input_channels: ChannelCount, + output_channels: ChannelCount, + sample_rate: SampleRate, + buffer_size: SupportedBufferSize, + sample_format: SampleFormat, +} + /// A buffer of dynamically typed audio data, passed to raw stream callbacks. /// /// Raw input stream callbacks receive `&Data`, while raw output stream callbacks expect `&mut @@ -321,6 +362,21 @@ pub struct OutputStreamTimestamp { pub playback: StreamInstant, } +/// A timestamp associated with a call to a duplex stream's data callback. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct DuplexStreamTimestamp { + /// The instant the stream's data callback was invoked. + pub callback: StreamInstant, + /// The instant that data was captured from the device. + /// + /// E.g. The instant data was read from an ADC. + pub capture: StreamInstant, + /// The predicted instant that data written will be delivered to the device for playback. + /// + /// E.g. The instant data will be played by a DAC. + pub playback: StreamInstant, +} + /// Information relevant to a single call to the user's input stream data callback. #[derive(Debug, Clone, PartialEq)] pub struct InputCallbackInfo { @@ -333,6 +389,12 @@ pub struct OutputCallbackInfo { timestamp: OutputStreamTimestamp, } +/// Information relevant to a single call to the user's duplex stream data callback. +#[derive(Debug, Clone, PartialEq)] +pub struct DuplexCallbackInfo { + timestamp: DuplexStreamTimestamp, +} + impl SupportedStreamConfig { pub fn channels(&self) -> ChannelCount { self.channels @@ -359,6 +421,37 @@ impl SupportedStreamConfig { } } +impl SupportedDuplexStreamConfig { + pub fn input_channels(&self) -> ChannelCount { + self.input_channels + } + + pub fn output_channels(&self) -> ChannelCount { + self.output_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) -> DuplexStreamConfig { + DuplexStreamConfig { + input_channels: self.input_channels, + output_channels: self.output_channels, + sample_rate: self.sample_rate, + buffer_size: BufferSize::Default, + } + } +} + impl StreamInstant { /// The amount of time elapsed from another instant to this one. /// @@ -444,6 +537,13 @@ impl OutputCallbackInfo { } } +impl DuplexCallbackInfo { + /// The timestamp associated with the call to a duplex stream's data callback. + pub fn timestamp(&self) -> DuplexStreamTimestamp { + self.timestamp + } +} + #[allow(clippy::len_without_is_empty)] impl Data { // Internal constructor for host implementations to use. @@ -655,6 +755,75 @@ impl SupportedStreamConfigRange { } } +impl SupportedDuplexStreamConfigRange { + pub fn input_channels(&self) -> ChannelCount { + self.input_channels + } + + pub fn output_channels(&self) -> ChannelCount { + self.output_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 + } + + /// Retrieve a `SupportedDuplexStreamConfig` with the given sample rate and buffer size. + /// + /// **panic!**s if the given `sample_rate` is outside the range specified within this + /// `SupportedDuplexStreamConfigRange` instance. + pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedDuplexStreamConfig { + assert!(self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate); + SupportedDuplexStreamConfig { + input_channels: self.input_channels, + output_channels: self.output_channels, + sample_rate, + sample_format: self.sample_format, + buffer_size: self.buffer_size, + } + } + + /// Turns this `SupportedDuplexStreamConfigRange` into a `SupportedDuplexStreamConfig` corresponding to the maximum samples rate. + #[inline] + pub fn with_max_sample_rate(self) -> SupportedDuplexStreamConfig { + SupportedDuplexStreamConfig { + input_channels: self.input_channels, + output_channels: self.output_channels, + sample_rate: self.max_sample_rate, + sample_format: self.sample_format, + buffer_size: self.buffer_size, + } + } + + /// A comparison function which compares two `SupportedDuplexStreamConfigRange`s in terms of their priority of + /// use as a default stream format. + /// + /// Refer to [`SupportStreamConfigRange::cmp_default_heuristics`] for more details. The duplex + /// version prioritizes channel configurations as follows: + /// + /// - Both input and output stereo + /// - Mono input and stereo output (does this make sense?) + /// - Mono input and mono output + /// - Max output channels + /// - Max input channels + pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering { + // TODO: Refactor out common parts with the half duplex implementation + unimplemented!() + } +} + #[test] fn test_cmp_default_heuristics() { let mut formats = vec![ diff --git a/src/platform/mod.rs b/src/platform/mod.rs index d75e9005e..5278b8bc6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -75,6 +75,10 @@ macro_rules! impl_platform_host { /// dispatched **Host** type. pub struct SupportedOutputConfigs(SupportedOutputConfigsInner); + /// The **SupportedDuplexConfigs** iterator associated with the platform's dynamically + /// dispatched **Host** type. + pub struct SupportedDuplexConfigs(SupportedDuplexConfigsInner); + /// Unique identifier for available hosts on the platform. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum HostId { @@ -119,6 +123,12 @@ macro_rules! impl_platform_host { )* } + enum SupportedDuplexConfigsInner { + $( + $HostVariant(crate::host::$host_mod::SupportedDuplexConfigs), + )* + } + impl HostId { pub fn name(&self) -> &'static str { match self { @@ -202,9 +212,30 @@ macro_rules! impl_platform_host { } } + impl Iterator for SupportedDuplexConfigs { + type Item = crate::SupportedDuplexStreamConfigRange; + + fn next(&mut self) -> Option { + match self.0 { + $( + SupportedDuplexConfigsInner::$HostVariant(ref mut s) => s.next(), + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + SupportedDuplexConfigsInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + impl crate::traits::DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -239,6 +270,18 @@ macro_rules! impl_platform_host { } } + fn supported_duplex_configs(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => { + d.supported_duplex_configs() + .map(SupportedDuplexConfigsInner::$HostVariant) + .map(SupportedDuplexConfigs) + } + )* + } + } + fn default_input_config(&self) -> Result { match self.0 { $( @@ -255,6 +298,14 @@ macro_rules! impl_platform_host { } } + fn default_duplex_config(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.default_duplex_config(), + )* + } + } + fn build_input_stream_raw( &self, config: &crate::StreamConfig, @@ -306,6 +357,32 @@ macro_rules! impl_platform_host { )* } } + + fn build_duplex_stream_raw( + &self, + config: &crate::DuplexStreamConfig, + sample_format: crate::SampleFormat, + data_callback: D, + error_callback: E, + ) -> Result + where + D: FnMut(&crate::Data, &mut crate::Data, &crate::DuplexCallbackInfo) + Send + 'static, + E: FnMut(crate::StreamError) + Send + 'static, + { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d + .build_duplex_stream_raw( + config, + sample_format, + data_callback, + error_callback, + ) + .map(StreamInner::$HostVariant) + .map(Stream::from), + )* + } + } } impl crate::traits::HostTrait for Host { @@ -345,6 +422,16 @@ macro_rules! impl_platform_host { )* } } + + fn default_duplex_device(&self) -> Option { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.default_duplex_device().map(DeviceInner::$HostVariant).map(Device::from) + } + )* + } + } } impl crate::traits::StreamTrait for Stream { @@ -449,15 +536,23 @@ macro_rules! impl_platform_host { #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))] mod platform_impl { pub use crate::host::alsa::{ - Device as AlsaDevice, Devices as AlsaDevices, Host as AlsaHost, Stream as AlsaStream, + Device as AlsaDevice, + Devices as AlsaDevices, + Host as AlsaHost, + Stream as AlsaStream, SupportedInputConfigs as AlsaSupportedInputConfigs, SupportedOutputConfigs as AlsaSupportedOutputConfigs, + // SupportedDuplexConfigs as AlsaSupportedDuplexConfigs, }; #[cfg(feature = "jack")] pub use crate::host::jack::{ - Device as JackDevice, Devices as JackDevices, Host as JackHost, Stream as JackStream, + Device as JackDevice, + Devices as JackDevices, + Host as JackHost, + Stream as JackStream, SupportedInputConfigs as JackSupportedInputConfigs, SupportedOutputConfigs as JackSupportedOutputConfigs, + // SupportedDuplexConfigs as JackSupportedDuplexConfigs, }; #[cfg(feature = "jack")] @@ -477,9 +572,13 @@ mod platform_impl { #[cfg(any(target_os = "macos", target_os = "ios"))] mod platform_impl { pub use crate::host::coreaudio::{ - Device as CoreAudioDevice, Devices as CoreAudioDevices, Host as CoreAudioHost, - Stream as CoreAudioStream, SupportedInputConfigs as CoreAudioSupportedInputConfigs, + Device as CoreAudioDevice, + Devices as CoreAudioDevices, + Host as CoreAudioHost, + Stream as CoreAudioStream, + SupportedInputConfigs as CoreAudioSupportedInputConfigs, SupportedOutputConfigs as CoreAudioSupportedOutputConfigs, + // SupportedDuplexConfigs as CoreAudioSupportedDuplexConfigs, }; impl_platform_host!(CoreAudio coreaudio "CoreAudio"); @@ -495,9 +594,13 @@ mod platform_impl { #[cfg(target_os = "emscripten")] mod platform_impl { pub use crate::host::emscripten::{ - Device as EmscriptenDevice, Devices as EmscriptenDevices, Host as EmscriptenHost, - Stream as EmscriptenStream, SupportedInputConfigs as EmscriptenSupportedInputConfigs, + Device as EmscriptenDevice, + Devices as EmscriptenDevices, + Host as EmscriptenHost, + Stream as EmscriptenStream, + SupportedInputConfigs as EmscriptenSupportedInputConfigs, SupportedOutputConfigs as EmscriptenSupportedOutputConfigs, + // SupportedDuplexConfigs as EmscriptenSupportedDuplexConfigs, }; impl_platform_host!(Emscripten emscripten "Emscripten"); @@ -513,9 +616,13 @@ mod platform_impl { #[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] mod platform_impl { pub use crate::host::webaudio::{ - Device as WebAudioDevice, Devices as WebAudioDevices, Host as WebAudioHost, - Stream as WebAudioStream, SupportedInputConfigs as WebAudioSupportedInputConfigs, + Device as WebAudioDevice, + Devices as WebAudioDevices, + Host as WebAudioHost, + Stream as WebAudioStream, + SupportedInputConfigs as WebAudioSupportedInputConfigs, SupportedOutputConfigs as WebAudioSupportedOutputConfigs, + // SupportedDuplexConfigs as WebAudioSupportedDuplexConfigs, }; impl_platform_host!(WebAudio webaudio "WebAudio"); @@ -532,14 +639,22 @@ mod platform_impl { mod platform_impl { #[cfg(feature = "asio")] pub use crate::host::asio::{ - Device as AsioDevice, Devices as AsioDevices, Host as AsioHost, Stream as AsioStream, + Device as AsioDevice, + Devices as AsioDevices, + Host as AsioHost, + Stream as AsioStream, SupportedInputConfigs as AsioSupportedInputConfigs, SupportedOutputConfigs as AsioSupportedOutputConfigs, + // SupportedDuplexConfigs as AsioSupportedDuplexConfigs, }; pub use crate::host::wasapi::{ - Device as WasapiDevice, Devices as WasapiDevices, Host as WasapiHost, - Stream as WasapiStream, SupportedInputConfigs as WasapiSupportedInputConfigs, + Device as WasapiDevice, + Devices as WasapiDevices, + Host as WasapiHost, + Stream as WasapiStream, + SupportedInputConfigs as WasapiSupportedInputConfigs, SupportedOutputConfigs as WasapiSupportedOutputConfigs, + // SupportedDuplexConfigs as WasapiSupportedDuplexConfigs, }; #[cfg(feature = "asio")] @@ -559,9 +674,13 @@ mod platform_impl { #[cfg(target_os = "android")] mod platform_impl { pub use crate::host::oboe::{ - Device as OboeDevice, Devices as OboeDevices, Host as OboeHost, Stream as OboeStream, + Device as OboeDevice, + Devices as OboeDevices, + Host as OboeHost, + Stream as OboeStream, SupportedInputConfigs as OboeSupportedInputConfigs, SupportedOutputConfigs as OboeSupportedOutputConfigs, + // SupportedDuplexConfigs as OboeSupportedDuplexConfigs, }; impl_platform_host!(Oboe oboe "Oboe"); @@ -587,9 +706,12 @@ mod platform_impl { )))] mod platform_impl { pub use crate::host::null::{ - Device as NullDevice, Devices as NullDevices, Host as NullHost, + Device as NullDevice, + Devices as NullDevices, + Host as NullHost, SupportedInputConfigs as NullSupportedInputConfigs, SupportedOutputConfigs as NullSupportedOutputConfigs, + // SupportedDuplexConfigs as NullSupportedDuplexConfigs, }; impl_platform_host!(Null null "Null"); diff --git a/src/traits.rs b/src/traits.rs index aa13cfc9e..59fd4f02e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,9 +2,10 @@ use { BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, - PlayStreamError, Sample, SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + DuplexCallbackInfo, DuplexDevices, DuplexStreamConfig, InputCallbackInfo, InputDevices, + OutputCallbackInfo, OutputDevices, PauseStreamError, PlayStreamError, Sample, SampleFormat, + StreamConfig, StreamError, SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; /// A **Host** provides access to the available audio devices on the system. @@ -50,6 +51,11 @@ pub trait HostTrait { /// Returns `None` if no output device is available. fn default_output_device(&self) -> Option; + /// The default duplex audio device on the system. + /// + /// Returns `None` if no duplex device is available. + fn default_duplex_device(&self) -> Option; + /// An iterator yielding all `Device`s currently available to the system that support one or more /// input stream formats. /// @@ -77,9 +83,23 @@ pub trait HostTrait { } Ok(self.devices()?.filter(supports_output::)) } + + /// An iterator yielding all `Device`s currently available to the system that support one or more + /// duplex stream formats. + /// + /// Can be empty if the system does not support duplex audio. + fn duplex_devices(&self) -> Result, DevicesError> { + fn supports_duplex(device: &D) -> bool { + device + .supported_duplex_configs() + .map(|mut iter| iter.next().is_some()) + .unwrap_or(false) + } + Ok(self.devices()?.filter(supports_duplex::)) + } } -/// A device that is capable of audio input and/or output. +/// A device that is capable of audio input and/or output, or both synchronized as full duplex. /// /// Please note that `Device`s may become invalid if they get disconnected. Therefore, all the /// methods that involve a device return a `Result` allowing the user to handle this case. @@ -88,7 +108,10 @@ pub trait DeviceTrait { type SupportedInputConfigs: Iterator; /// The iterator type yielding supported output stream formats. type SupportedOutputConfigs: Iterator; - /// The stream type created by `build_input_stream_raw` and `build_output_stream_raw`. + /// The iterator type yielding supported duplex stream formats. + type SupportedDuplexConfigs: Iterator; + /// The stream type created by `build_input_stream_raw`, `build_output_stream_raw` + /// and `build_duplex_stream_raw`. type Stream: StreamTrait; /// The human-readable name of the device. @@ -108,12 +131,24 @@ pub trait DeviceTrait { &self, ) -> Result; + /// An iterator yielding duplex stream formats that are supported by the device. + /// + /// Can return an error if the device is no longer valid (eg. it has been disconnected). + fn supported_duplex_configs( + &self, + ) -> Result; + /// The default input stream format for the device. fn default_input_config(&self) -> Result; /// The default output stream format for the device. fn default_output_config(&self) -> Result; + /// The default duplex stream format for the device. + fn default_duplex_config( + &self, + ) -> Result; + /// Create an input stream. fn build_input_stream( &self, @@ -166,6 +201,36 @@ pub trait DeviceTrait { ) } + /// Create a duplex stream. + fn build_duplex_stream( + &self, + config: &DuplexStreamConfig, + mut data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(&[T], &mut [T], &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + self.build_duplex_stream_raw( + config, + T::FORMAT, + move |input_data, output_data, info| { + data_callback( + input_data + .as_slice_mut() + .expect("host supplied incorrect sample type"), + output_data + .as_slice_mut() + .expect("host supplied incorrect sample type"), + info, + ) + }, + error_callback, + ) + } + /// Create a dynamically typed input stream. fn build_input_stream_raw( &self, @@ -189,6 +254,18 @@ pub trait DeviceTrait { where D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static; + + /// Create a dynamically typed duplex stream. + fn build_duplex_stream_raw( + &self, + config: &DuplexStreamConfig, + sample_format: SampleFormat, + data_callback: D, + error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static; } /// A stream created from `Device`, with methods to control playback. From 97a0e1932ba7cff38f56a424d1fe0d72853cc2c5 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Thu, 11 Mar 2021 00:10:52 +0200 Subject: [PATCH 2/9] Implement duplex for null host --- src/host/null/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index ca167cbfa..f850a1f3a 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -3,6 +3,8 @@ use crate::{ InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + SupportedDuplexStreamConfig, DuplexStreamConfig, SupportedDuplexStreamConfigRange, + DuplexCallbackInfo, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -19,6 +21,7 @@ pub struct Stream; pub struct SupportedInputConfigs; pub struct SupportedOutputConfigs; +pub struct SupportedDuplexConfigs; impl Host { #[allow(dead_code)] @@ -36,6 +39,7 @@ impl Devices { impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; #[inline] @@ -57,6 +61,13 @@ impl DeviceTrait for Device { unimplemented!() } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + unimplemented!() + } + #[inline] fn default_input_config(&self) -> Result { unimplemented!() @@ -67,6 +78,11 @@ impl DeviceTrait for Device { unimplemented!() } + #[inline] + fn default_duplex_config(&self) -> Result { + unimplemented!() + } + fn build_input_stream_raw( &self, _config: &StreamConfig, @@ -95,6 +111,20 @@ impl DeviceTrait for Device { { unimplemented!() } + + fn build_duplex_stream_raw( + &self, + _config: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + unimplemented!() + } } impl HostTrait for Host { @@ -116,6 +146,10 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { None } + + fn default_duplex_device(&self) -> Option { + None + } } impl StreamTrait for Stream { @@ -154,3 +188,12 @@ impl Iterator for SupportedOutputConfigs { None } } + +impl Iterator for SupportedDuplexConfigs { + type Item = SupportedDuplexStreamConfigRange; + + #[inline] + fn next(&mut self) -> Option { + None + } +} diff --git a/src/lib.rs b/src/lib.rs index 8308e0439..a8b28fecc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -820,7 +820,7 @@ impl SupportedDuplexStreamConfigRange { /// - Max input channels pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering { // TODO: Refactor out common parts with the half duplex implementation - unimplemented!() + todo!() } } From e57653c92fce44ef02872768d6e20a86a435f3b0 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Thu, 11 Mar 2021 20:46:11 +0200 Subject: [PATCH 3/9] Duplex channel selection API draft v2 This makes the duplex API somewhat asymmetric compared to standalone input / output. However, something more robust than providing all possible channel combinations is necessary for the duplex mode. Rationale: - Hosts might provide e.g. 32 different channel count options for both input and output, and even this could be limiting for some high-end devices or esoteric setups. 32*32 = 1024 is too much to provide as separate `SupportedDuplexStreamConfigRange` items. - The duplex mode is meant for "pro audio" use-cases, where static channel semantics (mono, stereo, surround, etc.) are less prevalent compared to binding a bunch of channels, and leaving semantics to the user. This makes for a vague justification for the asymmetry. - min/max is not quite sufficient, and neither is defaulting to stereo or mono. This is why I've added a `default` field for the channel ranges in duplex mode. On some hosts, like jack, there is no meaningful maximum channel count, since channels can be patched arbitrarily. There is however, often a physical device available where default counts can potentially be inferred from. Alternatively the default could be stereo. On CoreAudio, audio streams are always bound to concrete devices (either physical or statically configured). There it makes sense to default to the maximum channel count for the device in question. The host can opt to report no defaults, in which case stereo is preferred in `default_with_sample_rate` and `default_with_max_sample_rate`. Before a more thorough redesign of the configuration API as a whole, this seems like a reasonable middle-ground. --- src/host/null/mod.rs | 13 ++++---- src/lib.rs | 78 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index f850a1f3a..4afba72eb 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,10 +1,9 @@ use crate::{ BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, - SupportedDuplexStreamConfig, DuplexStreamConfig, SupportedDuplexStreamConfigRange, - DuplexCallbackInfo, + DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, + SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -79,7 +78,9 @@ impl DeviceTrait for Device { } #[inline] - fn default_duplex_config(&self) -> Result { + fn default_duplex_config( + &self, + ) -> Result { unimplemented!() } diff --git a/src/lib.rs b/src/lib.rs index a8b28fecc..0d592f8f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,12 +268,24 @@ pub struct SupportedStreamConfigRange { pub(crate) sample_format: SampleFormat, } +/// Describes the minimum and maximum supported channel counts for one direction of a full duplex +/// stream. A default value may also be provided. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SupportedDuplexChannels { + pub min: ChannelCount, + pub max: ChannelCount, + pub default: Option, +} + + /// Describes a range of supported *duplex* stream configurations, retrieved via the /// `Device::supported_duplex_configs` method. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SupportedDuplexStreamConfigRange { - pub(crate) input_channels: ChannelCount, - pub(crate) output_channels: ChannelCount, + /// The range of possible input channel configurations. + pub(crate) input_channels: SupportedDuplexChannels, + /// The range of possible output channel configurations. + pub(crate) output_channels: SupportedDuplexChannels, /// Minimum value for the samples rate of the supported formats. pub(crate) min_sample_rate: SampleRate, /// Maximum value for the samples rate of the supported formats. @@ -756,11 +768,11 @@ impl SupportedStreamConfigRange { } impl SupportedDuplexStreamConfigRange { - pub fn input_channels(&self) -> ChannelCount { + pub fn input_channels(&self) -> SupportedDuplexChannels { self.input_channels } - pub fn output_channels(&self) -> ChannelCount { + pub fn output_channels(&self) -> SupportedDuplexChannels { self.output_channels } @@ -780,33 +792,69 @@ impl SupportedDuplexStreamConfigRange { self.sample_format } - /// Retrieve a `SupportedDuplexStreamConfig` with the given sample rate and buffer size. + /// Retrieve a `SupportedDuplexStreamConfig` with the given sample rate and default channel + /// counts. + /// + /// If there is no `default` channel count available, at most 2 will be selected. /// /// **panic!**s if the given `sample_rate` is outside the range specified within this /// `SupportedDuplexStreamConfigRange` instance. - pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedDuplexStreamConfig { + pub fn default_with_sample_rate(self, sample_rate: SampleRate) -> SupportedDuplexStreamConfig { assert!(self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate); SupportedDuplexStreamConfig { - input_channels: self.input_channels, - output_channels: self.output_channels, + input_channels: match self.input_channels.default { + Some(count) => count, + None => 2.max(self.input_channels.min).min(self.input_channels.max), + }, + output_channels: match self.output_channels.default { + Some(count) => count, + None => 2.max(self.input_channels.min).min(self.input_channels.max), + }, sample_rate, sample_format: self.sample_format, buffer_size: self.buffer_size, } } - - /// Turns this `SupportedDuplexStreamConfigRange` into a `SupportedDuplexStreamConfig` corresponding to the maximum samples rate. - #[inline] - pub fn with_max_sample_rate(self) -> SupportedDuplexStreamConfig { + + /// Turns this `SupportedDuplexStreamConfigRange` into a `SupportedDuplexStreamConfig` + /// corresponding to the maximum sample rate and default channel counts. + /// + /// If there is no `default` channel count available, at most 2 will be selected. + pub fn default_with_max_sample_rate(self) -> SupportedDuplexStreamConfig { SupportedDuplexStreamConfig { - input_channels: self.input_channels, - output_channels: self.output_channels, + input_channels: match self.input_channels.default { + Some(count) => count, + None => 2.max(self.input_channels.min).min(self.input_channels.max), + }, + output_channels: match self.output_channels.default { + Some(count) => count, + None => 2.max(self.input_channels.min).min(self.input_channels.max), + }, sample_rate: self.max_sample_rate, sample_format: self.sample_format, buffer_size: self.buffer_size, } } + /// Retrieve a `SupportedDuplexStreamConfig` with the given channel counts and sample rate. + /// + /// **panic!**s if the given `input_channels`, `output_channels` or `sample_rate` are outside + /// the range specified within this `SupportedDuplexStreamConfigRange` instance. + pub fn with_channels_sample_rate(self, input_channels: ChannelCount, output_channels: ChannelCount, sample_rate: SampleRate) -> SupportedDuplexStreamConfig { + assert!( + self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate + && self.input_channels.min <= input_channels && input_channels <= self.input_channels.max + && self.output_channels.min <= output_channels && output_channels <= self.output_channels.max + ); + SupportedDuplexStreamConfig { + input_channels, + output_channels, + sample_rate, + sample_format: self.sample_format, + buffer_size: self.buffer_size, + } + } + /// A comparison function which compares two `SupportedDuplexStreamConfigRange`s in terms of their priority of /// use as a default stream format. /// @@ -819,7 +867,7 @@ impl SupportedDuplexStreamConfigRange { /// - Max output channels /// - Max input channels pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering { - // TODO: Refactor out common parts with the half duplex implementation + // TODO: Refactor out common parts with the half duplex implementation? todo!() } } From 3fb2a7c8f0fbef62ae0da65369b9120f3200c82d Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Sun, 14 Mar 2021 02:13:44 +0200 Subject: [PATCH 4/9] Make duplex compile --- src/lib.rs | 6 +++--- src/traits.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0d592f8f1..c090b5e7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -769,11 +769,11 @@ impl SupportedStreamConfigRange { impl SupportedDuplexStreamConfigRange { pub fn input_channels(&self) -> SupportedDuplexChannels { - self.input_channels + self.input_channels.clone() } pub fn output_channels(&self) -> SupportedDuplexChannels { - self.output_channels + self.output_channels.clone() } pub fn min_sample_rate(&self) -> SampleRate { @@ -866,7 +866,7 @@ impl SupportedDuplexStreamConfigRange { /// - Mono input and mono output /// - Max output channels /// - Max input channels - pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering { + pub fn cmp_default_heuristics(&self, _other: &Self) -> std::cmp::Ordering { // TODO: Refactor out common parts with the half duplex implementation? todo!() } diff --git a/src/traits.rs b/src/traits.rs index 59fd4f02e..6c8cb9b0b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -219,7 +219,7 @@ pub trait DeviceTrait { move |input_data, output_data, info| { data_callback( input_data - .as_slice_mut() + .as_slice() .expect("host supplied incorrect sample type"), output_data .as_slice_mut() From 401e1cfca4c9fb835b86e37cbd2740338e04c983 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Sun, 14 Mar 2021 02:14:22 +0200 Subject: [PATCH 5/9] Duplex ALSA stubs --- src/host/alsa/mod.rs | 41 +++++++++++++++++++++++++++++++++++++---- src/lib.rs | 19 +++++++++++++------ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 5505bcc66..9e28276f9 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -6,10 +6,11 @@ use self::alsa::poll::Descriptors; use self::parking_lot::Mutex; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, - PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + DefaultStreamConfigError, DeviceNameError, DevicesError, DuplexCallbackInfo, + DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, + SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, + SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use std::cmp; use std::convert::TryInto; @@ -22,6 +23,7 @@ pub use self::enumerate::{default_input_device, default_output_device, Devices}; pub type SupportedInputConfigs = VecIntoIter; pub type SupportedOutputConfigs = VecIntoIter; +pub type SupportedDuplexConfigs = VecIntoIter; mod enumerate; @@ -55,11 +57,16 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + todo!() + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -78,6 +85,12 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + fn supported_duplex_configs( + &self, + ) -> Result { + todo!() + } + fn default_input_config(&self) -> Result { Device::default_input_config(self) } @@ -86,6 +99,12 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + fn default_duplex_config( + &self, + ) -> Result { + todo!() + } + fn build_input_stream_raw( &self, conf: &StreamConfig, @@ -119,6 +138,20 @@ impl DeviceTrait for Device { let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback); Ok(stream) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + todo!() + } } struct TriggerSender(libc::c_int); diff --git a/src/lib.rs b/src/lib.rs index c090b5e7d..9afa0c1ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -277,7 +277,6 @@ pub struct SupportedDuplexChannels { pub default: Option, } - /// Describes a range of supported *duplex* stream configurations, retrieved via the /// `Device::supported_duplex_configs` method. #[derive(Debug, Clone, PartialEq, Eq)] @@ -815,7 +814,7 @@ impl SupportedDuplexStreamConfigRange { buffer_size: self.buffer_size, } } - + /// Turns this `SupportedDuplexStreamConfigRange` into a `SupportedDuplexStreamConfig` /// corresponding to the maximum sample rate and default channel counts. /// @@ -840,11 +839,19 @@ impl SupportedDuplexStreamConfigRange { /// /// **panic!**s if the given `input_channels`, `output_channels` or `sample_rate` are outside /// the range specified within this `SupportedDuplexStreamConfigRange` instance. - pub fn with_channels_sample_rate(self, input_channels: ChannelCount, output_channels: ChannelCount, sample_rate: SampleRate) -> SupportedDuplexStreamConfig { + pub fn with_channels_sample_rate( + self, + input_channels: ChannelCount, + output_channels: ChannelCount, + sample_rate: SampleRate, + ) -> SupportedDuplexStreamConfig { assert!( - self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate - && self.input_channels.min <= input_channels && input_channels <= self.input_channels.max - && self.output_channels.min <= output_channels && output_channels <= self.output_channels.max + self.min_sample_rate <= sample_rate + && sample_rate <= self.max_sample_rate + && self.input_channels.min <= input_channels + && input_channels <= self.input_channels.max + && self.output_channels.min <= output_channels + && output_channels <= self.output_channels.max ); SupportedDuplexStreamConfig { input_channels, From b47809ba398eac0d66c5c456e5eeeacf892ee1d0 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Tue, 16 Mar 2021 19:23:39 +0200 Subject: [PATCH 6/9] Add duplex example --- Cargo.toml | 3 ++ examples/duplex.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +++ 3 files changed, 136 insertions(+) create mode 100644 examples/duplex.rs diff --git a/Cargo.toml b/Cargo.toml index bb0e35f49..005baa728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,5 +72,8 @@ name = "enumerate" [[example]] name = "feedback" +[[example]] +name = "duplex" + [[example]] name = "record_wav" diff --git a/examples/duplex.rs b/examples/duplex.rs new file mode 100644 index 000000000..d0e0570ea --- /dev/null +++ b/examples/duplex.rs @@ -0,0 +1,127 @@ +//! Feeds back the input stream directly into the output stream by opening the device in full +//! duplex mode. +//! +//! Assumes that the device can use the f32 sample format. + +extern crate anyhow; +extern crate clap; +extern crate cpal; + +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; + +#[derive(Debug)] +struct Opt { + #[cfg(all( + any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"), + feature = "jack" + ))] + jack: bool, + + device: String, +} + +impl Opt { + fn from_args() -> anyhow::Result { + let app = clap::App::new("duplex").arg_from_usage("[DEVICE] 'The audio device to use'"); + + #[cfg(all( + any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"), + feature = "jack" + ))] + let app = app.arg_from_usage("-j, --jack 'Use the JACK host"); + let matches = app.get_matches(); + let device = matches.value_of("DEVICE").unwrap_or("default").to_string(); + + #[cfg(all( + any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"), + feature = "jack" + ))] + return Ok(Opt { + jack: matches.is_present("jack"), + device, + }); + + #[cfg(any( + not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")), + not(feature = "jack") + ))] + Ok(Opt { device }) + } +} + +fn main() -> anyhow::Result<()> { + let opt = Opt::from_args()?; + + // Conditionally compile with jack if the feature is specified. + #[cfg(all( + any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"), + feature = "jack" + ))] + // Manually check for flags. Can be passed through cargo with -- e.g. + // cargo run --release --example beep --features jack -- --jack + let host = if opt.jack { + cpal::host_from_id(cpal::available_hosts() + .into_iter() + .find(|id| *id == cpal::HostId::Jack) + .expect( + "make sure --features jack is specified. only works on OSes where jack is available", + )).expect("jack host unavailable") + } else { + cpal::default_host() + }; + + #[cfg(any( + not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")), + not(feature = "jack") + ))] + let host = cpal::default_host(); + + // Find the device + let device = if opt.device == "default" { + host.default_duplex_device() + } else { + host.duplex_devices()? + .find(|x| x.name().map(|y| y == opt.device).unwrap_or(false)) + } + .expect("failed to find device"); + + println!("Using device: \"{}\"", device.name()?); + + let config: cpal::DuplexStreamConfig = device.default_duplex_config()?.into(); + let input_channels: usize = config.input_channels.into(); + let output_channels: usize = config.output_channels.into(); + // Simply copy as many channels as both input and output allow + let copy_channels = input_channels.min(output_channels); + + let data_fn = move |data_in: &[f32], data_out: &mut [f32], _: &cpal::DuplexCallbackInfo| { + for (frame_in, frame_out) in data_in + .chunks(input_channels) + .zip(data_out.chunks_mut(output_channels)) + { + frame_out[..copy_channels].clone_from_slice(&frame_in[..copy_channels]); + } + }; + + // Build streams. + println!( + "Attempting to build stream with f32 samples and `{:?}`.", + config + ); + let stream = device.build_duplex_stream(&config, data_fn, err_fn)?; + println!("Successfully built stream."); + + // Play the stream. + println!("Starting the duplex stream."); + stream.play()?; + + // Run for 3 seconds before closing. + println!("Playing for 3 seconds... "); + std::thread::sleep(std::time::Duration::from_secs(3)); + drop(stream); + println!("Done!"); + Ok(()) +} + +fn err_fn(err: cpal::StreamError) { + eprintln!("an error occurred on stream: {}", err); +} diff --git a/src/lib.rs b/src/lib.rs index 9afa0c1ca..2f593bcd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -954,6 +954,12 @@ impl From for StreamConfig { } } +impl From for DuplexStreamConfig { + fn from(conf: SupportedDuplexStreamConfig) -> Self { + conf.config() + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // From 8e7b3239adbcc63afabd7de50f42375809773d95 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Tue, 16 Mar 2021 19:46:10 +0200 Subject: [PATCH 7/9] Fix doc build --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2f593bcd8..60a2e09f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -865,7 +865,7 @@ impl SupportedDuplexStreamConfigRange { /// A comparison function which compares two `SupportedDuplexStreamConfigRange`s in terms of their priority of /// use as a default stream format. /// - /// Refer to [`SupportStreamConfigRange::cmp_default_heuristics`] for more details. The duplex + /// Refer to [`SupportedStreamConfigRange::cmp_default_heuristics`] for more details. The duplex /// version prioritizes channel configurations as follows: /// /// - Both input and output stereo From 154b1fb72c16e345279bc0c6870a28bb7f6da80d Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Tue, 4 Jan 2022 02:38:55 +0200 Subject: [PATCH 8/9] Stub all hosts with duplex API --- src/host/alsa/enumerate.rs | 6 +++ src/host/alsa/mod.rs | 27 ++++++++--- src/host/asio/device.rs | 16 +++++++ src/host/asio/mod.rs | 42 +++++++++++++++-- src/host/coreaudio/ios/enumerate.rs | 9 +++- src/host/coreaudio/ios/mod.rs | 61 ++++++++++++++++++++++--- src/host/coreaudio/macos/enumerate.rs | 11 ++++- src/host/coreaudio/macos/mod.rs | 57 ++++++++++++++++++++--- src/host/coreaudio/mod.rs | 4 +- src/host/emscripten/mod.rs | 52 +++++++++++++++++++-- src/host/oboe/mod.rs | 43 ++++++++++++++++-- src/host/wasapi/device.rs | 39 +++++++++++++++- src/host/wasapi/mod.rs | 8 +++- src/host/webaudio/mod.rs | 65 +++++++++++++++++++++++++-- 14 files changed, 405 insertions(+), 35 deletions(-) diff --git a/src/host/alsa/enumerate.rs b/src/host/alsa/enumerate.rs index cb75d990d..0212797c6 100644 --- a/src/host/alsa/enumerate.rs +++ b/src/host/alsa/enumerate.rs @@ -62,6 +62,12 @@ pub fn default_output_device() -> Option { }) } +#[inline] +pub fn default_duplex_device() -> Option { + // TODO + None +} + impl From for DevicesError { fn from(err: alsa::Error) -> Self { let err: BackendSpecificError = err.into(); diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 9e28276f9..30b571214 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -19,7 +19,9 @@ use std::thread::{self, JoinHandle}; use std::vec::IntoIter as VecIntoIter; use traits::{DeviceTrait, HostTrait, StreamTrait}; -pub use self::enumerate::{default_input_device, default_output_device, Devices}; +pub use self::enumerate::{ + default_duplex_device, default_input_device, default_output_device, Devices, +}; pub type SupportedInputConfigs = VecIntoIter; pub type SupportedOutputConfigs = VecIntoIter; @@ -59,7 +61,7 @@ impl HostTrait for Host { } fn default_duplex_device(&self) -> Option { - todo!() + default_duplex_device() } } @@ -88,7 +90,7 @@ impl DeviceTrait for Device { fn supported_duplex_configs( &self, ) -> Result { - todo!() + Device::supported_duplex_configs(self) } fn default_input_config(&self) -> Result { @@ -102,7 +104,7 @@ impl DeviceTrait for Device { fn default_duplex_config( &self, ) -> Result { - todo!() + Device::default_duplex_config(self) } fn build_input_stream_raw( @@ -150,7 +152,8 @@ impl DeviceTrait for Device { D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - todo!() + // TODO + Err(BuildStreamError::StreamConfigNotSupported) } } @@ -475,6 +478,13 @@ impl Device { self.supported_configs(alsa::Direction::Playback) } + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + // ALSA does not offer default stream formats, so instead we compare all supported formats by // the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest. fn default_config( @@ -522,6 +532,13 @@ impl Device { fn default_output_config(&self) -> Result { self.default_config(alsa::Direction::Playback) } + + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } } struct StreamInner { diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index cb8feb3e3..ed4c0c68a 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -1,6 +1,7 @@ use std; pub type SupportedInputConfigs = std::vec::IntoIter; pub type SupportedOutputConfigs = std::vec::IntoIter; +pub type SupportedDuplexConfigs = std::vec::IntoIter; use super::parking_lot::Mutex; use super::sys; @@ -13,6 +14,7 @@ use DevicesError; use SampleFormat; use SampleRate; use SupportedBufferSize; +use SupportedDupolexStreamConfigRange; use SupportedStreamConfig; use SupportedStreamConfigRange; use SupportedStreamConfigsError; @@ -127,6 +129,13 @@ impl Device { Ok(supported_configs.into_iter()) } + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + /// Returns the default input config pub fn default_input_config(&self) -> Result { let channels = self.driver.channels().map_err(default_config_err)?.ins as u16; @@ -167,6 +176,13 @@ impl Device { sample_format, }) } + + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } } impl Devices { diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index ae99c3e38..349185404 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -3,12 +3,15 @@ extern crate parking_lot; use crate::{ BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError, + DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, + SupportedDuplexStreamConfig, SupportedStreamConfig, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; -pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs}; +pub use self::device::{ + Device, Devices, SuppoortedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs, +}; pub use self::stream::Stream; use std::sync::Arc; @@ -51,11 +54,17 @@ impl HostTrait for Host { // ASIO has no concept of a default device, so just use the first. self.output_devices().ok().and_then(|mut ds| ds.next()) } + + fn default_duplex_device(&self) -> Option { + // TODO + None + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -74,6 +83,12 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + fn default_input_config(&self) -> Result { Device::default_input_config(self) } @@ -82,6 +97,12 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, config: &StreamConfig, @@ -109,6 +130,21 @@ impl DeviceTrait for Device { { Device::build_output_stream_raw(self, config, sample_format, data_callback, error_callback) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } impl StreamTrait for Stream { diff --git a/src/host/coreaudio/ios/enumerate.rs b/src/host/coreaudio/ios/enumerate.rs index 850649632..4eafee6cc 100644 --- a/src/host/coreaudio/ios/enumerate.rs +++ b/src/host/coreaudio/ios/enumerate.rs @@ -1,12 +1,13 @@ use std::vec::IntoIter as VecIntoIter; use DevicesError; -use SupportedStreamConfigRange; +use {SupportedDuplexStreamConfigRange, SupportedStreamConfigRange}; use super::Device; pub type SupportedInputConfigs = ::std::vec::IntoIter; pub type SupportedOutputConfigs = ::std::vec::IntoIter; +pub type SupportedDuplexConfigs = ::std::vec::IntoIter; // TODO: Support enumerating earpiece vs headset vs speaker etc? pub struct Devices(VecIntoIter); @@ -41,3 +42,9 @@ pub fn default_input_device() -> Option { pub fn default_output_device() -> Option { Some(Device) } + +#[inline] +pub fn default_duplex_device() -> Option { + // TODO + None +} diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index 698e8877b..1f400fa2b 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -24,14 +24,15 @@ use traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + DeviceNameError, DevicesError, DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, + OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, + StreamError, SupportedBufferSize, SupportedDuplexStreamConfig, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use self::enumerate::{ - default_input_device, default_output_device, Devices, SupportedInputConfigs, - SupportedOutputConfigs, + default_duplex_device, default_input_device, default_output_device, Devices, + SupportedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs, }; use std::slice; @@ -70,6 +71,10 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + default_duplex_device() + } } impl Device { @@ -119,6 +124,14 @@ impl Device { Ok(configs.into_iter()) } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + #[inline] fn default_input_config(&self) -> Result { let asbd: AudioStreamBasicDescription = default_input_asbd()?; @@ -132,11 +145,20 @@ impl Device { let stream_config = stream_config_from_asbd(asbd); Ok(stream_config) } + + #[inline] + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; #[inline] @@ -158,6 +180,13 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + #[inline] fn default_input_config(&self) -> Result { Device::default_input_config(self) @@ -168,6 +197,13 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + #[inline] + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, config: &StreamConfig, @@ -324,6 +360,21 @@ impl DeviceTrait for Device { audio_unit, })) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } pub struct Stream { diff --git a/src/host/coreaudio/macos/enumerate.rs b/src/host/coreaudio/macos/enumerate.rs index d28c1b58d..d69832bb5 100644 --- a/src/host/coreaudio/macos/enumerate.rs +++ b/src/host/coreaudio/macos/enumerate.rs @@ -11,7 +11,10 @@ use super::Device; use std::mem; use std::ptr::null; use std::vec::IntoIter as VecIntoIter; -use {BackendSpecificError, DevicesError, SupportedStreamConfigRange}; +use { + BackendSpecificError, DevicesError, SupportedDuplexStreamConfigRange, + SupportedStreamConfigRange, +}; unsafe fn audio_devices() -> Result, OSStatus> { let property_address = AudioObjectPropertyAddress { @@ -141,5 +144,11 @@ pub fn default_output_device() -> Option { Some(device) } +pub fn default_duplex_device() -> Option { + // TODO + None +} + pub type SupportedInputConfigs = VecIntoIter; pub type SupportedOutputConfigs = VecIntoIter; +pub type SupportedDuplexConfigs = VecIntoIter; diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 7a96fa637..348b9e273 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -23,9 +23,10 @@ use self::coreaudio::sys::{ use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, - PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + DefaultStreamConfigError, DeviceNameError, DevicesError, DuplexCallbackInfo, + DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, + SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, + SupportedDuplexStreamConfig, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; use std::cell::RefCell; @@ -39,8 +40,8 @@ use std::thread; use std::time::Duration; pub use self::enumerate::{ - default_input_device, default_output_device, Devices, SupportedInputConfigs, - SupportedOutputConfigs, + default_duplex_device, default_input_device, default_output_device, Devices, + SupportedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs, }; pub mod enumerate; @@ -75,11 +76,16 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + default_duplex_device() + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -98,6 +104,12 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + fn default_input_config(&self) -> Result { Device::default_input_config(self) } @@ -106,6 +118,12 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, config: &StreamConfig, @@ -133,6 +151,21 @@ impl DeviceTrait for Device { { Device::build_output_stream_raw(self, config, sample_format, data_callback, error_callback) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } #[derive(Clone, PartialEq, Eq)] @@ -308,6 +341,13 @@ impl Device { self.supported_configs(kAudioObjectPropertyScopeOutput) } + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + fn default_config( &self, scope: AudioObjectPropertyScope, @@ -398,6 +438,13 @@ impl Device { fn default_output_config(&self) -> Result { self.default_config(kAudioObjectPropertyScopeOutput) } + + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } } impl fmt::Debug for Device { diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 3263b876f..bbf10920c 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -17,13 +17,13 @@ mod macos; #[cfg(target_os = "ios")] pub use self::ios::{ - enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs}, + enumerate::{Devices, SupportedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs}, Device, Host, Stream, }; #[cfg(target_os = "macos")] pub use self::macos::{ - enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs}, + enumerate::{Devices, SupportedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs}, Device, Host, Stream, }; diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 036bc4da8..ae5c23fd7 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -9,9 +9,10 @@ use stdweb::Reference; use crate::{ BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSize, SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -40,6 +41,7 @@ pub struct StreamId(usize); pub type SupportedInputConfigs = ::std::vec::IntoIter; pub type SupportedOutputConfigs = ::std::vec::IntoIter; +pub type SupportedDuplexConfigs = ::std::vec::IntoIter; const MIN_CHANNELS: u16 = 1; const MAX_CHANNELS: u16 = 32; @@ -97,6 +99,13 @@ impl Device { Ok(configs.into_iter()) } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + unimplemented!() + } + fn default_input_config(&self) -> Result { unimplemented!(); } @@ -112,6 +121,12 @@ impl Device { Ok(config) } + + fn default_duplex_config( + &self, + ) -> Result { + unimplemented!() + } } impl HostTrait for Host { @@ -134,11 +149,16 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + default_duplex_device() + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -157,6 +177,12 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + fn default_input_config(&self) -> Result { Device::default_input_config(self) } @@ -165,6 +191,12 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, _config: &StreamConfig, @@ -233,6 +265,20 @@ impl DeviceTrait for Device { Ok(stream) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + unimplemented!() + } } impl StreamTrait for Stream { diff --git a/src/host/oboe/mod.rs b/src/host/oboe/mod.rs index 4e245cdb0..c520c7a07 100644 --- a/src/host/oboe/mod.rs +++ b/src/host/oboe/mod.rs @@ -8,9 +8,10 @@ extern crate oboe; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + DeviceNameError, DevicesError, DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, + OutputCallbackInfo, PauseStreamError, PlayStreamError, Sample, SampleFormat, SampleRate, + StreamConfig, StreamError, SupportedBufferSize, SupportedDuplexStreamConfig, + SupportedDuplexStreamConfigRange, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; @@ -43,6 +44,7 @@ pub enum Stream { } pub type SupportedInputConfigs = VecIntoIter; pub type SupportedOutputConfigs = VecIntoIter; +pub type SupportedDuplexConfigs = VecIntoIter; pub type Devices = VecIntoIter; impl Host { @@ -87,6 +89,11 @@ impl HostTrait for Host { Some(Device(None)) } } + + fn default_duplex_device(&self) -> Option { + // TODO + None + } } fn buffer_size_range_for_params( @@ -276,6 +283,7 @@ where impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -305,6 +313,13 @@ impl DeviceTrait for Device { } } + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + fn default_input_config(&self) -> Result { let mut configs: Vec<_> = self.supported_input_configs().unwrap().collect(); configs.sort_by(|a, b| b.cmp_default_heuristics(a)); @@ -327,6 +342,13 @@ impl DeviceTrait for Device { Ok(config) } + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } + fn build_input_stream_raw( &self, config: &StreamConfig, @@ -472,6 +494,21 @@ impl DeviceTrait for Device { .into()), } } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } impl StreamTrait for Stream { diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 70445cfc5..3a7438aa5 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,7 +1,8 @@ use crate::{ BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, - DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + DevicesError, DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, + SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedDuplexStreamConfig, + SupportedDuplexStreamConfigRange, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, }; use std; @@ -53,6 +54,7 @@ use crate::{traits::DeviceTrait, BuildStreamError, StreamError}; pub type SupportedInputConfigs = std::vec::IntoIter; pub type SupportedOutputConfigs = std::vec::IntoIter; +pub type SupportedDuplexConfigs = std::vec::IntoIter; /// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers. #[derive(Copy, Clone)] @@ -71,6 +73,7 @@ pub struct Device { impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -89,6 +92,12 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + fn default_input_config(&self) -> Result { Device::default_input_config(self) } @@ -97,6 +106,12 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, config: &StreamConfig, @@ -134,6 +149,21 @@ impl DeviceTrait for Device { error_callback, )) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } struct Endpoint { @@ -1195,6 +1225,11 @@ pub fn default_output_device() -> Option { default_device(eRender) } +pub fn default_duplex_device() -> Option { + // TODO + None +} + /// Get the audio clock used to produce `StreamInstant`s. unsafe fn get_audio_clock( audio_client: *mut audioclient::IAudioClient, diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 80221fccd..26909f0f9 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,8 +1,8 @@ extern crate winapi; pub use self::device::{ - default_input_device, default_output_device, Device, Devices, SupportedInputConfigs, - SupportedOutputConfigs, + default_duplex_device, default_input_device, default_output_device, Device, Devices, + SupportedDuplexConfigs, SupportedInputConfigs, SupportedOutputConfigs, }; pub use self::stream::Stream; use self::winapi::um::winnt::HRESULT; @@ -49,6 +49,10 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + default_duplex_device() + } } #[inline] diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 3216f557c..284aa0d60 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -8,9 +8,11 @@ use self::wasm_bindgen::JsCast; use self::web_sys::{AudioContext, AudioContextOptions}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + DeviceNameError, DevicesError, DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, + OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, + StreamError, SupportedBufferSize, SupportedDuplexStreamConfig, + SupportedDuplexStreamConfigRange, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::ops::DerefMut; use std::sync::{Arc, Mutex, RwLock}; @@ -33,6 +35,7 @@ pub struct Stream { pub type SupportedInputConfigs = ::std::vec::IntoIter; pub type SupportedOutputConfigs = ::std::vec::IntoIter; +pub type SupportedDuplexConfigs = ::std::vec::IntoIter; const MIN_CHANNELS: u16 = 1; const MAX_CHANNELS: u16 = 32; @@ -70,6 +73,10 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } + + fn default_duplex_device(&self) -> Option { + default_duplex_device() + } } impl Devices { @@ -112,6 +119,14 @@ impl Device { Ok(configs.into_iter()) } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + // TODO + Ok(Vec::new().into_iter()) + } + #[inline] fn default_input_config(&self) -> Result { // TODO @@ -130,11 +145,20 @@ impl Device { Ok(config) } + + #[inline] + fn default_duplex_config( + &self, + ) -> Result { + // TODO + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; #[inline] @@ -156,6 +180,13 @@ impl DeviceTrait for Device { Device::supported_output_configs(self) } + #[inline] + fn supported_duplex_configs( + &self, + ) -> Result { + Device::supported_duplex_configs(self) + } + #[inline] fn default_input_config(&self) -> Result { Device::default_input_config(self) @@ -166,6 +197,13 @@ impl DeviceTrait for Device { Device::default_output_config(self) } + #[inline] + fn default_duplex_config( + &self, + ) -> Result { + Device::default_duplex_config(self) + } + fn build_input_stream_raw( &self, _config: &StreamConfig, @@ -348,6 +386,21 @@ impl DeviceTrait for Device { buffer_size_frames, }) } + + fn build_duplex_stream_raw( + &self, + _conf: &DuplexStreamConfig, + _sample_format: SampleFormat, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO + Err(BuildStreamError::StreamConfigNotSupported) + } } impl StreamTrait for Stream { @@ -439,6 +492,12 @@ fn default_output_device() -> Option { } } +#[inline] +fn default_duplex_device() -> Option { + // TODO + None +} + // Detects whether the `AudioContext` global variable is available. fn is_webaudio_available() -> bool { if let Ok(audio_context_is_defined) = eval("typeof AudioContext !== 'undefined'") { From 26a9e87b4d6507bb350c850661866831dc1ecec7 Mon Sep 17 00:00:00 2001 From: Roope Salmi Date: Mon, 3 Jan 2022 11:56:41 -0800 Subject: [PATCH 9/9] Initial implementation of duplex for jack Rebased with help from nyanpasu64. --- It almost compiles. Original (outdated) commit message: The way the temporary interlacing buffers are handled currently is not great. The root of the issue seems to be that JACK actually provides a callback that allows performing allocations on the realtime thread when buffer size changes, but this is not exposed as such in rust-jack. See: https://github.com/RustAudio/rust-jack/issues/137 The duplex implementation now mirrors input, and this panicks if buffer size becomes greater than the original. Co-authored-by: nyanpasu64 --- src/host/jack/device.rs | 131 +++++++++++++- src/host/jack/mod.rs | 33 +++- src/host/jack/stream.rs | 367 ++++++++++++++++++++++++++-------------- 3 files changed, 395 insertions(+), 136 deletions(-) diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 340f87719..070b49a9d 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -1,8 +1,9 @@ use crate::{ BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, - InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + DuplexCallbackInfo, DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, SampleFormat, + SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedDuplexChannels, + SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use std::hash::{Hash, Hasher}; use traits::DeviceTrait; @@ -12,9 +13,12 @@ use super::JACK_SAMPLE_FORMAT; pub type SupportedInputConfigs = std::vec::IntoIter; pub type SupportedOutputConfigs = std::vec::IntoIter; +pub type SupportedDuplexConfigs = std::option::IntoIter; +// TODO: Retrieve from "system" devices instead? const DEFAULT_NUM_CHANNELS: u16 = 2; const DEFAULT_SUPPORTED_CHANNELS: [u16; 10] = [1, 2, 4, 6, 8, 16, 24, 32, 48, 64]; +const DEFAULT_DUPLEX_MAX_CHANNELS: u16 = u16::MAX; /// If a device is for input or output. /// Until we have duplex stream support JACK clients and CPAL devices for JACK will be either input or output. @@ -22,6 +26,7 @@ const DEFAULT_SUPPORTED_CHANNELS: [u16; 10] = [1, 2, 4, 6, 8, 16, 24, 32, 48, 64 pub enum DeviceType { InputDevice, OutputDevice, + DuplexDevice, } #[derive(Clone, Debug)] pub struct Device { @@ -91,6 +96,21 @@ impl Device { ) } + pub fn default_duplex_device( + name: &str, + connect_ports_automatically: bool, + start_server_automatically: bool, + ) -> Result { + let duplex_client_name = name.to_owned(); + Device::new_device( + duplex_client_name, + connect_ports_automatically, + start_server_automatically, + DeviceType::DuplexDevice, + ) + } + + /// Default config for input and output, not duplex. pub fn default_config(&self) -> Result { let channels = DEFAULT_NUM_CHANNELS; let sample_rate = self.sample_rate; @@ -107,6 +127,7 @@ impl Device { }) } + /// Supported configs for input and output, not duplex. pub fn supported_configs(&self) -> Vec { let f = match self.default_config() { Err(_) => return vec![], @@ -134,11 +155,16 @@ impl Device { pub fn is_output(&self) -> bool { matches!(self.device_type, DeviceType::OutputDevice) } + + pub fn is_duplex(&self) -> bool { + matches!(self.device_type, DeviceType::DuplexDevice) + } } impl DeviceTrait for Device { type SupportedInputConfigs = SupportedInputConfigs; type SupportedOutputConfigs = SupportedOutputConfigs; + type SupportedDuplexConfigs = SupportedDuplexConfigs; type Stream = Stream; fn name(&self) -> Result { @@ -157,6 +183,31 @@ impl DeviceTrait for Device { Ok(self.supported_configs().into_iter()) } + fn supported_duplex_configs( + &self, + ) -> Result { + let config = match self.default_duplex_config() { + Err(_) => None, + Ok(f) => Some(SupportedDuplexStreamConfigRange { + input_channels: SupportedDuplexChannels { + min: 0, + max: DEFAULT_DUPLEX_MAX_CHANNELS, + default: Some(f.input_channels), + }, + output_channels: SupportedDuplexChannels { + min: 0, + max: DEFAULT_DUPLEX_MAX_CHANNELS, + default: Some(f.output_channels), + }, + min_sample_rate: f.sample_rate, + max_sample_rate: f.sample_rate, + buffer_size: f.buffer_size.clone(), + sample_format: f.sample_format.clone(), + }), + }; + Ok(config.into_iter()) + } + /// Returns the default input config /// The sample format for JACK audio ports is always "32-bit float mono audio" unless using a custom type. /// The sample rate is set by the JACK server. @@ -171,6 +222,24 @@ impl DeviceTrait for Device { self.default_config() } + /// Returns the default duplex config + /// The sample format for JACK audio ports is always "32 bit float mono audio" unless using a custom type. + /// The sample rate is set by the JACK server. + fn default_duplex_config( + &self, + ) -> Result { + let sample_rate = self.sample_rate; + let buffer_size = self.buffer_size.clone(); + let sample_format = JACK_SAMPLE_FORMAT; + Ok(SupportedDuplexStreamConfig { + input_channels: DEFAULT_NUM_CHANNELS, + output_channels: DEFAULT_NUM_CHANNELS, + sample_rate, + buffer_size, + sample_format, + }) + } + fn build_input_stream_raw( &self, conf: &StreamConfig, @@ -182,8 +251,8 @@ impl DeviceTrait for Device { D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - if let DeviceType::OutputDevice = &self.device_type { - // Trying to create an input stream from an output device + if !matches!(self.device_type, DeviceType::InputDevice) { + // Trying to create an input stream from another type of device return Err(BuildStreamError::StreamConfigNotSupported); } if conf.sample_rate != self.sample_rate || sample_format != JACK_SAMPLE_FORMAT { @@ -220,8 +289,8 @@ impl DeviceTrait for Device { D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - if let DeviceType::InputDevice = &self.device_type { - // Trying to create an output stream from an input device + if !matches!(self.device_type, DeviceType::OutputDevice) { + // Trying to create an output stream from another type of device return Err(BuildStreamError::StreamConfigNotSupported); } if conf.sample_rate != self.sample_rate || sample_format != JACK_SAMPLE_FORMAT { @@ -247,6 +316,54 @@ impl DeviceTrait for Device { Ok(stream) } + + fn build_duplex_stream_raw( + &self, + conf: &DuplexStreamConfig, + sample_format: SampleFormat, + data_callback: D, + error_callback: E, + ) -> Result + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + if !matches!(self.device_type, DeviceType::DuplexDevice) { + // Trying to create a duplex stream from another type of device + return Err(BuildStreamError::StreamConfigNotSupported); + } + if conf.sample_rate != self.sample_rate || sample_format != JACK_SAMPLE_FORMAT { + return Err(BuildStreamError::StreamConfigNotSupported); + } + + // The settings should be fine, create a Client + let client_options = super::get_client_options(self.start_server_automatically); + let client; + match super::get_client(&self.name, client_options) { + Ok(c) => client = c, + Err(e) => { + return Err(BuildStreamError::BackendSpecific { + err: BackendSpecificError { + description: e.to_string(), + }, + }) + } + }; + let mut stream = Stream::new_duplex( + client, + conf.input_channels, + conf.output_channels, + data_callback, + error_callback, + ); + + if self.connect_ports_automatically { + stream.connect_to_system_inputs(); + stream.connect_to_system_outputs(); + } + + Ok(stream) + } } impl PartialEq for Device { diff --git a/src/host/jack/mod.rs b/src/host/jack/mod.rs index afa20acba..b7ba7b03f 100644 --- a/src/host/jack/mod.rs +++ b/src/host/jack/mod.rs @@ -1,6 +1,6 @@ extern crate jack; -use crate::{DevicesError, SampleFormat, SupportedStreamConfigRange}; +use crate::{DevicesError, SampleFormat}; use traits::HostTrait; mod device; @@ -10,8 +10,9 @@ mod stream; const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; -pub type SupportedInputConfigs = std::vec::IntoIter; -pub type SupportedOutputConfigs = std::vec::IntoIter; +pub type SupportedInputConfigs = self::device::SupportedInputConfigs; +pub type SupportedOutputConfigs = self::device::SupportedOutputConfigs; +pub type SupportedDuplexConfigs = self::device::SupportedDuplexConfigs; pub type Devices = std::vec::IntoIter; /// The JACK Host type @@ -62,6 +63,11 @@ impl Host { self.default_output_device() } + pub fn duplex_device_with_name(&mut self, name: &str) -> Option { + self.name = name.to_owned(); + self.default_duplex_device() + } + fn initialize_default_devices(&mut self) { let in_device_res = Device::default_input_device( &self.name, @@ -87,6 +93,18 @@ impl Host { println!("{}", err); } } + + let duplex_device_res = Device::default_duplex_device( + &self.name, + self.connect_ports_automatically, + self.start_server_automatically, + ); + match duplex_device_res { + Ok(device) => self.devices_created.push(device), + Err(err) => { + println!("{}", err); + } + } } } @@ -129,6 +147,15 @@ impl HostTrait for Host { } None } + + fn default_duplex_device(&self) -> Option { + for device in &self.devices_created { + if device.is_duplex() { + return Some(device.clone()); + } + } + None + } } fn get_client_options(start_server_automatically: bool) -> jack::ClientOptions { diff --git a/src/host/jack/stream.rs b/src/host/jack/stream.rs index 003c3e248..a3f241e34 100644 --- a/src/host/jack/stream.rs +++ b/src/host/jack/stream.rs @@ -4,8 +4,8 @@ use std::sync::{Arc, Mutex}; use traits::StreamTrait; use crate::{ - BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleRate, StreamError, + BackendSpecificError, Data, DuplexCallbackInfo, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleRate, StreamError, }; use super::JACK_SAMPLE_FORMAT; @@ -33,44 +33,19 @@ impl Stream { D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let mut ports = vec![]; - let mut port_names: Vec = vec![]; - // Create ports - for i in 0..channels { - let port_try = client.register_port(&format!("in_{}", i), jack::AudioIn::default()); - match port_try { - Ok(port) => { - // Get the port name in order to later connect it automatically - if let Ok(port_name) = port.name() { - port_names.push(port_name); - } - // Store the port into a Vec to move to the ProcessHandler - ports.push(port); - } - Err(e) => { - // If port creation failed, send the error back via the error_callback - error_callback( - BackendSpecificError { - description: e.to_string(), - } - .into(), - ); - } - } - } + let (ports, port_names) = Self::create_input_ports(&client, channels, &mut error_callback); let playing = Arc::new(AtomicBool::new(true)); let error_callback_ptr = Arc::new(Mutex::new(error_callback)) as ErrorCallbackPtr; let input_process_handler = LocalProcessHandler::new( - vec![], ports, + vec![], SampleRate(client.sample_rate() as u32), client.buffer_size() as usize, - Some(Box::new(data_callback)), - None, - playing.clone(), + LocalDataCallback::Input(Box::new(data_callback)), + Arc::clone(&playing), Arc::clone(&error_callback_ptr), ); @@ -98,44 +73,19 @@ impl Stream { D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let mut ports = vec![]; - let mut port_names: Vec = vec![]; - // Create ports - for i in 0..channels { - let port_try = client.register_port(&format!("out_{}", i), jack::AudioOut::default()); - match port_try { - Ok(port) => { - // Get the port name in order to later connect it automatically - if let Ok(port_name) = port.name() { - port_names.push(port_name); - } - // Store the port into a Vec to move to the ProcessHandler - ports.push(port); - } - Err(e) => { - // If port creation failed, send the error back via the error_callback - error_callback( - BackendSpecificError { - description: e.to_string(), - } - .into(), - ); - } - } - } + let (ports, port_names) = Self::create_output_ports(&client, channels, &mut error_callback); let playing = Arc::new(AtomicBool::new(true)); let error_callback_ptr = Arc::new(Mutex::new(error_callback)) as ErrorCallbackPtr; let output_process_handler = LocalProcessHandler::new( - ports, vec![], + ports, SampleRate(client.sample_rate() as u32), client.buffer_size() as usize, - None, - Some(Box::new(data_callback)), - playing.clone(), + LocalDataCallback::Output(Box::new(data_callback)), + Arc::clone(&playing), Arc::clone(&error_callback_ptr), ); @@ -153,6 +103,121 @@ impl Stream { } } + pub fn new_duplex( + client: jack::Client, + input_channels: ChannelCount, + output_channels: ChannelCount, + data_callback: D, + mut error_callback: E, + ) -> Stream + where + D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + let (input_ports, input_port_names) = + Self::create_input_ports(&client, input_channels, &mut error_callback); + let (output_ports, output_port_names) = + Self::create_output_ports(&client, output_channels, &mut error_callback); + + let playing = Arc::new(AtomicBool::new(true)); + + let error_callback_ptr = Arc::new(Mutex::new(error_callback)) as ErrorCallbackPtr; + + let output_process_handler = LocalProcessHandler::new( + input_ports, + output_ports, + SampleRate(client.sample_rate() as u32), + client.buffer_size() as usize, + LocalDataCallback::Duplex(Box::new(data_callback)), + Arc::clone(&playing), + Arc::clone(&error_callback_ptr), + ); + + let notification_handler = JackNotificationHandler::new(error_callback_ptr); + + let async_client = client + .activate_async(notification_handler, output_process_handler) + .unwrap(); + + Stream { + playing, + async_client, + input_port_names, + output_port_names, + } + } + + fn create_input_ports( + client: &jack::Client, + amount: ChannelCount, + error_callback: &mut E, + ) -> (Vec>, Vec) + where + E: FnMut(StreamError) + Send + 'static, + { + let mut ports = vec![]; + let mut port_names: Vec = vec![]; + for i in 0..amount { + let port_try = client.register_port(&format!("in_{}", i), jack::AudioIn::default()); + match port_try { + Ok(port) => { + // Get the port name in order to later connect it automatically + if let Ok(port_name) = port.name() { + port_names.push(port_name); + } + // Store the port into a Vec to move to the ProcessHandler + ports.push(port); + } + Err(e) => { + // If port creation failed, send the error back via the error_callback + error_callback( + BackendSpecificError { + description: e.to_string(), + } + .into(), + ); + } + } + } + (ports, port_names) + } + + fn create_output_ports( + client: &jack::Client, + amount: ChannelCount, + error_callback: &mut E, + ) -> (Vec>, Vec) + where + E: FnMut(StreamError) + Send + 'static, + { + let mut ports = vec![]; + let mut port_names: Vec = vec![]; + // Create ports + for i in 0..amount { + let port_try = client.register_port(&format!("out_{}", i), jack::AudioOut::default()); + match port_try { + Ok(port) => { + // Get the port name in order to later connect it automatically + if let Ok(port_name) = port.name() { + port_names.push(port_name); + } + // Store the port into a Vec to move to the ProcessHandler + ports.push(port); + } + Err(e) => { + // If port creation failed, send the error back via the error_callback + error_callback( + BackendSpecificError { + description: e.to_string(), + } + .into(), + ); + } + } + } + (ports, port_names) + } + /// Connect to the standard system outputs in jack, system:playback_1 and system:playback_2 /// This has to be done after the client is activated, doing it just after creating the ports doesn't work. pub fn connect_to_system_outputs(&mut self) { @@ -220,13 +285,12 @@ impl StreamTrait for Stream { struct LocalProcessHandler { /// No new ports are allowed to be created after the creation of the LocalProcessHandler as that would invalidate the buffer sizes - out_ports: Vec>, in_ports: Vec>, + out_ports: Vec>, sample_rate: SampleRate, buffer_size: usize, - input_data_callback: Option>, - output_data_callback: Option>, + callback: LocalDataCallback, // JACK audio samples are 32-bit float (unless you do some custom dark magic) temp_input_buffer: Vec, @@ -237,16 +301,19 @@ struct LocalProcessHandler { error_callback_ptr: ErrorCallbackPtr, } +enum LocalDataCallback { + Input(Box), + Output(Box), + Duplex(Box), +} + impl LocalProcessHandler { fn new( - out_ports: Vec>, in_ports: Vec>, + out_ports: Vec>, sample_rate: SampleRate, buffer_size: usize, - input_data_callback: Option>, - output_data_callback: Option< - Box, - >, + callback: LocalDataCallback, playing: Arc, error_callback_ptr: ErrorCallbackPtr, ) -> Self { @@ -255,12 +322,11 @@ impl LocalProcessHandler { let temp_output_buffer = vec![0.0; out_ports.len() * buffer_size]; LocalProcessHandler { - out_ports, in_ports, + out_ports, sample_rate, buffer_size, - input_data_callback, - output_data_callback, + callback, temp_input_buffer, temp_output_buffer, playing, @@ -308,65 +374,114 @@ impl jack::ProcessHandler for LocalProcessHandler { )) .expect("`playback` occurs beyond representation supported by `StreamInstant`"); - if let Some(input_callback) = &mut self.input_data_callback { - // Let's get the data from the input ports and run the callback + match &mut self.callback { + LocalDataCallback::Input(input_callback) => { + // Let's get the data from the input ports and run the callback - let num_in_channels = self.in_ports.len(); + let num_in_channels = self.in_ports.len(); - // Read the data from the input ports into the temporary buffer - // Go through every channel and store its data in the temporary input buffer - for ch_ix in 0..num_in_channels { - let input_channel = &self.in_ports[ch_ix].as_slice(process_scope); - for i in 0..current_frame_count { - self.temp_input_buffer[ch_ix + i * num_in_channels] = input_channel[i]; + // Read the data from the input ports into the temporary buffer + // Go through every channel and store its data in the temporary input buffer + for ch_ix in 0..num_in_channels { + let input_channel = &self.in_ports[ch_ix].as_slice(process_scope); + for i in 0..current_frame_count { + self.temp_input_buffer[ch_ix + i * num_in_channels] = input_channel[i]; + } } + // Create a slice of exactly current_frame_count frames + let data = temp_buffer_to_data( + &mut self.temp_input_buffer, + current_frame_count * num_in_channels, + ); + // Create timestamp + let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize; + let duration_since_cycle_start = + frames_to_duration(frames_since_cycle_start, self.sample_rate); + let callback = start_callback_instant + .add(duration_since_cycle_start) + .expect("`playback` occurs beyond representation supported by `StreamInstant`"); + let capture = start_callback_instant; + let timestamp = crate::InputStreamTimestamp { callback, capture }; + let info = crate::InputCallbackInfo { timestamp }; + input_callback(&data, &info); } - // Create a slice of exactly current_frame_count frames - let data = temp_buffer_to_data( - &mut self.temp_input_buffer, - current_frame_count * num_in_channels, - ); - // Create timestamp - let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize; - let duration_since_cycle_start = - frames_to_duration(frames_since_cycle_start, self.sample_rate); - let callback = start_callback_instant - .add(duration_since_cycle_start) - .expect("`playback` occurs beyond representation supported by `StreamInstant`"); - let capture = start_callback_instant; - let timestamp = crate::InputStreamTimestamp { callback, capture }; - let info = crate::InputCallbackInfo { timestamp }; - input_callback(&data, &info); - } - - if let Some(output_callback) = &mut self.output_data_callback { - let num_out_channels = self.out_ports.len(); - - // Create a slice of exactly current_frame_count frames - let mut data = temp_buffer_to_data( - &mut self.temp_output_buffer, - current_frame_count * num_out_channels, - ); - // Create timestamp - let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize; - let duration_since_cycle_start = - frames_to_duration(frames_since_cycle_start, self.sample_rate); - let callback = start_callback_instant - .add(duration_since_cycle_start) - .expect("`playback` occurs beyond representation supported by `StreamInstant`"); - let buffer_duration = frames_to_duration(current_frame_count, self.sample_rate); - let playback = start_cycle_instant - .add(buffer_duration) - .expect("`playback` occurs beyond representation supported by `StreamInstant`"); - let timestamp = crate::OutputStreamTimestamp { callback, playback }; - let info = crate::OutputCallbackInfo { timestamp }; - output_callback(&mut data, &info); - - // Deinterlace - for ch_ix in 0..num_out_channels { - let output_channel = &mut self.out_ports[ch_ix].as_mut_slice(process_scope); - for i in 0..current_frame_count { - output_channel[i] = self.temp_output_buffer[ch_ix + i * num_out_channels]; + LocalDataCallback::Output(output_callback) => { + let num_out_channels = self.out_ports.len(); + + // Create a slice of exactly current_frame_count frames + let mut data = temp_buffer_to_data( + &mut self.temp_output_buffer, + current_frame_count * num_out_channels, + ); + // Create timestamp + let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize; + let duration_since_cycle_start = + frames_to_duration(frames_since_cycle_start, self.sample_rate); + let callback = start_callback_instant + .add(duration_since_cycle_start) + .expect("`playback` occurs beyond representation supported by `StreamInstant`"); + let buffer_duration = frames_to_duration(current_frame_count, self.sample_rate); + let playback = start_cycle_instant + .add(buffer_duration) + .expect("`playback` occurs beyond representation supported by `StreamInstant`"); + let timestamp = crate::OutputStreamTimestamp { callback, playback }; + let info = crate::OutputCallbackInfo { timestamp }; + output_callback(&mut data, &info); + + // Deinterlace + for ch_ix in 0..num_out_channels { + let output_channel = &mut self.out_ports[ch_ix].as_mut_slice(process_scope); + for i in 0..current_frame_count { + output_channel[i] = self.temp_output_buffer[ch_ix + i * num_out_channels]; + } + } + } + LocalDataCallback::Duplex(duplex_callback) => { + let num_in_channels = self.in_ports.len(); + let num_out_channels = self.out_ports.len(); + + // Read the data from the input ports into the temporary buffer + // Go through every channel and store its data in the temporary input buffer + for ch_ix in 0..num_in_channels { + let input_channel = &self.in_ports[ch_ix].as_slice(process_scope); + for i in 0..current_frame_count { + self.temp_input_buffer[ch_ix + i * num_in_channels] = input_channel[i]; + } + } + // Create a slice of exactly current_frame_count frames for both input and output + let input_data = temp_buffer_to_data( + &mut self.temp_input_buffer, + current_frame_count * num_in_channels, + ); + + let mut output_data = temp_buffer_to_data( + &mut self.temp_output_buffer, + current_frame_count * num_out_channels, + ); + + // Create timestamp + let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize; + let duration_since_cycle_start = + frames_to_duration(frames_since_cycle_start, self.sample_rate); + let callback = start_callback_instant + .add(duration_since_cycle_start) + .expect("`playback` occurs beyond representation supported by `StreamInstant`"); + let capture = start_callback_instant; + // TODO: Think this through better. What is the most relevant info we can give? How + // about latencies? + let timestamp = crate::DuplexStreamTimestamp { + callback, + capture, + playback: capture, + }; + let info = crate::DuplexCallbackInfo { timestamp }; + duplex_callback(&input_data, &mut output_data, &info); + + for ch_ix in 0..num_out_channels { + let output_channel = &mut self.out_ports[ch_ix].as_mut_slice(process_scope); + for i in 0..current_frame_count { + output_channel[i] = self.temp_output_buffer[ch_ix + i * num_out_channels]; + } } } }