Port ASIO host to new stream-based API

This commit is contained in:
msiglreith 2019-10-04 18:18:18 +02:00 committed by mitchmindtree
parent 64ba84de81
commit bfda575218
5 changed files with 219 additions and 400 deletions

View File

@ -3,7 +3,7 @@ pub type SupportedInputFormats = std::vec::IntoIter<SupportedFormat>;
pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>; pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::{Mutex, Arc};
use BackendSpecificError; use BackendSpecificError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError; use DeviceNameError;
@ -16,10 +16,14 @@ use SupportedFormatsError;
use super::sys; use super::sys;
/// A ASIO Device /// A ASIO Device
#[derive(Debug)]
pub struct Device { pub struct Device {
/// The driver represented by this device. /// The driver represented by this device.
pub driver: Arc<sys::Driver>, pub driver: Arc<sys::Driver>,
// Input and/or Output stream.
// An driver can only have one of each.
// They need to be created at the same time.
pub asio_streams: Arc<Mutex<Option<sys::AsioStreams>>>,
} }
/// All available devices. /// All available devices.
@ -148,7 +152,11 @@ impl Iterator for Devices {
loop { loop {
match self.drivers.next() { match self.drivers.next() {
Some(name) => match self.asio.load_driver(&name) { Some(name) => match self.asio.load_driver(&name) {
Ok(driver) => return Some(Device { driver: Arc::new(driver) }), Ok(driver) => {
let driver = Arc::new(driver);
let asio_streams = Arc::new(Mutex::new(None));
return Some(Device { driver, asio_streams });
}
Err(_) => continue, Err(_) => continue,
} }
None => return None, None => return None,

View File

@ -8,18 +8,18 @@ use {
Format, Format,
PauseStreamError, PauseStreamError,
PlayStreamError, PlayStreamError,
StreamDataResult,
SupportedFormatsError, SupportedFormatsError,
StreamData,
StreamError,
}; };
use traits::{ use traits::{
DeviceTrait, DeviceTrait,
EventLoopTrait,
HostTrait, HostTrait,
StreamIdTrait, StreamTrait,
}; };
pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats};
pub use self::stream::{EventLoop, StreamId}; pub use self::stream::Stream;
use std::sync::Arc; use std::sync::Arc;
mod device; mod device;
@ -42,7 +42,6 @@ impl Host {
impl HostTrait for Host { impl HostTrait for Host {
type Devices = Devices; type Devices = Devices;
type Device = Device; type Device = Device;
type EventLoop = EventLoop;
fn is_available() -> bool { fn is_available() -> bool {
true true
@ -62,15 +61,12 @@ impl HostTrait for Host {
// ASIO has no concept of a default device, so just use the first. // ASIO has no concept of a default device, so just use the first.
self.output_devices().ok().and_then(|mut ds| ds.next()) self.output_devices().ok().and_then(|mut ds| ds.next())
} }
fn event_loop(&self) -> Self::EventLoop {
EventLoop::new()
}
} }
impl DeviceTrait for Device { impl DeviceTrait for Device {
type SupportedInputFormats = SupportedInputFormats; type SupportedInputFormats = SupportedInputFormats;
type SupportedOutputFormats = SupportedOutputFormats; type SupportedOutputFormats = SupportedOutputFormats;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> { fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self) Device::name(self)
@ -91,46 +87,28 @@ impl DeviceTrait for Device {
fn default_output_format(&self) -> Result<Format, DefaultFormatError> { fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
Device::default_output_format(self) Device::default_output_format(self)
} }
}
impl EventLoopTrait for EventLoop { fn build_input_stream<D, E>(&self, format: &Format, data_callback: D, error_callback: E) -> Result<Self::Stream, BuildStreamError>
type Device = Device;
type StreamId = StreamId;
fn build_input_stream(
&self,
device: &Self::Device,
format: &Format,
) -> Result<Self::StreamId, BuildStreamError> {
EventLoop::build_input_stream(self, device, format)
}
fn build_output_stream(
&self,
device: &Self::Device,
format: &Format,
) -> Result<Self::StreamId, BuildStreamError> {
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<F>(&self, callback: F) -> !
where where
F: FnMut(Self::StreamId, StreamDataResult) + Send, D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static
{ {
EventLoop::run(self, callback) Device::build_input_stream(self, format, data_callback, error_callback)
}
fn build_output_stream<D, E>(&self, format: &Format, data_callback: D, error_callback: E) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static
{
Device::build_output_stream(self, format, data_callback, error_callback)
} }
} }
impl StreamIdTrait for StreamId {} impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
Stream::play(self)
}
fn pause(&self) -> Result<(), PauseStreamError> {
Stream::pause(self)
}
}

View File

@ -4,11 +4,8 @@ extern crate num_traits;
use self::num_traits::PrimInt; use self::num_traits::PrimInt;
use super::Device; use super::Device;
use std; use std;
use std::mem; use std::sync::atomic::{Ordering, AtomicBool};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use BackendSpecificError; use BackendSpecificError;
use BuildStreamError; use BuildStreamError;
use Format; use Format;
@ -16,9 +13,9 @@ use PauseStreamError;
use PlayStreamError; use PlayStreamError;
use SampleFormat; use SampleFormat;
use StreamData; use StreamData;
use StreamDataResult;
use UnknownTypeInputBuffer; use UnknownTypeInputBuffer;
use UnknownTypeOutputBuffer; use UnknownTypeOutputBuffer;
use StreamError;
/// Sample types whose constant silent value is known. /// Sample types whose constant silent value is known.
trait Silence { trait Silence {
@ -34,35 +31,6 @@ trait InterleavedSample: Clone + Copy + Silence {
/// Constraints on the ASIO sample types. /// Constraints on the ASIO sample types.
trait AsioSample: Clone + Copy + Silence + std::ops::Add<Self, Output = Self> {} trait AsioSample: Clone + Copy + Silence + std::ops::Add<Self, Output = Self> {}
/// Controls all streams
pub struct EventLoop {
/// The input and output ASIO streams
asio_streams: Arc<Mutex<sys::AsioStreams>>,
/// List of all CPAL streams
cpal_streams: Arc<Mutex<Vec<Option<Stream>>>>,
/// Total stream count.
stream_count: AtomicUsize,
/// The CPAL callback that the user gives to fill the buffers.
callbacks: Arc<Mutex<Option<&'static mut (FnMut(StreamId, StreamDataResult) + Send)>>>,
}
/// Id for each stream.
/// Created depending on the number they are created.
/// Starting at one! not zero.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct StreamId(usize);
/// CPAL stream.
/// This decouples the many cpal streams
/// from the single input and single output
/// ASIO streams.
/// Each stream can be playing or paused.
struct Stream {
playing: bool,
// The driver associated with this stream.
driver: Arc<sys::Driver>,
}
// Used to keep track of whether or not the current current asio stream buffer requires // Used to keep track of whether or not the current current asio stream buffer requires
// being silencing before summing audio. // being silencing before summing audio.
#[derive(Default)] #[derive(Default)]
@ -71,114 +39,36 @@ struct SilenceAsioBuffer {
second: bool, second: bool,
} }
impl EventLoop { pub struct Stream {
pub fn new() -> EventLoop { playing: Arc<AtomicBool>,
EventLoop { }
asio_streams: Arc::new(Mutex::new(sys::AsioStreams {
input: None, impl Stream {
output: None, pub fn play(&self) -> Result<(), PlayStreamError> {
})), self.playing.store(true, Ordering::SeqCst);
cpal_streams: Arc::new(Mutex::new(Vec::new())), Ok(())
// This is why the Id's count from one not zero
// because at this point there is no streams
stream_count: AtomicUsize::new(0),
callbacks: Arc::new(Mutex::new(None)),
}
} }
/// Create a new CPAL Input Stream. pub fn pause(&self) -> Result<(), PauseStreamError> {
/// self.playing.store(false, Ordering::SeqCst);
/// If there is no existing ASIO Input Stream it will be created. Ok(())
///
/// On success, the buffer size of the stream is returned.
fn get_or_create_input_stream(
&self,
driver: &sys::Driver,
format: &Format,
device: &Device,
) -> Result<usize, BuildStreamError> {
match device.default_input_format() {
Ok(f) => {
let num_asio_channels = f.channels;
check_format(driver, format, num_asio_channels)
},
Err(_) => Err(BuildStreamError::FormatNotSupported),
}?;
let num_channels = format.channels as usize;
let ref mut streams = *self.asio_streams.lock().unwrap();
// Either create a stream if thers none or had back the
// size of the current one.
match streams.input {
Some(ref input) => Ok(input.buffer_size as usize),
None => {
let output = streams.output.take();
driver
.prepare_input_stream(output, num_channels)
.map(|new_streams| {
let bs = match new_streams.input {
Some(ref inp) => inp.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
}).map_err(|ref e| {
println!("Error preparing stream: {}", e);
BuildStreamError::DeviceNotAvailable
})
}
}
} }
}
/// Create a new CPAL Output Stream. // TODO: drop implementation
///
/// If there is no existing ASIO Output Stream it will be created.
///
/// On success, the buffer size of the stream is returned.
fn get_or_create_output_stream(
&self,
driver: &sys::Driver,
format: &Format,
device: &Device,
) -> Result<usize, BuildStreamError> {
match device.default_output_format() {
Ok(f) => {
let num_asio_channels = f.channels;
check_format(driver, format, num_asio_channels)
},
Err(_) => Err(BuildStreamError::FormatNotSupported),
}?;
let num_channels = format.channels as usize;
let ref mut streams = *self.asio_streams.lock().unwrap();
// Either create a stream if there's none or return the size of the current one.
match streams.output {
Some(ref output) => Ok(output.buffer_size as usize),
None => {
let input = streams.input.take();
driver
.prepare_output_stream(input, num_channels)
.map(|new_streams| {
let bs = match new_streams.output {
Some(ref out) => out.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
}).map_err(|ref e| {
println!("Error preparing stream: {}", e);
BuildStreamError::DeviceNotAvailable
})
}
}
}
/// Builds a new cpal input stream impl Device {
pub fn build_input_stream( pub fn build_input_stream<D, E>(
&self, &self,
device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, BuildStreamError> { mut data_callback: D,
let Device { driver, .. } = device; _error_callback: E,
let stream_type = driver.input_data_type().map_err(build_stream_err)?; ) -> Result<Stream, BuildStreamError>
where
D: FnMut(StreamData) + Send + 'static,
E: FnMut(StreamError) + Send + 'static
{
let stream_type = self.driver.input_data_type().map_err(build_stream_err)?;
// Ensure that the desired sample type is supported. // Ensure that the desired sample type is supported.
let data_type = super::device::convert_data_type(&stream_type) let data_type = super::device::convert_data_type(&stream_type)
@ -188,48 +78,28 @@ impl EventLoop {
} }
let num_channels = format.channels.clone(); let num_channels = format.channels.clone();
let stream_buffer_size = self.get_or_create_input_stream(&driver, format, device)?; let asio_stream = self.get_or_create_input_stream(format)?;
let cpal_num_samples = stream_buffer_size * num_channels as usize; let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize;
let count = self.stream_count.fetch_add(1, Ordering::SeqCst);
let asio_streams = self.asio_streams.clone();
let cpal_streams = self.cpal_streams.clone();
let callbacks = self.callbacks.clone();
// Create the buffer depending on the size of the data type. // Create the buffer depending on the size of the data type.
let stream_id = StreamId(count);
let len_bytes = cpal_num_samples * data_type.sample_size(); let len_bytes = cpal_num_samples * data_type.sample_size();
let mut interleaved = vec![0u8; len_bytes]; let mut interleaved = vec![0u8; len_bytes];
let stream_playing = Arc::new(AtomicBool::new(false));
let playing = Arc::clone(&stream_playing);
// Set the input callback. // Set the input callback.
// This is most performance critical part of the ASIO bindings. // This is most performance critical part of the ASIO bindings.
driver.set_callback(move |buffer_index| unsafe { self.driver.set_callback(move |buffer_index| unsafe {
// If not playing return early. // If not playing return early.
// TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. if !playing.load(Ordering::SeqCst) {
if let Some(s) = cpal_streams.lock().unwrap().get(count) { return
if let Some(s) = s {
if !s.playing {
return;
} }
}
}
// Acquire the stream and callback.
let stream_lock = asio_streams.lock().unwrap();
let ref asio_stream = match stream_lock.input {
Some(ref asio_stream) => asio_stream,
None => return,
};
let mut callbacks = callbacks.lock().unwrap();
let callback = match callbacks.as_mut() {
Some(callback) => callback,
None => return,
};
/// 1. Write from the ASIO buffer to the interleaved CPAL buffer. /// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
/// 2. Deliver the CPAL buffer to the user callback. /// 2. Deliver the CPAL buffer to the user callback.
unsafe fn process_input_callback<A, B, F, G>( unsafe fn process_input_callback<A, B, D, F, G>(
stream_id: StreamId, callback: &mut D,
callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send),
interleaved: &mut [u8], interleaved: &mut [u8],
asio_stream: &sys::AsioStream, asio_stream: &sys::AsioStream,
buffer_index: usize, buffer_index: usize,
@ -239,6 +109,7 @@ impl EventLoop {
where where
A: AsioSample, A: AsioSample,
B: InterleavedSample, B: InterleavedSample,
D: FnMut(StreamData) + Send + 'static,
F: Fn(A) -> A, F: Fn(A) -> A,
G: Fn(A) -> B, G: Fn(A) -> B,
{ {
@ -254,29 +125,26 @@ impl EventLoop {
// 2. Deliver the interleaved buffer to the callback. // 2. Deliver the interleaved buffer to the callback.
callback( callback(
stream_id, StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) },
Ok(StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }),
); );
} }
match (&stream_type, data_type) { match (&stream_type, data_type) {
(&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => {
process_input_callback::<i16, i16, _, _>( process_input_callback::<i16, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
from_le, from_le,
std::convert::identity::<i16>, std::convert::identity::<i16>,
); );
} }
(&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => {
process_input_callback::<i16, i16, _, _>( process_input_callback::<i16, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
from_be, from_be,
std::convert::identity::<i16>, std::convert::identity::<i16>,
@ -287,11 +155,10 @@ impl EventLoop {
// trait for the `to_le` and `to_be` methods, but this does not support floats. // trait for the `to_le` and `to_be` methods, but this does not support floats.
(&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) |
(&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => {
process_input_callback::<f32, f32, _, _>( process_input_callback::<f32, f32, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
std::convert::identity::<f32>, std::convert::identity::<f32>,
std::convert::identity::<f32>, std::convert::identity::<f32>,
@ -302,22 +169,20 @@ impl EventLoop {
// `process_output_callback` function above by removing the unnecessary sample // `process_output_callback` function above by removing the unnecessary sample
// conversion function. // conversion function.
(&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => {
process_input_callback::<i32, i16, _, _>( process_input_callback::<i32, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
from_le, from_le,
|s| (s >> 16) as i16, |s| (s >> 16) as i16,
); );
} }
(&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => {
process_input_callback::<i32, i16, _, _>( process_input_callback::<i32, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
from_be, from_be,
|s| (s >> 16) as i16, |s| (s >> 16) as i16,
@ -327,11 +192,10 @@ impl EventLoop {
// trait for the `to_le` and `to_be` methods, but this does not support floats. // trait for the `to_le` and `to_be` methods, but this does not support floats.
(&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) |
(&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => { (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => {
process_input_callback::<f64, f32, _, _>( process_input_callback::<f64, f32, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
std::convert::identity::<f64>, std::convert::identity::<f64>,
|s| s as f32, |s| s as f32,
@ -345,23 +209,23 @@ impl EventLoop {
} }
}); });
// Create stream and set to paused // Immediately start the device?
self.cpal_streams self.driver.start().map_err(build_stream_err)?;
.lock()
.unwrap()
.push(Some(Stream { driver: driver.clone(), playing: false }));
Ok(StreamId(count)) Ok(Stream { playing: stream_playing })
} }
/// Create the an output cpal stream. pub fn build_output_stream<D, E>(
pub fn build_output_stream(
&self, &self,
device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, BuildStreamError> { mut data_callback: D,
let Device { driver, .. } = device; _error_callback: E,
let stream_type = driver.output_data_type().map_err(build_stream_err)?; ) -> Result<Stream, BuildStreamError>
where
D: FnMut(StreamData) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let stream_type = self.driver.output_data_type().map_err(build_stream_err)?;
// Ensure that the desired sample type is supported. // Ensure that the desired sample type is supported.
let data_type = super::device::convert_data_type(&stream_type) let data_type = super::device::convert_data_type(&stream_type)
@ -371,38 +235,22 @@ impl EventLoop {
} }
let num_channels = format.channels.clone(); let num_channels = format.channels.clone();
let stream_buffer_size = self.get_or_create_output_stream(&driver, format, device)?; let asio_stream = self.get_or_create_output_stream(format)?;
let cpal_num_samples = stream_buffer_size * num_channels as usize; let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize;
let count = self.stream_count.fetch_add(1, Ordering::SeqCst);
let asio_streams = self.asio_streams.clone();
let cpal_streams = self.cpal_streams.clone();
let callbacks = self.callbacks.clone();
// Create buffers depending on data type. // Create buffers depending on data type.
let stream_id = StreamId(count);
let len_bytes = cpal_num_samples * data_type.sample_size(); let len_bytes = cpal_num_samples * data_type.sample_size();
let mut interleaved = vec![0u8; len_bytes]; let mut interleaved = vec![0u8; len_bytes];
let mut silence_asio_buffer = SilenceAsioBuffer::default(); let mut silence_asio_buffer = SilenceAsioBuffer::default();
driver.set_callback(move |buffer_index| unsafe { let stream_playing = Arc::new(AtomicBool::new(false));
// If not playing, return early. let playing = Arc::clone(&stream_playing);
// TODO: Don't assume `count` is valid - we should search for the matching `StreamId`.
if let Some(s) = cpal_streams.lock().unwrap().get(count) {
if let Some(s) = s {
if !s.playing {
return ();
}
}
}
// Acquire the stream and callback. self.driver.set_callback(move |buffer_index| unsafe {
let stream_lock = asio_streams.lock().unwrap(); // If not playing, return early.
let ref asio_stream = match stream_lock.output { if !playing.load(Ordering::SeqCst) {
Some(ref asio_stream) => asio_stream, return
None => return, }
};
let mut callbacks = callbacks.lock().unwrap();
let callback = callbacks.as_mut();
// Silence the ASIO buffer that is about to be used. // Silence the ASIO buffer that is about to be used.
// //
@ -430,9 +278,8 @@ impl EventLoop {
/// 2. If required, silence the ASIO buffer. /// 2. If required, silence the ASIO buffer.
/// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer, /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
/// performing endianness conversions as necessary. /// performing endianness conversions as necessary.
unsafe fn process_output_callback<A, B, F, G>( unsafe fn process_output_callback<A, B, D, F, G>(
stream_id: StreamId, callback: &mut D,
callback: Option<&mut &mut (dyn FnMut(StreamId, StreamDataResult) + Send)>,
interleaved: &mut [u8], interleaved: &mut [u8],
silence_asio_buffer: bool, silence_asio_buffer: bool,
asio_stream: &sys::AsioStream, asio_stream: &sys::AsioStream,
@ -443,18 +290,14 @@ impl EventLoop {
where where
A: InterleavedSample, A: InterleavedSample,
B: AsioSample, B: AsioSample,
D: FnMut(StreamData) + Send + 'static,
F: Fn(A) -> B, F: Fn(A) -> B,
G: Fn(B) -> B, G: Fn(B) -> B,
{ {
// 1. Render interleaved buffer from callback. // 1. Render interleaved buffer from callback.
let interleaved: &mut [A] = cast_slice_mut(interleaved); let interleaved: &mut [A] = cast_slice_mut(interleaved);
match callback {
None => interleaved.iter_mut().for_each(|s| *s = A::SILENCE),
Some(callback) => {
let buffer = A::unknown_type_output_buffer(interleaved); let buffer = A::unknown_type_output_buffer(interleaved);
callback(stream_id, Ok(StreamData::Output { buffer })); callback(StreamData::Output { buffer });
}
}
// 2. Silence ASIO channels if necessary. // 2. Silence ASIO channels if necessary.
let n_channels = interleaved.len() / asio_stream.buffer_size as usize; let n_channels = interleaved.len() / asio_stream.buffer_size as usize;
@ -478,24 +321,22 @@ impl EventLoop {
match (data_type, &stream_type) { match (data_type, &stream_type) {
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => {
process_output_callback::<i16, i16, _, _>( process_output_callback::<i16, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
std::convert::identity::<i16>, std::convert::identity::<i16>,
to_le, to_le,
); );
} }
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => {
process_output_callback::<i16, i16, _, _>( process_output_callback::<i16, i16, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
std::convert::identity::<i16>, std::convert::identity::<i16>,
to_be, to_be,
@ -506,12 +347,11 @@ impl EventLoop {
// trait for the `to_le` and `to_be` methods, but this does not support floats. // trait for the `to_le` and `to_be` methods, but this does not support floats.
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) |
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => { (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => {
process_output_callback::<f32, f32, _, _>( process_output_callback::<f32, f32, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
std::convert::identity::<f32>, std::convert::identity::<f32>,
std::convert::identity::<f32>, std::convert::identity::<f32>,
@ -522,24 +362,22 @@ impl EventLoop {
// `process_output_callback` function above by removing the unnecessary sample // `process_output_callback` function above by removing the unnecessary sample
// conversion function. // conversion function.
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => {
process_output_callback::<i16, i32, _, _>( process_output_callback::<i16, i32, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
|s| (s as i32) << 16, |s| (s as i32) << 16,
to_le, to_le,
); );
} }
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => {
process_output_callback::<i16, i32, _, _>( process_output_callback::<i16, i32, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
|s| (s as i32) << 16, |s| (s as i32) << 16,
to_be, to_be,
@ -549,12 +387,11 @@ impl EventLoop {
// trait for the `to_le` and `to_be` methods, but this does not support floats. // trait for the `to_le` and `to_be` methods, but this does not support floats.
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64LSB) |
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64MSB) => { (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64MSB) => {
process_output_callback::<f32, f64, _, _>( process_output_callback::<f32, f64, _, _, _>(
stream_id, &mut data_callback,
callback,
&mut interleaved, &mut interleaved,
silence, silence,
asio_stream, &asio_stream,
buffer_index as usize, buffer_index as usize,
|s| s as f64, |s| s as f64,
std::convert::identity::<f64>, std::convert::identity::<f64>,
@ -568,78 +405,92 @@ impl EventLoop {
} }
}); });
// Create the stream paused // Immediately start the device?
self.cpal_streams self.driver.start().map_err(build_stream_err)?;
.lock()
.unwrap()
.push(Some(Stream { driver: driver.clone(), playing: false }));
// Give the ID based on the stream count Ok(Stream { playing: stream_playing })
Ok(StreamId(count))
} }
/// Play the cpal stream for the given ID. /// Create a new CPAL Input Stream.
pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
let mut streams = self.cpal_streams.lock().unwrap();
if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") {
s.playing = true;
// Calling play when already playing is a no-op
s.driver.start().map_err(play_stream_err)?;
}
Ok(())
}
/// Pause the cpal stream for the given ID.
/// ///
/// Pause the ASIO streams if there are no other CPAL streams playing, as ASIO only allows /// If there is no existing ASIO Input Stream it will be created.
/// stopping the entire driver. ///
pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { /// On success, the buffer size of the stream is returned.
let mut streams = self.cpal_streams.lock().unwrap(); fn get_or_create_input_stream(
let streams_playing = streams.iter() &self,
.filter(|s| s.as_ref().map(|s| s.playing).unwrap_or(false)) format: &Format,
.count(); ) -> Result<sys::AsioStream, BuildStreamError> {
if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { match self.default_input_format() {
if streams_playing <= 1 { Ok(f) => {
s.driver.stop().map_err(pause_stream_err)?; let num_asio_channels = f.channels;
check_format(&self.driver, format, num_asio_channels)
},
Err(_) => Err(BuildStreamError::FormatNotSupported),
}?;
let num_channels = format.channels as usize;
let ref mut streams = *self.asio_streams.lock().unwrap();
match streams {
Some(streams) => match streams.input.take() {
Some(input) => Ok(input),
None => {
println!("ASIO streams have been already created");
Err(BuildStreamError::DeviceNotAvailable)
}
},
None => {
match self.driver.prepare_input_stream(None, num_channels) {
Ok(mut new_streams) => {
let input = new_streams.input.take().expect("missing input stream");
*streams = Some(new_streams);
Ok(input)
}
Err(e) => {
println!("Error preparing stream: {}", e);
Err(BuildStreamError::DeviceNotAvailable)
}
}
} }
s.playing = false;
} }
Ok(())
} }
/// Destroy the cpal stream based on the ID. /// Create a new CPAL Output Stream.
pub fn destroy_stream(&self, stream_id: StreamId) { ///
// TODO: Should we not also remove an ASIO stream here? /// If there is no existing ASIO Output Stream it will be created.
// Yes, and we should update the logic in the callbacks to search for the stream with fn get_or_create_output_stream(
// the matching ID, rather than assuming the index associated with the ID is valid. &self,
let mut streams = self.cpal_streams.lock().unwrap(); format: &Format,
streams.get_mut(stream_id.0).take(); ) -> Result<sys::AsioStream, BuildStreamError> {
match self.default_output_format() {
Ok(f) => {
let num_asio_channels = f.channels;
check_format(&self.driver, format, num_asio_channels)
},
Err(_) => Err(BuildStreamError::FormatNotSupported),
}?;
let num_channels = format.channels as usize;
let ref mut streams = *self.asio_streams.lock().unwrap();
match streams {
Some(streams) => match streams.output.take() {
Some(output) => Ok(output),
None => {
println!("ASIO streams have been already created");
Err(BuildStreamError::DeviceNotAvailable)
}
},
None => {
match self.driver.prepare_output_stream(None, num_channels) {
Ok(mut new_streams) => {
let output = new_streams.output.take().expect("missing output stream");
*streams = Some(new_streams);
Ok(output)
}
Err(e) => {
println!("Error preparing stream: {}", e);
Err(BuildStreamError::DeviceNotAvailable)
}
} }
/// Run the cpal callbacks
pub fn run<F>(&self, mut callback: F) -> !
where
F: FnMut(StreamId, StreamDataResult) + Send,
{
let callback: &mut (FnMut(StreamId, StreamDataResult) + Send) = &mut callback;
// Transmute needed to convince the compiler that the callback has a static lifetime
*self.callbacks.lock().unwrap() = Some(unsafe { mem::transmute(callback) });
loop {
// A sleep here to prevent the loop being
// removed in --release
thread::sleep(Duration::new(1u64, 0u32));
} }
} }
}
/// Clean up if event loop is dropped.
/// Currently event loop is never dropped.
impl Drop for EventLoop {
fn drop(&mut self) {
*self.asio_streams.lock().unwrap() = sys::AsioStreams {
output: None,
input: None,
};
} }
} }
@ -790,25 +641,3 @@ fn build_stream_err(e: sys::AsioError) -> BuildStreamError {
} }
} }
} }
fn pause_stream_err(e: sys::AsioError) -> PauseStreamError {
match e {
sys::AsioError::NoDrivers |
sys::AsioError::HardwareMalfunction => PauseStreamError::DeviceNotAvailable,
err => {
let description = format!("{}", err);
BackendSpecificError { description }.into()
}
}
}
fn play_stream_err(e: sys::AsioError) -> PlayStreamError {
match e {
sys::AsioError::NoDrivers |
sys::AsioError::HardwareMalfunction => PlayStreamError::DeviceNotAvailable,
err => {
let description = format!("{}", err);
BackendSpecificError { description }.into()
}
}
}

View File

@ -202,6 +202,7 @@ pub struct SupportedFormat {
} }
/// Stream data passed to the `EventLoop::run` callback. /// Stream data passed to the `EventLoop::run` callback.
#[derive(Debug)]
pub enum StreamData<'a> { pub enum StreamData<'a> {
Input { Input {
buffer: UnknownTypeInputBuffer<'a>, buffer: UnknownTypeInputBuffer<'a>,
@ -217,6 +218,7 @@ pub enum StreamData<'a> {
/// same way as reading from a `Vec` or any other kind of Rust array. /// same way as reading from a `Vec` or any other kind of Rust array.
// TODO: explain audio stuff in general // TODO: explain audio stuff in general
// TODO: remove the wrapper and just use slices in next major version // TODO: remove the wrapper and just use slices in next major version
#[derive(Debug)]
pub struct InputBuffer<'a, T: 'a> pub struct InputBuffer<'a, T: 'a>
where where
T: Sample, T: Sample,
@ -232,6 +234,7 @@ where
// TODO: explain audio stuff in general // TODO: explain audio stuff in general
// TODO: remove the wrapper and just use slices // TODO: remove the wrapper and just use slices
#[must_use] #[must_use]
#[derive(Debug)]
pub struct OutputBuffer<'a, T: 'a> pub struct OutputBuffer<'a, T: 'a>
where where
T: Sample, T: Sample,
@ -242,6 +245,7 @@ where
/// This is the struct that is provided to you by cpal when you want to read samples from a buffer. /// This is the struct that is provided to you by cpal when you want to read samples from a buffer.
/// ///
/// Since the type of data is only known at runtime, you have to read the right buffer. /// Since the type of data is only known at runtime, you have to read the right buffer.
#[derive(Debug)]
pub enum UnknownTypeInputBuffer<'a> { pub enum UnknownTypeInputBuffer<'a> {
/// Samples whose format is `u16`. /// Samples whose format is `u16`.
U16(InputBuffer<'a, u16>), U16(InputBuffer<'a, u16>),
@ -254,6 +258,7 @@ pub enum UnknownTypeInputBuffer<'a> {
/// This is the struct that is provided to you by cpal when you want to write samples to a buffer. /// This is the struct that is provided to you by cpal when you want to write samples to a buffer.
/// ///
/// Since the type of data is only known at runtime, you have to fill the right buffer. /// Since the type of data is only known at runtime, you have to fill the right buffer.
#[derive(Debug)]
pub enum UnknownTypeOutputBuffer<'a> { pub enum UnknownTypeOutputBuffer<'a> {
/// Samples whose format is `u16`. /// Samples whose format is `u16`.
U16(OutputBuffer<'a, u16>), U16(OutputBuffer<'a, u16>),

View File

@ -457,9 +457,8 @@ mod platform_impl {
pub use crate::host::asio::{ pub use crate::host::asio::{
Device as AsioDevice, Device as AsioDevice,
Devices as AsioDevices, Devices as AsioDevices,
EventLoop as AsioEventLoop, Stream as AsioStream,
Host as AsioHost, Host as AsioHost,
StreamId as AsioStreamId,
SupportedInputFormats as AsioSupportedInputFormats, SupportedInputFormats as AsioSupportedInputFormats,
SupportedOutputFormats as AsioSupportedOutputFormats, SupportedOutputFormats as AsioSupportedOutputFormats,
}; };