From 5e4f38499260bc571bc1b937d6e6844d954c6427 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 28 Jun 2019 23:42:07 +0200 Subject: [PATCH] Refactor `Host` and related traits into a new `traits` module This is a draft implementation of #294. I'll leave this open for feedback and potentially better trait naming suggestions or better solutions in general! cc @ishitatsuyuki --- examples/beep.rs | 2 +- examples/enumerate.rs | 2 +- examples/feedback.rs | 2 +- examples/record_wav.rs | 2 +- src/host/alsa/mod.rs | 5 +- src/host/coreaudio/mod.rs | 5 +- src/host/emscripten/mod.rs | 5 +- src/host/null/mod.rs | 5 +- src/host/wasapi/mod.rs | 5 +- src/lib.rs | 203 +++---------------------------------- src/platform/mod.rs | 10 +- src/traits.rs | 198 ++++++++++++++++++++++++++++++++++++ 12 files changed, 225 insertions(+), 219 deletions(-) create mode 100644 src/traits.rs diff --git a/examples/beep.rs b/examples/beep.rs index 629d550..564231b 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,7 +1,7 @@ extern crate cpal; extern crate failure; -use cpal::{Device, EventLoop, Host}; +use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; fn main() -> Result<(), failure::Error> { let host = cpal::default_host(); diff --git a/examples/enumerate.rs b/examples/enumerate.rs index 6832b6d..eddf2f4 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,7 +1,7 @@ extern crate cpal; extern crate failure; -use cpal::{Device, Host}; +use cpal::traits::{DeviceTrait, HostTrait}; fn main() -> Result<(), failure::Error> { println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS); diff --git a/examples/feedback.rs b/examples/feedback.rs index 4c80d65..42d36bc 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -9,7 +9,7 @@ extern crate cpal; extern crate failure; -use cpal::{Device, EventLoop, Host}; +use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; const LATENCY_MS: f32 = 150.0; diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 8fa4aaa..9127123 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -6,7 +6,7 @@ extern crate cpal; extern crate failure; extern crate hound; -use cpal::{Device, EventLoop, Host}; +use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; fn main() -> Result<(), failure::Error> { // Use the default host for working with audio devices. diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 6b8b2f5..8ab4c81 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -7,12 +7,9 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; -use Device as DeviceTrait; use DeviceNameError; use DevicesError; -use EventLoop as EventLoopTrait; use Format; -use Host as HostTrait; use PauseStreamError; use PlayStreamError; use SampleFormat; @@ -21,10 +18,10 @@ use SupportedFormatsError; use StreamData; use StreamDataResult; use StreamError; -use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; +use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; use std::{cmp, ffi, mem, ptr}; use std::sync::Mutex; diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 7654653..491b4b8 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -5,12 +5,9 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; -use Device as DeviceTrait; use DeviceNameError; use DevicesError; -use EventLoop as EventLoopTrait; use Format; -use Host as HostTrait; use PauseStreamError; use PlayStreamError; use SupportedFormatsError; @@ -18,10 +15,10 @@ use SampleFormat; use SampleRate; use StreamData; use StreamDataResult; -use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; +use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; use std::ffi::CStr; use std::fmt; diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 15502df..136c88f 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -10,20 +10,17 @@ use stdweb::web::set_timeout; use BuildStreamError; use DefaultFormatError; -use Device as DeviceTrait; use DeviceNameError; use DevicesError; -use EventLoop as EventLoopTrait; use Format; -use Host as HostTrait; use PauseStreamError; use PlayStreamError; use SupportedFormatsError; use StreamData; use StreamDataResult; -use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeOutputBuffer; +use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; /// The default emscripten host type. #[derive(Debug)] diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index d45200f..2126207 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -2,18 +2,15 @@ use BuildStreamError; use DefaultFormatError; -use Device as DeviceTrait; use DevicesError; use DeviceNameError; -use EventLoop as EventLoopTrait; use Format; -use Host as HostTrait; use PauseStreamError; use PlayStreamError; use StreamDataResult; use SupportedFormatsError; -use StreamId as StreamIdTrait; use SupportedFormat; +use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; #[derive(Default)] pub struct Devices; diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index f890cc0..eadeb95 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -3,19 +3,16 @@ extern crate winapi; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; -use Device as DeviceTrait; use DeviceNameError; use DevicesError; -use EventLoop as EventLoopTrait; use Format; -use Host as HostTrait; use PlayStreamError; use PauseStreamError; use StreamDataResult; -use StreamId as StreamIdTrait; use SupportedFormatsError; use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; +use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; pub use self::stream::{EventLoop, StreamId}; diff --git a/src/lib.rs b/src/lib.rs index d4817df..8257051 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ //! `EventLoop`: //! //! ``` -//! use cpal::Host; +//! use cpal::traits::HostTrait; //! let host = cpal::default_host(); //! let event_loop = host.event_loop(); //! ``` @@ -30,7 +30,7 @@ //! stream type on the system. //! //! ```no_run -//! # use cpal::Host; +//! # use cpal::traits::HostTrait; //! # let host = cpal::default_host(); //! let device = host.default_output_device().expect("no output device available"); //! ``` @@ -46,7 +46,7 @@ //! > has been disconnected. //! //! ```no_run -//! use cpal::{Device, Host}; +//! use cpal::traits::{DeviceTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! let mut supported_formats_range = device.supported_output_formats() @@ -59,7 +59,7 @@ //! Now that we have everything for the stream, we can create it from our event loop: //! //! ```no_run -//! use cpal::{Device, EventLoop, Host}; +//! use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let event_loop = host.event_loop(); //! # let device = host.default_output_device().unwrap(); @@ -73,7 +73,7 @@ //! Now we must start the stream. This is done with the `play_stream()` method on the event loop. //! //! ```no_run -//! # use cpal::{EventLoop, Host}; +//! # use cpal::traits::{EventLoopTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let event_loop = host.event_loop(); //! # let stream_id = unimplemented!(); @@ -83,7 +83,7 @@ //! Now everything is ready! We call `run()` on the `event_loop` to begin processing. //! //! ```no_run -//! # use cpal::{EventLoop, Host}; +//! # use cpal::traits::{EventLoopTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let event_loop = host.event_loop(); //! event_loop.run(move |_stream_id, _stream_result| { @@ -103,7 +103,8 @@ //! In this example, we simply fill the given output buffer with zeroes. //! //! ```no_run -//! use cpal::{EventLoop, Host, StreamData, UnknownTypeOutputBuffer}; +//! use cpal::{StreamData, UnknownTypeOutputBuffer}; +//! use cpal::traits::{EventLoopTrait, HostTrait}; //! # let host = cpal::default_host(); //! # let event_loop = host.event_loop(); //! event_loop.run(move |stream_id, stream_result| { @@ -148,7 +149,10 @@ extern crate lazy_static; #[macro_use] extern crate stdweb; -pub use platform::{ALL_HOSTS, HostId, available_hosts, default_host, host_from_id}; +pub use platform::{ + ALL_HOSTS, Device, EventLoop, Host, HostId, StreamId, available_hosts, + default_host, host_from_id, +}; pub use samples_formats::{Sample, SampleFormat}; use failure::Fail; @@ -157,188 +161,7 @@ use std::ops::{Deref, DerefMut}; mod host; pub mod platform; mod samples_formats; - -/// A **Host** provides access to the available audio devices on the system. -/// -/// Each platform may have a number of available hosts depending on the system, each with their own -/// pros and cons. -/// -/// For example, WASAPI is the standard audio host API that ships with the Windows operating -/// system. However, due to historical limitations with respect to performance and flexibility, -/// Steinberg created the ASIO API providing better audio device support for pro audio and -/// low-latency applications. As a result, it is common for some devices and device capabilities to -/// only be available via ASIO, while others are only available via WASAPI. -/// -/// Another great example is the Linux platform. While the ALSA host API is the lowest-level API -/// available to almost all distributions of Linux, its flexibility is limited as it requires that -/// each process have exclusive access to the devices with which they establish streams. PulseAudio -/// is another popular host API that aims to solve this issue by providing user-space mixing, -/// however it has its own limitations w.r.t. low-latency and high-performance audio applications. -/// JACK is yet another host API that is more suitable to pro-audio applications, however it is -/// less readily available by default in many Linux distributions and is known to be tricky to -/// setup. -pub trait Host { - /// The type used for enumerating available devices by the host. - type Devices: Iterator; - /// The `Device` type yielded by the host. - type Device: Device; - /// The event loop type used by the `Host` - type EventLoop: EventLoop; - - /// Whether or not the host is available on the system. - fn is_available() -> bool; - - /// An iterator yielding all `Device`s currently available to the host on the system. - /// - /// Can be empty if the system does not support audio in general. - fn devices(&self) -> Result; - - /// The default input audio device on the system. - /// - /// Returns `None` if no input device is available. - fn default_input_device(&self) -> Option; - - /// The default output audio device on the system. - /// - /// Returns `None` if no output device is available. - fn default_output_device(&self) -> Option; - - /// Initialise the event loop, ready for managing audio streams. - fn event_loop(&self) -> Self::EventLoop; - - /// An iterator yielding all `Device`s currently available to the system that support one or more - /// input stream formats. - /// - /// Can be empty if the system does not support audio input. - fn input_devices(&self) -> Result, DevicesError> { - fn supports_input(device: &D) -> bool { - device.supported_input_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(self.devices()?.filter(supports_input::)) - } - - /// An iterator yielding all `Device`s currently available to the system that support one or more - /// output stream formats. - /// - /// Can be empty if the system does not support audio output. - fn output_devices(&self) -> Result, DevicesError> { - fn supports_output(device: &D) -> bool { - device.supported_output_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(self.devices()?.filter(supports_output::)) - } -} - -/// A device that is capable of audio input and/or output. -/// -/// 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. -pub trait Device { - /// The iterator type yielding supported input stream formats. - type SupportedInputFormats: Iterator; - /// The iterator type yielding supported output stream formats. - type SupportedOutputFormats: Iterator; - - /// The human-readable name of the device. - fn name(&self) -> Result; - - /// An iterator yielding formats that are supported by the backend. - /// - /// Can return an error if the device is no longer valid (eg. it has been disconnected). - fn supported_input_formats(&self) -> Result; - - /// An iterator yielding output 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_output_formats(&self) -> Result; - - /// The default input stream format for the device. - fn default_input_format(&self) -> Result; - - /// The default output stream format for the device. - fn default_output_format(&self) -> Result; -} - -/// Collection of streams managed together. -/// -/// Created with the `Host::event_loop` method. -pub trait EventLoop { - /// The `Device` type yielded by the host. - type Device: Device; - /// The type used to uniquely distinguish between streams. - type StreamId: StreamId; - - /// Creates a new input stream that will run from the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the input stream format is not - /// supported by the device. - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result; - - /// Creates a new output stream that will play on the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the output stream format is not - /// supported by the device. - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result; - - /// Instructs the audio device that it should start playing the stream with the given ID. - /// - /// Has no effect is the stream was already playing. - /// - /// Only call this after you have submitted some data, otherwise you may hear some glitches. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError>; - - /// Instructs the audio device that it should stop playing the stream with the given ID. - /// - /// Has no effect is the stream was already paused. - /// - /// If you call `play` afterwards, the playback will resume where it was. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError>; - - /// Destroys an existing stream. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn destroy_stream(&self, stream: Self::StreamId); - - /// Takes control of the current thread and begins the stream processing. - /// - /// > **Note**: Since it takes control of the thread, this method is best called on a separate - /// > thread. - /// - /// Whenever a stream needs to be fed some data, the closure passed as parameter is called. - /// You can call the other methods of `EventLoop` without getting a deadlock. - fn run(&self, callback: F) -> ! - where - F: FnMut(Self::StreamId, StreamDataResult) + Send; -} - -/// The set of required bounds for host `StreamId` types. -pub trait StreamId: Clone + std::fmt::Debug + PartialEq + Eq {} +pub mod traits; /// A host's device iterator yielding only *input* devices. pub type InputDevices = std::iter::Filter::Item) -> bool>; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 8dcc658..74030a5 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -199,7 +199,7 @@ macro_rules! impl_platform_host { } } - impl crate::Device for Device { + impl crate::traits::DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; @@ -252,7 +252,7 @@ macro_rules! impl_platform_host { } } - impl crate::EventLoop for EventLoop { + impl crate::traits::EventLoopTrait for EventLoop { type StreamId = StreamId; type Device = Device; @@ -335,7 +335,7 @@ macro_rules! impl_platform_host { } } - impl crate::Host for Host { + impl crate::traits::HostTrait for Host { type Devices = Devices; type Device = Device; type EventLoop = EventLoop; @@ -385,7 +385,7 @@ macro_rules! impl_platform_host { } } - impl crate::StreamId for StreamId {} + impl crate::traits::StreamIdTrait for StreamId {} $( impl From for Device { @@ -423,7 +423,7 @@ macro_rules! impl_platform_host { pub fn available_hosts() -> Vec { let mut host_ids = vec![]; $( - if ::is_available() { + if ::is_available() { host_ids.push(HostId::$HostVariant); } )* diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..fb48829 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,198 @@ +//! The suite of traits allowing CPAL to abstract over hosts, devices, event loops and stream IDs. + +use { + BuildStreamError, + DefaultFormatError, + DeviceNameError, + DevicesError, + Format, + InputDevices, + OutputDevices, + PauseStreamError, + PlayStreamError, + StreamDataResult, + SupportedFormat, + SupportedFormatsError, +}; + +/// A **Host** provides access to the available audio devices on the system. +/// +/// Each platform may have a number of available hosts depending on the system, each with their own +/// pros and cons. +/// +/// For example, WASAPI is the standard audio host API that ships with the Windows operating +/// system. However, due to historical limitations with respect to performance and flexibility, +/// Steinberg created the ASIO API providing better audio device support for pro audio and +/// low-latency applications. As a result, it is common for some devices and device capabilities to +/// only be available via ASIO, while others are only available via WASAPI. +/// +/// Another great example is the Linux platform. While the ALSA host API is the lowest-level API +/// available to almost all distributions of Linux, its flexibility is limited as it requires that +/// each process have exclusive access to the devices with which they establish streams. PulseAudio +/// is another popular host API that aims to solve this issue by providing user-space mixing, +/// however it has its own limitations w.r.t. low-latency and high-performance audio applications. +/// JACK is yet another host API that is more suitable to pro-audio applications, however it is +/// less readily available by default in many Linux distributions and is known to be tricky to +/// setup. +pub trait HostTrait { + /// The type used for enumerating available devices by the host. + type Devices: Iterator; + /// The `Device` type yielded by the host. + type Device: DeviceTrait; + /// The event loop type used by the `Host` + type EventLoop: EventLoopTrait; + + /// Whether or not the host is available on the system. + fn is_available() -> bool; + + /// An iterator yielding all `Device`s currently available to the host on the system. + /// + /// Can be empty if the system does not support audio in general. + fn devices(&self) -> Result; + + /// The default input audio device on the system. + /// + /// Returns `None` if no input device is available. + fn default_input_device(&self) -> Option; + + /// The default output audio device on the system. + /// + /// Returns `None` if no output device is available. + fn default_output_device(&self) -> Option; + + /// Initialise the event loop, ready for managing audio streams. + fn event_loop(&self) -> Self::EventLoop; + + /// An iterator yielding all `Device`s currently available to the system that support one or more + /// input stream formats. + /// + /// Can be empty if the system does not support audio input. + fn input_devices(&self) -> Result, DevicesError> { + fn supports_input(device: &D) -> bool { + device.supported_input_formats() + .map(|mut iter| iter.next().is_some()) + .unwrap_or(false) + } + Ok(self.devices()?.filter(supports_input::)) + } + + /// An iterator yielding all `Device`s currently available to the system that support one or more + /// output stream formats. + /// + /// Can be empty if the system does not support audio output. + fn output_devices(&self) -> Result, DevicesError> { + fn supports_output(device: &D) -> bool { + device.supported_output_formats() + .map(|mut iter| iter.next().is_some()) + .unwrap_or(false) + } + Ok(self.devices()?.filter(supports_output::)) + } +} + +/// A device that is capable of audio input and/or output. +/// +/// 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. +pub trait DeviceTrait { + /// The iterator type yielding supported input stream formats. + type SupportedInputFormats: Iterator; + /// The iterator type yielding supported output stream formats. + type SupportedOutputFormats: Iterator; + + /// The human-readable name of the device. + fn name(&self) -> Result; + + /// An iterator yielding formats that are supported by the backend. + /// + /// Can return an error if the device is no longer valid (eg. it has been disconnected). + fn supported_input_formats(&self) -> Result; + + /// An iterator yielding output 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_output_formats(&self) -> Result; + + /// The default input stream format for the device. + fn default_input_format(&self) -> Result; + + /// The default output stream format for the device. + fn default_output_format(&self) -> Result; +} + +/// Collection of streams managed together. +/// +/// Created with the `Host::event_loop` method. +pub trait EventLoopTrait { + /// The `Device` type yielded by the host. + type Device: DeviceTrait; + /// The type used to uniquely distinguish between streams. + type StreamId: StreamIdTrait; + + /// Creates a new input stream that will run from the given device and with the given format. + /// + /// On success, returns an identifier for the stream. + /// + /// Can return an error if the device is no longer valid, or if the input stream format is not + /// supported by the device. + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result; + + /// Creates a new output stream that will play on the given device and with the given format. + /// + /// On success, returns an identifier for the stream. + /// + /// Can return an error if the device is no longer valid, or if the output stream format is not + /// supported by the device. + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result; + + /// Instructs the audio device that it should start playing the stream with the given ID. + /// + /// Has no effect is the stream was already playing. + /// + /// Only call this after you have submitted some data, otherwise you may hear some glitches. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError>; + + /// Instructs the audio device that it should stop playing the stream with the given ID. + /// + /// Has no effect is the stream was already paused. + /// + /// If you call `play` afterwards, the playback will resume where it was. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError>; + + /// Destroys an existing stream. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn destroy_stream(&self, stream: Self::StreamId); + + /// Takes control of the current thread and begins the stream processing. + /// + /// > **Note**: Since it takes control of the thread, this method is best called on a separate + /// > thread. + /// + /// Whenever a stream needs to be fed some data, the closure passed as parameter is called. + /// You can call the other methods of `EventLoop` without getting a deadlock. + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send; +} + +/// The set of required bounds for host `StreamId` types. +pub trait StreamIdTrait: Clone + std::fmt::Debug + PartialEq + Eq {}