From 616974353b5d17446d185cd7fd8acb8161b0ee83 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 6 Nov 2018 12:15:17 +1100 Subject: [PATCH] opt in and bug fix --- Cargo.toml | 7 +- asio-sys/build.rs | 1 + asio-sys/src/{ => bindings}/asio_import.rs | 0 asio-sys/src/{ => bindings}/errors.rs | 0 asio-sys/src/bindings/mod.rs | 884 ++++++++++++++++++++ asio-sys/src/lib.rs | 887 +-------------------- build.rs | 12 + src/os/windows/mod.rs | 1 + src/platform/windows/asio/device.rs | 41 +- 9 files changed, 928 insertions(+), 905 deletions(-) rename asio-sys/src/{ => bindings}/asio_import.rs (100%) rename asio-sys/src/{ => bindings}/errors.rs (100%) create mode 100644 asio-sys/src/bindings/mod.rs create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 7b1f2dc..4c191d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ documentation = "https://docs.rs/cpal" license = "Apache-2.0" keywords = ["audio", "sound"] +[profile.release] +debug = true + [dependencies] failure = "0.1.5" lazy_static = "1.3" @@ -16,11 +19,9 @@ num-traits = "0.2.6" [dev-dependencies] hound = "3.4" -[target.'cfg(any(target_os = "windows" ))'.dependencies] -asio-sys = { version = "0.1", path = "asio-sys" } - [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "std", "synchapi", "winuser"] } +asio-sys = { version = "0.1", path = "asio-sys" } [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies] alsa-sys = { version = "0.1", path = "alsa-sys" } diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 8d2be50..0a56362 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -38,6 +38,7 @@ fn main() { println!("cargo:rustc-link-lib=dylib=User32"); println!("cargo:rustc-link-search={}", out_dir.display()); println!("cargo:rustc-link-lib=static=asio"); + println!("cargo:rustc-cfg=asio"); // Check if bindings exist // if they dont create them diff --git a/asio-sys/src/asio_import.rs b/asio-sys/src/bindings/asio_import.rs similarity index 100% rename from asio-sys/src/asio_import.rs rename to asio-sys/src/bindings/asio_import.rs diff --git a/asio-sys/src/errors.rs b/asio-sys/src/bindings/errors.rs similarity index 100% rename from asio-sys/src/errors.rs rename to asio-sys/src/bindings/errors.rs diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs new file mode 100644 index 0000000..fada89c --- /dev/null +++ b/asio-sys/src/bindings/mod.rs @@ -0,0 +1,884 @@ +mod asio_import; +#[macro_use] +pub mod errors; + +use self::errors::{AsioDriverError, AsioError, AsioErrorWrapper}; +use std::ffi::CStr; +use std::ffi::CString; +use std::mem; +use std::os::raw::{c_char, c_double, c_long, c_void}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Mutex, MutexGuard}; +use std; +use num; + +// Bindings import +use self::asio_import as ai; + +// TODO I dont think this is needed anymore +/* +pub struct CbArgs { + pub stream_id: S, + pub data: D, +} +*/ + +/// Holds the pointer to the callbacks that come from cpal +struct BufferCallback(Box); + +/// A global way to access all the callbacks. +/// This is required because of how ASIO +/// calls the buffer_switch function. +/// Options are used so that when a callback is +/// removed we don't change the Vec indicies. +/// The indicies are how we match a callback +/// with a stream. +lazy_static! { + static ref buffer_callback: Mutex>> = Mutex::new(Vec::new()); +} + +/// Globally available state of the ASIO driver. +/// This allows all calls the the driver to ensure +/// they are calling in the correct state. +/// It also prevents multiple calls happening at once. +lazy_static! { + static ref ASIO_DRIVERS: Mutex = Mutex::new(AsioWrapper { + state: AsioState::Offline, + }); +} + +/// Count of active device and streams. +/// Used to clean up the driver connection +/// when there are no active connections. +static STREAM_DRIVER_COUNT: AtomicUsize = AtomicUsize::new(0); +/// Tracks which buffer needs to be silenced. +pub static SILENCE_FIRST: AtomicBool = AtomicBool::new(false); +pub static SILENCE_SECOND: AtomicBool = AtomicBool::new(false); + +/// Amount of input and output +/// channels available. +#[derive(Debug)] +pub struct Channel { + pub ins: i64, + pub outs: i64, +} + +/// Sample rate of the ASIO device. +#[derive(Debug)] +pub struct SampleRate { + pub rate: u32, +} + +/// A marker type to make sure +/// all calls to the driver have an +/// active connection. +#[derive(Debug, Clone)] +pub struct Drivers; + +/// Tracks the current state of the +/// ASIO drivers. +#[derive(Debug)] +struct AsioWrapper { + state: AsioState, +} + +/// All possible states of the +/// ASIO driver. Mapped to the +/// FSM in the ASIO SDK docs. +#[derive(Debug)] +enum AsioState { + Offline, + Loaded, + Initialized, + Prepared, + Running, +} + +/// Input and Output streams. +/// There is only ever max one +/// input and one output. Only one +/// is required. +pub struct AsioStreams { + pub input: Option, + pub output: Option, +} + +/// A stream to ASIO. +/// Contains the buffers. +pub struct AsioStream { + /// A Double buffer per channel + pub buffer_infos: Vec, + /// Size of each buffer + pub buffer_size: i32, +} + +/// All the possible types from ASIO. +/// This is a direct copy of the ASIOSampleType +/// inside ASIO SDK. +#[derive(Debug, FromPrimitive)] +#[repr(C)] +pub enum AsioSampleType { + ASIOSTInt16MSB = 0, + ASIOSTInt24MSB = 1, // used for 20 bits as well + ASIOSTInt32MSB = 2, + ASIOSTFloat32MSB = 3, // IEEE 754 32 bit float + ASIOSTFloat64MSB = 4, // IEEE 754 64 bit double float + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can be more easily used with these + ASIOSTInt32MSB16 = 8, // 32 bit data with 16 bit alignment + ASIOSTInt32MSB18 = 9, // 32 bit data with 18 bit alignment + ASIOSTInt32MSB20 = 10, // 32 bit data with 20 bit alignment + ASIOSTInt32MSB24 = 11, // 32 bit data with 24 bit alignment + + ASIOSTInt16LSB = 16, + ASIOSTInt24LSB = 17, // used for 20 bits as well + ASIOSTInt32LSB = 18, + ASIOSTFloat32LSB = 19, // IEEE 754 32 bit float, as found on Intel x86 architecture + ASIOSTFloat64LSB = 20, // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + ASIOSTInt32LSB16 = 24, // 32 bit data with 18 bit alignment + ASIOSTInt32LSB18 = 25, // 32 bit data with 18 bit alignment + ASIOSTInt32LSB20 = 26, // 32 bit data with 20 bit alignment + ASIOSTInt32LSB24 = 27, // 32 bit data with 24 bit alignment + + // ASIO DSD format. + ASIOSTDSDInt8LSB1 = 32, // DSD 1 bit data, 8 samples per byte. First sample in Least significant bit. + ASIOSTDSDInt8MSB1 = 33, // DSD 1 bit data, 8 samples per byte. First sample in Most significant bit. + ASIOSTDSDInt8NER8 = 40, // DSD 8 bit data, 1 sample per byte. No Endianness required. + + ASIOSTLastEntry, +} + +/// Gives information about buffers +/// Receives pointers to buffers +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct AsioBufferInfo { + /// 0 for output 1 for input + pub is_input: c_long, + /// Which channel. Starts at 0 + pub channel_num: c_long, + /// Pointer to each half of the double buffer. + pub buffers: [*mut c_void; 2], +} + +/// Callbacks that ASIO calls +#[repr(C)] +struct AsioCallbacks { + buffer_switch: extern "C" fn(double_buffer_index: c_long, direct_process: c_long) -> (), + sample_rate_did_change: extern "C" fn(s_rate: c_double) -> (), + asio_message: + extern "C" fn(selector: c_long, value: c_long, message: *mut (), opt: *mut c_double) + -> c_long, + buffer_switch_time_info: extern "C" fn( + params: *mut ai::ASIOTime, + double_buffer_index: c_long, + direct_process: c_long, + ) -> *mut ai::ASIOTime, +} + +/// This is called by ASIO. +/// Here we run the callback for each stream. +/// double_buffer_index is either 0 or 1 +/// indicating which buffer to fill +extern "C" fn buffer_switch(double_buffer_index: c_long, _direct_process: c_long) -> () { + // This lock is probably unavoidable + // but locks in the audio stream is not great + let mut bcs = buffer_callback.lock().unwrap(); + + for mut bc in bcs.iter_mut() { + if let Some(ref mut bc) = bc { + bc.run(double_buffer_index); + } + } +} + +/// Idicates the sample rate has changed +/// TODO Change the sample rate when this +/// is called. +extern "C" fn sample_rate_did_change(_s_rate: c_double) -> () {} + +/// Messages for ASIO +/// This is not currently used +extern "C" fn asio_message( + _selector: c_long, _value: c_long, _message: *mut (), _opt: *mut c_double, +) -> c_long { + // TODO Impliment this to give proper responses + 4 as c_long +} + +/// Similar to buffer switch but with time info +/// Not currently used +extern "C" fn buffer_switch_time_info( + params: *mut ai::ASIOTime, _double_buffer_index: c_long, _direct_process: c_long, +) -> *mut ai::ASIOTime { + params +} + +/// Helper function for getting the drivers. +/// Note this is a lock. +fn get_drivers() -> MutexGuard<'static, AsioWrapper> { + ASIO_DRIVERS.lock().unwrap() +} + +impl Drivers { + /// Load the drivers from a driver name. + /// This will destroy the old drivers. + #[allow(unused_assignments)] + pub fn load(driver_name: &str) -> Result { + let mut drivers = get_drivers(); + // Make owned CString to send to load driver + let mut my_driver_name = CString::new(driver_name).expect("Can't go from str to CString"); + let raw = my_driver_name.into_raw(); + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + unsafe { + // Destroy old drivers and load new drivers. + let load_result = drivers.load(raw); + // Take back ownership + my_driver_name = CString::from_raw(raw); + if load_result { + // Initialize ASIO + match drivers.asio_init(&mut driver_info) { + Ok(_) => { + // If it worked then add a active connection to the drivers + // TODO Make sure this is decremented when old drivers are dropped + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + Ok(Drivers) + }, + Err(_) => Err(AsioDriverError::DriverLoadError), + } + } else { + Err(AsioDriverError::DriverLoadError) + } + } + } + + /// Returns the number of input and output + /// channels for the active drivers + pub fn get_channels(&self) -> Channel { + let channel: Channel; + + // Initialize memory for calls + let mut ins: c_long = 0; + let mut outs: c_long = 0; + unsafe { + get_drivers() + .asio_get_channels(&mut ins, &mut outs) + // TODO pass this result along + // and handle it without panic + .expect("failed to get channels"); + channel = Channel { + ins: ins as i64, + outs: outs as i64, + }; + } + + channel + } + + /// Get current sample rate of the active drivers + pub fn get_sample_rate(&self) -> SampleRate { + let sample_rate: SampleRate; + + // Initialize memory for calls + let mut rate: c_double = 0.0f64; + + unsafe { + get_drivers() + .asio_get_sample_rate(&mut rate) + // TODO pass this result along + // and handle it without panic + .expect("failed to get sample rate"); + sample_rate = SampleRate { rate: rate as u32 }; + } + + sample_rate + } + + /// Set the sample rate for the active drivers + pub fn set_sample_rate(&self, sample_rate: u32) -> Result<(), AsioError> { + // Initialize memory for calls + let rate: c_double = c_double::from(sample_rate); + + unsafe { get_drivers().asio_set_sample_rate(rate) } + } + + /// Can the drivers accept the given sample rate + pub fn can_sample_rate(&self, sample_rate: u32) -> bool { + // Initialize memory for calls + let rate: c_double = c_double::from(sample_rate); + + // TODO this gives an error is it can't handle the sample + // rate but it can also give error for no divers + // Both should be handled. + unsafe { get_drivers().asio_can_sample_rate(rate).is_ok() } + } + + /// Get the current data type of the active drivers. + /// Just queries a single channels type as all channels + /// have the same sample type. + pub fn get_data_type(&self) -> Result { + // TODO make this a seperate call for input and output as + // it is possible that input and output have different sample types + // Initialize memory for calls + let mut channel_info = ai::ASIOChannelInfo { + // Which channel we are querying + channel: 0, + // Was it input or output + isInput: 0, + // Was it active + isActive: 0, + channelGroup: 0, + // The sample type + type_: 0, + name: [0 as c_char; 32], + }; + unsafe { + match get_drivers().asio_get_channel_info(&mut channel_info) { + Ok(_) => num::FromPrimitive::from_i32(channel_info.type_) + .map_or(Err(AsioDriverError::TypeError), |t| Ok(t)), + Err(e) => { + println!("Error getting data type {}", e); + Err(AsioDriverError::DriverLoadError) + }, + } + } + } + + /// Prepare the input stream. + /// Because only the latest call + /// to ASIOCreateBuffers is relevant this + /// call will destroy all past active buffers + /// and recreate them. For this reason we take + /// the output stream if it exists. + /// num_channels is the number of input channels. + /// This returns a full AsioStreams with both input + /// and output if output was active. + pub fn prepare_input_stream( + &self, output: Option, num_channels: usize, + ) -> Result { + let buffer_infos = (0 .. num_channels) + .map(|i| AsioBufferInfo { + // These are output channels + is_input: 1, + // Channel index + channel_num: i as c_long, + // Double buffer. We don't know the type + // at this point + buffers: [std::ptr::null_mut(); 2], + }).collect(); + + // Create the streams + let streams = AsioStreams { + input: Some(AsioStream { + buffer_infos, + buffer_size: 0, + }), + output, + }; + self.create_streams(streams) + } + + /// Prepare the output stream. + /// Because only the latest call + /// to ASIOCreateBuffers is relevant this + /// call will destroy all past active buffers + /// and recreate them. For this reason we take + /// the input stream if it exists. + /// num_channels is the number of output channels. + /// This returns a full AsioStreams with both input + /// and output if input was active. + pub fn prepare_output_stream( + &self, input: Option, num_channels: usize, + ) -> Result { + // Initialize data for FFI + let buffer_infos = (0 .. num_channels) + .map(|i| AsioBufferInfo { + // These are outputs + is_input: 0, + // Channel index + channel_num: i as c_long, + // Pointer to each buffer. We don't know + // the type yet. + buffers: [std::ptr::null_mut(); 2], + }).collect(); + + // Create streams + let streams = AsioStreams { + output: Some(AsioStream { + buffer_infos, + buffer_size: 0, + }), + input, + }; + self.create_streams(streams) + } + + /// Creates the streams. + /// Both input and output streams + /// need to be created together as + /// a single slice of ASIOBufferInfo + fn create_streams(&self, streams: AsioStreams) -> Result { + let AsioStreams { input, output } = streams; + match (input, output) { + // Both stream exist. + (Some(input), Some(mut output)) => { + let split_point = input.buffer_infos.len(); + let mut bi = input.buffer_infos; + // Append the output to the input channels + bi.append(&mut output.buffer_infos); + // Create the buffers. + // if successful then split the output + // and input again + self.create_buffers(bi).map(|(mut bi, buffer_size)| { + let out_bi = bi.split_off(split_point); + let in_bi = bi; + let output = Some(AsioStream { + buffer_infos: out_bi, + buffer_size, + }); + let input = Some(AsioStream { + buffer_infos: in_bi, + buffer_size, + }); + AsioStreams { output, input } + }) + }, + // Just input + (Some(input), None) => { + self.create_buffers(input.buffer_infos) + .map(|(buffer_infos, buffer_size)| { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + AsioStreams { + input: Some(AsioStream { + buffer_infos, + buffer_size, + }), + output: None, + } + }) + }, + // Just output + (None, Some(output)) => { + self.create_buffers(output.buffer_infos) + .map(|(buffer_infos, buffer_size)| { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + AsioStreams { + output: Some(AsioStream { + buffer_infos, + buffer_size, + }), + input: None, + } + }) + }, + // Impossible + (None, None) => panic!("Trying to create streams without preparing"), + } + } + + /// Ask ASIO to allocate the buffers + /// and give the callback pointers. + /// This will destroy any already allocated + /// buffers. + /// The prefered buffer size from ASIO is used. + fn create_buffers( + &self, buffer_infos: Vec, + ) -> Result<(Vec, c_long), AsioDriverError> { + let num_channels = buffer_infos.len(); + let callbacks = AsioCallbacks { + buffer_switch: buffer_switch, + sample_rate_did_change: sample_rate_did_change, + asio_message: asio_message, + buffer_switch_time_info: buffer_switch_time_info, + }; + + let mut min_b_size: c_long = 0; + let mut max_b_size: c_long = 0; + let mut pref_b_size: c_long = 0; + let mut grans: c_long = 0; + + let mut drivers = get_drivers(); + + unsafe { + // Get the buffer sizes + // min possilbe size + // max possible size + // preferred size + // granularity + drivers + .asio_get_buffer_size( + &mut min_b_size, + &mut max_b_size, + &mut pref_b_size, + &mut grans, + ).expect("Failed getting buffers"); + if pref_b_size > 0 { + // Convert Rust structs to opaque ASIO structs + let mut buffer_info_convert = + mem::transmute::, Vec>(buffer_infos); + let mut callbacks_convert = + mem::transmute::(callbacks); + drivers + .asio_create_buffers( + buffer_info_convert.as_mut_ptr(), + num_channels as i32, + pref_b_size, + &mut callbacks_convert, + ).map(|_| { + let buffer_infos = mem::transmute::< + Vec, + Vec, + >(buffer_info_convert); + (buffer_infos, pref_b_size) + }).map_err(|e| { + AsioDriverError::BufferError(format!( + "failed to create buffers, error code: {:?}", + e + )) + }) + } else { + Err(AsioDriverError::BufferError("bad buffer size".to_owned())) + } + } + } +} + +/// If all drivers and streams are gone +/// clean up drivers +impl Drop for Drivers { + fn drop(&mut self) { + let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); + if count == 1 { + clean_up(); + } + } +} + +/// Required for Mutex +unsafe impl Send for AsioWrapper {} +/// Required for Mutex +unsafe impl Send for AsioStream {} + +impl BufferCallback { + /// Calls the inner callback + fn run(&mut self, index: i32) { + let cb = &mut self.0; + cb(index); + } +} + +/// Adds a callback to the list of active callbacks +pub fn set_callback(callback: F) -> () +where + F: FnMut(i32) + Send, +{ + let mut bc = buffer_callback.lock().unwrap(); + bc.push(Some(BufferCallback(Box::new(callback)))); +} + +/// Returns a list of all the ASIO devices. +/// This is used at the start to allow the +/// user to choose which device they want. +#[allow(unused_assignments)] +pub fn get_driver_list() -> Vec { + // The most devices we can take + const MAX_DRIVERS: usize = 100; + // Max length for divers name + const CHAR_LEN: usize = 32; + + // 2D array of driver names set to 0 + let mut driver_names: [[c_char; CHAR_LEN]; MAX_DRIVERS] = [[0; CHAR_LEN]; MAX_DRIVERS]; + // Pointer to each driver name + let mut p_driver_name: [*mut i8; MAX_DRIVERS] = [0 as *mut i8; MAX_DRIVERS]; + + for i in 0 .. MAX_DRIVERS { + p_driver_name[i] = driver_names[i].as_mut_ptr(); + } + + unsafe { + let num_drivers = ai::get_driver_names(p_driver_name.as_mut_ptr(), MAX_DRIVERS as i32); + + (0 .. num_drivers) + .map(|i| { + let mut my_driver_name = CString::new("").unwrap(); + let name = CStr::from_ptr(p_driver_name[i as usize]); + my_driver_name = name.to_owned(); + my_driver_name + .into_string() + .expect("Failed to convert driver name") + }).collect() + } +} + +/// Cleans up the drivers and +/// any allocations +pub fn clean_up() { + let mut drivers = get_drivers(); + drivers.clean_up(); +} + +/// Starts input and output streams playing +pub fn play() { + unsafe { + // TODO handle result instead of printing + let result = get_drivers().asio_start(); + println!("start result: {:?}", result); + } +} + +/// Stops input and output streams playing +pub fn stop() { + unsafe { + // TODO handle result instead of printing + let result = get_drivers().asio_stop(); + println!("stop result: {:?}", result); + } +} + +/// All the actual calls to ASIO. +/// This is where we handle the state +/// and assert that all calls +/// happen in the correct state. +/// TODO it is possible to enforce most of this +/// at compile time using type parameters. +/// All calls have results that are converted +/// to Rust style results. +impl AsioWrapper { + /// Load the driver. + /// Unloads the previous driver. + /// Sets state to Loaded on success. + unsafe fn load(&mut self, raw: *mut i8) -> bool { + use self::AsioState::*; + self.clean_up(); + if ai::load_asio_driver(raw) { + self.state = Loaded; + true + } else { + false + } + } + + /// Unloads the current driver from ASIO + unsafe fn unload(&mut self) { + ai::remove_current_driver(); + } + + /// Initializes ASIO. + /// Needs to be already Loaded. + /// Initialized on success. + /// No-op if already Initialized or higher. + /// TODO should fail if Offline + unsafe fn asio_init(&mut self, di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { + if let AsioState::Loaded = self.state { + let result = ai::ASIOInit(di); + asio_result!(result).map(|_| self.state = AsioState::Initialized) + } else { + Ok(()) + } + } + + /// Gets the number of channels. + /// Needs to be atleast Loaded. + unsafe fn asio_get_channels( + &mut self, ins: &mut c_long, outs: &mut c_long, + ) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::ASIOGetChannels(ins, outs); + asio_result!(result) + } + } + + /// Gets the sample rate. + /// Needs to be atleast Loaded. + unsafe fn asio_get_sample_rate(&mut self, rate: &mut c_double) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::get_sample_rate(rate); + asio_result!(result) + } + } + + /// Sets the sample rate. + /// Needs to be atleast Loaded. + unsafe fn asio_set_sample_rate(&mut self, rate: c_double) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::set_sample_rate(rate); + asio_result!(result) + } + } + + /// Queries if the sample rate is possible. + /// Needs to be atleast Loaded. + unsafe fn asio_can_sample_rate(&mut self, rate: c_double) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::can_sample_rate(rate); + asio_result!(result) + } + } + + /// Get information about a channel. + /// Needs to be atleast Loaded. + unsafe fn asio_get_channel_info( + &mut self, ci: &mut ai::ASIOChannelInfo, + ) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::ASIOGetChannelInfo(ci); + asio_result!(result) + } + } + + /// Gets the buffer sizes. + /// Needs to be atleast Loaded. + unsafe fn asio_get_buffer_size( + &mut self, min_b_size: &mut c_long, max_b_size: &mut c_long, pref_b_size: &mut c_long, + grans: &mut c_long, + ) -> Result<(), AsioError> { + if let AsioState::Offline = self.state { + Err(AsioError::NoDrivers) + } else { + let result = ai::ASIOGetBufferSize(min_b_size, max_b_size, pref_b_size, grans); + asio_result!(result) + } + } + + /// Creates the buffers. + /// Needs to be atleast Loaded. + /// If Running or Prepared then old buffers + /// will be destoryed. + unsafe fn asio_create_buffers( + &mut self, buffer_info_convert: *mut ai::ASIOBufferInfo, num_channels: i32, + pref_b_size: c_long, callbacks_convert: &mut ai::ASIOCallbacks, + ) -> Result<(), AsioError> { + use self::AsioState::*; + match self.state { + Offline | Loaded => return Err(AsioError::NoDrivers), + Running => { + self.asio_stop().expect("Asio failed to stop"); + self.asio_dispose_buffers() + .expect("Failed to dispose buffers"); + self.state = Initialized; + }, + Prepared => { + self.asio_dispose_buffers() + .expect("Failed to dispose buffers"); + self.state = Initialized; + }, + _ => (), + } + let result = ai::ASIOCreateBuffers( + buffer_info_convert, + num_channels, + pref_b_size, + callbacks_convert, + ); + asio_result!(result).map(|_| self.state = AsioState::Prepared) + } + + /// Releases buffers allocations. + /// Needs to be atleast Loaded. + /// No op if already released. + unsafe fn asio_dispose_buffers(&mut self) -> Result<(), AsioError> { + use self::AsioState::*; + match self.state { + Offline | Loaded => return Err(AsioError::NoDrivers), + Running | Prepared => (), + Initialized => return Ok(()), + } + let result = ai::ASIODisposeBuffers(); + asio_result!(result).map(|_| self.state = AsioState::Initialized) + } + + /// Closes down ASIO. + /// Needs to be atleast Initialized. + unsafe fn asio_exit(&mut self) -> Result<(), AsioError> { + use self::AsioState::*; + match self.state { + Offline | Loaded => return Err(AsioError::NoDrivers), + _ => (), + } + let result = ai::ASIOExit(); + asio_result!(result).map(|_| self.state = AsioState::Offline) + } + + /// Starts ASIO streams playing. + /// Needs to be atleast Initialized. + unsafe fn asio_start(&mut self) -> Result<(), AsioError> { + use self::AsioState::*; + match self.state { + Offline | Loaded | Initialized => return Err(AsioError::NoDrivers), + Running => return Ok(()), + Prepared => (), + } + let result = ai::ASIOStart(); + asio_result!(result).map(|_| self.state = AsioState::Running) + } + + /// Stops ASIO streams playing. + /// Needs to be Running. + /// No-op if already stopped. + unsafe fn asio_stop(&mut self) -> Result<(), AsioError> { + use self::AsioState::*; + match self.state { + Offline | Loaded => return Err(AsioError::NoDrivers), + Running => (), + Initialized | Prepared => return Ok(()), + } + let result = ai::ASIOStop(); + asio_result!(result).map(|_| self.state = AsioState::Prepared) + } + + /// Cleans up the drivers based + /// on the current state of the driver. + fn clean_up(&mut self) { + match self.state { + AsioState::Offline => (), + AsioState::Loaded => { + unsafe { + self.unload(); + } + self.state = AsioState::Offline; + }, + AsioState::Initialized => { + unsafe { + self.asio_exit().expect("Failed to exit asio"); + self.unload(); + } + self.state = AsioState::Offline; + }, + AsioState::Prepared => { + unsafe { + self.asio_dispose_buffers() + .expect("Failed to dispose buffers"); + self.asio_exit().expect("Failed to exit asio"); + self.unload(); + } + self.state = AsioState::Offline; + }, + AsioState::Running => { + unsafe { + self.asio_stop().expect("Asio failed to stop"); + self.asio_dispose_buffers() + .expect("Failed to dispose buffers"); + self.asio_exit().expect("Failed to exit asio"); + self.unload(); + } + self.state = AsioState::Offline; + }, + } + } +} \ No newline at end of file diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 412d40e..53be5fd 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -1,890 +1,15 @@ #![allow(non_camel_case_types)] +#[allow(unused_imports)] #[macro_use] extern crate lazy_static; extern crate num; +#[allow(unused_imports)] #[macro_use] extern crate num_derive; -mod asio_import; -#[macro_use] -pub mod errors; - -use errors::{AsioDriverError, AsioError, AsioErrorWrapper}; -use std::ffi::CStr; -use std::ffi::CString; -use std::mem; -use std::os::raw::{c_char, c_double, c_long, c_void}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Mutex, MutexGuard}; - -// Bindings import -use asio_import as ai; - -// TODO I dont think this is needed anymore -pub struct CbArgs { - pub stream_id: S, - pub data: D, -} - -/// Holds the pointer to the callbacks that come from cpal -struct BufferCallback(Box); - -/// A global way to access all the callbacks. -/// This is required because of how ASIO -/// calls the buffer_switch function. -/// Options are used so that when a callback is -/// removed we don't change the Vec indicies. -/// The indicies are how we match a callback -/// with a stream. -lazy_static! { - static ref buffer_callback: Mutex>> = Mutex::new(Vec::new()); -} - -/// Globally available state of the ASIO driver. -/// This allows all calls the the driver to ensure -/// they are calling in the correct state. -/// It also prevents multiple calls happening at once. -lazy_static! { - static ref ASIO_DRIVERS: Mutex = Mutex::new(AsioWrapper { - state: AsioState::Offline, - }); -} - -/// Count of active device and streams. -/// Used to clean up the driver connection -/// when there are no active connections. -static STREAM_DRIVER_COUNT: AtomicUsize = AtomicUsize::new(0); -/// Tracks which buffer needs to be silenced. -pub static SILENCE_FIRST: AtomicBool = AtomicBool::new(false); -pub static SILENCE_SECOND: AtomicBool = AtomicBool::new(false); - -/// Amount of input and output -/// channels available. -#[derive(Debug)] -pub struct Channel { - pub ins: i64, - pub outs: i64, -} - -/// Sample rate of the ASIO device. -#[derive(Debug)] -pub struct SampleRate { - pub rate: u32, -} - -/// A marker type to make sure -/// all calls to the driver have an -/// active connection. -#[derive(Debug, Clone)] -pub struct Drivers; - -/// Tracks the current state of the -/// ASIO drivers. -#[derive(Debug)] -struct AsioWrapper { - state: AsioState, -} - -/// All possible states of the -/// ASIO driver. Mapped to the -/// FSM in the ASIO SDK docs. -#[derive(Debug)] -enum AsioState { - Offline, - Loaded, - Initialized, - Prepared, - Running, -} - -/// Input and Output streams. -/// There is only ever max one -/// input and one output. Only one -/// is required. -pub struct AsioStreams { - pub input: Option, - pub output: Option, -} - -/// A stream to ASIO. -/// Contains the buffers. -pub struct AsioStream { - /// A Double buffer per channel - pub buffer_infos: Vec, - /// Size of each buffer - pub buffer_size: i32, -} - -/// All the possible types from ASIO. -/// This is a direct copy of the ASIOSampleType -/// inside ASIO SDK. -#[derive(Debug, FromPrimitive)] -#[repr(C)] -pub enum AsioSampleType { - ASIOSTInt16MSB = 0, - ASIOSTInt24MSB = 1, // used for 20 bits as well - ASIOSTInt32MSB = 2, - ASIOSTFloat32MSB = 3, // IEEE 754 32 bit float - ASIOSTFloat64MSB = 4, // IEEE 754 64 bit double float - - // these are used for 32 bit data buffer, with different alignment of the data inside - // 32 bit PCI bus systems can be more easily used with these - ASIOSTInt32MSB16 = 8, // 32 bit data with 16 bit alignment - ASIOSTInt32MSB18 = 9, // 32 bit data with 18 bit alignment - ASIOSTInt32MSB20 = 10, // 32 bit data with 20 bit alignment - ASIOSTInt32MSB24 = 11, // 32 bit data with 24 bit alignment - - ASIOSTInt16LSB = 16, - ASIOSTInt24LSB = 17, // used for 20 bits as well - ASIOSTInt32LSB = 18, - ASIOSTFloat32LSB = 19, // IEEE 754 32 bit float, as found on Intel x86 architecture - ASIOSTFloat64LSB = 20, // IEEE 754 64 bit double float, as found on Intel x86 architecture - - // these are used for 32 bit data buffer, with different alignment of the data inside - // 32 bit PCI bus systems can more easily used with these - ASIOSTInt32LSB16 = 24, // 32 bit data with 18 bit alignment - ASIOSTInt32LSB18 = 25, // 32 bit data with 18 bit alignment - ASIOSTInt32LSB20 = 26, // 32 bit data with 20 bit alignment - ASIOSTInt32LSB24 = 27, // 32 bit data with 24 bit alignment - - // ASIO DSD format. - ASIOSTDSDInt8LSB1 = 32, // DSD 1 bit data, 8 samples per byte. First sample in Least significant bit. - ASIOSTDSDInt8MSB1 = 33, // DSD 1 bit data, 8 samples per byte. First sample in Most significant bit. - ASIOSTDSDInt8NER8 = 40, // DSD 8 bit data, 1 sample per byte. No Endianness required. - - ASIOSTLastEntry, -} - -/// Gives information about buffers -/// Receives pointers to buffers -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub struct AsioBufferInfo { - /// 0 for output 1 for input - pub is_input: c_long, - /// Which channel. Starts at 0 - pub channel_num: c_long, - /// Pointer to each half of the double buffer. - pub buffers: [*mut c_void; 2], -} - -/// Callbacks that ASIO calls -#[repr(C)] -struct AsioCallbacks { - buffer_switch: extern "C" fn(double_buffer_index: c_long, direct_process: c_long) -> (), - sample_rate_did_change: extern "C" fn(s_rate: c_double) -> (), - asio_message: - extern "C" fn(selector: c_long, value: c_long, message: *mut (), opt: *mut c_double) - -> c_long, - buffer_switch_time_info: extern "C" fn( - params: *mut ai::ASIOTime, - double_buffer_index: c_long, - direct_process: c_long, - ) -> *mut ai::ASIOTime, -} - -/// This is called by ASIO. -/// Here we run the callback for each stream. -/// double_buffer_index is either 0 or 1 -/// indicating which buffer to fill -extern "C" fn buffer_switch(double_buffer_index: c_long, _direct_process: c_long) -> () { - // This lock is probably unavoidable - // but locks in the audio stream is not great - let mut bcs = buffer_callback.lock().unwrap(); - - for mut bc in bcs.iter_mut() { - if let Some(ref mut bc) = bc { - bc.run(double_buffer_index); - } - } -} - -/// Idicates the sample rate has changed -/// TODO Change the sample rate when this -/// is called. -extern "C" fn sample_rate_did_change(_s_rate: c_double) -> () {} - -/// Messages for ASIO -/// This is not currently used -extern "C" fn asio_message( - _selector: c_long, _value: c_long, _message: *mut (), _opt: *mut c_double, -) -> c_long { - // TODO Impliment this to give proper responses - 4 as c_long -} - -/// Similar to buffer switch but with time info -/// Not currently used -extern "C" fn buffer_switch_time_info( - params: *mut ai::ASIOTime, _double_buffer_index: c_long, _direct_process: c_long, -) -> *mut ai::ASIOTime { - params -} - -/// Helper function for getting the drivers. -/// Note this is a lock. -fn get_drivers() -> MutexGuard<'static, AsioWrapper> { - ASIO_DRIVERS.lock().unwrap() -} - -impl Drivers { - /// Load the drivers from a driver name. - /// This will destroy the old drivers. - #[allow(unused_assignments)] - pub fn load(driver_name: &str) -> Result { - let mut drivers = get_drivers(); - // Make owned CString to send to load driver - let mut my_driver_name = CString::new(driver_name).expect("Can't go from str to CString"); - let raw = my_driver_name.into_raw(); - let mut driver_info = ai::ASIODriverInfo { - _bindgen_opaque_blob: [0u32; 43], - }; - unsafe { - // Destroy old drivers and load new drivers. - let load_result = drivers.load(raw); - // Take back ownership - my_driver_name = CString::from_raw(raw); - if load_result { - // Initialize ASIO - match drivers.asio_init(&mut driver_info) { - Ok(_) => { - // If it worked then add a active connection to the drivers - // TODO Make sure this is decremented when old drivers are dropped - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers) - }, - Err(_) => Err(AsioDriverError::DriverLoadError), - } - } else { - Err(AsioDriverError::DriverLoadError) - } - } - } - - /// Returns the number of input and output - /// channels for the active drivers - pub fn get_channels(&self) -> Channel { - let channel: Channel; - - // Initialize memory for calls - let mut ins: c_long = 0; - let mut outs: c_long = 0; - unsafe { - get_drivers() - .asio_get_channels(&mut ins, &mut outs) - // TODO pass this result along - // and handle it without panic - .expect("failed to get channels"); - channel = Channel { - ins: ins as i64, - outs: outs as i64, - }; - } - - channel - } - - /// Get current sample rate of the active drivers - pub fn get_sample_rate(&self) -> SampleRate { - let sample_rate: SampleRate; - - // Initialize memory for calls - let mut rate: c_double = 0.0f64; - - unsafe { - get_drivers() - .asio_get_sample_rate(&mut rate) - // TODO pass this result along - // and handle it without panic - .expect("failed to get sample rate"); - sample_rate = SampleRate { rate: rate as u32 }; - } - - sample_rate - } - - /// Set the sample rate for the active drivers - pub fn set_sample_rate(&self, sample_rate: u32) -> Result<(), AsioError> { - // Initialize memory for calls - let rate: c_double = c_double::from(sample_rate); - - unsafe { get_drivers().asio_set_sample_rate(rate) } - } - - /// Can the drivers accept the given sample rate - pub fn can_sample_rate(&self, sample_rate: u32) -> bool { - // Initialize memory for calls - let rate: c_double = c_double::from(sample_rate); - - // TODO this gives an error is it can't handle the sample - // rate but it can also give error for no divers - // Both should be handled. - unsafe { get_drivers().asio_can_sample_rate(rate).is_ok() } - } - - /// Get the current data type of the active drivers. - /// Just queries a single channels type as all channels - /// have the same sample type. - pub fn get_data_type(&self) -> Result { - // TODO make this a seperate call for input and output as - // it is possible that input and output have different sample types - // Initialize memory for calls - let mut channel_info = ai::ASIOChannelInfo { - // Which channel we are querying - channel: 0, - // Was it input or output - isInput: 0, - // Was it active - isActive: 0, - channelGroup: 0, - // The sample type - type_: 0, - name: [0 as c_char; 32], - }; - unsafe { - match get_drivers().asio_get_channel_info(&mut channel_info) { - Ok(_) => num::FromPrimitive::from_i32(channel_info.type_) - .map_or(Err(AsioDriverError::TypeError), |t| Ok(t)), - Err(e) => { - println!("Error getting data type {}", e); - Err(AsioDriverError::DriverLoadError) - }, - } - } - } - - /// Prepare the input stream. - /// Because only the latest call - /// to ASIOCreateBuffers is relevant this - /// call will destroy all past active buffers - /// and recreate them. For this reason we take - /// the output stream if it exists. - /// num_channels is the number of input channels. - /// This returns a full AsioStreams with both input - /// and output if output was active. - pub fn prepare_input_stream( - &self, output: Option, num_channels: usize, - ) -> Result { - let buffer_infos = (0 .. num_channels) - .map(|i| AsioBufferInfo { - // These are output channels - is_input: 1, - // Channel index - channel_num: i as c_long, - // Double buffer. We don't know the type - // at this point - buffers: [std::ptr::null_mut(); 2], - }).collect(); - - // Create the streams - let streams = AsioStreams { - input: Some(AsioStream { - buffer_infos, - buffer_size: 0, - }), - output, - }; - self.create_streams(streams) - } - - /// Prepare the output stream. - /// Because only the latest call - /// to ASIOCreateBuffers is relevant this - /// call will destroy all past active buffers - /// and recreate them. For this reason we take - /// the input stream if it exists. - /// num_channels is the number of output channels. - /// This returns a full AsioStreams with both input - /// and output if input was active. - pub fn prepare_output_stream( - &self, input: Option, num_channels: usize, - ) -> Result { - // Initialize data for FFI - let buffer_infos = (0 .. num_channels) - .map(|i| AsioBufferInfo { - // These are outputs - is_input: 0, - // Channel index - channel_num: i as c_long, - // Pointer to each buffer. We don't know - // the type yet. - buffers: [std::ptr::null_mut(); 2], - }).collect(); - - // Create streams - let streams = AsioStreams { - output: Some(AsioStream { - buffer_infos, - buffer_size: 0, - }), - input, - }; - self.create_streams(streams) - } - - /// Creates the streams. - /// Both input and output streams - /// need to be created together as - /// a single slice of ASIOBufferInfo - fn create_streams(&self, streams: AsioStreams) -> Result { - let AsioStreams { input, output } = streams; - match (input, output) { - // Both stream exist. - (Some(input), Some(mut output)) => { - let split_point = input.buffer_infos.len(); - let mut bi = input.buffer_infos; - // Append the output to the input channels - bi.append(&mut output.buffer_infos); - // Create the buffers. - // if successful then split the output - // and input again - self.create_buffers(bi).map(|(mut bi, buffer_size)| { - let out_bi = bi.split_off(split_point); - let in_bi = bi; - let output = Some(AsioStream { - buffer_infos: out_bi, - buffer_size, - }); - let input = Some(AsioStream { - buffer_infos: in_bi, - buffer_size, - }); - AsioStreams { output, input } - }) - }, - // Just input - (Some(input), None) => { - self.create_buffers(input.buffer_infos) - .map(|(buffer_infos, buffer_size)| { - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - AsioStreams { - input: Some(AsioStream { - buffer_infos, - buffer_size, - }), - output: None, - } - }) - }, - // Just output - (None, Some(output)) => { - self.create_buffers(output.buffer_infos) - .map(|(buffer_infos, buffer_size)| { - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - AsioStreams { - output: Some(AsioStream { - buffer_infos, - buffer_size, - }), - input: None, - } - }) - }, - // Impossible - (None, None) => panic!("Trying to create streams without preparing"), - } - } - - /// Ask ASIO to allocate the buffers - /// and give the callback pointers. - /// This will destroy any already allocated - /// buffers. - /// The prefered buffer size from ASIO is used. - fn create_buffers( - &self, buffer_infos: Vec, - ) -> Result<(Vec, c_long), AsioDriverError> { - let num_channels = buffer_infos.len(); - let callbacks = AsioCallbacks { - buffer_switch: buffer_switch, - sample_rate_did_change: sample_rate_did_change, - asio_message: asio_message, - buffer_switch_time_info: buffer_switch_time_info, - }; - - let mut min_b_size: c_long = 0; - let mut max_b_size: c_long = 0; - let mut pref_b_size: c_long = 0; - let mut grans: c_long = 0; - - let mut drivers = get_drivers(); - - unsafe { - // Get the buffer sizes - // min possilbe size - // max possible size - // preferred size - // granularity - drivers - .asio_get_buffer_size( - &mut min_b_size, - &mut max_b_size, - &mut pref_b_size, - &mut grans, - ).expect("Failed getting buffers"); - if pref_b_size > 0 { - // Convert Rust structs to opaque ASIO structs - let mut buffer_info_convert = - mem::transmute::, Vec>(buffer_infos); - let mut callbacks_convert = - mem::transmute::(callbacks); - drivers - .asio_create_buffers( - buffer_info_convert.as_mut_ptr(), - num_channels as i32, - pref_b_size, - &mut callbacks_convert, - ).map(|_| { - let buffer_infos = mem::transmute::< - Vec, - Vec, - >(buffer_info_convert); - (buffer_infos, pref_b_size) - }).map_err(|e| { - AsioDriverError::BufferError(format!( - "failed to create buffers, error code: {:?}", - e - )) - }) - } else { - Err(AsioDriverError::BufferError("bad buffer size".to_owned())) - } - } - } -} - -/// If all drivers and streams are gone -/// clean up drivers -impl Drop for Drivers { - fn drop(&mut self) { - let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); - if count == 1 { - clean_up(); - } - } -} - -/// Required for Mutex -unsafe impl Send for AsioWrapper {} -/// Required for Mutex -unsafe impl Send for AsioStream {} - -impl BufferCallback { - /// Calls the inner callback - fn run(&mut self, index: i32) { - let cb = &mut self.0; - cb(index); - } -} - -/// Adds a callback to the list of active callbacks -pub fn set_callback(callback: F) -> () -where - F: FnMut(i32) + Send, -{ - let mut bc = buffer_callback.lock().unwrap(); - bc.push(Some(BufferCallback(Box::new(callback)))); -} - -/// Returns a list of all the ASIO devices. -/// This is used at the start to allow the -/// user to choose which device they want. -#[allow(unused_assignments)] -pub fn get_driver_list() -> Vec { - // The most devices we can take - const MAX_DRIVERS: usize = 100; - // Max length for divers name - const CHAR_LEN: usize = 32; - - // 2D array of driver names set to 0 - let mut driver_names: [[c_char; CHAR_LEN]; MAX_DRIVERS] = [[0; CHAR_LEN]; MAX_DRIVERS]; - // Pointer to each driver name - let mut p_driver_name: [*mut i8; MAX_DRIVERS] = [0 as *mut i8; MAX_DRIVERS]; - - for i in 0 .. MAX_DRIVERS { - p_driver_name[i] = driver_names[i].as_mut_ptr(); - } - - unsafe { - let num_drivers = ai::get_driver_names(p_driver_name.as_mut_ptr(), MAX_DRIVERS as i32); - - (0 .. num_drivers) - .map(|i| { - let mut my_driver_name = CString::new("").unwrap(); - let name = CStr::from_ptr(p_driver_name[i as usize]); - my_driver_name = name.to_owned(); - my_driver_name - .into_string() - .expect("Failed to convert driver name") - }).collect() - } -} - -/// Cleans up the drivers and -/// any allocations -pub fn clean_up() { - let mut drivers = get_drivers(); - drivers.clean_up(); -} - -/// Starts input and output streams playing -pub fn play() { - unsafe { - // TODO handle result instead of printing - let result = get_drivers().asio_start(); - println!("start result: {:?}", result); - } -} - -/// Stops input and output streams playing -pub fn stop() { - unsafe { - // TODO handle result instead of printing - let result = get_drivers().asio_stop(); - println!("stop result: {:?}", result); - } -} - -/// All the actual calls to ASIO. -/// This is where we handle the state -/// and assert that all calls -/// happen in the correct state. -/// TODO it is possible to enforce most of this -/// at compile time using type parameters. -/// All calls have results that are converted -/// to Rust style results. -impl AsioWrapper { - /// Load the driver. - /// Unloads the previous driver. - /// Sets state to Loaded on success. - unsafe fn load(&mut self, raw: *mut i8) -> bool { - use AsioState::*; - self.clean_up(); - if ai::load_asio_driver(raw) { - self.state = Loaded; - true - } else { - false - } - } - - /// Unloads the current driver from ASIO - unsafe fn unload(&mut self) { - ai::remove_current_driver(); - } - - /// Initializes ASIO. - /// Needs to be already Loaded. - /// Initialized on success. - /// No-op if already Initialized or higher. - /// TODO should fail if Offline - unsafe fn asio_init(&mut self, di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { - if let AsioState::Loaded = self.state { - let result = ai::ASIOInit(di); - asio_result!(result).map(|_| self.state = AsioState::Initialized) - } else { - Ok(()) - } - } - - /// Gets the number of channels. - /// Needs to be atleast Loaded. - unsafe fn asio_get_channels( - &mut self, ins: &mut c_long, outs: &mut c_long, - ) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::ASIOGetChannels(ins, outs); - asio_result!(result) - } - } - - /// Gets the sample rate. - /// Needs to be atleast Loaded. - unsafe fn asio_get_sample_rate(&mut self, rate: &mut c_double) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::get_sample_rate(rate); - asio_result!(result) - } - } - - /// Sets the sample rate. - /// Needs to be atleast Loaded. - unsafe fn asio_set_sample_rate(&mut self, rate: c_double) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::set_sample_rate(rate); - asio_result!(result) - } - } - - /// Queries if the sample rate is possible. - /// Needs to be atleast Loaded. - unsafe fn asio_can_sample_rate(&mut self, rate: c_double) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::can_sample_rate(rate); - asio_result!(result) - } - } - - /// Get information about a channel. - /// Needs to be atleast Loaded. - unsafe fn asio_get_channel_info( - &mut self, ci: &mut ai::ASIOChannelInfo, - ) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::ASIOGetChannelInfo(ci); - asio_result!(result) - } - } - - /// Gets the buffer sizes. - /// Needs to be atleast Loaded. - unsafe fn asio_get_buffer_size( - &mut self, min_b_size: &mut c_long, max_b_size: &mut c_long, pref_b_size: &mut c_long, - grans: &mut c_long, - ) -> Result<(), AsioError> { - if let AsioState::Offline = self.state { - Err(AsioError::NoDrivers) - } else { - let result = ai::ASIOGetBufferSize(min_b_size, max_b_size, pref_b_size, grans); - asio_result!(result) - } - } - - /// Creates the buffers. - /// Needs to be atleast Loaded. - /// If Running or Prepared then old buffers - /// will be destoryed. - unsafe fn asio_create_buffers( - &mut self, buffer_info_convert: *mut ai::ASIOBufferInfo, num_channels: i32, - pref_b_size: c_long, callbacks_convert: &mut ai::ASIOCallbacks, - ) -> Result<(), AsioError> { - use AsioState::*; - match self.state { - Offline | Loaded => return Err(AsioError::NoDrivers), - Running => { - self.asio_stop().expect("Asio failed to stop"); - self.asio_dispose_buffers() - .expect("Failed to dispose buffers"); - self.state = Initialized; - }, - Prepared => { - self.asio_dispose_buffers() - .expect("Failed to dispose buffers"); - self.state = Initialized; - }, - _ => (), - } - let result = ai::ASIOCreateBuffers( - buffer_info_convert, - num_channels, - pref_b_size, - callbacks_convert, - ); - asio_result!(result).map(|_| self.state = AsioState::Prepared) - } - - /// Releases buffers allocations. - /// Needs to be atleast Loaded. - /// No op if already released. - unsafe fn asio_dispose_buffers(&mut self) -> Result<(), AsioError> { - use AsioState::*; - match self.state { - Offline | Loaded => return Err(AsioError::NoDrivers), - Running | Prepared => (), - Initialized => return Ok(()), - } - let result = ai::ASIODisposeBuffers(); - asio_result!(result).map(|_| self.state = AsioState::Initialized) - } - - /// Closes down ASIO. - /// Needs to be atleast Loaded. - unsafe fn asio_exit(&mut self) -> Result<(), AsioError> { - use AsioState::*; - match self.state { - Offline | Loaded => return Err(AsioError::NoDrivers), - _ => (), - } - let result = ai::ASIOExit(); - asio_result!(result).map(|_| self.state = AsioState::Offline) - } - - /// Starts ASIO streams playing. - /// Needs to be atleast Initialized. - unsafe fn asio_start(&mut self) -> Result<(), AsioError> { - use AsioState::*; - match self.state { - Offline | Loaded | Initialized => return Err(AsioError::NoDrivers), - Running => return Ok(()), - Prepared => (), - } - let result = ai::ASIOStart(); - asio_result!(result).map(|_| self.state = AsioState::Running) - } - - /// Stops ASIO streams playing. - /// Needs to be Running. - /// No-op if already stopped. - unsafe fn asio_stop(&mut self) -> Result<(), AsioError> { - use AsioState::*; - match self.state { - Offline | Loaded => return Err(AsioError::NoDrivers), - Running => (), - Initialized | Prepared => return Ok(()), - } - let result = ai::ASIOStop(); - asio_result!(result).map(|_| self.state = AsioState::Prepared) - } - - /// Cleans up the drivers based - /// on the current state of the driver. - fn clean_up(&mut self) { - match self.state { - AsioState::Offline => (), - AsioState::Loaded => { - unsafe { - self.asio_exit().expect("Failed to exit asio"); - self.unload(); - } - self.state = AsioState::Offline; - }, - AsioState::Initialized => { - unsafe { - self.asio_exit().expect("Failed to exit asio"); - self.unload(); - } - self.state = AsioState::Offline; - }, - AsioState::Prepared => { - unsafe { - self.asio_dispose_buffers() - .expect("Failed to dispose buffers"); - self.asio_exit().expect("Failed to exit asio"); - self.unload(); - } - self.state = AsioState::Offline; - }, - AsioState::Running => { - unsafe { - self.asio_stop().expect("Asio failed to stop"); - self.asio_dispose_buffers() - .expect("Failed to dispose buffers"); - self.asio_exit().expect("Failed to exit asio"); - self.unload(); - } - self.state = AsioState::Offline; - }, - } - } -} +#[cfg(asio)] +pub mod bindings; +#[cfg(asio)] +pub use bindings::*; \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3975ea9 --- /dev/null +++ b/build.rs @@ -0,0 +1,12 @@ +use std::env; + +const CPAL_ASIO_DIR: &'static str = "CPAL_ASIO_DIR"; + +fn main() { + // If ASIO directory isn't set silently return early + // otherwise set the asio config flag + match env::var(CPAL_ASIO_DIR) { + Err(_) => return, + Ok(_) => println!("cargo:rustc-cfg=asio"), + }; +} \ No newline at end of file diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 17f029f..8c40f02 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -16,6 +16,7 @@ pub fn which_backend() -> BackEnd { (*BACK_END.lock().unwrap()).clone() } +#[cfg(asio)] pub fn use_asio_backend() -> Result<(), BackEndError> { *BACK_END.lock().unwrap() = BackEnd::Asio; Ok(()) diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 968081c..c8c07ca 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -1,4 +1,3 @@ -extern crate asio_sys as sys; use std; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -10,6 +9,7 @@ use FormatsEnumerationError; use SampleFormat; use SampleRate; use SupportedFormat; +use super::sys; /// A ASIO Device #[derive(Debug, Clone)] @@ -133,11 +133,7 @@ impl Device { impl Default for Devices { fn default() -> Devices { - // Remove offline drivers - let driver_names: Vec = sys::get_driver_list() - .into_iter() - .filter(|name| sys::Drivers::load(&name).is_ok()) - .collect(); + let driver_names = online_devices(); Devices { drivers: driver_names.into_iter(), } @@ -168,7 +164,17 @@ impl Iterator for Devices { /// Asio doesn't have a concept of default /// so returning first in list as default pub fn default_input_device() -> Option { - let mut driver_list = sys::get_driver_list(); + first_device() +} + +/// Asio doesn't have a concept of default +/// so returning first in list as default +pub fn default_output_device() -> Option { + first_device() +} + +fn first_device() -> Option { + let mut driver_list = online_devices(); match driver_list.pop() { Some(name) => sys::Drivers::load(&name) .or_else(|e| { @@ -180,17 +186,10 @@ pub fn default_input_device() -> Option { } } -/// Asio doesn't have a concept of default -/// so returning first in list as default -pub fn default_output_device() -> Option { - let mut driver_list = sys::get_driver_list(); - match driver_list.pop() { - Some(name) => sys::Drivers::load(&name) - .or_else(|e| { - eprintln!("{}", e); - Err(e) - }).ok() - .map(|drivers| Device { drivers, name }), - None => None, - } -} +/// Remove offline drivers +fn online_devices() -> Vec { + sys::get_driver_list() + .into_iter() + .filter(|name| sys::Drivers::load(&name).is_ok()) + .collect() +} \ No newline at end of file