diff --git a/examples/beep.rs b/examples/beep.rs index 35718d2..9df4f2b 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -9,8 +9,22 @@ fn main() -> Result<(), anyhow::Error> { .default_output_device() .expect("failed to find a default output device"); let format = device.default_output_format()?; - let sample_rate = format.sample_rate.0 as f32; - let channels = format.channels as usize; + + match format.data_type { + cpal::SampleFormat::F32 => run::(&device, &format.shape())?, + cpal::SampleFormat::I16 => run::(&device, &format.shape())?, + cpal::SampleFormat::U16 => run::(&device, &format.shape())?, + } + + Ok(()) +} + +fn run(device: &cpal::Device, shape: &cpal::Shape) -> Result<(), anyhow::Error> +where + T: cpal::Sample, +{ + let sample_rate = shape.sample_rate.0 as f32; + let channels = shape.channels as usize; // Produce a sinusoid of maximum amplitude. let mut sample_clock = 0f32; @@ -21,14 +35,11 @@ fn main() -> Result<(), anyhow::Error> { let err_fn = |err| eprintln!("an error occurred on stream: {}", err); - let data_fn = move |data: &mut cpal::Data| match data.sample_format() { - cpal::SampleFormat::F32 => write_data::(data, channels, &mut next_value), - cpal::SampleFormat::I16 => write_data::(data, channels, &mut next_value), - cpal::SampleFormat::U16 => write_data::(data, channels, &mut next_value), - }; - - let stream = device.build_output_stream(&format, data_fn, err_fn)?; - + let stream = device.build_output_stream( + shape, + move |data: &mut [T]| write_data(data, channels, &mut next_value), + err_fn, + )?; stream.play()?; std::thread::sleep(std::time::Duration::from_millis(1000)); @@ -36,11 +47,10 @@ fn main() -> Result<(), anyhow::Error> { Ok(()) } -fn write_data(output: &mut cpal::Data, channels: usize, next_sample: &mut dyn FnMut() -> f32) +fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) where T: cpal::Sample, { - let output = output.as_slice_mut::().expect("unexpected sample type"); for frame in output.chunks_mut(channels) { let value: T = cpal::Sample::from::(&next_sample()); for sample in frame.iter_mut() { diff --git a/examples/feedback.rs b/examples/feedback.rs index e935c5d..06d3970 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -29,12 +29,11 @@ fn main() -> Result<(), anyhow::Error> { println!("Using default output device: \"{}\"", output_device.name()?); // We'll try and use the same format between streams to keep it simple - let mut format = input_device.default_input_format()?; - format.data_type = cpal::SampleFormat::F32; + let shape = input_device.default_input_format()?.shape(); // Create a delay in case the input and output devices aren't synced. - let latency_frames = (LATENCY_MS / 1_000.0) * format.sample_rate.0 as f32; - let latency_samples = latency_frames as usize * format.channels as usize; + let latency_frames = (LATENCY_MS / 1_000.0) * shape.sample_rate.0 as f32; + let latency_samples = latency_frames as usize * shape.channels as usize; // The buffer to share samples let ring = RingBuffer::new(latency_samples * 2); @@ -47,9 +46,8 @@ fn main() -> Result<(), anyhow::Error> { producer.push(0.0).unwrap(); } - let input_data_fn = move |data: &cpal::Data| { + let input_data_fn = move |data: &[f32]| { let mut output_fell_behind = false; - let data = data.as_slice::().expect("unexpected sample type"); for &sample in data { if producer.push(sample).is_err() { output_fell_behind = true; @@ -60,9 +58,8 @@ fn main() -> Result<(), anyhow::Error> { } }; - let output_data_fn = move |data: &mut cpal::Data| { + let output_data_fn = move |data: &mut [f32]| { let mut input_fell_behind = None; - let data = data.as_slice_mut::().expect("unexpected sample type"); for sample in data { *sample = match consumer.pop() { Ok(s) => s, @@ -81,9 +78,12 @@ fn main() -> Result<(), anyhow::Error> { }; // Build streams. - println!("Attempting to build both streams with `{:?}`.", format); - let input_stream = input_device.build_input_stream(&format, input_data_fn, err_fn)?; - let output_stream = output_device.build_output_stream(&format, output_data_fn, err_fn)?; + println!( + "Attempting to build both streams with f32 samples and `{:?}`.", + shape + ); + let input_stream = input_device.build_input_stream(&shape, input_data_fn, err_fn)?; + let output_stream = output_device.build_output_stream(&shape, output_data_fn, err_fn)?; println!("Successfully built streams."); // Play the streams. diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 44207b6..96a8b93 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -40,14 +40,24 @@ fn main() -> Result<(), anyhow::Error> { eprintln!("an error occurred on stream: {}", err); }; - let data_fn = move |data: &cpal::Data| match data.sample_format() { - cpal::SampleFormat::F32 => write_input_data::(data, &writer_2), - cpal::SampleFormat::I16 => write_input_data::(data, &writer_2), - cpal::SampleFormat::U16 => write_input_data::(data, &writer_2), + let stream = match format.data_type { + cpal::SampleFormat::F32 => device.build_input_stream( + &format.shape(), + move |data| write_input_data::(data, &writer_2), + err_fn, + )?, + cpal::SampleFormat::I16 => device.build_input_stream( + &format.shape(), + move |data| write_input_data::(data, &writer_2), + err_fn, + )?, + cpal::SampleFormat::U16 => device.build_input_stream( + &format.shape(), + move |data| write_input_data::(data, &writer_2), + err_fn, + )?, }; - let stream = device.build_input_stream(&format, data_fn, err_fn)?; - stream.play()?; // Let recording go for roughly three seconds. @@ -77,12 +87,11 @@ fn wav_spec_from_format(format: &cpal::Format) -> hound::WavSpec { type WavWriterHandle = Arc>>>>; -fn write_input_data(input: &cpal::Data, writer: &WavWriterHandle) +fn write_input_data(input: &[T], writer: &WavWriterHandle) where T: cpal::Sample, U: cpal::Sample + hound::Sample, { - let input = input.as_slice::().expect("unexpected sample format"); if let Ok(mut guard) = writer.try_lock() { if let Some(writer) = guard.as_mut() { for &sample in input.iter() { diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index a241b7e..d556359 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -80,7 +80,7 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream_raw( &self, format: &Format, data_callback: D, @@ -95,7 +95,7 @@ impl DeviceTrait for Device { Ok(stream) } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, data_callback: D, diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index b875469..24713a0 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -81,7 +81,7 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream_raw( &self, format: &Format, data_callback: D, @@ -91,10 +91,10 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_input_stream(self, format, data_callback, error_callback) + Device::build_input_stream_raw(self, format, data_callback, error_callback) } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, data_callback: D, @@ -104,7 +104,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_output_stream(self, format, data_callback, error_callback) + Device::build_output_stream_raw(self, format, data_callback, error_callback) } } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 24fd59f..e938b5f 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -57,7 +57,7 @@ impl Stream { } impl Device { - pub fn build_input_stream( + pub fn build_input_stream_raw( &self, format: &Format, mut data_callback: D, @@ -202,7 +202,7 @@ impl Device { } unsupported_format_pair => unreachable!( - "`build_input_stream` should have returned with unsupported \ + "`build_input_stream_raw` should have returned with unsupported \ format {:?}", unsupported_format_pair ), @@ -223,7 +223,7 @@ impl Device { }) } - pub fn build_output_stream( + pub fn build_output_stream_raw( &self, format: &Format, mut data_callback: D, @@ -410,7 +410,7 @@ impl Device { } unsupported_format_pair => unreachable!( - "`build_output_stream` should have returned with unsupported \ + "`build_output_stream_raw` should have returned with unsupported \ format {:?}", unsupported_format_pair ), diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index b145359..cefcf88 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -102,7 +102,7 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream_raw( &self, format: &Format, data_callback: D, @@ -112,10 +112,10 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_input_stream(self, format, data_callback, error_callback) + Device::build_input_stream_raw(self, format, data_callback, error_callback) } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, data_callback: D, @@ -125,7 +125,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Device::build_output_stream(self, format, data_callback, error_callback) + Device::build_output_stream_raw(self, format, data_callback, error_callback) } } @@ -467,7 +467,7 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result( + fn build_input_stream_raw( &self, format: &Format, mut data_callback: D, @@ -655,7 +655,7 @@ impl Device { })) } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, mut data_callback: D, diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index a561be7..1b2d797 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -148,7 +148,7 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream_raw( &self, _format: &Format, _data_callback: D, @@ -161,7 +161,7 @@ impl DeviceTrait for Device { unimplemented!() } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, data_callback: D, diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 1f62b6e..2cdbff2 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -61,7 +61,7 @@ impl DeviceTrait for Device { unimplemented!() } - fn build_input_stream( + fn build_input_stream_raw( &self, _format: &Format, _data_callback: D, @@ -75,7 +75,7 @@ impl DeviceTrait for Device { } /// Create an output stream. - fn build_output_stream( + fn build_output_stream_raw( &self, _format: &Format, _data_callback: D, diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 1a98541..69c82f2 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -95,7 +95,7 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream_raw( &self, format: &Format, data_callback: D, @@ -105,7 +105,7 @@ impl DeviceTrait for Device { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_input_stream_inner(format)?; + let stream_inner = self.build_input_stream_raw_inner(format)?; Ok(Stream::new_input( stream_inner, data_callback, @@ -113,7 +113,7 @@ impl DeviceTrait for Device { )) } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &Format, data_callback: D, @@ -123,7 +123,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_output_stream_inner(format)?; + let stream_inner = self.build_output_stream_raw_inner(format)?; Ok(Stream::new_output( stream_inner, data_callback, @@ -610,7 +610,7 @@ impl Device { } } - pub(crate) fn build_input_stream_inner( + pub(crate) fn build_input_stream_raw_inner( &self, format: &Format, ) -> Result { @@ -754,7 +754,7 @@ impl Device { } } - pub(crate) fn build_output_stream_inner( + pub(crate) fn build_output_stream_raw_inner( &self, format: &Format, ) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 0884312..f05cb7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,10 +59,10 @@ //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); -//! # let format = device.default_output_format().unwrap(); +//! # let shape = device.default_output_format().unwrap().shape(); //! let stream = device.build_output_stream( -//! &format, -//! move |data: &mut Data| { +//! &shape, +//! move |data: &mut [f32]| { //! // react to stream events and read or write stream data here. //! }, //! move |err| { @@ -93,15 +93,14 @@ //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); //! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err); -//! let data_fn = move |data: &mut Data| match data.sample_format() { -//! SampleFormat::F32 => write_silence::(data), -//! SampleFormat::I16 => write_silence::(data), -//! SampleFormat::U16 => write_silence::(data), -//! }; -//! let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); +//! let shape = format.shape(); +//! let stream = match format.data_type { +//! SampleFormat::F32 => device.build_output_stream(&shape, write_silence::, err_fn), +//! SampleFormat::I16 => device.build_output_stream(&shape, write_silence::, err_fn), +//! SampleFormat::U16 => device.build_output_stream(&shape, write_silence::, err_fn), +//! }.unwrap(); //! -//! fn write_silence(data: &mut Data) { -//! let data = data.as_slice_mut::().unwrap(); +//! fn write_silence(data: &mut [T]) { //! for sample in data.iter_mut() { //! *sample = Sample::from(&0.0); //! } @@ -118,7 +117,7 @@ //! # let format = device.default_output_format().unwrap(); //! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; -//! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); +//! # let stream = device.build_output_stream_raw(&format, data_fn, err_fn).unwrap(); //! stream.play().unwrap(); //! ``` //! @@ -132,7 +131,7 @@ //! # let format = device.default_output_format().unwrap(); //! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; -//! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); +//! # let stream = device.build_output_stream_raw(&format, data_fn, err_fn).unwrap(); //! stream.pause().unwrap(); //! ``` @@ -181,6 +180,38 @@ pub struct Format { pub data_type: SampleFormat, } +impl Format { + /// Construct a format having a particular `shape` and `data_type`. + pub fn with_shape(shape: &Shape, data_type: SampleFormat) -> Self { + Self { + channels: shape.channels, + sample_rate: shape.sample_rate, + data_type, + } + } + + /// Extract aspects of the format independent of the data type. + pub fn shape(&self) -> Shape { + self.clone().into() + } +} + +/// The properties of an input or output audio stream excluding its data type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Shape { + pub channels: ChannelCount, + pub sample_rate: SampleRate, +} + +impl From for Shape { + fn from(x: Format) -> Self { + Self { + channels: x.channels, + sample_rate: x.sample_rate, + } + } +} + /// Describes a range of supported stream formats. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SupportedFormat { @@ -193,9 +224,10 @@ pub struct SupportedFormat { pub data_type: SampleFormat, } -/// Represents a buffer of audio data, delivered via a user's stream data callback function. +/// A buffer of dynamically typed audio data, passed to raw stream callbacks. /// -/// Input stream callbacks receive `&Data`, while output stream callbacks expect `&mut Data`. +/// Raw input stream callbacks receive `&Data`, while raw output stream callbacks expect `&mut +/// Data`. #[derive(Debug)] pub struct Data { data: *mut (), diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 87062e8..6296ce5 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -255,7 +255,7 @@ macro_rules! impl_platform_host { } } - fn build_input_stream( + fn build_input_stream_raw( &self, format: &crate::Format, data_callback: D, @@ -267,14 +267,14 @@ macro_rules! impl_platform_host { { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, data_callback, error_callback) + DeviceInner::$HostVariant(ref d) => d.build_input_stream_raw(format, data_callback, error_callback) .map(StreamInner::$HostVariant) .map(Stream::from), )* } } - fn build_output_stream( + fn build_output_stream_raw( &self, format: &crate::Format, data_callback: D, @@ -286,7 +286,7 @@ macro_rules! impl_platform_host { { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, data_callback, error_callback) + DeviceInner::$HostVariant(ref d) => d.build_output_stream_raw(format, data_callback, error_callback) .map(StreamInner::$HostVariant) .map(Stream::from), )* diff --git a/src/traits.rs b/src/traits.rs index d12e98a..7ab61b7 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,8 +2,8 @@ use { BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputDevices, OutputDevices, PauseStreamError, PlayStreamError, StreamError, SupportedFormat, - SupportedFormatsError, + InputDevices, OutputDevices, PauseStreamError, PlayStreamError, Sample, Shape, StreamError, + SupportedFormat, SupportedFormatsError, }; /// A **Host** provides access to the available audio devices on the system. @@ -87,7 +87,7 @@ pub trait DeviceTrait { type SupportedInputFormats: Iterator; /// The iterator type yielding supported output stream formats. type SupportedOutputFormats: Iterator; - /// The stream type created by `build_input_stream` and `build_output_stream`. + /// The stream type created by `build_input_stream_raw` and `build_output_stream_raw`. type Stream: StreamTrait; /// The human-readable name of the device. @@ -113,7 +113,55 @@ pub trait DeviceTrait { fn default_output_format(&self) -> Result; /// Create an input stream. - fn build_input_stream( + fn build_input_stream( + &self, + shape: &Shape, + mut data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(&[T]) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + self.build_input_stream_raw( + &Format::with_shape(shape, T::FORMAT), + move |data| { + data_callback( + data.as_slice() + .expect("host supplied incorrect sample type"), + ) + }, + error_callback, + ) + } + + /// Create an output stream. + fn build_output_stream( + &self, + shape: &Shape, + mut data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(&mut [T]) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + self.build_output_stream_raw( + &Format::with_shape(shape, T::FORMAT), + move |data| { + data_callback( + data.as_slice_mut() + .expect("host supplied incorrect sample type"), + ) + }, + error_callback, + ) + } + + /// Create a dynamically typed input stream. + fn build_input_stream_raw( &self, format: &Format, data_callback: D, @@ -123,8 +171,8 @@ pub trait DeviceTrait { D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static; - /// Create an output stream. - fn build_output_stream( + /// Create a dynamically typed output stream. + fn build_output_stream_raw( &self, format: &Format, data_callback: D,