diff --git a/.gitignore b/.gitignore index b14cebf..555191e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ .cargo/ .DS_Store recorded.wav +asio-sys/target +asio-sys/Cargo.lock +asio-sys/.cargo/ +asio-sys/.DS_Store +*~ +*.swap +*.swo diff --git a/Cargo.toml b/Cargo.toml index b6d42c4..b526289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,14 @@ keywords = ["audio", "sound"] [dependencies] failure = "0.1.5" lazy_static = "1.3" +itertools = "0.7.8" [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"] } diff --git a/asio-sys/.Cargo.toml.swp b/asio-sys/.Cargo.toml.swp new file mode 100644 index 0000000..e0dc7b5 Binary files /dev/null and b/asio-sys/.Cargo.toml.swp differ diff --git a/asio-sys/.build.rs.swp b/asio-sys/.build.rs.swp new file mode 100644 index 0000000..e6df5f9 Binary files /dev/null and b/asio-sys/.build.rs.swp differ diff --git a/asio-sys/Cargo.toml b/asio-sys/Cargo.toml new file mode 100644 index 0000000..7346b61 --- /dev/null +++ b/asio-sys/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "asio-sys" +version = "0.1.0" +authors = ["Tom Gowan "] +build = "build.rs" + +[target.'cfg(any(target_os = "windows"))'.build-dependencies] +bindgen = "0.33.1" +walkdir = "2" +cc = "1.0.4" + +[dependencies] +lazy_static = "1.0.0" +num = "0.1.42" +num-traits = "0.2.2" +num-derive = "0.2.0" diff --git a/asio-sys/asio-link/helpers.cpp b/asio-sys/asio-link/helpers.cpp new file mode 100644 index 0000000..ded6328 --- /dev/null +++ b/asio-sys/asio-link/helpers.cpp @@ -0,0 +1,9 @@ +#include "helpers.hpp" + +extern "C" void destruct_AsioDrivers(AsioDrivers * a){ + a->~AsioDrivers(); +} + +extern "C" ASIOError get_sample_rate(double * rate){ + return ASIOGetSampleRate(reinterpret_cast(rate)); +} diff --git a/asio-sys/asio-link/helpers.hpp b/asio-sys/asio-link/helpers.hpp new file mode 100644 index 0000000..16264e1 --- /dev/null +++ b/asio-sys/asio-link/helpers.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "asiodrivers.h" +#include "asio.h" + +// Helper function to call destructors from within library +extern "C" void destruct_AsioDrivers(AsioDrivers * a); + +// Helper function to wrap confusing preprocessor +extern "C" ASIOError get_sample_rate(double * rate); + diff --git a/asio-sys/build.bat b/asio-sys/build.bat new file mode 100644 index 0000000..d55f1d1 --- /dev/null +++ b/asio-sys/build.bat @@ -0,0 +1,2 @@ +@CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" %* +@start powershell diff --git a/asio-sys/build.rs b/asio-sys/build.rs new file mode 100644 index 0000000..1c96c80 --- /dev/null +++ b/asio-sys/build.rs @@ -0,0 +1,185 @@ +extern crate bindgen; +extern crate cc; +extern crate walkdir; + +use std::env; +use std::path::PathBuf; +use walkdir::WalkDir; + +const CPAL_ASIO_DIR: &'static str = "CPAL_ASIO_DIR"; + +const ASIO_HEADER: &'static str = "asio.h"; +const ASIO_SYS_HEADER: &'static str = "asiosys.h"; +const ASIO_DRIVERS_HEADER: &'static str = "asiodrivers.h"; + +fn main() { + // If ASIO directory isn't set silently return early + let cpal_asio_dir_var = match env::var(CPAL_ASIO_DIR) { + Err(_) => return, + Ok(var) => var, + }; + + // Asio directory + let cpal_asio_dir = PathBuf::from(cpal_asio_dir_var); + + // Directory where bindings and library are created + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("bad path")); + + // Check if library exists + // if it doesn't create it + let mut lib_path = out_dir.clone(); + lib_path.push("libasio.a"); + if !lib_path.exists() { + create_lib(&cpal_asio_dir); + } + + // Print out links to needed libraries + println!("cargo:rustc-link-lib=dylib=ole32"); + println!("cargo:rustc-link-lib=dylib=User32"); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rustc-link-lib=static=asio"); + + // Check if bindings exist + // if they dont create them + let mut binding_path = out_dir.clone(); + binding_path.push("asio_bindings.rs"); + if !binding_path.exists() { + create_bindings(&cpal_asio_dir); + } +} + +fn create_lib(cpal_asio_dir: &PathBuf) { + let mut cpp_paths: Vec = Vec::new(); + let mut host_dir = cpal_asio_dir.clone(); + let mut pc_dir = cpal_asio_dir.clone(); + let mut common_dir = cpal_asio_dir.clone(); + host_dir.push("host"); + common_dir.push("common"); + pc_dir.push("host/pc"); + + // Gathers cpp files from directories + let walk_a_dir = |dir_to_walk, paths: &mut Vec| { + for entry in WalkDir::new(&dir_to_walk).max_depth(1) { + let entry = match entry { + Err(_) => continue, + Ok(entry) => entry, + }; + match entry.path().extension().and_then(|s| s.to_str()) { + None => continue, + Some("cpp") => { + // Skip macos bindings + if entry.path().file_name().unwrap().to_str() == Some("asiodrvr.cpp") { + continue; + } + paths.push(entry.path().to_path_buf()) + } + Some(_) => continue, + }; + } + }; + + // Get all cpp files for building SDK library + walk_a_dir(host_dir, &mut cpp_paths); + walk_a_dir(pc_dir, &mut cpp_paths); + walk_a_dir(common_dir, &mut cpp_paths); + + // build the asio lib + cc::Build::new() + .include(format!("{}/{}", cpal_asio_dir.display(), "host")) + .include(format!("{}/{}", cpal_asio_dir.display(), "common")) + .include(format!("{}/{}", cpal_asio_dir.display(), "host/pc")) + .include("asio-link/helpers.hpp") + .file("asio-link/helpers.cpp") + .files(cpp_paths) + .cpp(true) + .compile("libasio.a"); +} + +fn create_bindings(cpal_asio_dir: &PathBuf) { + let mut asio_header = None; + let mut asio_sys_header = None; + let mut asio_drivers_header = None; + + // Recursively walk given cpal dir to find required headers + for entry in WalkDir::new(&cpal_asio_dir) { + let entry = match entry { + Err(_) => continue, + Ok(entry) => entry, + }; + let file_name = match entry.path().file_name().and_then(|s| s.to_str()) { + None => continue, + Some(file_name) => file_name, + }; + + match file_name { + ASIO_HEADER => asio_header = Some(entry.path().to_path_buf()), + ASIO_SYS_HEADER => asio_sys_header = Some(entry.path().to_path_buf()), + ASIO_DRIVERS_HEADER => asio_drivers_header = Some(entry.path().to_path_buf()), + _ => (), + } + } + + macro_rules! header_or_panic { + ($opt_header:expr, $FILE_NAME:expr) => { + match $opt_header.as_ref() { + None => { + panic!("Could not find {} in {}: {}", $FILE_NAME, CPAL_ASIO_DIR, cpal_asio_dir.display()); + }, + Some(path) => path.to_str().expect("Could not convert path to str"), + } + }; + } + + // Only continue if found all headers that we need + let asio_header = header_or_panic!(asio_header, ASIO_HEADER); + let asio_sys_header = header_or_panic!(asio_sys_header, ASIO_SYS_HEADER); + let asio_drivers_header = header_or_panic!(asio_drivers_header, ASIO_DRIVERS_HEADER); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header(asio_header) + .header(asio_sys_header) + .header(asio_drivers_header) + .header("asio-link/helpers.hpp") + .clang_arg("-x") + .clang_arg("c++") + .clang_arg("-std=c++14") + .clang_arg( format!("-I{}/{}", cpal_asio_dir.display(), "host/pc") ) + .clang_arg( format!("-I{}/{}", cpal_asio_dir.display(), "host") ) + .clang_arg( format!("-I{}/{}", cpal_asio_dir.display(), "common") ) + // Need to whitelist to avoid binding tp c++ std::* + .whitelist_type("AsioDrivers") + .whitelist_type("AsioDriver") + .whitelist_type("ASIOTime") + .whitelist_type("ASIODriverInfo") + .whitelist_type("ASIOBufferInfo") + .whitelist_type("ASIOCallbacks") + .whitelist_type("ASIOSampleType") + .whitelist_type("ASIOChannelInfo") + .whitelist_function("destruct_AsioDrivers") + .whitelist_function("ASIOGetChannels") + .whitelist_function("ASIOGetChannelInfo") + .whitelist_function("ASIOGetBufferSize") + .whitelist_function("get_sample_rate") + .whitelist_function("ASIOInit") + .whitelist_function("ASIOCreateBuffers") + .whitelist_function("ASIOStart") + .whitelist_function("ASIOStop") + .whitelist_function("ASIODisposeBuffers") + .whitelist_function("ASIOExit") + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").expect("bad path")); + //panic!("path: {}", out_path.display()); + bindings + .write_to_file(out_path.join("asio_bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/asio-sys/examples/.test.rs.swp b/asio-sys/examples/.test.rs.swp new file mode 100644 index 0000000..d041a32 Binary files /dev/null and b/asio-sys/examples/.test.rs.swp differ diff --git a/asio-sys/examples/enumerate.rs b/asio-sys/examples/enumerate.rs new file mode 100644 index 0000000..df29ce4 --- /dev/null +++ b/asio-sys/examples/enumerate.rs @@ -0,0 +1,72 @@ +/* This example aims to produce the same behaviour + * as the enumerate example in cpal + * by Tom Gowan + */ + +extern crate asio_sys as sys; + +// This is the same data that enumerate +// is trying to find +// Basically these are stubbed versions +// +// Format that each sample has. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SampleFormat { + // The value 0 corresponds to 0. + I16, + // The value 0 corresponds to 32768. + U16, + // The boundaries are (-1.0, 1.0). + F32, +} +// Number of channels. +pub type ChannelCount = u16; + +// The number of samples processed per second for a single channel of audio. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SampleRate(pub u32); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Format { + pub channels: ChannelCount, + pub sample_rate: SampleRate, + pub data_type: SampleFormat, +} + +fn main() { + let driver_list = sys::get_driver_list(); + + let format = Format { + channels: 0, + sample_rate: SampleRate(0), + // TODO Not sure about how to set the data type + data_type: SampleFormat::F32, + }; + if driver_list.len() > 0 { + let format = match sys::get_channels(&driver_list[0]) { + Ok(channels) => Format { + channels: channels.ins as u16, + sample_rate: format.sample_rate, + data_type: format.data_type, + }, + Err(e) => { + println!("Error retrieving channels: {}", e); + format + } + }; + + let format = match sys::get_sample_rate(&driver_list[0]) { + Ok(sample_rate) => Format { + channels: format.channels, + sample_rate: SampleRate(sample_rate.rate), + data_type: format.data_type, + }, + Err(e) => { + println!("Error retrieving sample rate: {}", e); + format + } + }; + + println!("Format {:?}", format); + } +} diff --git a/asio-sys/examples/test.rs b/asio-sys/examples/test.rs new file mode 100644 index 0000000..88d532e --- /dev/null +++ b/asio-sys/examples/test.rs @@ -0,0 +1,16 @@ +extern crate asio_sys as sys; + +fn main() { + let driver_list = sys::get_driver_list(); + + for driver in &driver_list { + println!("Driver: {}", driver); + } + + if driver_list.len() > 0 { + match sys::get_channels(&driver_list[0]) { + Ok(channels) => println!("Channels: {:?}", channels), + Err(e) => println!("Error retrieving channels: {}", e), + } + } +} diff --git a/asio-sys/src/.asio_import.rs.swp b/asio-sys/src/.asio_import.rs.swp new file mode 100644 index 0000000..b6862ea Binary files /dev/null and b/asio-sys/src/.asio_import.rs.swp differ diff --git a/asio-sys/src/.errors.rs.swp b/asio-sys/src/.errors.rs.swp new file mode 100644 index 0000000..261c2e5 Binary files /dev/null and b/asio-sys/src/.errors.rs.swp differ diff --git a/asio-sys/src/.lib.rs.swp b/asio-sys/src/.lib.rs.swp new file mode 100644 index 0000000..e70723b Binary files /dev/null and b/asio-sys/src/.lib.rs.swp differ diff --git a/asio-sys/src/asio_import.rs b/asio-sys/src/asio_import.rs new file mode 100644 index 0000000..dd6b441 --- /dev/null +++ b/asio-sys/src/asio_import.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +include!(concat!(env!("OUT_DIR"), "/asio_bindings.rs")); diff --git a/asio-sys/src/errors.rs b/asio-sys/src/errors.rs new file mode 100644 index 0000000..72df46f --- /dev/null +++ b/asio-sys/src/errors.rs @@ -0,0 +1,32 @@ +use std::error::Error; +use std::fmt; + +#[derive(Debug)] +pub enum ASIOError { + NoResult(String), + BufferError(String), + DriverLoadError, + TypeError, +} + +impl fmt::Display for ASIOError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ASIOError::NoResult(ref e) => write!(f, "Driver {} not found", e), + ASIOError::BufferError(ref e) => write!(f, "Buffer Error: {}", e), + ASIOError::DriverLoadError => write!(f, "Couldn't load the driver"), + ASIOError::TypeError => write!(f, "Couldn't convert sample type"), + } + } +} + +impl Error for ASIOError { + fn description(&self) -> &str { + match *self { + ASIOError::NoResult(_) => "Couln't find driver", + ASIOError::BufferError(_) => "Error creating the buffer", + ASIOError::DriverLoadError => "Error loading the driver", + ASIOError::TypeError => "Error getting sample type", + } + } +} diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs new file mode 100644 index 0000000..1dfc497 --- /dev/null +++ b/asio-sys/src/lib.rs @@ -0,0 +1,447 @@ +#[macro_use] +extern crate lazy_static; + +extern crate num; +#[macro_use] +extern crate num_derive; + +mod asio_import; +pub mod errors; + +use std::os::raw::c_char; +use std::ffi::CStr; +use std::ffi::CString; +use std::os::raw::c_long; +use std::os::raw::c_void; +use std::os::raw::c_double; +use errors::ASIOError; +use std::mem; +use std::sync::Mutex; + +use asio_import as ai; + +const MAX_DRIVER: usize = 32; + +pub struct CbArgs { + pub stream_id: S, + pub data: D, +} + +struct BufferCallback(Box); + +lazy_static!{ + static ref buffer_callback: Mutex> = Mutex::new(None); +} + +#[derive(Debug)] +pub struct Channel { + pub ins: i64, + pub outs: i64, +} + +#[derive(Debug)] +pub struct SampleRate { + pub rate: u32, +} + +pub struct AsioStream { + pub buffer_infos: [AsioBufferInfo; 2], + driver: ai::AsioDrivers, + pub buffer_size: i32, +} + +// 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, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct AsioBufferInfo { + pub is_input: c_long, + pub channel_num: c_long, + pub buffers: [*mut std::os::raw::c_void; 2], +} + +#[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, +} + +extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) -> () { + let mut bc = buffer_callback.lock().unwrap(); + + if let Some(ref mut bc) = *bc { + bc.run(double_buffer_index); + } +} + +extern "C" fn sample_rate_did_change(s_rate: c_double) -> () {} + +extern "C" fn asio_message( + selector: c_long, + value: c_long, + message: *mut (), + opt: *mut c_double, +) -> c_long { + 4 as c_long +} + +extern "C" fn buffer_switch_time_info( + params: *mut ai::ASIOTime, + double_buffer_index: c_long, + direct_process: c_long, +) -> *mut ai::ASIOTime { + params +} + +impl AsioStream { + fn pop_driver(self) -> ai::AsioDrivers { + self.driver + } +} + +impl BufferCallback { + fn run(&mut self, index: i32) { + let mut cb = &mut self.0; + cb(index); + } +} + +unsafe impl Send for AsioStream {} + +pub fn set_callback(mut callback: F) -> () +where + F: FnMut(i32) + Send, +{ + let mut bc = buffer_callback.lock().unwrap(); + *bc = Some(BufferCallback(Box::new(callback))); +} +/// Returns the channels for the driver it's passed +/// +/// # Arguments +/// * `driver name` - Name of the driver +/// # Usage +/// Use the get_driver_list() to get the list of names +/// Then pass the one you want to get_channels +pub fn get_channels(driver_name: &str) -> Result { + let channel: Result; + // 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(); + + // Initialize memory for calls + let mut ins: c_long = 0; + let mut outs: c_long = 0; + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + unsafe { + let mut asio_drivers = ai::AsioDrivers::new(); + + let load_result = asio_drivers.loadDriver(raw); + + // Take back ownership + my_driver_name = CString::from_raw(raw); + + if load_result { + ai::ASIOInit(&mut driver_info); + ai::ASIOGetChannels(&mut ins, &mut outs); + asio_drivers.removeCurrentDriver(); + channel = Ok(Channel { + ins: ins as i64, + outs: outs as i64, + }); + } else { + channel = Err(ASIOError::NoResult(driver_name.to_owned())); + } + ai::destruct_AsioDrivers(&mut asio_drivers); + } + + channel +} + +/// Returns a list of all the ASIO drivers +pub fn get_driver_list() -> Vec { + let mut driver_list: Vec = Vec::new(); + + let mut driver_names: [[c_char; MAX_DRIVER]; MAX_DRIVER] = [[0; MAX_DRIVER]; MAX_DRIVER]; + let mut p_driver_name: [*mut i8; MAX_DRIVER] = [0 as *mut i8; MAX_DRIVER]; + + for i in 0..MAX_DRIVER { + p_driver_name[i] = driver_names[i].as_mut_ptr(); + } + + unsafe { + let mut asio_drivers = ai::AsioDrivers::new(); + + let num_drivers = + asio_drivers.getDriverNames(p_driver_name.as_mut_ptr(), MAX_DRIVER as i32); + + if num_drivers > 0 { + for i in 0..num_drivers { + 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(); + match my_driver_name.into_string() { + Ok(s) => driver_list.push(s), + Err(_) => println!("Failed converting from CString"), + } + } + } else { + println!("No ASIO drivers found"); + } + + ai::destruct_AsioDrivers(&mut asio_drivers); + } + + driver_list +} + +pub fn get_sample_rate(driver_name: &str) -> Result { + let sample_rate: Result; + // 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(); + + // Initialize memory for calls + let mut rate: c_double = 0.0f64; + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + unsafe { + let mut asio_drivers = ai::AsioDrivers::new(); + + let load_result = asio_drivers.loadDriver(raw); + + // Take back ownership + my_driver_name = CString::from_raw(raw); + + if load_result { + ai::ASIOInit(&mut driver_info); + ai::get_sample_rate(&mut rate); + asio_drivers.removeCurrentDriver(); + sample_rate = Ok(SampleRate { rate: rate as u32 }); + } else { + sample_rate = Err(ASIOError::NoResult(driver_name.to_owned())); + } + ai::destruct_AsioDrivers(&mut asio_drivers); + } + + sample_rate +} + +pub fn get_data_type(driver_name: &str) -> Result { + let data_type: Result; + // 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(); + + // Initialize memory for calls + let mut channel_info = ai::ASIOChannelInfo { + channel: 0, + isInput: 0, + isActive: 0, + channelGroup: 0, + type_: 0, + name: [0 as c_char; 32], + }; + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + unsafe { + let mut asio_drivers = ai::AsioDrivers::new(); + + let load_result = asio_drivers.loadDriver(raw); + + // Take back ownership + my_driver_name = CString::from_raw(raw); + + if load_result { + ai::ASIOInit(&mut driver_info); + ai::ASIOGetChannelInfo(&mut channel_info); + asio_drivers.removeCurrentDriver(); + data_type = num::FromPrimitive::from_i32(channel_info.type_) + .map_or(Err(ASIOError::TypeError), |t| Ok(t)); + } else { + data_type = Err(ASIOError::NoResult(driver_name.to_owned())); + } + ai::destruct_AsioDrivers(&mut asio_drivers); + } + + data_type +} + +pub fn prepare_stream(driver_name: &str) -> Result { + //let mut buffer_info = ai::ASIOBufferInfo{_bindgen_opaque_blob: [0u32; 6]}; + let mut buffer_infos = [ + AsioBufferInfo { + is_input: 0, + channel_num: 0, + buffers: [std::ptr::null_mut(); 2], + }, + AsioBufferInfo { + is_input: 0, + channel_num: 0, + buffers: [std::ptr::null_mut(); 2], + }, + ]; + + let num_channels = 2; + //let mut callbacks = ai::ASIOCallbacks{_bindgen_opaque_blob: [0u32; 8]}; + let mut 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 driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + // 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 result = Err(ASIOError::NoResult("not implimented".to_owned())); + + unsafe { + let mut asio_drivers = ai::AsioDrivers::new(); + let load_result = asio_drivers.loadDriver(raw); + // Take back ownership + my_driver_name = CString::from_raw(raw); + if !load_result { + return Err(ASIOError::DriverLoadError); + } + + for d in &buffer_infos { + println!("before {:?}", d); + } + + ai::ASIOInit(&mut driver_info); + ai::ASIOGetBufferSize( + &mut min_b_size, + &mut max_b_size, + &mut pref_b_size, + &mut grans, + ); + result = if pref_b_size > 0 { + let mut buffer_info_convert = [ + mem::transmute::(buffer_infos[0]), + mem::transmute::(buffer_infos[1]), + ]; + let mut callbacks_convert = + mem::transmute::(callbacks); + let buffer_result = ai::ASIOCreateBuffers( + buffer_info_convert.as_mut_ptr(), + num_channels, + pref_b_size, + &mut callbacks_convert, + ); + if buffer_result == 0 { + let buffer_infos = [ + mem::transmute::(buffer_info_convert[0]), + mem::transmute::(buffer_info_convert[1]), + ]; + for d in &buffer_infos { + println!("after {:?}", d); + } + println!("channels: {:?}", num_channels); + + return Ok(AsioStream { + buffer_infos: buffer_infos, + driver: asio_drivers, + buffer_size: pref_b_size, + }); + } + Err(ASIOError::BufferError(format!( + "failed to create buffers, + error code: {}", + buffer_result + ))) + } else { + Err(ASIOError::BufferError( + "Failed to get buffer size".to_owned(), + )) + }; + + asio_drivers.removeCurrentDriver(); + ai::destruct_AsioDrivers(&mut asio_drivers); + } + result +} + +pub fn destroy_stream(stream: AsioStream) { + unsafe { + ai::ASIODisposeBuffers(); + let mut asio_drivers = stream.pop_driver(); + asio_drivers.removeCurrentDriver(); + ai::destruct_AsioDrivers(&mut asio_drivers); + } +} + +pub fn play() { + unsafe { + let result = ai::ASIOStart(); + println!("start result: {}", result); + } +} + +pub fn stop() { + unsafe { + let result = ai::ASIOStop(); + println!("start result: {}", result); + } +} diff --git a/src/os/mod.rs b/src/os/mod.rs new file mode 100644 index 0000000..0d034fd --- /dev/null +++ b/src/os/mod.rs @@ -0,0 +1 @@ +pub mod windows; diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs new file mode 100644 index 0000000..ad447d8 --- /dev/null +++ b/src/os/windows/mod.rs @@ -0,0 +1,12 @@ +pub enum Backend { + Wasapi, + Asio, +} + +// TODO This needs to be set once at run time +// by the cpal user +static backend: Backend = Backend::Asio; + +pub fn which_backend() -> &'static Backend { + &backend +} diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs new file mode 100644 index 0000000..5e85ae8 --- /dev/null +++ b/src/platform/windows/asio/device.rs @@ -0,0 +1,206 @@ +extern crate asio_sys as sys; +use std; +pub type SupportedInputFormats = std::vec::IntoIter; +pub type SupportedOutputFormats = std::vec::IntoIter; + +use Format; +use FormatsEnumerationError; +use DefaultFormatError; +use SupportedFormat; +use SampleFormat; +use SampleRate; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Device{ + // Calls to get a driver in memory + // require a name so I will store the + // name here as a handle to the driver + pub driver_name: String, +} + +pub struct Devices{ + drivers: std::vec::IntoIter, +} + +impl Device { + pub fn name(&self) -> String { + self.driver_name.clone() + } + + // Just supporting default for now + pub fn supported_input_formats(&self) -> Result { + match self.default_input_format() { + Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), + Err(e) => Err(FormatsEnumerationError::DeviceNotAvailable), + } + } + + pub fn supported_output_formats(&self) -> Result { + match self.default_output_format() { + Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), + Err(e) => Err(FormatsEnumerationError::DeviceNotAvailable), + } + } + + // TODO Pass errors along + pub fn default_input_format(&self) -> Result { + let format = Format{channels: 0, sample_rate: SampleRate(0), + // TODO Not sure about how to set the data type + data_type: SampleFormat::F32}; + + let format = match sys::get_channels(&self.driver_name) { + Ok(channels) => { + Format{channels: channels.ins as u16, + sample_rate: format.sample_rate, + data_type: format.data_type} + }, + Err(e) => { + println!("Error retrieving channels: {}", e); + format + }, + }; + + + let format = match sys::get_sample_rate(&self.driver_name) { + Ok(sample_rate) => { + Format{channels: format.channels, + sample_rate: SampleRate(sample_rate.rate), + data_type: format.data_type} + }, + Err(e) => { + println!("Error retrieving sample rate: {}", e); + format + }, + }; + + let format = match sys::get_data_type(&self.driver_name) { + Ok(data_type) => { + println!("Audio Type: {:?}", data_type); + let data_type = match data_type{ + sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32, + sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16, + // TODO This should not be set to 16bit but is for testing + sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32, + _ => panic!("Unsupported Audio Type: {:?}", data_type), + }; + Format{channels: format.channels, + sample_rate: format.sample_rate, + data_type: data_type} + }, + Err(e) => { + println!("Error retrieving sample rate: {}", e); + format + }, + }; + + Ok(format) + + } + + pub fn default_output_format(&self) -> Result { + let format = Format{channels: 0, sample_rate: SampleRate(0), + // TODO Not sure about how to set the data type + data_type: SampleFormat::F32}; + + let format = match sys::get_channels(&self.driver_name) { + Ok(channels) => { + Format{channels: channels.outs as u16, + sample_rate: format.sample_rate, + data_type: format.data_type} + }, + Err(e) => { + println!("Error retrieving channels: {}", e); + format + }, + }; + + + let format = match sys::get_sample_rate(&self.driver_name) { + Ok(sample_rate) => { + Format{channels: format.channels, + sample_rate: SampleRate(sample_rate.rate), + data_type: format.data_type} + }, + Err(e) => { + println!("Error retrieving sample rate: {}", e); + format + }, + }; + + let format = match sys::get_data_type(&self.driver_name) { + Ok(data_type) => { + let data_type = match data_type{ + sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32, + sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16, + // TODO This should not be set to 16bit but is for testing + sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32, + _ => panic!("Unsupported Audio Type: {:?}", data_type), + }; + Format{channels: format.channels, + sample_rate: format.sample_rate, + data_type: data_type} + }, + Err(e) => { + println!("Error retrieving sample rate: {}", e); + format + }, + }; + + Ok(format) + } +} + +impl Default for Devices { + fn default() -> Devices { + Devices{ drivers: sys::get_driver_list().into_iter() } + + } +} + +impl Iterator for Devices { + type Item = Device; + + fn next(&mut self) -> Option { + match self.drivers.next() { + Some(dn) => Some(Device{driver_name: dn}), + None => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + unimplemented!() + } +} + +// 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(); + match driver_list.pop() { + Some(dn) => Some(Device{ driver_name: dn }), + None => None, + } +} + +pub fn default_output_device() -> Option { + let mut driver_list = sys::get_driver_list(); + // TODO For build test only, + // remove if inproduction + for dn in &driver_list{ + if dn == "ASIO4ALL v2"{ + println!("Defaulted to ASIO4ALL **remove from production**"); + return Some(Device{ driver_name: dn.clone() }); + } + } + // end remove + match driver_list.pop() { + Some(dn) => Some(Device{ driver_name: dn }), + None => None, + } +} diff --git a/src/platform/windows/asio/mod.rs b/src/platform/windows/asio/mod.rs new file mode 100644 index 0000000..8f98594 --- /dev/null +++ b/src/platform/windows/asio/mod.rs @@ -0,0 +1,8 @@ +extern crate asio_sys as sys; + +pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; + +pub use self::stream::{InputBuffer, OutputBuffer, EventLoop, StreamId}; + +mod device; +mod stream; diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs new file mode 100644 index 0000000..9c097d4 --- /dev/null +++ b/src/platform/windows/asio/stream.rs @@ -0,0 +1,336 @@ +extern crate asio_sys as sys; +extern crate itertools; + +use std; +use Format; +use CreationError; +use StreamData; +use std::marker::PhantomData; +use super::Device; +use std::cell::Cell; +use UnknownTypeOutputBuffer; +use std::sync::{Arc, Mutex}; +use std::mem; +use self::itertools::Itertools; + +pub struct EventLoop { + asio_stream: Arc>>, + stream_count: Cell, + callbacks: Arc>>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StreamId(usize); + +pub struct InputBuffer<'a, T: 'a> { + marker: PhantomData<&'a T>, +} +pub struct OutputBuffer<'a, T: 'a> { + buffer: &'a mut [T], +} + +impl EventLoop { + pub fn new() -> EventLoop { + EventLoop { + asio_stream: Arc::new(Mutex::new(None)), + stream_count: Cell::new(0), + callbacks: Arc::new(Mutex::new(Vec::new())), + } + } + + pub fn build_input_stream( + &self, + device: &Device, + format: &Format, + ) -> Result { + let stream_type = sys::get_data_type(&device.driver_name).expect("Couldn't load data type"); + match sys::prepare_stream(&device.driver_name) { + Ok(stream) => { + { + *self.asio_stream.lock().unwrap() = Some(stream); + } + let count = self.stream_count.get(); + self.stream_count.set(count + 1); + let asio_stream = self.asio_stream.clone(); + let callbacks = self.callbacks.clone(); + let bytes_per_channel = format.data_type.sample_size(); + let num_channels = format.channels.clone(); + + // Get stream types + + sys::set_callback(move |index| unsafe { + if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { + // Number of samples needed total + let cpal_num_samples = + (asio_stream.buffer_size as usize) * num_channels as usize; + let mut callbacks = callbacks.lock().unwrap(); + + // Assuming only one callback, probably needs to change + match callbacks.first_mut() { + Some(callback) => { + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident) => { + // Buffer that is filled by cpal. + let mut cpal_buffer: Vec<$SampleType> = vec![0 as $SampleType; cpal_num_samples]; + // Call in block because of mut borrow + { + } + // Function for deinterleaving because + // cpal writes to buffer interleaved + fn interleave(channels: Vec>) -> Vec<$SampleType>{ + let mut buffer: Vec<$SampleType> = Vec::new(); + let length = channels[0].len(); + for i in 0..length{ + for channel in channels{ + buffer.push(channel[i]); + } + } + buffer + } + // Deinter all the channels + let deinter_channels = deinterleave(&mut cpal_buffer[..], + num_channels as usize); + + // For each channel write the cpal data to + // the asio buffer + // Also need to check for Endian + for (i, channel) in deinter_channels.into_iter().enumerate(){ + let buff_ptr = (asio_stream + .buffer_infos[i] + .buffers[index as usize] as *mut $AsioType) + .offset(asio_stream.buffer_size as isize * i as isize); + let asio_buffer: &'static mut [$AsioType] = + std::slice::from_raw_parts_mut( + buff_ptr, + asio_stream.buffer_size as usize); + for (asio_s, cpal_s) in asio_buffer.iter_mut() + .zip(&channel){ + *asio_s = (*cpal_s as i64 * + ::std::$AsioTypeIdent::MAX as i64 / + ::std::$SampleTypeIdent::MAX as i64) as $AsioType; + } + + } + + + let buff = InputBuffer{ + buffer: &mut cpal_buffer + }; + callback( + StreamId(count), + StreamData::Input{ + buffer: UnknownTypeInputBuffer::$SampleFormat( + ::InputBuffer{ + target: Some(super::super::InputBuffer::Asio(buff)) + }) + } + ); + }; + } + // Generic over types + // TODO check for endianess + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!(I16, i16, i16, i32, i32); + } + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!(I16, i16, i16, i16, i16); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f32, f32); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f64, f64); + } + _ => println!("unsupported format {:?}", stream_type), + } + } + None => return (), + } + } + }); + Ok(StreamId(count)) + } + Err(ref e) => { + println!("Error preparing stream: {}", e); + Err(CreationError::DeviceNotAvailable) + } + } + } + + pub fn build_output_stream( + &self, + device: &Device, + format: &Format, + ) -> Result { + let stream_type = sys::get_data_type(&device.driver_name).expect("Couldn't load data type"); + match sys::prepare_stream(&device.driver_name) { + Ok(stream) => { + { + *self.asio_stream.lock().unwrap() = Some(stream); + } + let count = self.stream_count.get(); + self.stream_count.set(count + 1); + let asio_stream = self.asio_stream.clone(); + let callbacks = self.callbacks.clone(); + let bytes_per_channel = format.data_type.sample_size(); + let num_channels = format.channels.clone(); + + // Get stream types + + sys::set_callback(move |index| unsafe { + if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { + // Number of samples needed total + let cpal_num_samples = + (asio_stream.buffer_size as usize) * num_channels as usize; + let mut callbacks = callbacks.lock().unwrap(); + + // Assuming only one callback, probably needs to change + match callbacks.first_mut() { + Some(callback) => { + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident) => { + // Buffer that is filled by cpal. + let mut cpal_buffer: Vec<$SampleType> = vec![0 as $SampleType; cpal_num_samples]; + // Call in block because of mut borrow + { + let buff = OutputBuffer{ + buffer: &mut cpal_buffer + }; + callback( + StreamId(count), + StreamData::Output{ + buffer: UnknownTypeOutputBuffer::$SampleFormat( + ::OutputBuffer{ + target: Some(super::super::OutputBuffer::Asio(buff)) + }) + } + ); + } + // Function for deinterleaving because + // cpal writes to buffer interleaved + fn deinterleave(data_slice: &mut [$SampleType], + num_channels: usize) -> Vec>{ + let mut channels: Vec> = Vec::new(); + for i in 0..num_channels{ + let mut it = data_slice.iter().skip(i).cloned(); + let channel = it.step(num_channels).collect(); + channels.push(channel); + } + channels + } + // Deinter all the channels + let deinter_channels = deinterleave(&mut cpal_buffer[..], + num_channels as usize); + + // For each channel write the cpal data to + // the asio buffer + // Also need to check for Endian + for (i, channel) in deinter_channels.into_iter().enumerate(){ + let buff_ptr = (asio_stream + .buffer_infos[i] + .buffers[index as usize] as *mut $AsioType) + .offset(asio_stream.buffer_size as isize * i as isize); + let asio_buffer: &'static mut [$AsioType] = + std::slice::from_raw_parts_mut( + buff_ptr, + asio_stream.buffer_size as usize); + for (asio_s, cpal_s) in asio_buffer.iter_mut() + .zip(&channel){ + *asio_s = (*cpal_s as i64 * + ::std::$AsioTypeIdent::MAX as i64 / + ::std::$SampleTypeIdent::MAX as i64) as $AsioType; + } + + } + }; + } + // Generic over types + // TODO check for endianess + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!(I16, i16, i16, i32, i32); + } + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!(I16, i16, i16, i16, i16); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f32, f32); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f64, f64); + } + _ => println!("unsupported format {:?}", stream_type), + } + } + None => return (), + } + } + }); + Ok(StreamId(count)) + } + Err(ref e) => { + println!("Error preparing stream: {}", e); + Err(CreationError::DeviceNotAvailable) + } + } + } + + pub fn play_stream(&self, stream: StreamId) { + sys::play(); + } + + pub fn pause_stream(&self, stream: StreamId) { + sys::stop(); + } + pub fn destroy_stream(&self, stream_id: StreamId) { + let mut asio_stream_lock = self.asio_stream.lock().unwrap(); + let old_stream = mem::replace(&mut *asio_stream_lock, None); + if let Some(old_stream) = old_stream { + sys::destroy_stream(old_stream); + } + } + pub fn run(&self, mut callback: F) -> ! + where + F: FnMut(StreamId, StreamData) + Send, + { + let callback: &mut (FnMut(StreamId, StreamData) + Send) = &mut callback; + self.callbacks + .lock() + .unwrap() + .push(unsafe { mem::transmute(callback) }); + loop { + // Might need a sleep here to prevent the loop being + // removed in --release + } + } +} + +impl<'a, T> InputBuffer<'a, T> { + pub fn buffer(&self) -> &[T] { + unimplemented!() + } + pub fn finish(self) { + unimplemented!() + } +} + +impl<'a, T> OutputBuffer<'a, T> { + pub fn buffer(&mut self) -> &mut [T] { + &mut self.buffer + } + + pub fn len(&self) -> usize { + self.buffer.len() + } + + pub fn finish(self) {} +} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs new file mode 100644 index 0000000..b274ec0 --- /dev/null +++ b/src/platform/windows/mod.rs @@ -0,0 +1,2 @@ +#[cfg(windows)] +mod asio;