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
This commit is contained in:
mitchmindtree 2019-06-28 23:42:07 +02:00
parent 36dee482bd
commit 5e4f384992
12 changed files with 225 additions and 219 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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)]

View File

@ -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;

View File

@ -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};

View File

@ -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<Item = Self::Device>;
/// The `Device` type yielded by the host.
type Device: Device;
/// The event loop type used by the `Host`
type EventLoop: EventLoop<Device = Self::Device>;
/// 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<Self::Devices, DevicesError>;
/// The default input audio device on the system.
///
/// Returns `None` if no input device is available.
fn default_input_device(&self) -> Option<Self::Device>;
/// The default output audio device on the system.
///
/// Returns `None` if no output device is available.
fn default_output_device(&self) -> Option<Self::Device>;
/// 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<InputDevices<Self::Devices>, DevicesError> {
fn supports_input<D: Device>(device: &D) -> bool {
device.supported_input_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_input::<Self::Device>))
}
/// 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<OutputDevices<Self::Devices>, DevicesError> {
fn supports_output<D: Device>(device: &D) -> bool {
device.supported_output_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_output::<Self::Device>))
}
}
/// 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<Item = SupportedFormat>;
/// The iterator type yielding supported output stream formats.
type SupportedOutputFormats: Iterator<Item = SupportedFormat>;
/// The human-readable name of the device.
fn name(&self) -> Result<String, DeviceNameError>;
/// 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<Self::SupportedInputFormats, SupportedFormatsError>;
/// 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<Self::SupportedOutputFormats, SupportedFormatsError>;
/// The default input stream format for the device.
fn default_input_format(&self) -> Result<Format, DefaultFormatError>;
/// The default output stream format for the device.
fn default_output_format(&self) -> Result<Format, DefaultFormatError>;
}
/// 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<Self::StreamId, BuildStreamError>;
/// 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<Self::StreamId, BuildStreamError>;
/// 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<F>(&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<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;

View File

@ -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<crate::host::$host_mod::Device> for Device {
@ -423,7 +423,7 @@ macro_rules! impl_platform_host {
pub fn available_hosts() -> Vec<HostId> {
let mut host_ids = vec![];
$(
if <crate::host::$host_mod::Host as crate::Host>::is_available() {
if <crate::host::$host_mod::Host as crate::traits::HostTrait>::is_available() {
host_ids.push(HostId::$HostVariant);
}
)*

198
src/traits.rs Normal file
View File

@ -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<Item = Self::Device>;
/// The `Device` type yielded by the host.
type Device: DeviceTrait;
/// The event loop type used by the `Host`
type EventLoop: EventLoopTrait<Device = Self::Device>;
/// 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<Self::Devices, DevicesError>;
/// The default input audio device on the system.
///
/// Returns `None` if no input device is available.
fn default_input_device(&self) -> Option<Self::Device>;
/// The default output audio device on the system.
///
/// Returns `None` if no output device is available.
fn default_output_device(&self) -> Option<Self::Device>;
/// 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<InputDevices<Self::Devices>, DevicesError> {
fn supports_input<D: DeviceTrait>(device: &D) -> bool {
device.supported_input_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_input::<Self::Device>))
}
/// 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<OutputDevices<Self::Devices>, DevicesError> {
fn supports_output<D: DeviceTrait>(device: &D) -> bool {
device.supported_output_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_output::<Self::Device>))
}
}
/// 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<Item = SupportedFormat>;
/// The iterator type yielding supported output stream formats.
type SupportedOutputFormats: Iterator<Item = SupportedFormat>;
/// The human-readable name of the device.
fn name(&self) -> Result<String, DeviceNameError>;
/// 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<Self::SupportedInputFormats, SupportedFormatsError>;
/// 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<Self::SupportedOutputFormats, SupportedFormatsError>;
/// The default input stream format for the device.
fn default_input_format(&self) -> Result<Format, DefaultFormatError>;
/// The default output stream format for the device.
fn default_output_format(&self) -> Result<Format, DefaultFormatError>;
}
/// 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<Self::StreamId, BuildStreamError>;
/// 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<Self::StreamId, BuildStreamError>;
/// 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<F>(&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 {}