diff --git a/examples/beep.rs b/examples/beep.rs index 96b9eaf..629d550 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,10 +1,13 @@ extern crate cpal; extern crate failure; +use cpal::{Device, EventLoop, Host}; + fn main() -> Result<(), failure::Error> { - let device = cpal::default_output_device().expect("failed to find a default output device"); + let host = cpal::default_host(); + let device = host.default_output_device().expect("failed to find a default output device"); let format = device.default_output_format()?; - let event_loop = cpal::EventLoop::new(); + let event_loop = host.event_loop(); let stream_id = event_loop.build_output_stream(&device, &format)?; event_loop.play_stream(stream_id.clone())?; diff --git a/examples/enumerate.rs b/examples/enumerate.rs index ba2e5ab..6832b6d 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,50 +1,60 @@ extern crate cpal; extern crate failure; +use cpal::{Device, Host}; + fn main() -> Result<(), failure::Error> { - let default_in = cpal::default_input_device().map(|e| e.name().unwrap()); - let default_out = cpal::default_output_device().map(|e| e.name().unwrap()); - println!("Default Input Device:\n {:?}", default_in); - println!("Default Output Device:\n {:?}", default_out); + println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS); + let available_hosts = cpal::available_hosts(); + println!("Available hosts:\n {:?}", available_hosts); - let devices = cpal::devices()?; - println!("Devices: "); - for (device_index, device) in devices.enumerate() { - println!("{}. \"{}\"", device_index + 1, device.name()?); + for host_id in available_hosts { + println!("{:?}", host_id); + let host = cpal::host_from_id(host_id)?; + let default_in = host.default_input_device().map(|e| e.name().unwrap()); + let default_out = host.default_output_device().map(|e| e.name().unwrap()); + println!(" Default Input Device:\n {:?}", default_in); + println!(" Default Output Device:\n {:?}", default_out); - // Input formats - if let Ok(fmt) = device.default_input_format() { - println!(" Default input stream format:\n {:?}", fmt); - } - let mut input_formats = match device.supported_input_formats() { - Ok(f) => f.peekable(), - Err(e) => { - println!("Error: {:?}", e); - continue; - }, - }; - if input_formats.peek().is_some() { - println!(" All supported input stream formats:"); - for (format_index, format) in input_formats.enumerate() { - println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + let devices = host.devices()?; + println!(" Devices: "); + for (device_index, device) in devices.enumerate() { + println!(" {}. \"{}\"", device_index + 1, device.name()?); + + // Input formats + if let Ok(fmt) = device.default_input_format() { + println!(" Default input stream format:\n {:?}", fmt); + } + let mut input_formats = match device.supported_input_formats() { + Ok(f) => f.peekable(), + Err(e) => { + println!("Error: {:?}", e); + continue; + }, + }; + if input_formats.peek().is_some() { + println!(" All supported input stream formats:"); + for (format_index, format) in input_formats.enumerate() { + println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + } } - } - // Output formats - if let Ok(fmt) = device.default_output_format() { - println!(" Default output stream format:\n {:?}", fmt); - } - let mut output_formats = match device.supported_output_formats() { - Ok(f) => f.peekable(), - Err(e) => { - println!("Error: {:?}", e); - continue; - }, - }; - if output_formats.peek().is_some() { - println!(" All supported output stream formats:"); - for (format_index, format) in output_formats.enumerate() { - println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + // Output formats + if let Ok(fmt) = device.default_output_format() { + println!(" Default output stream format:\n {:?}", fmt); + } + let mut output_formats = match device.supported_output_formats() { + Ok(f) => f.peekable(), + Err(e) => { + println!("Error: {:?}", e); + continue; + }, + }; + if output_formats.peek().is_some() { + println!(" All supported output stream formats:"); + for (format_index, format) in output_formats.enumerate() { + println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + } } } } diff --git a/examples/feedback.rs b/examples/feedback.rs index 95d634f..4c80d65 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -9,14 +9,17 @@ extern crate cpal; extern crate failure; +use cpal::{Device, EventLoop, Host}; + const LATENCY_MS: f32 = 150.0; fn main() -> Result<(), failure::Error> { - let event_loop = cpal::EventLoop::new(); + let host = cpal::default_host(); + let event_loop = host.event_loop(); // Default devices. - let input_device = cpal::default_input_device().expect("failed to get default input device"); - let output_device = cpal::default_output_device().expect("failed to get default output device"); + let input_device = host.default_input_device().expect("failed to get default input device"); + let output_device = host.default_output_device().expect("failed to get default output device"); println!("Using default input device: \"{}\"", input_device.name()?); println!("Using default output device: \"{}\"", output_device.name()?); diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 66002df..8fa4aaa 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -6,13 +6,18 @@ extern crate cpal; extern crate failure; extern crate hound; +use cpal::{Device, EventLoop, Host}; + fn main() -> Result<(), failure::Error> { + // Use the default host for working with audio devices. + let host = cpal::default_host(); + // Setup the default input device and stream with the default input format. - let device = cpal::default_input_device().expect("Failed to get default input device"); + let device = host.default_input_device().expect("Failed to get default input device"); println!("Default input device: {}", device.name()?); let format = device.default_input_format().expect("Failed to get default input format"); println!("Default input format: {:?}", format); - let event_loop = cpal::EventLoop::new(); + let event_loop = host.event_loop(); let stream_id = event_loop.build_input_stream(&device, &format)?; event_loop.play_stream(stream_id)?; diff --git a/src/alsa/enumerate.rs b/src/host/alsa/enumerate.rs similarity index 100% rename from src/alsa/enumerate.rs rename to src/host/alsa/enumerate.rs diff --git a/src/alsa/mod.rs b/src/host/alsa/mod.rs similarity index 92% rename from src/alsa/mod.rs rename to src/host/alsa/mod.rs index 4d276cc..6b8b2f5 100644 --- a/src/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -7,16 +7,21 @@ 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; use SampleFormat; use SampleRate; +use SupportedFormatsError; use StreamData; use StreamDataResult; use StreamError; +use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; @@ -32,6 +37,109 @@ pub type SupportedOutputFormats = VecIntoIter; mod enumerate; +/// The default linux and freebsd host type. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + // Assume ALSA is always available on linux/freebsd. + true + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} struct Trigger { // [read fd, write fd] @@ -79,7 +187,7 @@ pub struct Device(String); impl Device { #[inline] - pub fn name(&self) -> Result { + fn name(&self) -> Result { Ok(self.0.clone()) } @@ -287,13 +395,13 @@ impl Device { Ok(output.into_iter()) } - pub fn supported_input_formats(&self) -> Result { + fn supported_input_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE) } } - pub fn supported_output_formats(&self) -> Result { + fn supported_output_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK) } @@ -340,11 +448,11 @@ impl Device { } } - pub fn default_input_format(&self) -> Result { + fn default_input_format(&self) -> Result { self.default_format(alsa::SND_PCM_STREAM_CAPTURE) } - pub fn default_output_format(&self) -> Result { + fn default_output_format(&self) -> Result { self.default_format(alsa::SND_PCM_STREAM_PLAYBACK) } } @@ -434,7 +542,7 @@ enum StreamType { Input, Output } impl EventLoop { #[inline] - pub fn new() -> EventLoop { + fn new() -> EventLoop { let pending_command_trigger = Trigger::new(); let mut initial_descriptors = vec![]; @@ -460,7 +568,7 @@ impl EventLoop { } #[inline] - pub fn run(&self, mut callback: F) -> ! + fn run(&self, mut callback: F) -> ! where F: FnMut(StreamId, StreamDataResult) { self.run_inner(&mut callback) @@ -645,7 +753,7 @@ impl EventLoop { panic!("`cpal::EventLoop::run` API currently disallows returning"); } - pub fn build_input_stream( + fn build_input_stream( &self, device: &Device, format: &Format, @@ -724,7 +832,7 @@ impl EventLoop { } } - pub fn build_output_stream( + fn build_output_stream( &self, device: &Device, format: &Format, @@ -805,18 +913,18 @@ impl EventLoop { } #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { + fn destroy_stream(&self, stream_id: StreamId) { self.push_command(Command::DestroyStream(stream_id)); } #[inline] - pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { + fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream_id)); Ok(()) } #[inline] - pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { + fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream(stream_id)); Ok(()) } diff --git a/src/coreaudio/enumerate.rs b/src/host/coreaudio/enumerate.rs similarity index 100% rename from src/coreaudio/enumerate.rs rename to src/host/coreaudio/enumerate.rs diff --git a/src/coreaudio/mod.rs b/src/host/coreaudio/mod.rs similarity index 100% rename from src/coreaudio/mod.rs rename to src/host/coreaudio/mod.rs diff --git a/src/emscripten/mod.rs b/src/host/emscripten/mod.rs similarity index 100% rename from src/emscripten/mod.rs rename to src/host/emscripten/mod.rs diff --git a/src/host/mod.rs b/src/host/mod.rs new file mode 100644 index 0000000..25cfb73 --- /dev/null +++ b/src/host/mod.rs @@ -0,0 +1,10 @@ +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub(crate) mod alsa; +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod coreaudio; +//mod dynamic; +#[cfg(target_os = "emscripten")] +mod emscripten; +mod null; +#[cfg(windows)] +mod wasapi; diff --git a/src/null/mod.rs b/src/host/null/mod.rs similarity index 50% rename from src/null/mod.rs rename to src/host/null/mod.rs index ff5213a..45b6b9b 100644 --- a/src/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,64 +1,35 @@ #![allow(dead_code)] -use std::marker::PhantomData; - 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; +#[derive(Default)] +pub struct Devices; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Device; + pub struct EventLoop; -impl EventLoop { - #[inline] - pub fn new() -> EventLoop { - EventLoop - } - - #[inline] - pub fn run(&self, _callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) - { - loop { /* TODO: don't spin */ } - } - - #[inline] - pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - pub fn destroy_stream(&self, _: StreamId) { - unimplemented!() - } - - #[inline] - pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { - panic!() - } - - #[inline] - pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { - panic!() - } -} +pub struct Host; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamId; -#[derive(Default)] -pub struct Devices; +pub struct SupportedInputFormats; +pub struct SupportedOutputFormats; impl Devices { pub fn new() -> Result { @@ -66,6 +37,107 @@ impl Devices { } } +impl EventLoop { + pub fn new() -> EventLoop { + EventLoop + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + #[inline] + fn name(&self) -> Result { + Ok("null".to_owned()) + } + + #[inline] + fn supported_input_formats(&self) -> Result { + unimplemented!() + } + + #[inline] + fn supported_output_formats(&self) -> Result { + unimplemented!() + } + + #[inline] + fn default_input_format(&self) -> Result { + unimplemented!() + } + + #[inline] + fn default_output_format(&self) -> Result { + unimplemented!() + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + #[inline] + fn run(&self, _callback: F) -> ! + where F: FnMut(StreamId, StreamDataResult) + { + loop { /* TODO: don't spin */ } + } + + #[inline] + fn build_input_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) + } + + #[inline] + fn build_output_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) + } + + #[inline] + fn destroy_stream(&self, _: StreamId) { + unimplemented!() + } + + #[inline] + fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { + panic!() + } + + #[inline] + fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { + panic!() + } +} + +impl HostTrait for Host { + type Device = Device; + type Devices = Devices; + type EventLoop = EventLoop; + + fn is_available() -> bool { + false + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + None + } + + fn default_output_device(&self) -> Option { + None + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl StreamIdTrait for StreamId {} + impl Iterator for Devices { type Item = Device; @@ -75,49 +147,6 @@ impl Iterator for Devices { } } -#[inline] -pub fn default_input_device() -> Option { - None -} - -#[inline] -pub fn default_output_device() -> Option { - None -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Device; - -impl Device { - #[inline] - pub fn supported_input_formats(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn supported_output_formats(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn default_input_format(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn default_output_format(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn name(&self) -> Result { - Ok("null".to_owned()) - } -} - -pub struct SupportedInputFormats; -pub struct SupportedOutputFormats; - impl Iterator for SupportedInputFormats { type Item = SupportedFormat; @@ -135,11 +164,3 @@ impl Iterator for SupportedOutputFormats { None } } - -pub struct InputBuffer<'a, T: 'a> { - marker: PhantomData<&'a T>, -} - -pub struct OutputBuffer<'a, T: 'a> { - marker: PhantomData<&'a mut T>, -} diff --git a/src/wasapi/com.rs b/src/host/wasapi/com.rs similarity index 100% rename from src/wasapi/com.rs rename to src/host/wasapi/com.rs diff --git a/src/wasapi/device.rs b/src/host/wasapi/device.rs similarity index 100% rename from src/wasapi/device.rs rename to src/host/wasapi/device.rs diff --git a/src/wasapi/mod.rs b/src/host/wasapi/mod.rs similarity index 100% rename from src/wasapi/mod.rs rename to src/host/wasapi/mod.rs diff --git a/src/wasapi/stream.rs b/src/host/wasapi/stream.rs similarity index 100% rename from src/wasapi/stream.rs rename to src/host/wasapi/stream.rs diff --git a/src/lib.rs b/src/lib.rs index 0c04b11..b567f27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,19 +2,25 @@ //! //! Here are some concepts cpal exposes: //! -//! - A `Device` is an audio device that may have any number of input and output streams. -//! - A stream is an open audio channel. Input streams allow you to receive audio data, output -//! streams allow you to play audio data. You must choose which `Device` runs your stream before -//! you create one. -//! - An `EventLoop` is a collection of streams being run by one or more `Device`. Each stream must -//! belong to an `EventLoop`, and all the streams that belong to an `EventLoop` are managed -//! together. +//! - A [**Host**](./trait.Host.html) provides access to the available audio devices on the system. +//! Some platforms have more than one host available, but every platform supported by CPAL has at +//! least one [**DefaultHost**](./trait.Host.html) that is guaranteed to be available. +//! - A [**Device**](./trait.Device.html) is an audio device that may have any number of input and +//! output streams. +//! - A stream is an open flow of audio data. Input streams allow you to receive audio data, output +//! streams allow you to play audio data. You must choose which **Device** will run your stream +//! before you can create one. Often, a default device can be retrieved via the **Host**. +//! - An [**EventLoop**](./trait.EventLoop.html) is a collection of streams being run by one or +//! more **Device**s under a single **Host**. Each stream must belong to an **EventLoop**, and +//! all the streams that belong to an **EventLoop** are managed together. //! -//! The first step is to create an `EventLoop`: +//! The first step is to initialise the `Host` (for accessing audio devices) and create an +//! `EventLoop`: //! //! ``` -//! use cpal::EventLoop; -//! let event_loop = EventLoop::new(); +//! use cpal::Host; +//! let host = cpal::default_host(); +//! let event_loop = host.event_loop(); //! ``` //! //! Then choose a `Device`. The easiest way is to use the default input or output `Device` via the @@ -24,7 +30,9 @@ //! stream type on the system. //! //! ```no_run -//! let device = cpal::default_output_device().expect("no output device available"); +//! # use cpal::Host; +//! # let host = cpal::default_host(); +//! let device = host.default_output_device().expect("no output device available"); //! ``` //! //! Before we can create a stream, we must decide what the format of the audio samples is going to @@ -38,7 +46,9 @@ //! > has been disconnected. //! //! ```no_run -//! # let device = cpal::default_output_device().unwrap(); +//! use cpal::{Device, Host}; +//! # let host = cpal::default_host(); +//! # let device = host.default_output_device().unwrap(); //! let mut supported_formats_range = device.supported_output_formats() //! .expect("error while querying formats"); //! let format = supported_formats_range.next() @@ -49,9 +59,11 @@ //! Now that we have everything for the stream, we can create it from our event loop: //! //! ```no_run -//! # let device = cpal::default_output_device().unwrap(); +//! use cpal::{Device, EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); +//! # let device = host.default_output_device().unwrap(); //! # let format = device.supported_output_formats().unwrap().next().unwrap().with_max_sample_rate(); -//! # let event_loop = cpal::EventLoop::new(); //! let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); //! ``` //! @@ -61,15 +73,19 @@ //! Now we must start the stream. This is done with the `play_stream()` method on the event loop. //! //! ```no_run -//! # let event_loop: cpal::EventLoop = return; -//! # let stream_id: cpal::StreamId = return; +//! # use cpal::{EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); +//! # let stream_id = unimplemented!(); //! event_loop.play_stream(stream_id).expect("failed to play_stream"); //! ``` //! //! Now everything is ready! We call `run()` on the `event_loop` to begin processing. //! //! ```no_run -//! # let event_loop = cpal::EventLoop::new(); +//! # use cpal::{EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); //! event_loop.run(move |_stream_id, _stream_result| { //! // react to stream events and read or write stream data here //! }); @@ -87,9 +103,9 @@ //! In this example, we simply fill the given output buffer with zeroes. //! //! ```no_run -//! use cpal::{StreamData, UnknownTypeOutputBuffer}; -//! -//! # let event_loop = cpal::EventLoop::new(); +//! use cpal::{EventLoop, Host, StreamData, UnknownTypeOutputBuffer}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); //! event_loop.run(move |stream_id, stream_result| { //! let stream_data = match stream_result { //! Ok(data) => data, @@ -132,51 +148,203 @@ extern crate lazy_static; #[macro_use] extern crate stdweb; +pub use platform::{ALL_HOSTS, DefaultHost, HostId, available_hosts, default_host, host_from_id}; pub use samples_formats::{Sample, SampleFormat}; -#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", - target_os = "macos", target_os = "ios", target_os = "emscripten")))] -use null as cpal_impl; - use failure::Fail; -use std::fmt; -use std::iter; use std::ops::{Deref, DerefMut}; -mod null; +mod host; +pub mod platform; mod samples_formats; -#[cfg(any(target_os = "linux", target_os = "freebsd"))] -#[path = "alsa/mod.rs"] -mod cpal_impl; +/// 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. PortAudio +/// 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; -#[cfg(windows)] -#[path = "wasapi/mod.rs"] -mod cpal_impl; + /// Whether or not the host is available on the system. + fn is_available() -> bool; -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[path = "coreaudio/mod.rs"] -mod cpal_impl; + /// 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; -#[cfg(target_os = "emscripten")] -#[path = "emscripten/mod.rs"] -mod cpal_impl; + /// The default input audio device on the system. + /// + /// Returns `None` if no input device is available. + fn default_input_device(&self) -> Option; -/// An opaque type that identifies a device that is capable of either audio input or output. + /// 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`. -#[derive(Clone, PartialEq, Eq)] -pub struct Device(cpal_impl::Device); +/// 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 [`new`](struct.EventLoop.html#method.new) method. -pub struct EventLoop(cpal_impl::EventLoop); +/// 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; -/// Identifier of a stream within the `EventLoop`. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(cpal_impl::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 {} + +/// A host's device iterator yielding only *input* devices. +pub type InputDevices = std::iter::Filter::Item) -> bool>; + +/// A host's device iterator yielding only *output* devices. +pub type OutputDevices = std::iter::Filter::Item) -> bool>; /// Number of channels. pub type ChannelCount = u16; @@ -271,26 +439,10 @@ pub enum UnknownTypeOutputBuffer<'a> { F32(OutputBuffer<'a, f32>), } -/// An iterator yielding all `Device`s currently available to the system. -/// -/// See [`devices()`](fn.devices.html). -pub struct Devices(cpal_impl::Devices); - -/// A `Devices` yielding only *input* devices. -pub type InputDevices = iter::Filter bool>; - -/// A `Devices` yielding only *output* devices. -pub type OutputDevices = iter::Filter bool>; - -/// An iterator that produces a list of input stream formats supported by the device. -/// -/// See [`Device::supported_input_formats()`](struct.Device.html#method.supported_input_formats). -pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats); - -/// An iterator that produces a list of output stream formats supported by the device. -/// -/// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats). -pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); +/// The requested host, although supported on this platform, is unavailable. +#[derive(Clone, Debug, Fail)] +#[fail(display = "the requested host is unavailable")] +pub struct HostUnavailable; /// Some error has occurred that is specific to the backend from which it was produced. /// @@ -440,191 +592,6 @@ pub enum StreamError { } } -/// An iterator yielding all `Device`s currently available to the system. -/// -/// Can be empty if the system does not support audio in general. -#[inline] -pub fn devices() -> Result { - Ok(Devices(cpal_impl::Devices::new()?)) -} - -/// 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. -pub fn input_devices() -> Result { - fn supports_input(device: &Device) -> bool { - device.supported_input_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(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. -pub fn output_devices() -> Result { - fn supports_output(device: &Device) -> bool { - device.supported_output_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(devices()?.filter(supports_output)) -} - -/// The default input audio device on the system. -/// -/// Returns `None` if no input device is available. -pub fn default_input_device() -> Option { - cpal_impl::default_input_device().map(Device) -} - -/// The default output audio device on the system. -/// -/// Returns `None` if no output device is available. -pub fn default_output_device() -> Option { - cpal_impl::default_output_device().map(Device) -} - -impl Device { - /// The human-readable name of the device. - #[inline] - pub fn name(&self) -> Result { - self.0.name() - } - - /// 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). - #[inline] - pub fn supported_input_formats(&self) -> Result { - Ok(SupportedInputFormats(self.0.supported_input_formats()?)) - } - - /// 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). - #[inline] - pub fn supported_output_formats(&self) -> Result { - Ok(SupportedOutputFormats(self.0.supported_output_formats()?)) - } - - /// The default input stream format for the device. - #[inline] - pub fn default_input_format(&self) -> Result { - self.0.default_input_format() - } - - /// The default output stream format for the device. - #[inline] - pub fn default_output_format(&self) -> Result { - self.0.default_output_format() - } -} - -impl fmt::Debug for Device { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl EventLoop { - /// Initializes a new events loop. - #[inline] - pub fn new() -> EventLoop { - EventLoop(cpal_impl::EventLoop::new()) - } - - /// 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. - #[inline] - pub fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - self.0.build_input_stream(&device.0, format).map(StreamId) - } - - /// 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. - #[inline] - pub fn build_output_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - self.0.build_output_stream(&device.0, format).map(StreamId) - } - - /// 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. - /// - #[inline] - pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { - self.0.play_stream(stream.0) - } - - /// 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. - /// - #[inline] - pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { - self.0.pause_stream(stream.0) - } - - /// Destroys an existing stream. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - /// - #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { - self.0.destroy_stream(stream_id.0) - } - - /// 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. - #[inline] - pub fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) + Send - { - self.0.run(move |id, data| callback(StreamId(id), data)) - } -} - impl SupportedFormat { /// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate. #[inline] @@ -775,48 +742,6 @@ impl From for SupportedFormat { } } -impl Iterator for Devices { - type Item = Device; - - #[inline] - fn next(&mut self) -> Option { - self.0.next().map(Device) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - -impl Iterator for SupportedInputFormats { - type Item = SupportedFormat; - - #[inline] - fn next(&mut self) -> Option { - self.0.next() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - -impl Iterator for SupportedOutputFormats { - type Item = SupportedFormat; - - #[inline] - fn next(&mut self) -> Option { - self.0.next() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - impl From for DevicesError { fn from(err: BackendSpecificError) -> Self { DevicesError::BackendSpecific { err } diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..6ba5324 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,457 @@ +//! Platform-specific items. +//! +//! This module also contains the implementation of the platform's dynamically dispatched `Host` +//! type and its associated `EventLoop`, `Device`, `StreamId` and other associated types. These +//! types are useful in the case that users require switching between audio host APIs at runtime. + +// A macro to assist with implementing a platform's dynamically dispatched `Host` type. +// +// These dynamically dispatched types are necessary to allow for users to switch between hosts at +// runtime. +// +// For example the invocation `impl_platform_host(Wasapi wasapi, Asio asio)`, this macro should +// expand to: +// +// ``` +// pub enum HostId { +// Wasapi, +// Asio, +// } +// +// pub enum Host { +// Wasapi(crate::host::wasapi::Host), +// Asio(crate::host::asio::Host), +// } +// ``` +// +// And so on for Device, Devices, EventLoop, Host, StreamId, SupportedInputFormats, +// SupportedOutputFormats and all their necessary trait implementations. +// ``` +macro_rules! impl_platform_host { + ($($HostVariant:ident $host_mod:ident),*) => { + /// All hosts supported by CPAL on this platform. + pub const ALL_HOSTS: &'static [HostId] = &[ + $( + HostId::$HostVariant, + )* + ]; + + /// The platform's dynamically dispatched **Host** type. + /// + /// An instance of this **Host** type may represent one of any of the **Host**s available + /// on the platform. + /// + /// Use this type if you require switching between available hosts at runtime. + /// + /// This type may be constructed via the **host_from_id** function. **HostId**s may + /// be acquired via the **ALL_HOSTS** const and the **available_hosts** function. + pub struct Host(HostInner); + + /// The **Device** implementation associated with the platform's dynamically dispatched + /// **Host** type. + pub struct Device(DeviceInner); + + /// The **Devices** iterator associated with the platform's dynamically dispatched **Host** + /// type. + pub struct Devices(DevicesInner); + + /// The **EventLoop** implementation associated with the platform's dynamically dispatched + /// **Host** type. + pub struct EventLoop(EventLoopInner); + + /// The **StreamId** implementation associated with the platform's dynamically dispatched + /// **Host** type. + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct StreamId(StreamIdInner); + + /// The **SupportedInputFormats** iterator associated with the platform's dynamically + /// dispatched **Host** type. + pub struct SupportedInputFormats(SupportedInputFormatsInner); + + /// The **SupportedOutputFormats** iterator associated with the platform's dynamically + /// dispatched **Host** type. + pub struct SupportedOutputFormats(SupportedOutputFormatsInner); + + /// Unique identifier for available hosts on the platform. + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] + pub enum HostId { + $( + $HostVariant, + )* + } + + enum DeviceInner { + $( + $HostVariant(crate::host::$host_mod::Device), + )* + } + + enum DevicesInner { + $( + $HostVariant(crate::host::$host_mod::Devices), + )* + } + + enum EventLoopInner { + $( + $HostVariant(crate::host::$host_mod::EventLoop), + )* + } + + enum HostInner { + $( + $HostVariant(crate::host::$host_mod::Host), + )* + } + + #[derive(Clone, Debug, Eq, PartialEq)] + enum StreamIdInner { + $( + $HostVariant(crate::host::$host_mod::StreamId), + )* + } + + enum SupportedInputFormatsInner { + $( + $HostVariant(crate::host::$host_mod::SupportedInputFormats), + )* + } + + enum SupportedOutputFormatsInner { + $( + $HostVariant(crate::host::$host_mod::SupportedOutputFormats), + )* + } + + impl Host { + /// The unique identifier associated with this host. + pub fn id(&self) -> HostId { + match self.0 { + $( + HostInner::$HostVariant(_) => HostId::$HostVariant, + )* + } + } + } + + impl Iterator for Devices { + type Item = Device; + + fn next(&mut self) -> Option { + match self.0 { + $( + DevicesInner::$HostVariant(ref mut d) => { + d.next().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + DevicesInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl Iterator for SupportedInputFormats { + type Item = crate::SupportedFormat; + + fn next(&mut self) -> Option { + match self.0 { + $( + SupportedInputFormatsInner::$HostVariant(ref mut s) => s.next(), + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + SupportedInputFormatsInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl Iterator for SupportedOutputFormats { + type Item = crate::SupportedFormat; + + fn next(&mut self) -> Option { + match self.0 { + $( + SupportedOutputFormatsInner::$HostVariant(ref mut s) => s.next(), + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + SupportedOutputFormatsInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl crate::Device for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.name(), + )* + } + } + + fn supported_input_formats(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => { + d.supported_input_formats() + .map(SupportedInputFormatsInner::$HostVariant) + .map(SupportedInputFormats) + } + )* + } + } + + fn supported_output_formats(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => { + d.supported_output_formats() + .map(SupportedOutputFormatsInner::$HostVariant) + .map(SupportedOutputFormats) + } + )* + } + } + + fn default_input_format(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.default_input_format(), + )* + } + } + + fn default_output_format(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.default_output_format(), + )* + } + } + } + + impl crate::EventLoop for EventLoop { + type StreamId = StreamId; + type Device = Device; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &crate::Format, + ) -> Result { + match (&self.0, &device.0) { + $( + (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { + e.build_input_stream(d, format) + .map(StreamIdInner::$HostVariant) + .map(StreamId) + } + )* + } + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &crate::Format, + ) -> Result { + match (&self.0, &device.0) { + $( + (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { + e.build_output_stream(d, format) + .map(StreamIdInner::$HostVariant) + .map(StreamId) + } + )* + } + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), crate::PlayStreamError> { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.play_stream(s) + } + )* + } + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), crate::PauseStreamError> { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.pause_stream(s) + } + )* + } + } + + fn destroy_stream(&self, stream: Self::StreamId) { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.destroy_stream(s) + } + )* + } + } + + fn run(&self, mut callback: F) -> ! + where + F: FnMut(Self::StreamId, crate::StreamDataResult) + Send + { + match self.0 { + $( + EventLoopInner::$HostVariant(ref e) => { + e.run(|id, result| { + let result = result; + callback(StreamId(StreamIdInner::$HostVariant(id)), result); + }); + }, + )* + } + } + } + + impl crate::Host for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + $( crate::host::$host_mod::Host::is_available() ||)* false + } + + fn devices(&self) -> Result { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.devices().map(DevicesInner::$HostVariant).map(Devices) + } + )* + } + } + + fn default_input_device(&self) -> Option { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.default_input_device().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn default_output_device(&self) -> Option { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.default_output_device().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn event_loop(&self) -> Self::EventLoop { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + EventLoop(EventLoopInner::$HostVariant(h.event_loop())) + } + )* + } + } + } + + impl crate::StreamId for StreamId {} + + /// Produces a list of hosts that are currently available on the system. + pub fn available_hosts() -> Vec { + let mut host_ids = vec![]; + $( + if ::is_available() { + host_ids.push(HostId::$HostVariant); + } + )* + host_ids + } + + /// Given a unique host identifier, initialise and produce the host if it is available. + pub fn host_from_id(id: HostId) -> Result { + match id { + $( + HostId::$HostVariant => { + crate::host::$host_mod::Host::new() + .map(HostInner::$HostVariant) + .map(Host) + } + )* + } + } + }; +} + +// TODO: Add pulseaudio and jack here eventually. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +impl_platform_host!(Alsa alsa); + +#[cfg(any(target_os = "macos", target_os = "ios"))] +impl_platform_host!(CoreAudio coreaudio); + +#[cfg(target_os = "emscripten")] +impl_platform_host!(Emscripten emscripten); + +// TODO: Add `Asio asio` once #221 lands. +#[cfg(windows)] +impl_platform_host!(Wasapi wasapi); + +#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", + target_os = "ios", target_os = "emscripten")))] +impl_platform_host!(Null null); + +/// The default host for the current compilation target platform. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub type DefaultHost = crate::host::alsa::Host; + +/// The default host for the current compilation target platform. +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub type DefaultHost = crate::host::coreaudio::Host; + +/// The default host for the current compilation target platform. +#[cfg(target_os = "emscripten")] +pub type DefaultHost = crate::host::emscripten::Host; + +#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", + target_os = "ios", target_os = "emscripten")))] +pub type DefaultHost = crate::host::null::Host; + +/// The default host for the current compilation target platform. +#[cfg(windows)] +pub type DefaultHost = crate::host::wasapi::Host; + +/// Retrieve the default host for the system. +/// +/// There should *always* be a default host for each of the supported target platforms, regardless +/// of whether or not there are any available audio devices. +pub fn default_host() -> DefaultHost { + DefaultHost::new().expect("the default host should always be available") +}