From 49968f4b82b0245eb094200fad737f9e68052e0f Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Sun, 22 Apr 2018 22:07:47 +1000 Subject: [PATCH 01/75] change of branch --- .gitignore | 7 + Cargo.toml | 4 + asio-sys/.Cargo.toml.swp | Bin 0 -> 12288 bytes asio-sys/.build.rs.swp | Bin 0 -> 16384 bytes asio-sys/Cargo.toml | 16 + asio-sys/asio-link/helpers.cpp | 9 + asio-sys/asio-link/helpers.hpp | 10 + asio-sys/build.bat | 2 + asio-sys/build.rs | 185 ++++++++++++ asio-sys/examples/.test.rs.swp | Bin 0 -> 12288 bytes asio-sys/examples/enumerate.rs | 72 +++++ asio-sys/examples/test.rs | 16 + asio-sys/src/.asio_import.rs.swp | Bin 0 -> 12288 bytes asio-sys/src/.errors.rs.swp | Bin 0 -> 12288 bytes asio-sys/src/.lib.rs.swp | Bin 0 -> 12288 bytes asio-sys/src/asio_import.rs | 6 + asio-sys/src/errors.rs | 32 ++ asio-sys/src/lib.rs | 447 ++++++++++++++++++++++++++++ src/os/mod.rs | 1 + src/os/windows/mod.rs | 12 + src/platform/windows/asio/device.rs | 206 +++++++++++++ src/platform/windows/asio/mod.rs | 8 + src/platform/windows/asio/stream.rs | 336 +++++++++++++++++++++ src/platform/windows/mod.rs | 2 + 24 files changed, 1371 insertions(+) create mode 100644 asio-sys/.Cargo.toml.swp create mode 100644 asio-sys/.build.rs.swp create mode 100644 asio-sys/Cargo.toml create mode 100644 asio-sys/asio-link/helpers.cpp create mode 100644 asio-sys/asio-link/helpers.hpp create mode 100644 asio-sys/build.bat create mode 100644 asio-sys/build.rs create mode 100644 asio-sys/examples/.test.rs.swp create mode 100644 asio-sys/examples/enumerate.rs create mode 100644 asio-sys/examples/test.rs create mode 100644 asio-sys/src/.asio_import.rs.swp create mode 100644 asio-sys/src/.errors.rs.swp create mode 100644 asio-sys/src/.lib.rs.swp create mode 100644 asio-sys/src/asio_import.rs create mode 100644 asio-sys/src/errors.rs create mode 100644 asio-sys/src/lib.rs create mode 100644 src/os/mod.rs create mode 100644 src/os/windows/mod.rs create mode 100644 src/platform/windows/asio/device.rs create mode 100644 src/platform/windows/asio/mod.rs create mode 100644 src/platform/windows/asio/stream.rs create mode 100644 src/platform/windows/mod.rs 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 0000000000000000000000000000000000000000..e0dc7b55894817c47370ea9b7025b3dcffcb4316 GIT binary patch literal 12288 zcmeI&O>WdM7zW_4EZ86wL@$tC}g)?vfR&3$(bdX@tvS##JdNs-CAH~lu6HoG2&tD|_GAC{~h>pUiH}Ue>H6mSm zZO%+(TjOHwqW1E2`uOm0l=m+hu&R&lXUE<+pZ&RT_QV!lZJOY-dU0Bn6Q32WsxswE zoBD;%M#@dC47RTRu{^pV0D(&dHfhlB<+sFkvUT(L(vj$d00bZa0SG_<0uX=z1lCU= zv>Wu3w{?ZXul%&J3jd%%00Izz00bZa0SG_<0uX=z1R$^`0!kBo*(CbTiQoU@e*izO z6Mf@+<$UIRJO$uAL zw&9cIiPAcW#nij^U0KXhPM+GiYLWv#xHG)h+rRsWH_7vZbafe|(VbM?Louc^ eK4D+l)dTlrE~rtpiA(=JQqMbRRWVajGp1iRfsZBt literal 0 HcmV?d00001 diff --git a/asio-sys/.build.rs.swp b/asio-sys/.build.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..e6df5f97633c97f90baeee0f3ec6b39593363ff5 GIT binary patch literal 16384 zcmeHOU2Ggz6&}h@2|p&)&^WGdnZP zPwcJjqCSvB;)%i&tq`a$MZ8p1DitUaYAR7tl^4VV1PNYCbI+?e<6q|3cHYIJ+1>uf{~JFwh_6*>My z)QcJ$iQ~46Fgiea)m$-9F>n9_hqbB66UUF%#@LaMuN@#;HCGH&3{(tM3{(tM3{(tM z3{(tM4E(<_5C?Z?xA9PS01oh;6z|37N#K$6Z+ui&#X!YC#X!YC#X!YC#X!YC#X!YC z#X!YC#X!Zt|0n}iOVdo){%I-z;Qc?<0N%Ju(|!xw0A2#F15W{0fNucHz!Gp2`0HU! zdmgwBJOlW^6mSpl_m67YpMaad4d6xKr@-UD4lo5w0*8P2VMn!3490G049OA z@mqvH051Yh0Z#%C1D^!m{19{lPXgC~Q^03{w+?CAw}CFO3VZ^1`-7VH8{j42I`Bi_ z8Q@Xi9PoMIZs7Kvnsy8L9&jEw1l;_9racQh0c-;g1M9#AU>R5f?grj?zoz{N=mA^6 zS%3le0Jq-<8-U*d&jD9})4*Zibrf7)2EGq`3z!Bz3%rSW`8{wG_!-~sMP$Mrk z^qO5=TryX`vTB}Ln4Mo(VKb+g74=KMdm+a+@dFLB!>tw%*$i9tyIjXFIl`V1SaC-GA$HgN2saZvd#)pf1d?y$Dwa?`WAoNa^m!nHeH`jc9Oxmkxgv6KC^ z*VJ>q$4SKYz1Z;*UY2lqoOcEl(wY(%nQri6c`Y;Ua8KWfc34C-MR5p*U^Q8$t^r0b zU6Kq7bA|r0A=Gst40Tfo3^!JEEgM=GyzPffN;n={x7_SKF#Ff3_{0?U2jaU*;aVbZ-z+}+Y_$iUBW*b zGf`~Cjy*$TDgFEOh`1Hnotf?3+ITiz%ZUQl>S1L`^Y-aZ1GakfFa1VqYI1}T^De&2 zWe-8NWJBr!g$0U5OcX5a@B3@VcgM(WgP1CeqO;CMFutD(6Cw z*645-KUIrJS*=gh@oYC~!2}w=EZrF{jeUTfjQQE#Z3tpZtKZ1z9cQ(UAH|KpuGNPO z{0>xsuCDL>Cp3{!MLo9NY@Vjy)5*=8n~KdVerLzvmBj92n5ujhX)~J^En!_6M_J$;tO-K|ZiYmnCzDNN3BU&nNC?4YXe9>JyMRQ*XbYS5$&~HL4a0WsxBqK*_V0 zqnxJ1`#*;(CQ0S0a}xXe!%vllEe4dt+YGHhMqCj}*i0Ia#e#2zm*IS9ofn*ZQNM_V zH7S80x2+W$)%X2FAW01fdweVA9`>9!de|dPLuPT5@JHPb3V|d;zIy*KNK*TRLhJz( zb5D!CZgj0Jv&93XJtvAJ(;)e7Kq%NL^crVkNj2q0y1?x(En|tDPTHq-Fa;V7PnX<6 za^dBECSNG|mP}=o1Ib0y&MGJ=x?mNya!s(b?CVtM(u7I)NA;1`jV`ZD8NEm5VVbPS z5ftFh)RRq1xng`_MP}9@gZQ3kq#jAhVT2)(nuY_>zci~Q_zvw7JH z_1e2 z7^Ni<7hVE709S#>0ea5Ifa{Mf5* zi=2j3<(ZI(xh$)u2!+{sKTY1Khsp#=U{Biij#5E6K3f+LOXqN99o5a~?_+5`*-#C5 z8c}sL^0NXd$p~EYtPPh_cf@)w@&nbipF<>NYvK$kMq1%f;-U8L}5{}`Bih_C3c^@I!p)5evwshsw)Rb30 z8qZ_1J6?d|n(WOsk=TYg?);F$g6~6 zucP*z_0R1g5nm}A7yB0jMXcv1X}vHBm>fv|`XQXVIIIp38y|Ei>~q$lTRwF|HRmF!*8IFUNMkxv!|asJsU~@h zENB5;OCFBAstCwX$7uTr+@MaSFvrtUp%;OxVHkMxH;8~~#3>Hv#Ma7STmM9pA z@TOivW${Aa-Vl0~samMM;5<)*J24J$H3j+c}u zFBAp!f=m-d92C8C^TZ87U*4%}*`aTS30_-_^)Fc}T`1*!vzFag?Ix9R^Z^vhYD%f< PL81dT16KCIZ)E=klM_y3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d041a32118a297802fd2a215b14b539b97514601 GIT binary patch literal 12288 zcmeI2&u-H|5XQG~L<;|2U>c;#mfF~qOXaAoM2b`nh)Spwfgp07Nn>F<*6tFus&ef^ z@Br`{yaW#b55W~?9S8doA+FIk(r53^&dl!oj`p61ub;o5yrI1oOLCH 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 0000000000000000000000000000000000000000..b6862eaf61897da1b165ce25e79d43a5895d066a GIT binary patch literal 12288 zcmeI&!Aiq07zgmogBL+XUtsMnEx5JLi|Ebm;11%LRzZ=HCSerQq%_@RFQFGto_!7< z#aGdE8z{Qr#nbo){rWbLV z<+>Cu0uX=z1Rwwb2tZ(U1Ua0SG_<0uX=z1Rwwb2tWV=5cn5?R53Z?!tt0^jFyfk)Y$Rd^XM|}_6DxM zS9fPy+LKiAR1J;Y)b9HJF(D1-mP)A~JT54YndZX3`zk7}Vx#E2h#57%I*evQPCIcq V$%XlO=P0-P_-Fm0)CrZ~X`5D^t55ULz|T`bvNYj+(KS^fe( z0T7?S0r3$BZyOUGL00Iv#Z&@88Fpn_ZxB2_PN3ep-k3 z&I5$04#ij`G8d}OEgHioo!f&!ue-Hq!7ShN+ygC?c27Up?aQD@MP{^{P7afJq}?F* z6W73^KS(`K*Bfhi<~18jf| zumLu}2H3z-8Zh|^{G!gz5m$+o8Fb;n2G{@_U;}J`4X^<=zy{a=8(;%$fDJ5(0Y3!z zZ~<~18jf|umLvkUmK_Zv4W}|6j5xiv)E>Ab{^Gtd&B*Q zcj`~HJ@0{hBy^FO)?piWcCpcuMUrh8jN&ZBP{m`R8qR!k>YLyNUeh9pN}G%_422F< zoSRr?t!6#b#?CG_wNY3?oaPDIqcD={SOQpHBB zwIT-_eOn}!6=ss^mPHme99s7Gs-x6Qt93B)u$dOLH8q}hS1M}K2xYslsi~k@FE-PU zb)NW>Gg_*vpDP_26=z3ONM4LkWJP+q|Mh2~s4H*v#i%%HKL;qZKy5IKD+aM1*V-`|^=?##BOcqKE*XWM=A-tYb1 z_x*mqH-)KHkB%MW{pBjdwS}=K!}pKVuLmAt%xX$YoDz=L5hZGT4hQKUslqFE2EwnHia9=N^?PX?3%2YEyJl8()N1ff~-j2tf0B<%XQ4>w{rz@ z1#YXrqpYuYSGB9Kjcb5=eo4EqH0=WXY0=WXY0=WXY0=WXY0(YMRL2CoMg=O6X z9Iznc;_yv3d&VdOq4z54I*oWXEI1gS1 z`@tya2YUbmx9(@`J#Yb>0mr~r@Y6=dJ^*#F8(hU6Tn0aY&%s;Zb#NBE1V+IK=mWbz z70|xz1^r+TcotCGU-vThF?bW42j{>U@G95~Hi7(FR$vLgEisPE^6gW?$BQU3V5%W% zrdx)sX_kR6S_GTDxyixOiZLH|OqG27C zR&&DBBaLNgAX1pLZL3V*TE6WDj=QZ`829+_;jxK$TO_Z0p=%W|lDg5g#DyaTUu5cA z{E@XrjVoKIb-(Wy^%5T(O3nC!VLQU&rspCK0^4oyy6-jl$l(A^H43F}Mk70~iMgUw zhEnlUZiR9d%_~PVjli7cc;6}R?HWx{#*#f9(S=!xrH%#BblL8FkgHJ`0B*04hHvts^O5>0c(e6dg{l|y&lH(H&Z z)IFcu+;(}jTz11|vit2Fv+F$7&WCt)u^kv{4ee1_3cVSY7%4YI5C!=-B$U~8l?|!G zakb!5H$OQ1iat6yc69$FE!XbrO~)Ti6DP^CaheM$pNipVsmZv|l}25}zP1CbL)#gc zNo}*7wr4VqMWLJ)sqJWNJ8>>!S>w|?$z!{JAhk4_ZMsSeTw?M=$m^3)D5m&)ZRNM%HOI9ZIX zV>whts7m3dhb$ZSCWQ>0pqLW4lr}>Zs~!GXTPP$Wk=u*N8L82Sir43McXKZc&_bVa z(^AST*~#RFFPh#dF%tSdUayR@q%${Fe8ck>MG( ~PLko2z4kX6u}VvI3*VTsw#a zi&tv9buZNyiS2lXrTa<_lr%;@(zSLiQ6(#8kWiXWPD12WH=Ox<{X18i+as=t%h)QuVtJJ zgm`ylZ9owVO*x{$IGUps8klGTfz*Ag%QKu5%7i=ot!w* zc;F?&lCvAW9;6LbhgWiw`bpth^pmzME}YAb$0?_w4p4&ai(|H1WxvY4p#hdEC~<$8 z&P0uGhexWstZgN#Hb-WK%Ugz&s=|1#;0s>J=M6U?2dQEu6R6^Zmz!0`m8C4|t!^fD zixiV;SQnp?MnkCIck&aFqUzi}!FxEJYgFf`#znW{LBoC{dT*M(tG1*#QrE;Wwf*g} z+zxg~wIpFBRkB^xmaTG^$qZNHR@}a(6(DPkRu3FxOF4 literal 0 HcmV?d00001 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; From 2cfd60757a015d2da7eb0caf939f13b71a40b19b Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Sun, 22 Apr 2018 22:23:19 +1000 Subject: [PATCH 02/75] working on windows --- asio-sys/src/.asio_import.rs.swp | Bin 12288 -> 0 bytes asio-sys/src/.errors.rs.swp | Bin 12288 -> 0 bytes asio-sys/src/.lib.rs.swp | Bin 12288 -> 0 bytes src/platform/windows/asio/stream.rs | 3 +++ 4 files changed, 3 insertions(+) delete mode 100644 asio-sys/src/.asio_import.rs.swp delete mode 100644 asio-sys/src/.errors.rs.swp delete mode 100644 asio-sys/src/.lib.rs.swp diff --git a/asio-sys/src/.asio_import.rs.swp b/asio-sys/src/.asio_import.rs.swp deleted file mode 100644 index b6862eaf61897da1b165ce25e79d43a5895d066a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&!Aiq07zgmogBL+XUtsMnEx5JLi|Ebm;11%LRzZ=HCSerQq%_@RFQFGto_!7< z#aGdE8z{Qr#nbo){rWbLV z<+>Cu0uX=z1Rwwb2tZ(U1Ua0SG_<0uX=z1Rwwb2tWV=5cn5?R53Z?!tt0^jFyfk)Y$Rd^XM|}_6DxM zS9fPy+LKiAR1J;Y)b9HJF(D1-mP)A~JT54YndZX3`zk7}Vx#E2h#57%I*evQPCIcq V$%XlO=P0-P_-Fm0)CrZ~X`5D^t55ULz|T`bvNYj+(KS^fe( z0T7?S0r3$BZyOUGL00Iv#Z&@88Fpn_ZxB2_PN3ep-k3 z&I5$04#ij`G8d}OEgHioo!f&!ue-Hq!7ShN+ygC?c27Up?aQD@MP{^{P7afJq}?F* z6W73^KS(`K*Bfhi<~18jf| zumLu}2H3z-8Zh|^{G!gz5m$+o8Fb;n2G{@_U;}J`4X^<=zy{a=8(;%$fDJ5(0Y3!z zZ~<~18jf|umLvkUmK_Zv4W}|6j5xiv)E>Ab{^Gtd&B*Q zcj`~HJ@0{hBy^FO)?piWcCpcuMUrh8jN&ZBP{m`R8qR!k>YLyNUeh9pN}G%_422F< zoSRr?t!6#b#?CG_wNY3?oaPDIqcD={SOQpHBB zwIT-_eOn}!6=ss^mPHme99s7Gs-x6Qt93B)u$dOLH8q}hS1M}K2xYslsi~k@FE-PU zb)NW>Gg_*vpDP_26=z3ONM4LkWJP+q|Mh2~s4H*v#i%%HKL;qZKy5IKD+aM1*V-`|^=?##BOcqKE*XWM=A-tYb1 z_x*mqH-)KHkB%MW{pBjdwS}=K!}pKVuLmAt%xX$YoDz=L5hZGT4hQKUslqFE2EwnHia9=N^?PX?3%2YEyJl8()N1ff~-j2tf0B<%XQ4>w{rz@ z1#YXrqpYuYSGB9Kjcb5=eo4EqH0=WXY0=WXY0=WXY0=WXY0(YMRL2CoMg=O6X z9Iznc;_yv3d&VdOq4z54I*oWXEI1gS1 z`@tya2YUbmx9(@`J#Yb>0mr~r@Y6=dJ^*#F8(hU6Tn0aY&%s;Zb#NBE1V+IK=mWbz z70|xz1^r+TcotCGU-vThF?bW42j{>U@G95~Hi7(FR$vLgEisPE^6gW?$BQU3V5%W% zrdx)sX_kR6S_GTDxyixOiZLH|OqG27C zR&&DBBaLNgAX1pLZL3V*TE6WDj=QZ`829+_;jxK$TO_Z0p=%W|lDg5g#DyaTUu5cA z{E@XrjVoKIb-(Wy^%5T(O3nC!VLQU&rspCK0^4oyy6-jl$l(A^H43F}Mk70~iMgUw zhEnlUZiR9d%_~PVjli7cc;6}R?HWx{#*#f9(S=!xrH%#BblL8FkgHJ`0B*04hHvts^O5>0c(e6dg{l|y&lH(H&Z z)IFcu+;(}jTz11|vit2Fv+F$7&WCt)u^kv{4ee1_3cVSY7%4YI5C!=-B$U~8l?|!G zakb!5H$OQ1iat6yc69$FE!XbrO~)Ti6DP^CaheM$pNipVsmZv|l}25}zP1CbL)#gc zNo}*7wr4VqMWLJ)sqJWNJ8>>!S>w|?$z!{JAhk4_ZMsSeTw?M=$m^3)D5m&)ZRNM%HOI9ZIX zV>whts7m3dhb$ZSCWQ>0pqLW4lr}>Zs~!GXTPP$Wk=u*N8L82Sir43McXKZc&_bVa z(^AST*~#RFFPh#dF%tSdUayR@q%${Fe8ck>MG( ~PLko2z4kX6u}VvI3*VTsw#a zi&tv9buZNyiS2lXrTa<_lr%;@(zSLiQ6(#8kWiXWPD12WH=Ox<{X18i+as=t%h)QuVtJJ zgm`ylZ9owVO*x{$IGUps8klGTfz*Ag%QKu5%7i=ot!w* zc;F?&lCvAW9;6LbhgWiw`bpth^pmzME}YAb$0?_w4p4&ai(|H1WxvY4p#hdEC~<$8 z&P0uGhexWstZgN#Hb-WK%Ugz&s=|1#;0s>J=M6U?2dQEu6R6^Zmz!0`m8C4|t!^fD zixiV;SQnp?MnkCIck&aFqUzi}!FxEJYgFf`#znW{LBoC{dT*M(tG1*#QrE;Wwf*g} z+zxg~wIpFBRkB^xmaTG^$qZNHR@}a(6(DPkRu3FxOF4 diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 9c097d4..90f8caa 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -43,6 +43,7 @@ impl EventLoop { 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) => { @@ -160,6 +161,8 @@ impl EventLoop { Err(CreationError::DeviceNotAvailable) } } + */ + unimplemented!() } pub fn build_output_stream( From c978583863052bdfb5ba961f197ba12a54aecc81 Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Sun, 22 Apr 2018 23:24:05 +1000 Subject: [PATCH 03/75] recording v1 --- src/platform/windows/asio/device.rs | 6 + src/platform/windows/asio/stream.rs | 409 ++++++++++++++-------------- 2 files changed, 208 insertions(+), 207 deletions(-) diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 5e85ae8..e301742 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -182,6 +182,12 @@ impl Iterator for Devices { // so returning first in list as default pub fn default_input_device() -> Option { let mut driver_list = sys::get_driver_list(); + for dn in &driver_list{ + if dn == "ASIO4ALL v2"{ + println!("Defaulted to ASIO4ALL **remove from production**"); + return Some(Device{ driver_name: dn.clone() }); + } + } match driver_list.pop() { Some(dn) => Some(Device{ driver_name: dn }), None => None, diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 90f8caa..b93953d 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -9,6 +9,7 @@ use std::marker::PhantomData; use super::Device; use std::cell::Cell; use UnknownTypeOutputBuffer; +use UnknownTypeInputBuffer; use std::sync::{Arc, Mutex}; use std::mem; use self::itertools::Itertools; @@ -23,7 +24,7 @@ pub struct EventLoop { pub struct StreamId(usize); pub struct InputBuffer<'a, T: 'a> { - marker: PhantomData<&'a T>, + buffer: &'a mut [T], } pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], @@ -42,8 +43,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result { - /* + ) -> 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) => { @@ -57,7 +57,6 @@ impl EventLoop { 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() { @@ -70,192 +69,67 @@ impl EventLoop { 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]); - } + ($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; - } + buffer + } + let mut channels: Vec> = vec![Vec::new(); 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 channels.iter_mut().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 in asio_buffer.iter(){ + channel.push( (*asio_s as i64 * + ::std::$SampleTypeIdent::MAX as i64 / + ::std::$AsioTypeIdent::MAX as i64) as $SampleType); } + } + + // interleave all the channels + let inter_buffer = interleave(channels); - let buff = InputBuffer{ - buffer: &mut cpal_buffer - }; - callback( - StreamId(count), - StreamData::Input{ - buffer: UnknownTypeInputBuffer::$SampleFormat( - ::InputBuffer{ - target: Some(super::super::InputBuffer::Asio(buff)) - }) - } - ); + let buff = InputBuffer{ + buffer: &mut cpal_buffer }; - } - // 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) - } - } - */ - unimplemented!() - } - - 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)) - }) - } - ); + callback( + StreamId(count), + StreamData::Input{ + buffer: UnknownTypeInputBuffer::$SampleFormat( + ::InputBuffer{ + buffer: Some(super::super::InputBuffer::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 { @@ -287,42 +161,163 @@ impl EventLoop { } } - pub fn play_stream(&self, stream: StreamId) { - sys::play(); - } +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(); - 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); + // 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)) } - } - 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 + 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!() + &self.buffer } pub fn finish(self) { - unimplemented!() } } From fe4d1084772a682995b17a4184250dbc5a1ad3f1 Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Wed, 25 Apr 2018 12:56:58 +1000 Subject: [PATCH 04/75] minor fixes --- asio-sys/Cargo.toml | 2 +- src/platform/windows/asio/stream.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/asio-sys/Cargo.toml b/asio-sys/Cargo.toml index 7346b61..ae357f4 100644 --- a/asio-sys/Cargo.toml +++ b/asio-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "asio-sys" version = "0.1.0" -authors = ["Tom Gowan "] +authors = ["Tom Gowan "] build = "build.rs" [target.'cfg(any(target_os = "windows"))'.build-dependencies] diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index b93953d..82ef064 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -24,7 +24,7 @@ pub struct EventLoop { pub struct StreamId(usize); pub struct InputBuffer<'a, T: 'a> { - buffer: &'a mut [T], + buffer: &'a [T], } pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], @@ -142,7 +142,7 @@ impl EventLoop { sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32); } - sys::AsioSampleType::ASIOSTFloat32LSB => { + sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64); } _ => println!("unsupported format {:?}", stream_type), From a1740a928215e669147f95d67dc0b8b6d6b6cd76 Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Wed, 25 Apr 2018 14:52:48 +1000 Subject: [PATCH 05/75] record wav working v1 --- asio-sys/src/lib.rs | 103 ++++++++++++++++++++++++++++ src/platform/windows/asio/stream.rs | 21 +++--- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 1dfc497..6b0d9a4 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -318,6 +318,109 @@ pub fn get_data_type(driver_name: &str) -> Result { data_type } +pub fn prepare_input_stream(driver_name: &str) -> Result { + let mut buffer_infos = [ + AsioBufferInfo { + is_input: 1, + channel_num: 0, + buffers: [std::ptr::null_mut(); 2], + }, + AsioBufferInfo { + is_input: 1, + channel_num: 1, + buffers: [std::ptr::null_mut(); 2], + }, + ]; + let num_channels = 2; + + 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 prepare_stream(driver_name: &str) -> Result { //let mut buffer_info = ai::ASIOBufferInfo{_bindgen_opaque_blob: [0u32; 6]}; let mut buffer_infos = [ diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 82ef064..df16183 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -45,7 +45,7 @@ impl EventLoop { 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) { + match sys::prepare_input_stream(&device.driver_name) { Ok(stream) => { { *self.asio_stream.lock().unwrap() = Some(stream); @@ -76,9 +76,6 @@ impl EventLoop { $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>{ @@ -96,13 +93,14 @@ impl EventLoop { // For each channel write the cpal data to // the asio buffer // Also need to check for Endian + for (i, channel) in channels.iter_mut().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( + .buffers[index as usize] as *mut $AsioType); + //.offset(asio_stream.buffer_size as isize * i as isize); + let asio_buffer: &'static [$AsioType] = + std::slice::from_raw_parts( buff_ptr, asio_stream.buffer_size as usize); for asio_s in asio_buffer.iter(){ @@ -112,12 +110,13 @@ impl EventLoop { } } + // interleave all the channels - let inter_buffer = interleave(channels); + let mut inter_buffer = interleave(channels); let buff = InputBuffer{ - buffer: &mut cpal_buffer + buffer: &mut inter_buffer }; callback( StreamId(count), @@ -264,7 +263,7 @@ pub fn build_output_stream( sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32); } - sys::AsioSampleType::ASIOSTFloat32LSB => { + sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64); } _ => println!("unsupported format {:?}", stream_type), From 892024f5d8fc39dbc556b5fb05dd4ebb45237217 Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Wed, 25 Apr 2018 19:44:45 +1000 Subject: [PATCH 06/75] minor fixes 2 --- src/platform/windows/asio/device.rs | 71 +++++++++-------------------- src/platform/windows/asio/stream.rs | 17 +++---- 2 files changed, 31 insertions(+), 57 deletions(-) diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index e301742..19fbf23 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -102,57 +102,30 @@ impl Device { } 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 num_channels = sys::get_channels(&self.driver_name) + .map(|c| c.outs as u16); + let sample_rate = sys::get_sample_rate(&self.driver_name) + .map(|s| SampleRate(s.rate)); + let data_type = sys::get_data_type(&self.driver_name); + let data_type = match data_type{ + Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), + Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), + // TODO This should not be set to 16bit but is for testing + Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), + _ => Err(DefaultFormatError::StreamTypeNotSupported), + }; + let format = match (num_channels, sample_rate, data_type){ + (Ok(num_channels), Ok(sample_rate), Ok(data_type)) =>{ + Ok(Format{channels: num_channels, + sample_rate: sample_rate, + data_type: data_type}) + } + _ => Err(DefaultFormatError::StreamTypeNotSupported), }; - - 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) + format } } diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index df16183..7a6fba3 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -13,10 +13,11 @@ use UnknownTypeInputBuffer; use std::sync::{Arc, Mutex}; use std::mem; use self::itertools::Itertools; +use std::sync::atomic::{AtomicUsize, Ordering}; pub struct EventLoop { asio_stream: Arc>>, - stream_count: Cell, + stream_count: Arc, callbacks: Arc>>, } @@ -34,7 +35,7 @@ impl EventLoop { pub fn new() -> EventLoop { EventLoop { asio_stream: Arc::new(Mutex::new(None)), - stream_count: Cell::new(0), + stream_count: Arc::new(AtomicUsize::new(0)), callbacks: Arc::new(Mutex::new(Vec::new())), } } @@ -50,8 +51,8 @@ impl EventLoop { { *self.asio_stream.lock().unwrap() = Some(stream); } - let count = self.stream_count.get(); - self.stream_count.set(count + 1); + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); let asio_stream = self.asio_stream.clone(); let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); @@ -95,9 +96,9 @@ impl EventLoop { // Also need to check for Endian for (i, channel) in channels.iter_mut().enumerate(){ - let buff_ptr = (asio_stream + let buff_ptr = asio_stream .buffer_infos[i] - .buffers[index as usize] as *mut $AsioType); + .buffers[index as usize] as *mut $AsioType; //.offset(asio_stream.buffer_size as isize * i as isize); let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( @@ -171,8 +172,8 @@ pub fn build_output_stream( { *self.asio_stream.lock().unwrap() = Some(stream); } - let count = self.stream_count.get(); - self.stream_count.set(count + 1); + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); let asio_stream = self.asio_stream.clone(); let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); From 9a084347dbd744bb518cb0be624501f41176a81f Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Thu, 26 Apr 2018 12:02:02 +1000 Subject: [PATCH 07/75] removed itertools --- Cargo.toml | 1 - src/platform/windows/asio/stream.rs | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b526289..d7f234d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ keywords = ["audio", "sound"] [dependencies] failure = "0.1.5" lazy_static = "1.3" -itertools = "0.7.8" [dev-dependencies] hound = "3.4" diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 7a6fba3..e00e873 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -1,5 +1,4 @@ extern crate asio_sys as sys; -extern crate itertools; use std; use Format; @@ -12,7 +11,6 @@ use UnknownTypeOutputBuffer; use UnknownTypeInputBuffer; use std::sync::{Arc, Mutex}; use std::mem; -use self::itertools::Itertools; use std::sync::atomic::{AtomicUsize, Ordering}; pub struct EventLoop { @@ -218,11 +216,13 @@ pub fn build_output_stream( // 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); + let channel_len = data_slice.len() / num_channels; + let mut channels: Vec<_> = (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(); + for (i, &sample) in data_slice.iter().enumerate() { + let ch = i % num_channels; + channels[ch].push(sample); } channels } From ab7e17558fb0b09e15eb1a007ddb3f9d9d41cc6e Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Thu, 26 Apr 2018 14:58:36 +1000 Subject: [PATCH 08/75] attemp at removing buffer --- src/platform/windows/asio/stream.rs | 96 +++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index e00e873..0e5ecd3 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -12,6 +12,7 @@ use UnknownTypeInputBuffer; use std::sync::{Arc, Mutex}; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; +use SampleFormat; pub struct EventLoop { asio_stream: Arc>>, @@ -55,13 +56,54 @@ impl EventLoop { let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); let num_channels = format.channels.clone(); + + // Create buffers + let cpal_num_samples = + (stream.buffer_size as usize) * num_channels as usize; + let channel_len = cpal_num_samples + / num_channels as usize; + enum CpalBuffer{ + I16(Vec), + U16(Vec), + F32(Vec), + } + enum ChannelBuffer{ + I16(Vec>), + U16(Vec>), + F32(Vec>), + } + let (mut cpal_buffer, + mut channels) = match format.data_type{ + SampleFormat::I16 => { + let mut cpal_buffer = CpalBuffer::I16(vec![0 as i16; cpal_num_samples]); + let mut channels = ChannelBuffer::I16( + (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()); + (cpal_buffer, channels) + } + SampleFormat::U16 => { + let mut cpal_buffer = CpalBuffer::U16(vec![0 as u16; cpal_num_samples]); + let mut channels = ChannelBuffer::U16( + (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()); + (cpal_buffer, channels) + } + SampleFormat::F32 => { + let mut cpal_buffer = CpalBuffer::F32(vec![0 as f32; cpal_num_samples]); + let mut channels = ChannelBuffer::F32( + (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()); + (cpal_buffer, channels) + } + }; 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 @@ -73,22 +115,18 @@ impl EventLoop { $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]; // Function for deinterleaving because // cpal writes to buffer interleaved - fn interleave(channels: Vec>) -> Vec<$SampleType>{ - let mut buffer: Vec<$SampleType> = Vec::new(); + fn interleave(channels: &[Vec<$SampleType>], + buffer: &mut Vec<$SampleType>) { let length = channels[0].len(); for i in 0..length{ - for channel in &channels{ + for channel in channels{ buffer.push(channel[i]); } } - buffer } - let mut channels: Vec> = vec![Vec::new(); num_channels as usize]; // For each channel write the cpal data to // the asio buffer // Also need to check for Endian @@ -111,11 +149,11 @@ impl EventLoop { // interleave all the channels - let mut inter_buffer = interleave(channels); + interleave(&channels, &mut cpal_buffer); let buff = InputBuffer{ - buffer: &mut inter_buffer + buffer: &mut cpal_buffer }; callback( StreamId(count), @@ -130,17 +168,25 @@ impl EventLoop { }; // Generic over types // TODO check for endianess - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { + match (stream_type, cpal_buffer, channels) { + (sys::AsioSampleType::ASIOSTInt32LSB, + CpalBuffer::I16(cpal_buffer), + ChannelBuffer::I16(channels))=> { try_callback!(I16, i16, i16, i32, i32); } - sys::AsioSampleType::ASIOSTInt16LSB => { + (sys::AsioSampleType::ASIOSTInt16LSB, + CpalBuffer::I16(cpal_buffer), + ChannelBuffer::I16(channels))=> { try_callback!(I16, i16, i16, i16, i16); } - sys::AsioSampleType::ASIOSTFloat32LSB => { + (sys::AsioSampleType::ASIOSTFloat32LSB, + CpalBuffer::F32(cpal_buffer), + ChannelBuffer::F32(channels))=> { try_callback!(F32, f32, f32, f32, f32); } - sys::AsioSampleType::ASIOSTFloat64LSB => { + (sys::AsioSampleType::ASIOSTFloat64LSB, + CpalBuffer::F32(cpal_buffer), + ChannelBuffer::F32(channels))=> { try_callback!(F32, f32, f32, f64, f64); } _ => println!("unsupported format {:?}", stream_type), @@ -215,20 +261,22 @@ pub fn build_output_stream( // Function for deinterleaving because // cpal writes to buffer interleaved fn deinterleave(data_slice: &mut [$SampleType], - num_channels: usize) -> Vec>{ - let channel_len = data_slice.len() / num_channels; - let mut channels: Vec<_> = (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(); + num_channels: usize, + channels: &mut [Vec<$SampleType>]) { for (i, &sample) in data_slice.iter().enumerate() { let ch = i % num_channels; channels[ch].push(sample); } - channels } // Deinter all the channels - let deinter_channels = deinterleave(&mut cpal_buffer[..], - num_channels as usize); + let channel_len = cpal_buffer.len() + / num_channels as usize; + let mut deinter_channels: Vec<_> = (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(); + deinterleave(&mut cpal_buffer[..], + num_channels as usize, + &mut deinter_channels); // For each channel write the cpal data to // the asio buffer From c8c00793c0816f897877c2df31d2bb23675f12c2 Mon Sep 17 00:00:00 2001 From: DMSDeveloper Date: Thu, 26 Apr 2018 15:25:04 +1000 Subject: [PATCH 09/75] attemp at removing buffer 2 --- src/platform/windows/asio/stream.rs | 82 ++++++++++++++++------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 0e5ecd3..b1ad6f5 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -63,41 +63,55 @@ impl EventLoop { let channel_len = cpal_num_samples / num_channels as usize; - enum CpalBuffer{ - I16(Vec), - U16(Vec), - F32(Vec), + struct I16Buffer{ + cpal: Vec, + channel: Vec>, } - enum ChannelBuffer{ - I16(Vec>), - U16(Vec>), - F32(Vec>), + struct U16Buffer{ + cpal: Vec, + channel: Vec>, } - let (mut cpal_buffer, - mut channels) = match format.data_type{ + struct F32Buffer{ + cpal: Vec, + channel: Vec>, + } + struct BufferTypes(T); + impl BufferTypes{ + fn into_buff(self) -> I16Buffer{ + self.0 + } + } + impl BufferTypes{ + fn into_buff(self) -> U16Buffer{ + self.0 + } + } + impl BufferTypes{ + fn into_buff(self) -> F32Buffer{ + self.0 + } + } + let mut buffers = match format.data_type{ SampleFormat::I16 => { - let mut cpal_buffer = CpalBuffer::I16(vec![0 as i16; cpal_num_samples]); - let mut channels = ChannelBuffer::I16( - (0..num_channels) + BufferTypes(I16Buffer{ + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()); - (cpal_buffer, channels) + .collect()}) } SampleFormat::U16 => { - let mut cpal_buffer = CpalBuffer::U16(vec![0 as u16; cpal_num_samples]); - let mut channels = ChannelBuffer::U16( - (0..num_channels) + BufferTypes(U16Buffer{ + cpal: vec![0 as u16; cpal_num_samples], + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()); - (cpal_buffer, channels) + .collect()}) } SampleFormat::F32 => { - let mut cpal_buffer = CpalBuffer::F32(vec![0 as f32; cpal_num_samples]); - let mut channels = ChannelBuffer::F32( - (0..num_channels) + BufferTypes(F32Buffer{ + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()); - (cpal_buffer, channels) + .collect()}) } }; @@ -168,25 +182,17 @@ impl EventLoop { }; // Generic over types // TODO check for endianess - match (stream_type, cpal_buffer, channels) { - (sys::AsioSampleType::ASIOSTInt32LSB, - CpalBuffer::I16(cpal_buffer), - ChannelBuffer::I16(channels))=> { + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { try_callback!(I16, i16, i16, i32, i32); } - (sys::AsioSampleType::ASIOSTInt16LSB, - CpalBuffer::I16(cpal_buffer), - ChannelBuffer::I16(channels))=> { + sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!(I16, i16, i16, i16, i16); } - (sys::AsioSampleType::ASIOSTFloat32LSB, - CpalBuffer::F32(cpal_buffer), - ChannelBuffer::F32(channels))=> { + sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32); } - (sys::AsioSampleType::ASIOSTFloat64LSB, - CpalBuffer::F32(cpal_buffer), - ChannelBuffer::F32(channels))=> { + sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64); } _ => println!("unsupported format {:?}", stream_type), From 9c355f55793137565d14bf82d80c096cae163362 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 9 Oct 2018 21:12:52 +1100 Subject: [PATCH 10/75] fixed buffer --- asio-sys/Cargo.toml | 4 +- asio-sys/build.rs | 5 +- src/platform/windows/asio/stream.rs | 87 ++++++++++++++++++----------- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/asio-sys/Cargo.toml b/asio-sys/Cargo.toml index ae357f4..34f598a 100644 --- a/asio-sys/Cargo.toml +++ b/asio-sys/Cargo.toml @@ -5,9 +5,9 @@ authors = ["Tom Gowan "] build = "build.rs" [target.'cfg(any(target_os = "windows"))'.build-dependencies] -bindgen = "0.33.1" +bindgen = "0.42.0" walkdir = "2" -cc = "1.0.4" +cc = "1.0.25" [dependencies] lazy_static = "1.0.0" diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 1c96c80..0a004f3 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -61,7 +61,10 @@ fn create_lib(cpal_asio_dir: &PathBuf) { 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, + Err(e) => { + println!("error: {}", e); + continue + }, Ok(entry) => entry, }; match entry.path().extension().and_then(|s| s.to_str()) { diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index b1ad6f5..9788e3f 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -47,6 +47,9 @@ impl EventLoop { let stream_type = sys::get_data_type(&device.driver_name).expect("Couldn't load data type"); match sys::prepare_input_stream(&device.driver_name) { Ok(stream) => { + let num_channels = format.channels.clone(); + let cpal_num_samples = + (stream.buffer_size as usize) * num_channels as usize; { *self.asio_stream.lock().unwrap() = Some(stream); } @@ -55,63 +58,65 @@ impl EventLoop { 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(); // Create buffers - let cpal_num_samples = - (stream.buffer_size as usize) * num_channels as usize; let channel_len = cpal_num_samples / num_channels as usize; - + + #[derive(Default)] struct I16Buffer{ cpal: Vec, channel: Vec>, } + #[derive(Default)] struct U16Buffer{ cpal: Vec, channel: Vec>, } + #[derive(Default)] struct F32Buffer{ cpal: Vec, channel: Vec>, } - struct BufferTypes(T); - impl BufferTypes{ - fn into_buff(self) -> I16Buffer{ - self.0 - } - } - impl BufferTypes{ - fn into_buff(self) -> U16Buffer{ - self.0 - } - } - impl BufferTypes{ - fn into_buff(self) -> F32Buffer{ - self.0 - } + struct Buffers { + i16_buff: I16Buffer, + u16_buff: U16Buffer, + f32_buff: F32Buffer, } + let mut buffers = match format.data_type{ SampleFormat::I16 => { - BufferTypes(I16Buffer{ + Buffers{ + i16_buff: I16Buffer{ cpal: vec![0 as i16; cpal_num_samples], channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()}) + .collect()}, + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer::default(), + } } SampleFormat::U16 => { - BufferTypes(U16Buffer{ + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer{ cpal: vec![0 as u16; cpal_num_samples], channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()}) + .collect()}, + f32_buff: F32Buffer::default(), + } } SampleFormat::F32 => { - BufferTypes(F32Buffer{ + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer{ cpal: vec![0 as f32; cpal_num_samples], channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) - .collect()}) + .collect()}, + } } }; @@ -128,11 +133,23 @@ impl EventLoop { $SampleType:ty, $SampleTypeIdent:ident, $AsioType:ty, - $AsioTypeIdent:ident) => { + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident + ) => { + // Function for deinterleaving because // cpal writes to buffer interleaved + /* fn interleave(channels: &[Vec<$SampleType>], buffer: &mut Vec<$SampleType>) { + */ + fn interleave(buffers: &mut $BuffersType) { + let $BuffersTypeIdent { + cpal: ref mut buffer, + channel: ref channels, + } = *buffers; let length = channels[0].len(); for i in 0..length{ for channel in channels{ @@ -145,7 +162,7 @@ impl EventLoop { // the asio buffer // Also need to check for Endian - for (i, channel) in channels.iter_mut().enumerate(){ + for (i, channel) in $Buffers.channel.iter_mut().enumerate(){ let buff_ptr = asio_stream .buffer_infos[i] .buffers[index as usize] as *mut $AsioType; @@ -163,11 +180,11 @@ impl EventLoop { // interleave all the channels - interleave(&channels, &mut cpal_buffer); + interleave(&mut $Buffers); let buff = InputBuffer{ - buffer: &mut cpal_buffer + buffer: &mut $Buffers.cpal, }; callback( StreamId(count), @@ -184,16 +201,20 @@ impl EventLoop { // TODO check for endianess match stream_type { sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!(I16, i16, i16, i32, i32); + try_callback!(I16, i16, i16, i32, i32, + buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16); + try_callback!(I16, i16, i16, i16, i16, + buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32); + try_callback!(F32, f32, f32, f32, f32, + buffers.f32_buff, F32Buffer, F32Buffer); } sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64); + try_callback!(F32, f32, f32, f64, f64, + buffers.f32_buff, F32Buffer, F32Buffer); } _ => println!("unsupported format {:?}", stream_type), } From 8d5cb951d87e4c686cb2277c69b5104829234ee7 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 12 Oct 2018 13:53:13 +1100 Subject: [PATCH 11/75] asio_utils --- Cargo.toml | 1 + src/platform/windows/asio/stream.rs | 409 ---------------------------- 2 files changed, 1 insertion(+), 409 deletions(-) delete mode 100644 src/platform/windows/asio/stream.rs diff --git a/Cargo.toml b/Cargo.toml index d7f234d..f0c977c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ hound = "3.4" [target.'cfg(any(target_os = "windows" ))'.dependencies] asio-sys = { version = "0.1", path = "asio-sys" } +asio-utils = { version = "0.1", path = "srs/platform/windows/asio/asio_utils" } [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/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs deleted file mode 100644 index 9788e3f..0000000 --- a/src/platform/windows/asio/stream.rs +++ /dev/null @@ -1,409 +0,0 @@ -extern crate asio_sys as sys; - -use std; -use Format; -use CreationError; -use StreamData; -use std::marker::PhantomData; -use super::Device; -use std::cell::Cell; -use UnknownTypeOutputBuffer; -use UnknownTypeInputBuffer; -use std::sync::{Arc, Mutex}; -use std::mem; -use std::sync::atomic::{AtomicUsize, Ordering}; -use SampleFormat; - -pub struct EventLoop { - asio_stream: Arc>>, - stream_count: Arc, - callbacks: Arc>>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(usize); - -pub struct InputBuffer<'a, T: 'a> { - buffer: &'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: Arc::new(AtomicUsize::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_input_stream(&device.driver_name) { - Ok(stream) => { - let num_channels = format.channels.clone(); - let cpal_num_samples = - (stream.buffer_size as usize) * num_channels as usize; - { - *self.asio_stream.lock().unwrap() = Some(stream); - } - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_stream = self.asio_stream.clone(); - let callbacks = self.callbacks.clone(); - let bytes_per_channel = format.data_type.sample_size(); - - // Create buffers - let channel_len = cpal_num_samples - / num_channels as usize; - - #[derive(Default)] - struct I16Buffer{ - cpal: Vec, - channel: Vec>, - } - #[derive(Default)] - struct U16Buffer{ - cpal: Vec, - channel: Vec>, - } - #[derive(Default)] - struct F32Buffer{ - cpal: Vec, - channel: Vec>, - } - struct Buffers { - i16_buff: I16Buffer, - u16_buff: U16Buffer, - f32_buff: F32Buffer, - } - - let mut buffers = match format.data_type{ - SampleFormat::I16 => { - Buffers{ - i16_buff: I16Buffer{ - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer::default(), - } - } - SampleFormat::U16 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer{ - cpal: vec![0 as u16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - f32_buff: F32Buffer::default(), - } - } - SampleFormat::F32 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer{ - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - } - } - }; - - sys::set_callback(move |index| unsafe { - if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { - // Number of samples needed total - 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, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident - ) => { - - // Function for deinterleaving because - // cpal writes to buffer interleaved - /* - fn interleave(channels: &[Vec<$SampleType>], - buffer: &mut Vec<$SampleType>) { - */ - fn interleave(buffers: &mut $BuffersType) { - let $BuffersTypeIdent { - cpal: ref mut buffer, - channel: ref channels, - } = *buffers; - let length = channels[0].len(); - for i in 0..length{ - for channel in channels{ - buffer.push(channel[i]); - } - } - } - - // For each channel write the cpal data to - // the asio buffer - // Also need to check for Endian - - for (i, channel) in $Buffers.channel.iter_mut().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 [$AsioType] = - std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize); - for asio_s in asio_buffer.iter(){ - channel.push( (*asio_s as i64 * - ::std::$SampleTypeIdent::MAX as i64 / - ::std::$AsioTypeIdent::MAX as i64) as $SampleType); - } - } - - - // interleave all the channels - interleave(&mut $Buffers); - - - let buff = InputBuffer{ - buffer: &mut $Buffers.cpal, - }; - callback( - StreamId(count), - StreamData::Input{ - buffer: UnknownTypeInputBuffer::$SampleFormat( - ::InputBuffer{ - buffer: 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, - buffers.i16_buff, I16Buffer, I16Buffer); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16, - buffers.i16_buff, I16Buffer, I16Buffer); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32, - buffers.f32_buff, F32Buffer, F32Buffer); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64, - buffers.f32_buff, F32Buffer, F32Buffer); - } - _ => 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.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); - 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, - channels: &mut [Vec<$SampleType>]) { - for (i, &sample) in data_slice.iter().enumerate() { - let ch = i % num_channels; - channels[ch].push(sample); - } - } - // Deinter all the channels - let channel_len = cpal_buffer.len() - / num_channels as usize; - let mut deinter_channels: Vec<_> = (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(); - deinterleave(&mut cpal_buffer[..], - num_channels as usize, - &mut deinter_channels); - - // 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::ASIOSTFloat64LSB => { - 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] { - &self.buffer - } - pub fn finish(self) { - } -} - -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) {} -} From d430000d2c8743f8c851b8f2ec33d07f3a2d8978 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 12 Oct 2018 16:54:26 +1100 Subject: [PATCH 12/75] beep working --- Cargo.toml | 2 +- .../windows/asio/asio_utils/Cargo.toml | 6 + .../windows/asio/asio_utils/src/lib.rs | 60 +++ .../windows/asio/asio_utils/src/tests.rs | 89 ++++ src/platform/windows/asio/stream.rs | 413 ++++++++++++++++++ 5 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 src/platform/windows/asio/asio_utils/Cargo.toml create mode 100644 src/platform/windows/asio/asio_utils/src/lib.rs create mode 100644 src/platform/windows/asio/asio_utils/src/tests.rs create mode 100644 src/platform/windows/asio/stream.rs diff --git a/Cargo.toml b/Cargo.toml index f0c977c..e863431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ hound = "3.4" [target.'cfg(any(target_os = "windows" ))'.dependencies] asio-sys = { version = "0.1", path = "asio-sys" } -asio-utils = { version = "0.1", path = "srs/platform/windows/asio/asio_utils" } +asio_utils = { version = "0.1", path = "src/platform/windows/asio/asio_utils" } [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/src/platform/windows/asio/asio_utils/Cargo.toml b/src/platform/windows/asio/asio_utils/Cargo.toml new file mode 100644 index 0000000..e949bad --- /dev/null +++ b/src/platform/windows/asio/asio_utils/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "asio_utils" +version = "0.1.0" +authors = ["Tom Gowan "] + +[dependencies] diff --git a/src/platform/windows/asio/asio_utils/src/lib.rs b/src/platform/windows/asio/asio_utils/src/lib.rs new file mode 100644 index 0000000..050079f --- /dev/null +++ b/src/platform/windows/asio/asio_utils/src/lib.rs @@ -0,0 +1,60 @@ +use std::cell::RefCell; +use std::iter::Cloned; +use std::slice::{Iter, IterMut}; + +#[cfg(test)] +mod tests; + +/// Interleave the buffer from asio to cpal +/// asio: LLLLRRRR +/// cpal: LRLRLRLR +/// More then stereo: +/// asio: 111122223333 +/// cpal: 123123123123 +/// cpal buffer must have a length of exactly sum( all asio channel lengths ) +/// this check is ommited for performance +pub fn interleave(channel_buffer: &[Vec], cpal_buffer: &mut [T]) +where + T: std::marker::Copy, +{ + // TODO avoid this heap allocation + // But we don't know how many channels we need. + // Could use arrayvec if we make an upper limit + let channels: Vec>>> = channel_buffer + .iter() + .map(|c| RefCell::new(c.iter().cloned())) + .collect(); + + for (c_buff, channel) in cpal_buffer.iter_mut().zip(channels.iter().cycle()) { + match channel.borrow_mut().next() { + Some(c) => *c_buff = c, + None => break, + } + } +} + +/// Function for deinterleaving because +/// cpal writes to buffer interleaved +/// cpal: LRLRLRLR +/// asio: LLLLRRRR +/// More then stereo: +/// cpal: 123123123123 +/// asio: 111122223333 +pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) +where + T: std::marker::Copy, +{ + // TODO avoid this heap allocation + // Possibly use arrayvec and some max channels + let channels: Vec>> = asio_channels + .iter_mut() + .map(|c| RefCell::new(c.iter_mut())) + .collect(); + + for (c_buff, a_channel) in cpal_buffer.iter().zip(channels.iter().cycle()) { + match a_channel.borrow_mut().next() { + Some(c) => *c = *c_buff, + None => break, + } + } +} diff --git a/src/platform/windows/asio/asio_utils/src/tests.rs b/src/platform/windows/asio/asio_utils/src/tests.rs new file mode 100644 index 0000000..c0d9ffd --- /dev/null +++ b/src/platform/windows/asio/asio_utils/src/tests.rs @@ -0,0 +1,89 @@ +use super::{deinterleave, deinterleave_index, interleave}; + +#[test] +fn interleave_two() { + let a = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2]]; + let goal = vec![1, 2, 1, 2, 1, 2, 1, 2]; + let mut result = vec![0; 8]; + + interleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn interleave_three() { + let a = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2], vec![3, 3, 3, 3]]; + let goal = vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]; + let mut result = vec![0; 12]; + + interleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn interleave_none() { + let a = vec![Vec::::new()]; + let goal = Vec::::new(); + let mut result = Vec::::new(); + + interleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn interleave_two_diff() { + let a = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; + let goal = vec![1, 5, 2, 6, 3, 7, 4, 8]; + let mut result = vec![0; 8]; + + interleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn deinterleave_two() { + let goal = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2]]; + let a = vec![1, 2, 1, 2, 1, 2, 1, 2]; + let mut result = vec![vec![0; 4]; 2]; + + deinterleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn deinterleave_three() { + let goal = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2], vec![3, 3, 3, 3]]; + let a = vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]; + let mut result = vec![vec![0; 4]; 3]; + + deinterleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn deinterleave_none() { + let goal = vec![Vec::::new()]; + let a = Vec::::new(); + let mut result = vec![Vec::::new()]; + + deinterleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} + +#[test] +fn deinterleave_two_diff() { + let goal = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; + let a = vec![1, 5, 2, 6, 3, 7, 4, 8]; + let mut result = vec![vec![0; 4]; 2]; + + deinterleave(&a[..], &mut result[..]); + + assert_eq!(goal, result); +} diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs new file mode 100644 index 0000000..5f8023e --- /dev/null +++ b/src/platform/windows/asio/stream.rs @@ -0,0 +1,413 @@ +extern crate asio_sys as sys; +extern crate asio_utils as au; + +use std; +use Format; +use CreationError; +use StreamData; +use std::marker::PhantomData; +use super::Device; +use std::cell::Cell; +use UnknownTypeOutputBuffer; +use UnknownTypeInputBuffer; +use std::sync::{Arc, Mutex}; +use std::mem; +use std::sync::atomic::{AtomicUsize, Ordering}; +use SampleFormat; + +pub struct EventLoop { + asio_stream: Arc>>, + stream_count: Arc, + callbacks: Arc>>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StreamId(usize); + +pub struct InputBuffer<'a, T: 'a> { + buffer: &'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: Arc::new(AtomicUsize::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_input_stream(&device.driver_name) { + Ok(stream) => { + let num_channels = format.channels.clone(); + let cpal_num_samples = + (stream.buffer_size as usize) * num_channels as usize; + { + *self.asio_stream.lock().unwrap() = Some(stream); + } + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); + let asio_stream = self.asio_stream.clone(); + let callbacks = self.callbacks.clone(); + let bytes_per_channel = format.data_type.sample_size(); + + // Create buffers + let channel_len = cpal_num_samples + / num_channels as usize; + + #[derive(Default)] + struct I16Buffer{ + cpal: Vec, + channel: Vec>, + } + #[derive(Default)] + struct U16Buffer{ + cpal: Vec, + channel: Vec>, + } + #[derive(Default)] + struct F32Buffer{ + cpal: Vec, + channel: Vec>, + } + struct Buffers { + i16_buff: I16Buffer, + u16_buff: U16Buffer, + f32_buff: F32Buffer, + } + + let mut buffers = match format.data_type{ + SampleFormat::I16 => { + Buffers{ + i16_buff: I16Buffer{ + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer::default(), + } + } + SampleFormat::U16 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer{ + cpal: vec![0 as u16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + f32_buff: F32Buffer::default(), + } + } + SampleFormat::F32 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer{ + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + } + } + }; + + sys::set_callback(move |index| unsafe { + if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { + // Number of samples needed total + 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, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident + ) => { + + // Function for interleaving because + // cpal writes to buffer interleaved + /* + let $BuffersTypeIdent { + cpal: ref mut buffer, + channel: ref channels, + } = *buffers; + let length = channels[0].len(); + for i in 0..length{ + for channel in channels{ + buffer.push(channel[i]); + } + } + } + */ + + // For each channel write the cpal data to + // the asio buffer + // Also need to check for Endian + + for (i, channel) in $Buffers.channel.iter_mut().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 [$AsioType] = + std::slice::from_raw_parts( + buff_ptr, + asio_stream.buffer_size as usize); + for asio_s in asio_buffer.iter(){ + channel.push( (*asio_s as i64 * + ::std::$SampleTypeIdent::MAX as i64 / + ::std::$AsioTypeIdent::MAX as i64) as $SampleType); + } + } + + + // interleave all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref channels, + } = $Buffers; + au::interleave(&channels, c_buffer); + } + + + let buff = InputBuffer{ + buffer: &mut $Buffers.cpal, + }; + callback( + StreamId(count), + StreamData::Input{ + buffer: UnknownTypeInputBuffer::$SampleFormat( + ::InputBuffer{ + buffer: 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, + buffers.i16_buff, I16Buffer, I16Buffer); + } + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!(I16, i16, i16, i16, i16, + buffers.i16_buff, I16Buffer, I16Buffer); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f32, f32, + buffers.f32_buff, F32Buffer, F32Buffer); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!(F32, f32, f32, f64, f64, + buffers.f32_buff, F32Buffer, F32Buffer); + } + _ => 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.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); + 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, + channels: &mut [Vec<$SampleType>]) { + for (i, &sample) in data_slice.iter().enumerate() { + let ch = i % num_channels; + channels[ch].push(sample); + } + } + */ + // Deinter all the channels + let channel_len = cpal_buffer.len() + / num_channels as usize; + let mut deinter_channels: Vec<_> = (0..num_channels) + .map(|_| vec![0.0 as $SampleType; channel_len]) + .collect(); + au::deinterleave(&cpal_buffer[..], &mut deinter_channels[..]); + + // 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::ASIOSTFloat64LSB => { + 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] { + &self.buffer + } + pub fn finish(self) { + } +} + +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) {} +} From 910e0ba3510a6f837313fb33e2d2d071e829dff5 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 12 Oct 2018 20:26:25 +1100 Subject: [PATCH 13/75] working record wav --- .../windows/asio/asio_utils/src/lib.rs | 26 +++++++++++++++++++ src/platform/windows/asio/stream.rs | 5 +++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/asio/asio_utils/src/lib.rs b/src/platform/windows/asio/asio_utils/src/lib.rs index 050079f..765ce6f 100644 --- a/src/platform/windows/asio/asio_utils/src/lib.rs +++ b/src/platform/windows/asio/asio_utils/src/lib.rs @@ -13,6 +13,16 @@ mod tests; /// cpal: 123123123123 /// cpal buffer must have a length of exactly sum( all asio channel lengths ) /// this check is ommited for performance +pub fn interleave(channels: &[Vec], target: &mut Vec) +where + T: std::marker::Copy, +{ + assert!(!channels.is_empty()); + target.clear(); + let frames = channels[0].len(); + target.extend((0 .. frames).flat_map(|f| channels.iter().map(move |ch| ch[f]))); +} +/* pub fn interleave(channel_buffer: &[Vec], cpal_buffer: &mut [T]) where T: std::marker::Copy, @@ -32,6 +42,7 @@ where } } } +*/ /// Function for deinterleaving because /// cpal writes to buffer interleaved @@ -41,6 +52,20 @@ where /// cpal: 123123123123 /// asio: 111122223333 pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) +where + T: std::marker::Copy, +{ + for ch in asio_channels.iter_mut() { + ch.clear(); + } + let num_channels = asio_channels.len(); + let mut ch = (0 .. num_channels).cycle(); + for &sample in cpal_buffer.iter() { + asio_channels[ch.next().unwrap()].push(sample); + } +} +/* +pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) where T: std::marker::Copy, { @@ -58,3 +83,4 @@ where } } } +*/ diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 5f8023e..fa1cd9c 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -181,9 +181,12 @@ impl EventLoop { { let $BuffersTypeIdent { cpal: ref mut c_buffer, - channel: ref channels, + channel: ref mut channels, } = $Buffers; au::interleave(&channels, c_buffer); + for c in channels.iter_mut() { + c.clear(); + } } From 056630cca37ee3e2a0a076e50b06bcb3c419311b Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 12 Oct 2018 23:38:55 +1100 Subject: [PATCH 14/75] removed allocation from output --- .../windows/asio/asio_utils/src/lib.rs | 47 +----- src/platform/windows/asio/stream.rs | 158 ++++++++++-------- 2 files changed, 91 insertions(+), 114 deletions(-) diff --git a/src/platform/windows/asio/asio_utils/src/lib.rs b/src/platform/windows/asio/asio_utils/src/lib.rs index 765ce6f..f9f78fe 100644 --- a/src/platform/windows/asio/asio_utils/src/lib.rs +++ b/src/platform/windows/asio/asio_utils/src/lib.rs @@ -1,7 +1,3 @@ -use std::cell::RefCell; -use std::iter::Cloned; -use std::slice::{Iter, IterMut}; - #[cfg(test)] mod tests; @@ -22,27 +18,6 @@ where let frames = channels[0].len(); target.extend((0 .. frames).flat_map(|f| channels.iter().map(move |ch| ch[f]))); } -/* -pub fn interleave(channel_buffer: &[Vec], cpal_buffer: &mut [T]) -where - T: std::marker::Copy, -{ - // TODO avoid this heap allocation - // But we don't know how many channels we need. - // Could use arrayvec if we make an upper limit - let channels: Vec>>> = channel_buffer - .iter() - .map(|c| RefCell::new(c.iter().cloned())) - .collect(); - - for (c_buff, channel) in cpal_buffer.iter_mut().zip(channels.iter().cycle()) { - match channel.borrow_mut().next() { - Some(c) => *c_buff = c, - None => break, - } - } -} -*/ /// Function for deinterleaving because /// cpal writes to buffer interleaved @@ -63,24 +38,4 @@ where for &sample in cpal_buffer.iter() { asio_channels[ch.next().unwrap()].push(sample); } -} -/* -pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) -where - T: std::marker::Copy, -{ - // TODO avoid this heap allocation - // Possibly use arrayvec and some max channels - let channels: Vec>> = asio_channels - .iter_mut() - .map(|c| RefCell::new(c.iter_mut())) - .collect(); - - for (c_buff, a_channel) in cpal_buffer.iter().zip(channels.iter().cycle()) { - match a_channel.borrow_mut().next() { - Some(c) => *c = *c_buff, - None => break, - } - } -} -*/ +} \ No newline at end of file diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index fa1cd9c..1f90bd1 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -31,6 +31,27 @@ pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], } +#[derive(Default)] +struct I16Buffer{ + cpal: Vec, + channel: Vec>, +} +#[derive(Default)] +struct U16Buffer{ + cpal: Vec, + channel: Vec>, +} +#[derive(Default)] +struct F32Buffer{ + cpal: Vec, + channel: Vec>, +} +struct Buffers { + i16_buff: I16Buffer, + u16_buff: U16Buffer, + f32_buff: F32Buffer, +} + impl EventLoop { pub fn new() -> EventLoop { EventLoop { @@ -64,26 +85,6 @@ impl EventLoop { let channel_len = cpal_num_samples / num_channels as usize; - #[derive(Default)] - struct I16Buffer{ - cpal: Vec, - channel: Vec>, - } - #[derive(Default)] - struct U16Buffer{ - cpal: Vec, - channel: Vec>, - } - #[derive(Default)] - struct F32Buffer{ - cpal: Vec, - channel: Vec>, - } - struct Buffers { - i16_buff: I16Buffer, - u16_buff: U16Buffer, - f32_buff: F32Buffer, - } let mut buffers = match format.data_type{ SampleFormat::I16 => { @@ -140,22 +141,6 @@ impl EventLoop { $BuffersTypeIdent:ident ) => { - // Function for interleaving because - // cpal writes to buffer interleaved - /* - let $BuffersTypeIdent { - cpal: ref mut buffer, - channel: ref channels, - } = *buffers; - let length = channels[0].len(); - for i in 0..length{ - for channel in channels{ - buffer.push(channel[i]); - } - } - } - */ - // For each channel write the cpal data to // the asio buffer // Also need to check for Endian @@ -247,6 +232,10 @@ pub fn build_output_stream( 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) => { + let num_channels = format.channels.clone(); + + let cpal_num_samples = + (stream.buffer_size as usize) * num_channels as usize; { *self.asio_stream.lock().unwrap() = Some(stream); } @@ -255,15 +244,50 @@ pub fn build_output_stream( 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 + // Create buffers + let channel_len = cpal_num_samples + / num_channels as usize; + + + let mut re_buffers = match format.data_type{ + SampleFormat::I16 => { + Buffers{ + i16_buff: I16Buffer{ + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer::default(), + } + } + SampleFormat::U16 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer{ + cpal: vec![0 as u16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + f32_buff: F32Buffer::default(), + } + } + SampleFormat::F32 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer{ + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + } + } + }; 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 @@ -274,13 +298,18 @@ pub fn build_output_stream( $SampleType:ty, $SampleTypeIdent:ident, $AsioType:ty, - $AsioTypeIdent:ident) => { + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident + ) => { + let mut my_buffers = $Buffers; // Buffer that is filled by cpal. - let mut cpal_buffer: Vec<$SampleType> = vec![0 as $SampleType; cpal_num_samples]; + //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 + buffer: &mut my_buffers.cpal }; callback( StreamId(count), @@ -292,30 +321,19 @@ pub fn build_output_stream( } ); } - // Function for deinterleaving because - // cpal writes to buffer interleaved - /* - fn deinterleave(data_slice: &mut [$SampleType], - num_channels: usize, - channels: &mut [Vec<$SampleType>]) { - for (i, &sample) in data_slice.iter().enumerate() { - let ch = i % num_channels; - channels[ch].push(sample); - } - } - */ // Deinter all the channels - let channel_len = cpal_buffer.len() - / num_channels as usize; - let mut deinter_channels: Vec<_> = (0..num_channels) - .map(|_| vec![0.0 as $SampleType; channel_len]) - .collect(); - au::deinterleave(&cpal_buffer[..], &mut deinter_channels[..]); + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = my_buffers; + au::deinterleave(&c_buffer[..], channels); + } // 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(){ + for (i, channel) in my_buffers.channel.iter().enumerate(){ let buff_ptr = (asio_stream .buffer_infos[i] .buffers[index as usize] as *mut $AsioType) @@ -325,7 +343,7 @@ pub fn build_output_stream( buff_ptr, asio_stream.buffer_size as usize); for (asio_s, cpal_s) in asio_buffer.iter_mut() - .zip(&channel){ + .zip(channel){ *asio_s = (*cpal_s as i64 * ::std::$AsioTypeIdent::MAX as i64 / ::std::$SampleTypeIdent::MAX as i64) as $AsioType; @@ -338,16 +356,20 @@ pub fn build_output_stream( // TODO check for endianess match stream_type { sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!(I16, i16, i16, i32, i32); + try_callback!(I16, i16, i16, i32, i32, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16); + try_callback!(I16, i16, i16, i16, i16, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32); + try_callback!(F32, f32, f32, f32, f32, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer); } sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64); + try_callback!(F32, f32, f32, f64, f64, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer); } _ => println!("unsupported format {:?}", stream_type), } From ae7a03d788d483f5ede92b1a9419bcb0bc7d23c5 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 16 Oct 2018 16:48:40 +1100 Subject: [PATCH 15/75] Device rewrite Fixes #1 Fixes #12 --- asio-sys/src/lib.rs | 612 +++++++++++++--------------- src/platform/windows/asio/device.rs | 164 ++++---- src/platform/windows/asio/stream.rs | 16 +- 3 files changed, 359 insertions(+), 433 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 6b0d9a4..b9c4f7a 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -16,7 +16,7 @@ use std::os::raw::c_void; use std::os::raw::c_double; use errors::ASIOError; use std::mem; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use asio_import as ai; @@ -44,9 +44,19 @@ pub struct SampleRate { pub rate: u32, } +#[derive(Debug, Clone)] +pub struct Drivers { + drivers: Arc>, +} + +#[derive(Debug)] +struct DriverWrapper { + pub drivers: ai::AsioDrivers, +} + pub struct AsioStream { pub buffer_infos: [AsioBufferInfo; 2], - driver: ai::AsioDrivers, + //driver: Driver, pub buffer_size: i32, } @@ -138,9 +148,271 @@ extern "C" fn buffer_switch_time_info( params } -impl AsioStream { - fn pop_driver(self) -> ai::AsioDrivers { - self.driver + +impl Drivers { + pub fn load(driver_name: &str) -> 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(); + 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 { + Ok(Drivers{ drivers: Arc::new(Mutex::new(DriverWrapper{drivers: asio_drivers})) }) + } else { + Err(ASIOError::DriverLoadError) + } + } + } + + /// 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(&self) -> Channel { + let channel: Channel; + + // 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 { + ai::ASIOInit(&mut driver_info); + ai::ASIOGetChannels(&mut ins, &mut outs); + channel = Channel { + ins: ins as i64, + outs: outs as i64, + }; + } + + channel + } + + pub fn get_sample_rate(&self) -> SampleRate { + let sample_rate: SampleRate; + + // Initialize memory for calls + let mut rate: c_double = 0.0f64; + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + unsafe { + ai::ASIOInit(&mut driver_info); + ai::get_sample_rate(&mut rate); + sample_rate = SampleRate { rate: rate as u32 }; + } + + sample_rate + } + + pub fn get_data_type(&self) -> Result { + let data_type: Result; + + // 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 { + ai::ASIOInit(&mut driver_info); + ai::ASIOGetChannelInfo(&mut channel_info); + data_type = num::FromPrimitive::from_i32(channel_info.type_) + .map_or(Err(ASIOError::TypeError), |t| Ok(t)); + } + + data_type + } + + pub fn prepare_input_stream(&self) -> Result { + let mut buffer_infos = [ + AsioBufferInfo { + is_input: 1, + channel_num: 0, + buffers: [std::ptr::null_mut(); 2], + }, + AsioBufferInfo { + is_input: 1, + channel_num: 1, + buffers: [std::ptr::null_mut(); 2], + }, + ]; + let num_channels = 2; + + 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], + }; + + + let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); + + unsafe { + 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, + 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(), + )) + }; + } + result + } + + pub fn prepare_output_stream(&self) -> Result { + 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 = 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], + }; + + let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); + + unsafe { + 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, + 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(), + )) + }; + } + result + } +} + +impl Drop for DriverWrapper { + fn drop(&mut self) { + unsafe{ + ai::destruct_AsioDrivers(&mut self.drivers); + } } } @@ -160,52 +432,9 @@ where 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 +//TODO this needs to not create and remove drivers pub fn get_driver_list() -> Vec { let mut driver_list: Vec = Vec::new(); @@ -242,296 +471,11 @@ pub fn get_driver_list() -> Vec { 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_input_stream(driver_name: &str) -> Result { - let mut buffer_infos = [ - AsioBufferInfo { - is_input: 1, - channel_num: 0, - buffers: [std::ptr::null_mut(); 2], - }, - AsioBufferInfo { - is_input: 1, - channel_num: 1, - buffers: [std::ptr::null_mut(); 2], - }, - ]; - let num_channels = 2; - - 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 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); } } diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 19fbf23..1bff1b9 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -9,22 +9,35 @@ use DefaultFormatError; use SupportedFormat; use SampleFormat; use SampleRate; +use std::hash::{Hash, Hasher}; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] 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 drivers: sys::Drivers, + pub name: String, } pub struct Devices{ drivers: std::vec::IntoIter, } +impl PartialEq for Device { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + impl Device { pub fn name(&self) -> String { - self.driver_name.clone() + self.name.clone() } // Just supporting default for now @@ -44,70 +57,28 @@ impl Device { } } - // 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) - + let channels = self.drivers.get_channels().ins as u16; + let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); + match self.drivers.get_data_type() { + Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), + Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), + // TODO This should not be set to 16bit but is for testing + Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), + _ => Err(DefaultFormatError::StreamTypeNotSupported), + }.map(|dt| { + Format{channels, + sample_rate, + data_type: dt } + }) } pub fn default_output_format(&self) -> Result { - let num_channels = sys::get_channels(&self.driver_name) - .map(|c| c.outs as u16); - let sample_rate = sys::get_sample_rate(&self.driver_name) - .map(|s| SampleRate(s.rate)); - let data_type = sys::get_data_type(&self.driver_name); - let data_type = match data_type{ + let channels = self.drivers.get_channels().outs as u16; + let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); + match self.drivers.get_data_type() { Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), @@ -115,17 +86,11 @@ impl Device { Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), _ => Err(DefaultFormatError::StreamTypeNotSupported), - }; - let format = match (num_channels, sample_rate, data_type){ - (Ok(num_channels), Ok(sample_rate), Ok(data_type)) =>{ - Ok(Format{channels: num_channels, - sample_rate: sample_rate, - data_type: data_type}) - } - _ => Err(DefaultFormatError::StreamTypeNotSupported), - }; - - format + }.map(|dt|{ + Format{channels, + sample_rate, + data_type: dt } + }) } } @@ -141,7 +106,15 @@ impl Iterator for Devices { fn next(&mut self) -> Option { match self.drivers.next() { - Some(dn) => Some(Device{driver_name: dn}), + Some(name) => { + sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }) + .ok() + .map(|drivers| Device{drivers, name} ) + }, None => None, } } @@ -155,31 +128,32 @@ impl Iterator for Devices { // so returning first in list as default pub fn default_input_device() -> Option { let mut driver_list = sys::get_driver_list(); - for dn in &driver_list{ - if dn == "ASIO4ALL v2"{ - println!("Defaulted to ASIO4ALL **remove from production**"); - return Some(Device{ driver_name: dn.clone() }); - } - } match driver_list.pop() { - Some(dn) => Some(Device{ driver_name: dn }), + Some(name) => { + sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }) + .ok() + .map(|drivers| Device{drivers, name} ) + }, 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 }), + Some(name) => { + sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }) + .ok() + .map(|drivers| Device{drivers, name} ) + }, None => None, } } diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 1f90bd1..3c21933 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -66,8 +66,12 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - let stream_type = sys::get_data_type(&device.driver_name).expect("Couldn't load data type"); - match sys::prepare_input_stream(&device.driver_name) { + let Device { + drivers, + .. + } = device; + let stream_type = drivers.get_data_type().expect("Couldn't load data type"); + match drivers.prepare_input_stream() { Ok(stream) => { let num_channels = format.channels.clone(); let cpal_num_samples = @@ -229,8 +233,12 @@ pub fn build_output_stream( 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) { + let Device { + drivers, + .. + } = device; + let stream_type = drivers.get_data_type().expect("Couldn't load data type"); + match drivers.prepare_output_stream() { Ok(stream) => { let num_channels = format.channels.clone(); From 2ef7008615cc2f993f02acc9ab2d72f81dc1a6a1 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 16 Oct 2018 22:25:37 +1100 Subject: [PATCH 16/75] Keep driver alive --- asio-sys/src/lib.rs | 93 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index b9c4f7a..088a538 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -17,6 +17,7 @@ use std::os::raw::c_double; use errors::ASIOError; use std::mem; use std::sync::{Arc, Mutex}; +use std::sync::atomic::{AtomicUsize, Ordering}; use asio_import as ai; @@ -33,6 +34,12 @@ lazy_static!{ static ref buffer_callback: Mutex> = Mutex::new(None); } +lazy_static!{ + static ref ASIO_DRIVERS: Mutex> = Mutex::new(None); +} + +static STREAM_DRIVER_COUNT: AtomicUsize = AtomicUsize::new(0); + #[derive(Debug)] pub struct Channel { pub ins: i64, @@ -45,9 +52,7 @@ pub struct SampleRate { } #[derive(Debug, Clone)] -pub struct Drivers { - drivers: Arc>, -} +pub struct Drivers; #[derive(Debug)] struct DriverWrapper { @@ -151,19 +156,30 @@ extern "C" fn buffer_switch_time_info( impl Drivers { pub fn load(driver_name: &str) -> 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(); - 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 { - Ok(Drivers{ drivers: Arc::new(Mutex::new(DriverWrapper{drivers: asio_drivers})) }) - } else { - Err(ASIOError::DriverLoadError) - } + let mut drivers = ASIO_DRIVERS.lock().unwrap(); + match *drivers { + Some(_) => { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + Ok(Drivers{}) + }, + None => { + // 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(); + 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 { + *drivers = Some(DriverWrapper{drivers: asio_drivers}); + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + Ok(Drivers{}) + } else { + Err(ASIOError::DriverLoadError) + } + } + }, } } @@ -305,6 +321,7 @@ impl Drivers { } println!("channels: {:?}", num_channels); + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); return Ok(AsioStream { buffer_infos: buffer_infos, buffer_size: pref_b_size, @@ -324,7 +341,9 @@ impl Drivers { result } + /// Creates the output stream pub fn prepare_output_stream(&self) -> Result { + // Initialize data for FFI let mut buffer_infos = [ AsioBufferInfo { is_input: 0, @@ -358,7 +377,13 @@ impl Drivers { let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); unsafe { + // Initialize ASIO stream ai::ASIOInit(&mut driver_info); + // Get the buffer sizes + // min possilbe size + // max possible size + // preferred size + // granularity ai::ASIOGetBufferSize( &mut min_b_size, &mut max_b_size, @@ -388,15 +413,14 @@ impl Drivers { } println!("channels: {:?}", num_channels); + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); return Ok(AsioStream { buffer_infos: buffer_infos, buffer_size: pref_b_size, }); } Err(ASIOError::BufferError(format!( - "failed to create buffers, - error code: {}", - buffer_result + "failed to create buffers, error code: {}", buffer_result ))) } else { Err(ASIOError::BufferError( @@ -408,14 +432,38 @@ impl Drivers { } } -impl Drop for DriverWrapper { +impl Drop for Drivers { fn drop(&mut self) { - unsafe{ - ai::destruct_AsioDrivers(&mut self.drivers); + println!("dropping drivers"); + let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); + if count == 1 { + println!("Destroying driver"); + unsafe{ + if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { + ai::destruct_AsioDrivers(&mut asio_drivers.drivers); + } + } } } } +impl Drop for AsioStream { + fn drop(&mut self) { + println!("dropping stream"); + let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); + if count == 1 { + println!("Destroying driver"); + unsafe{ + if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { + ai::destruct_AsioDrivers(&mut asio_drivers.drivers); + } + } + } + } +} + +unsafe impl Send for DriverWrapper {} + impl BufferCallback { fn run(&mut self, index: i32) { let mut cb = &mut self.0; @@ -476,6 +524,7 @@ pub fn get_driver_list() -> Vec { pub fn destroy_stream(stream: AsioStream) { unsafe { ai::ASIODisposeBuffers(); + ai::ASIOExit(); } } From 4ba291e8bef9464f8f4541ac9e1ac386315f6f61 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 19 Oct 2018 14:05:55 +1100 Subject: [PATCH 17/75] multi channel support --- asio-sys/src/lib.rs | 131 ++++++++++++++++------------ src/platform/windows/asio/stream.rs | 9 +- 2 files changed, 77 insertions(+), 63 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 088a538..af423bb 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(non_camel_case_types)] + #[macro_use] extern crate lazy_static; @@ -60,11 +62,47 @@ struct DriverWrapper { } pub struct AsioStream { - pub buffer_infos: [AsioBufferInfo; 2], - //driver: Driver, + pub buffer_infos: Vec, pub buffer_size: i32, } +#[derive(Debug)] +enum AsioErrorConvert { + ASE_OK = 0, // This value will be returned whenever the call succeeded + ASE_SUCCESS = 0x3f4847a0, // unique success return value for ASIOFuture calls + ASE_NotPresent = -1000, // hardware input or output is not present or available + ASE_HWMalfunction, // hardware is malfunctioning (can be returned by any ASIO function) + ASE_InvalidParameter, // input parameter invalid + ASE_InvalidMode, // hardware is in a bad mode or used in a bad mode + ASE_SPNotAdvancing, // hardware is not running when sample position is inquired + ASE_NoClock, // sample clock or rate cannot be determined or is not present + ASE_NoMemory, // not enough memory for completing the request + Invalid, +} + +macro_rules! asio_error { + ($x:expr, $ae:ident{ $($v:ident),+ }, $inval:ident) => { + match $x { + $(_ if $x == $ae::$v as i32 => $ae::$v,)+ + _ => $ae::$inval, + } + }; +} + +fn result_to_error(result: i32) -> AsioErrorConvert { + asio_error!(result, + AsioErrorConvert{ + ASE_OK, + ASE_SUCCESS, + ASE_NotPresent, + ASE_HWMalfunction, + ASE_InvalidParameter, + ASE_InvalidMode, + ASE_SPNotAdvancing, + ASE_NoClock, + ASE_NoMemory}, Invalid) +} + // This is a direct copy of the ASIOSampleType // inside ASIO SDK. #[derive(Debug, FromPrimitive)] @@ -166,12 +204,17 @@ impl 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 { let mut asio_drivers = ai::AsioDrivers::new(); let load_result = asio_drivers.loadDriver(raw); + ai::ASIOInit(&mut driver_info); // Take back ownership my_driver_name = CString::from_raw(raw); if load_result { + println!("Creating drivers"); *drivers = Some(DriverWrapper{drivers: asio_drivers}); STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); Ok(Drivers{}) @@ -196,13 +239,8 @@ impl Drivers { // 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 { - ai::ASIOInit(&mut driver_info); - ai::ASIOGetChannels(&mut ins, &mut outs); + println!("Channels result {:?}", result_to_error(ai::ASIOGetChannels(&mut ins, &mut outs))); channel = Channel { ins: ins as i64, outs: outs as i64, @@ -217,13 +255,9 @@ impl Drivers { // Initialize memory for calls let mut rate: c_double = 0.0f64; - let mut driver_info = ai::ASIODriverInfo { - _bindgen_opaque_blob: [0u32; 43], - }; unsafe { - ai::ASIOInit(&mut driver_info); - ai::get_sample_rate(&mut rate); + println!("sample rate {:?}", result_to_error(ai::get_sample_rate(&mut rate))); sample_rate = SampleRate { rate: rate as u32 }; } @@ -242,13 +276,8 @@ impl Drivers { type_: 0, name: [0 as c_char; 32], }; - let mut driver_info = ai::ASIODriverInfo { - _bindgen_opaque_blob: [0u32; 43], - }; - unsafe { - ai::ASIOInit(&mut driver_info); - ai::ASIOGetChannelInfo(&mut channel_info); + println!("data type {:?}", result_to_error(ai::ASIOGetChannelInfo(&mut channel_info))); data_type = num::FromPrimitive::from_i32(channel_info.type_) .map_or(Err(ASIOError::TypeError), |t| Ok(t)); } @@ -256,20 +285,14 @@ impl Drivers { data_type } - pub fn prepare_input_stream(&self) -> Result { - let mut buffer_infos = [ + pub fn prepare_input_stream(&self, num_channels: usize) -> Result { + let mut buffer_infos = vec![ AsioBufferInfo { is_input: 1, channel_num: 0, buffers: [std::ptr::null_mut(); 2], - }, - AsioBufferInfo { - is_input: 1, - channel_num: 1, - buffers: [std::ptr::null_mut(); 2], - }, + }; num_channels ]; - let num_channels = 2; let mut callbacks = AsioCallbacks { buffer_switch: buffer_switch, @@ -283,15 +306,10 @@ impl Drivers { 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], - }; - let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); unsafe { - ai::ASIOInit(&mut driver_info); ai::ASIOGetBufferSize( &mut min_b_size, &mut max_b_size, @@ -299,23 +317,21 @@ impl Drivers { &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 buffer_info_convert: Vec = buffer_infos.into_iter() + .map(|bi| mem::transmute::(bi)) + .collect(); let mut callbacks_convert = mem::transmute::(callbacks); let buffer_result = ai::ASIOCreateBuffers( buffer_info_convert.as_mut_ptr(), - num_channels, + num_channels as i32, 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]), - ]; + let mut buffer_infos: Vec = buffer_info_convert.into_iter() + .map(|bi| mem::transmute::(bi)) + .collect(); for d in &buffer_infos { println!("after {:?}", d); } @@ -342,22 +358,16 @@ impl Drivers { } /// Creates the output stream - pub fn prepare_output_stream(&self) -> Result { + pub fn prepare_output_stream(&self, num_channels: usize) -> Result { // Initialize data for FFI - let mut buffer_infos = [ + let mut buffer_infos = vec![ 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], - }, + }; num_channels ]; - let num_channels = 2; let mut callbacks = AsioCallbacks { buffer_switch: buffer_switch, sample_rate_did_change: sample_rate_did_change, @@ -370,15 +380,9 @@ impl Drivers { 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], - }; - let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); unsafe { - // Initialize ASIO stream - ai::ASIOInit(&mut driver_info); // Get the buffer sizes // min possilbe size // max possible size @@ -391,23 +395,33 @@ impl Drivers { &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 buffer_info_convert: Vec = buffer_infos.into_iter() + .map(|bi| mem::transmute::(bi)) + .collect(); let mut callbacks_convert = mem::transmute::(callbacks); let buffer_result = ai::ASIOCreateBuffers( buffer_info_convert.as_mut_ptr(), - num_channels, + num_channels as i32, 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]), ]; + */ + let mut buffer_infos: Vec = buffer_info_convert.into_iter() + .map(|bi| mem::transmute::(bi)) + .collect(); for d in &buffer_infos { println!("after {:?}", d); } @@ -541,3 +555,4 @@ pub fn stop() { println!("start result: {}", result); } } + diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 3c21933..43ac99f 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -70,10 +70,10 @@ impl EventLoop { drivers, .. } = device; + let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - match drivers.prepare_input_stream() { + match drivers.prepare_input_stream(num_channels as usize) { Ok(stream) => { - let num_channels = format.channels.clone(); let cpal_num_samples = (stream.buffer_size as usize) * num_channels as usize; { @@ -237,11 +237,10 @@ pub fn build_output_stream( drivers, .. } = device; + let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - match drivers.prepare_output_stream() { + match drivers.prepare_output_stream(num_channels as usize) { Ok(stream) => { - let num_channels = format.channels.clone(); - let cpal_num_samples = (stream.buffer_size as usize) * num_channels as usize; { From ec172bbc470e92ee634f94eeda09930f440a8691 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Sat, 20 Oct 2018 13:59:47 +1100 Subject: [PATCH 18/75] error wrappers --- asio-sys/src/errors.rs | 87 ++++++++++++++++++- asio-sys/src/lib.rs | 193 ++++++++++++++++++++--------------------- 2 files changed, 181 insertions(+), 99 deletions(-) diff --git a/asio-sys/src/errors.rs b/asio-sys/src/errors.rs index 72df46f..12096d9 100644 --- a/asio-sys/src/errors.rs +++ b/asio-sys/src/errors.rs @@ -2,20 +2,53 @@ use std::error::Error; use std::fmt; #[derive(Debug)] -pub enum ASIOError { +pub enum AsioError { NoResult(String), BufferError(String), DriverLoadError, TypeError, } -impl fmt::Display for ASIOError { +#[derive(Debug)] +enum AsioErrorWrapper { + ASE_OK = 0, // This value will be returned whenever the call succeeded + ASE_SUCCESS = 0x3f4847a0, // unique success return value for ASIOFuture calls + ASE_NotPresent = -1000, // hardware input or output is not present or available + ASE_HWMalfunction, // hardware is malfunctioning (can be returned by any ASIO function) + ASE_InvalidParameter, // input parameter invalid + ASE_InvalidMode, // hardware is in a bad mode or used in a bad mode + ASE_SPNotAdvancing, // hardware is not running when sample position is inquired + ASE_NoClock, // sample clock or rate cannot be determined or is not present + ASE_NoMemory, // not enough memory for completing the request + Invalid, +} + +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"), + AsioError::NoDrivers => { + write!(f, "hardware input or output is not present or available") + }, + AsioError::HardwareMalfunction => write!( + f, + "hardware is malfunctioning (can be returned by any ASIO function)" + ), + AsioError::InvalidInput => write!(f, "input parameter invalid"), + AsioError::BadMode => write!(f, "hardware is in a bad mode or used in a bad mode"), + AsioError::HardwareStuck => write!( + f, + "hardware is not running when sample position is inquired" + ), + AsioError::NoRate => write!( + f, + "sample clock or rate cannot be determined or is not present" + ), + AsioError::ASE_NoMemory => write!(f, "not enough memory for completing the request"), + AsioError::UnknownError => write!(f, "Error not in SDK"), } } } @@ -27,6 +60,56 @@ impl Error for ASIOError { ASIOError::BufferError(_) => "Error creating the buffer", ASIOError::DriverLoadError => "Error loading the driver", ASIOError::TypeError => "Error getting sample type", + AsioError::NoDrivers => "hardware input or output is not present or available", + AsioError::HardwareMalfunction => { + "hardware is malfunctioning (can be returned by any ASIO function)" + }, + AsioError::InvalidInput => "input parameter invalid", + AsioError::BadMode => "hardware is in a bad mode or used in a bad mode", + AsioError::HardwareStuck => "hardware is not running when sample position is inquired", + AsioError::NoRate => "sample clock or rate cannot be determined or is not present", + AsioError::ASE_NoMemory => "not enough memory for completing the request", + AsioError::UnknownError => "Error not in SDK", } } } + +macro_rules! asio_error_helper { + ($x:expr, $ae:ident{ $($v:ident),+ }, $inval:ident) => { + match $x { + $(_ if $x == $ae::$v as i32 => $ae::$v,)+ + _ => $ae::$inval, + } + }; +} + +macro_rules! asio_result { + ($result:expr) => { + match asio_error!( + $result, + AsioErrorWrapper { + ASE_OK, + ASE_SUCCESS, + ASE_NotPresent, + ASE_HWMalfunction, + ASE_InvalidParameter, + ASE_InvalidMode, + ASE_SPNotAdvancing, + ASE_NoClock, + ASE_NoMemory + }, + Invalid + ) { + ASE_OK => Ok(()), + ASE_SUCCESS => Ok(()), + ASE_NotPresent => Err(AsioError::NoDrivers), + ASE_HWMalfunction => Err(AsioError::HardwareMalfunction), + ASE_InvalidParameter => Err(AsioError::InvalidInput), + ASE_InvalidMode => Err(AsioError::BadMode), + ASE_SPNotAdvancing => Err(AsioError::HardwareStuck), + ASE_NoClock => Err(AsioError::NoRate), + ASE_NoMemory => Err(AsioError::ASE_NoMemory), + Invalid => Err(AsioError::UnknownError), + } + }; +} diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index af423bb..07c378e 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -10,16 +10,16 @@ extern crate num_derive; mod asio_import; pub mod errors; -use std::os::raw::c_char; +use errors::ASIOError; use std::ffi::CStr; use std::ffi::CString; +use std::mem; +use std::os::raw::c_char; +use std::os::raw::c_double; 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::{Arc, Mutex}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; use asio_import as ai; @@ -32,11 +32,11 @@ pub struct CbArgs { struct BufferCallback(Box); -lazy_static!{ +lazy_static! { static ref buffer_callback: Mutex> = Mutex::new(None); } -lazy_static!{ +lazy_static! { static ref ASIO_DRIVERS: Mutex> = Mutex::new(None); } @@ -66,43 +66,6 @@ pub struct AsioStream { pub buffer_size: i32, } -#[derive(Debug)] -enum AsioErrorConvert { - ASE_OK = 0, // This value will be returned whenever the call succeeded - ASE_SUCCESS = 0x3f4847a0, // unique success return value for ASIOFuture calls - ASE_NotPresent = -1000, // hardware input or output is not present or available - ASE_HWMalfunction, // hardware is malfunctioning (can be returned by any ASIO function) - ASE_InvalidParameter, // input parameter invalid - ASE_InvalidMode, // hardware is in a bad mode or used in a bad mode - ASE_SPNotAdvancing, // hardware is not running when sample position is inquired - ASE_NoClock, // sample clock or rate cannot be determined or is not present - ASE_NoMemory, // not enough memory for completing the request - Invalid, -} - -macro_rules! asio_error { - ($x:expr, $ae:ident{ $($v:ident),+ }, $inval:ident) => { - match $x { - $(_ if $x == $ae::$v as i32 => $ae::$v,)+ - _ => $ae::$inval, - } - }; -} - -fn result_to_error(result: i32) -> AsioErrorConvert { - asio_error!(result, - AsioErrorConvert{ - ASE_OK, - ASE_SUCCESS, - ASE_NotPresent, - ASE_HWMalfunction, - ASE_InvalidParameter, - ASE_InvalidMode, - ASE_SPNotAdvancing, - ASE_NoClock, - ASE_NoMemory}, Invalid) -} - // This is a direct copy of the ASIOSampleType // inside ASIO SDK. #[derive(Debug, FromPrimitive)] @@ -175,34 +138,29 @@ extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) 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, + 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, + params: *mut ai::ASIOTime, double_buffer_index: c_long, direct_process: c_long, ) -> *mut ai::ASIOTime { params } - impl Drivers { pub fn load(driver_name: &str) -> Result { let mut drivers = ASIO_DRIVERS.lock().unwrap(); match *drivers { Some(_) => { STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers{}) + Ok(Drivers {}) }, None => { // 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 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], @@ -210,14 +168,16 @@ impl Drivers { unsafe { let mut asio_drivers = ai::AsioDrivers::new(); let load_result = asio_drivers.loadDriver(raw); - ai::ASIOInit(&mut driver_info); + asio_init(&mut driver_info)?; // Take back ownership my_driver_name = CString::from_raw(raw); if load_result { println!("Creating drivers"); - *drivers = Some(DriverWrapper{drivers: asio_drivers}); + *drivers = Some(DriverWrapper { + drivers: asio_drivers, + }); STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers{}) + Ok(Drivers {}) } else { Err(ASIOError::DriverLoadError) } @@ -240,7 +200,7 @@ impl Drivers { let mut ins: c_long = 0; let mut outs: c_long = 0; unsafe { - println!("Channels result {:?}", result_to_error(ai::ASIOGetChannels(&mut ins, &mut outs))); + asio_get_channels(&mut ins, &mut outs)?; channel = Channel { ins: ins as i64, outs: outs as i64, @@ -257,7 +217,7 @@ impl Drivers { let mut rate: c_double = 0.0f64; unsafe { - println!("sample rate {:?}", result_to_error(ai::get_sample_rate(&mut rate))); + asio_get_sample_rate(&mut rate)?; sample_rate = SampleRate { rate: rate as u32 }; } @@ -277,7 +237,7 @@ impl Drivers { name: [0 as c_char; 32], }; unsafe { - println!("data type {:?}", result_to_error(ai::ASIOGetChannelInfo(&mut channel_info))); + asio_get_channel_info(&mut channel_info)?; data_type = num::FromPrimitive::from_i32(channel_info.type_) .map_or(Err(ASIOError::TypeError), |t| Ok(t)); } @@ -291,9 +251,10 @@ impl Drivers { is_input: 1, channel_num: 0, buffers: [std::ptr::null_mut(); 2], - }; num_channels + }; + num_channels ]; - + let mut callbacks = AsioCallbacks { buffer_switch: buffer_switch, sample_rate_did_change: sample_rate_did_change, @@ -306,30 +267,31 @@ impl Drivers { let mut pref_b_size: c_long = 0; let mut grans: c_long = 0; - let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); unsafe { - ai::ASIOGetBufferSize( + asio_get_buffer_size( &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: Vec = buffer_infos.into_iter() + let mut buffer_info_convert: Vec = buffer_infos + .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); let mut callbacks_convert = mem::transmute::(callbacks); - let buffer_result = ai::ASIOCreateBuffers( + let buffer_result = asio_create_buffers( buffer_info_convert.as_mut_ptr(), num_channels as i32, pref_b_size, &mut callbacks_convert, ); if buffer_result == 0 { - let mut buffer_infos: Vec = buffer_info_convert.into_iter() + let mut buffer_infos: Vec = buffer_info_convert + .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); for d in &buffer_infos { @@ -345,7 +307,8 @@ impl Drivers { } Err(ASIOError::BufferError(format!( "failed to create buffers, - error code: {}", + error \ + code: {}", buffer_result ))) } else { @@ -359,13 +322,14 @@ impl Drivers { /// Creates the output stream pub fn prepare_output_stream(&self, num_channels: usize) -> Result { - // Initialize data for FFI + // Initialize data for FFI let mut buffer_infos = vec![ AsioBufferInfo { is_input: 0, channel_num: 0, buffers: [std::ptr::null_mut(); 2], - }; num_channels + }; + num_channels ]; let mut callbacks = AsioCallbacks { @@ -388,38 +352,28 @@ impl Drivers { // max possible size // preferred size // granularity - ai::ASIOGetBufferSize( + asio_get_buffer_size( &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 buffer_info_convert: Vec = buffer_infos.into_iter() + let mut buffer_info_convert: Vec = buffer_infos + .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); let mut callbacks_convert = mem::transmute::(callbacks); - let buffer_result = ai::ASIOCreateBuffers( + let buffer_result = asio_create_buffers( buffer_info_convert.as_mut_ptr(), num_channels as i32, 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]), - ]; - */ - let mut buffer_infos: Vec = buffer_info_convert.into_iter() + let mut buffer_infos: Vec = buffer_info_convert + .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); for d in &buffer_infos { @@ -434,7 +388,8 @@ impl Drivers { }); } Err(ASIOError::BufferError(format!( - "failed to create buffers, error code: {}", buffer_result + "failed to create buffers, error code: {}", + buffer_result ))) } else { Err(ASIOError::BufferError( @@ -452,7 +407,7 @@ impl Drop for Drivers { let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); if count == 1 { println!("Destroying driver"); - unsafe{ + unsafe { if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { ai::destruct_AsioDrivers(&mut asio_drivers.drivers); } @@ -467,7 +422,7 @@ impl Drop for AsioStream { let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); if count == 1 { println!("Destroying driver"); - unsafe{ + unsafe { if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { ai::destruct_AsioDrivers(&mut asio_drivers.drivers); } @@ -503,7 +458,7 @@ pub fn get_driver_list() -> Vec { 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 { + for i in 0 .. MAX_DRIVER { p_driver_name[i] = driver_names[i].as_mut_ptr(); } @@ -514,7 +469,7 @@ pub fn get_driver_list() -> Vec { asio_drivers.getDriverNames(p_driver_name.as_mut_ptr(), MAX_DRIVER as i32); if num_drivers > 0 { - for i in 0..num_drivers { + 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(); @@ -533,12 +488,10 @@ pub fn get_driver_list() -> Vec { driver_list } - - pub fn destroy_stream(stream: AsioStream) { unsafe { - ai::ASIODisposeBuffers(); - ai::ASIOExit(); + asio_dispose_buffers()?; + asio_exit()?; } } @@ -556,3 +509,49 @@ pub fn stop() { } } +fn asio_init(di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { + asio_result!(ASIOInit(di)); +} + +fn asio_get_channels(ins: &mut c_long, outs: &mut outs) -> Result<(), AsioError> { + asio_result!(ai::ASIOGetChannels(ins, outs)) +} + +fn asio_get_sample_rate(rate: &mut c_double) -> Result<(), AsioError> { + asio_result!(ai::get_sample_rate(rate)) +} + +fn asio_get_channel_info(ci: &mut ai::ASIOChannelInfo) -> Result<(), AsioError> { + asio_result!(ai::ASIOGetChannelInfo(ci)) +} + +fn asio_get_buffer_size( + min_b_size: &mut c_long, max_b_size: &mut c_long, pref_b_size: &mut c_long, grans: &mut c_long, +) -> Result<(), AsioError> { + asio_result!(ai::ASIOGetBufferSize( + min_b_size, + max_b_size, + pref_b_size, + grans, + )) +} + +fn asio_create_buffers( + buffer_info_convert: &mut ai::ASIOBufferInfo, num_channels: i32, pref_b_size: c_long, + callbacks_convert: &mut ai::ASIOCallbacks, +) -> Result<(), AsioError> { + asio_result!(ai::ASIOCreateBuffers( + buffer_info_convert, + num_channels, + pref_b_size, + callbacks_convert, + )) +} + +fn asio_dispose_buffers() -> Result<(), AsioError> { + asio_result!(ai::ASIODisposeBuffers()) +} + +fn asio_exit() -> Result<(), AsioError> { + asio_result!(ai::ASIOExit()) +} From c24fa6202833014a87715ab0346af30b8bc0d2b9 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 23 Oct 2018 20:13:29 +1100 Subject: [PATCH 19/75] error handling --- asio-sys/src/errors.rs | 71 ++++++++++++++++--------- asio-sys/src/lib.rs | 114 +++++++++++++++++++++++------------------ 2 files changed, 111 insertions(+), 74 deletions(-) diff --git a/asio-sys/src/errors.rs b/asio-sys/src/errors.rs index 12096d9..0c92d02 100644 --- a/asio-sys/src/errors.rs +++ b/asio-sys/src/errors.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::fmt; #[derive(Debug)] -pub enum AsioError { +pub enum AsioDriverError { NoResult(String), BufferError(String), DriverLoadError, @@ -10,7 +10,19 @@ pub enum AsioError { } #[derive(Debug)] -enum AsioErrorWrapper { +pub enum AsioError { + NoDrivers, + HardwareMalfunction, + InvalidInput, + BadMode, + HardwareStuck, + NoRate, + ASE_NoMemory, + UnknownError, +} + +#[derive(Debug)] +pub enum AsioErrorWrapper { ASE_OK = 0, // This value will be returned whenever the call succeeded ASE_SUCCESS = 0x3f4847a0, // unique success return value for ASIOFuture calls ASE_NotPresent = -1000, // hardware input or output is not present or available @@ -23,13 +35,31 @@ enum AsioErrorWrapper { Invalid, } +impl fmt::Display for AsioDriverError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + AsioDriverError::NoResult(ref e) => write!(f, "Driver {} not found", e), + AsioDriverError::BufferError(ref e) => write!(f, "Buffer Error: {}", e), + AsioDriverError::DriverLoadError => write!(f, "Couldn't load the driver"), + AsioDriverError::TypeError => write!(f, "Couldn't convert sample type"), + } + } +} + +impl Error for AsioDriverError { + fn description(&self) -> &str { + match *self { + AsioDriverError::NoResult(_) => "Couln't find driver", + AsioDriverError::BufferError(_) => "Error creating the buffer", + AsioDriverError::DriverLoadError => "Error loading the driver", + AsioDriverError::TypeError => "Error getting sample type", + } + } +} + 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"), AsioError::NoDrivers => { write!(f, "hardware input or output is not present or available") }, @@ -53,13 +83,9 @@ impl fmt::Display for AsioError { } } -impl Error for ASIOError { +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", AsioError::NoDrivers => "hardware input or output is not present or available", AsioError::HardwareMalfunction => { "hardware is malfunctioning (can be returned by any ASIO function)" @@ -73,7 +99,6 @@ impl Error for ASIOError { } } } - macro_rules! asio_error_helper { ($x:expr, $ae:ident{ $($v:ident),+ }, $inval:ident) => { match $x { @@ -85,7 +110,7 @@ macro_rules! asio_error_helper { macro_rules! asio_result { ($result:expr) => { - match asio_error!( + match asio_error_helper!( $result, AsioErrorWrapper { ASE_OK, @@ -100,16 +125,16 @@ macro_rules! asio_result { }, Invalid ) { - ASE_OK => Ok(()), - ASE_SUCCESS => Ok(()), - ASE_NotPresent => Err(AsioError::NoDrivers), - ASE_HWMalfunction => Err(AsioError::HardwareMalfunction), - ASE_InvalidParameter => Err(AsioError::InvalidInput), - ASE_InvalidMode => Err(AsioError::BadMode), - ASE_SPNotAdvancing => Err(AsioError::HardwareStuck), - ASE_NoClock => Err(AsioError::NoRate), - ASE_NoMemory => Err(AsioError::ASE_NoMemory), - Invalid => Err(AsioError::UnknownError), + AsioErrorWrapper::ASE_OK => Ok(()), + AsioErrorWrapper::ASE_SUCCESS => Ok(()), + AsioErrorWrapper::ASE_NotPresent => Err(AsioError::NoDrivers), + AsioErrorWrapper::ASE_HWMalfunction => Err(AsioError::HardwareMalfunction), + AsioErrorWrapper::ASE_InvalidParameter => Err(AsioError::InvalidInput), + AsioErrorWrapper::ASE_InvalidMode => Err(AsioError::BadMode), + AsioErrorWrapper::ASE_SPNotAdvancing => Err(AsioError::HardwareStuck), + AsioErrorWrapper::ASE_NoClock => Err(AsioError::NoRate), + AsioErrorWrapper::ASE_NoMemory => Err(AsioError::ASE_NoMemory), + AsioErrorWrapper::Invalid => Err(AsioError::UnknownError), } }; } diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 07c378e..4016d39 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -8,9 +8,10 @@ extern crate num; extern crate num_derive; mod asio_import; +#[macro_use] pub mod errors; -use errors::ASIOError; +use errors::{AsioError, AsioDriverError, AsioErrorWrapper}; use std::ffi::CStr; use std::ffi::CString; use std::mem; @@ -150,7 +151,7 @@ extern "C" fn buffer_switch_time_info( } impl Drivers { - pub fn load(driver_name: &str) -> Result { + pub fn load(driver_name: &str) -> Result { let mut drivers = ASIO_DRIVERS.lock().unwrap(); match *drivers { Some(_) => { @@ -168,10 +169,10 @@ impl Drivers { unsafe { let mut asio_drivers = ai::AsioDrivers::new(); let load_result = asio_drivers.loadDriver(raw); - asio_init(&mut driver_info)?; + let init_result = asio_init(&mut driver_info); // Take back ownership my_driver_name = CString::from_raw(raw); - if load_result { + if load_result && init_result.is_ok() { println!("Creating drivers"); *drivers = Some(DriverWrapper { drivers: asio_drivers, @@ -179,7 +180,7 @@ impl Drivers { STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); Ok(Drivers {}) } else { - Err(ASIOError::DriverLoadError) + Err(AsioDriverError::DriverLoadError) } } }, @@ -200,7 +201,7 @@ impl Drivers { let mut ins: c_long = 0; let mut outs: c_long = 0; unsafe { - asio_get_channels(&mut ins, &mut outs)?; + asio_get_channels(&mut ins, &mut outs).expect("failed to get channels"); channel = Channel { ins: ins as i64, outs: outs as i64, @@ -217,16 +218,14 @@ impl Drivers { let mut rate: c_double = 0.0f64; unsafe { - asio_get_sample_rate(&mut rate)?; + asio_get_sample_rate(&mut rate).expect("failed to get sample rate"); sample_rate = SampleRate { rate: rate as u32 }; } sample_rate } - pub fn get_data_type(&self) -> Result { - let data_type: Result; - + pub fn get_data_type(&self) -> Result { // Initialize memory for calls let mut channel_info = ai::ASIOChannelInfo { channel: 0, @@ -237,15 +236,20 @@ impl Drivers { name: [0 as c_char; 32], }; unsafe { - asio_get_channel_info(&mut channel_info)?; - data_type = num::FromPrimitive::from_i32(channel_info.type_) - .map_or(Err(ASIOError::TypeError), |t| Ok(t)); + match 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) + }, + } } - - data_type } - pub fn prepare_input_stream(&self, num_channels: usize) -> Result { + pub fn prepare_input_stream(&self, num_channels: usize) -> Result { let mut buffer_infos = vec![ AsioBufferInfo { is_input: 1, @@ -267,7 +271,7 @@ impl Drivers { let mut pref_b_size: c_long = 0; let mut grans: c_long = 0; - let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); + let mut result = Err(AsioDriverError::NoResult("not implimented".to_owned())); unsafe { asio_get_buffer_size( @@ -275,7 +279,7 @@ impl Drivers { &mut max_b_size, &mut pref_b_size, &mut grans, - )?; + ).expect("Failed getting buffers"); result = if pref_b_size > 0 { let mut buffer_info_convert: Vec = buffer_infos .into_iter() @@ -289,7 +293,7 @@ impl Drivers { pref_b_size, &mut callbacks_convert, ); - if buffer_result == 0 { + if buffer_result.is_ok() { let mut buffer_infos: Vec = buffer_info_convert .into_iter() .map(|bi| mem::transmute::(bi)) @@ -305,14 +309,14 @@ impl Drivers { buffer_size: pref_b_size, }); } - Err(ASIOError::BufferError(format!( + Err(AsioDriverError::BufferError(format!( "failed to create buffers, error \ - code: {}", + code: {:?}", buffer_result ))) } else { - Err(ASIOError::BufferError( + Err(AsioDriverError::BufferError( "Failed to get buffer size".to_owned(), )) }; @@ -321,7 +325,7 @@ impl Drivers { } /// Creates the output stream - pub fn prepare_output_stream(&self, num_channels: usize) -> Result { + pub fn prepare_output_stream(&self, num_channels: usize) -> Result { // Initialize data for FFI let mut buffer_infos = vec![ AsioBufferInfo { @@ -344,7 +348,7 @@ impl Drivers { let mut pref_b_size: c_long = 0; let mut grans: c_long = 0; - let mut result = Err(ASIOError::NoResult("not implimented".to_owned())); + let mut result = Err(AsioDriverError::NoResult("not implimented".to_owned())); unsafe { // Get the buffer sizes @@ -357,7 +361,7 @@ impl Drivers { &mut max_b_size, &mut pref_b_size, &mut grans, - )?; + ).expect("Failed getting buffers"); result = if pref_b_size > 0 { let mut buffer_info_convert: Vec = buffer_infos .into_iter() @@ -371,7 +375,7 @@ impl Drivers { pref_b_size, &mut callbacks_convert, ); - if buffer_result == 0 { + if buffer_result.is_ok() { let mut buffer_infos: Vec = buffer_info_convert .into_iter() .map(|bi| mem::transmute::(bi)) @@ -387,12 +391,12 @@ impl Drivers { buffer_size: pref_b_size, }); } - Err(ASIOError::BufferError(format!( - "failed to create buffers, error code: {}", + Err(AsioDriverError::BufferError(format!( + "failed to create buffers, error code: {:?}", buffer_result ))) } else { - Err(ASIOError::BufferError( + Err(AsioDriverError::BufferError( "Failed to get buffer size".to_owned(), )) }; @@ -490,8 +494,8 @@ pub fn get_driver_list() -> Vec { pub fn destroy_stream(stream: AsioStream) { unsafe { - asio_dispose_buffers()?; - asio_exit()?; + asio_dispose_buffers().expect("Failed to dispose buffers"); + asio_exit().expect("Failed to exit asio"); } } @@ -509,49 +513,57 @@ pub fn stop() { } } -fn asio_init(di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { - asio_result!(ASIOInit(di)); +unsafe fn asio_init(di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { + let result = ai::ASIOInit(di); + asio_result!(result) } -fn asio_get_channels(ins: &mut c_long, outs: &mut outs) -> Result<(), AsioError> { - asio_result!(ai::ASIOGetChannels(ins, outs)) +unsafe fn asio_get_channels(ins: &mut c_long, outs: &mut c_long) -> Result<(), AsioError> { + let result = ai::ASIOGetChannels(ins, outs); + asio_result!(result) } -fn asio_get_sample_rate(rate: &mut c_double) -> Result<(), AsioError> { - asio_result!(ai::get_sample_rate(rate)) +unsafe fn asio_get_sample_rate(rate: &mut c_double) -> Result<(), AsioError> { + let result = ai::get_sample_rate(rate); + asio_result!(result) } -fn asio_get_channel_info(ci: &mut ai::ASIOChannelInfo) -> Result<(), AsioError> { - asio_result!(ai::ASIOGetChannelInfo(ci)) +unsafe fn asio_get_channel_info(ci: &mut ai::ASIOChannelInfo) -> Result<(), AsioError> { + let result = ai::ASIOGetChannelInfo(ci); + asio_result!(result) } -fn asio_get_buffer_size( +unsafe fn asio_get_buffer_size( min_b_size: &mut c_long, max_b_size: &mut c_long, pref_b_size: &mut c_long, grans: &mut c_long, ) -> Result<(), AsioError> { - asio_result!(ai::ASIOGetBufferSize( + let result = ai::ASIOGetBufferSize( min_b_size, max_b_size, pref_b_size, grans, - )) + ); + asio_result!(result) } -fn asio_create_buffers( - buffer_info_convert: &mut ai::ASIOBufferInfo, num_channels: i32, pref_b_size: c_long, +unsafe fn asio_create_buffers( + buffer_info_convert: *mut ai::ASIOBufferInfo, num_channels: i32, pref_b_size: c_long, callbacks_convert: &mut ai::ASIOCallbacks, ) -> Result<(), AsioError> { - asio_result!(ai::ASIOCreateBuffers( + let result = ai::ASIOCreateBuffers( buffer_info_convert, num_channels, pref_b_size, callbacks_convert, - )) + ); + asio_result!(result) } -fn asio_dispose_buffers() -> Result<(), AsioError> { - asio_result!(ai::ASIODisposeBuffers()) +unsafe fn asio_dispose_buffers() -> Result<(), AsioError> { + let result = ai::ASIODisposeBuffers(); + asio_result!(result) } -fn asio_exit() -> Result<(), AsioError> { - asio_result!(ai::ASIOExit()) -} +unsafe fn asio_exit() -> Result<(), AsioError> { + let result = ai::ASIOExit(); + asio_result!(result) +} \ No newline at end of file From 76eb07a2741dd925e3f944241f65a9ab07712d6a Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 24 Oct 2018 17:34:31 +1100 Subject: [PATCH 20/75] Multiple streams --- asio-sys/src/lib.rs | 49 ++++++++++++++++++++++------- src/platform/windows/asio/device.rs | 20 ++++++++++-- src/platform/windows/asio/stream.rs | 29 +++++++++-------- 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 4016d39..b2655c2 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -34,7 +34,7 @@ pub struct CbArgs { struct BufferCallback(Box); lazy_static! { - static ref buffer_callback: Mutex> = Mutex::new(None); + static ref buffer_callback: Mutex<[Option; 2]> = Mutex::new([None, None]); } lazy_static! { @@ -127,11 +127,20 @@ struct AsioCallbacks { direct_process: c_long, ) -> *mut ai::ASIOTime, } - -extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) -> () { +extern "C" fn buffer_switch_output(double_buffer_index: c_long, direct_process: c_long) -> () { let mut bc = buffer_callback.lock().unwrap(); + println!("output"); - if let Some(ref mut bc) = *bc { + if let Some(ref mut bc) = bc[0] { + bc.run(double_buffer_index); + } +} + +extern "C" fn buffer_switch_input(double_buffer_index: c_long, direct_process: c_long) -> () { + let mut bc = buffer_callback.lock().unwrap(); + println!("input"); + + if let Some(ref mut bc) = bc[1] { bc.run(double_buffer_index); } } @@ -260,7 +269,7 @@ impl Drivers { ]; let mut callbacks = AsioCallbacks { - buffer_switch: buffer_switch, + buffer_switch: buffer_switch_input, sample_rate_did_change: sample_rate_did_change, asio_message: asio_message, buffer_switch_time_info: buffer_switch_time_info, @@ -337,7 +346,7 @@ impl Drivers { ]; let mut callbacks = AsioCallbacks { - buffer_switch: buffer_switch, + buffer_switch: buffer_switch_output, sample_rate_did_change: sample_rate_did_change, asio_message: asio_message, buffer_switch_time_info: buffer_switch_time_info, @@ -446,12 +455,18 @@ impl BufferCallback { unsafe impl Send for AsioStream {} -pub fn set_callback(mut callback: F) -> () +pub fn set_callback(input: bool, mut callback: F) -> () where F: FnMut(i32) + Send, { let mut bc = buffer_callback.lock().unwrap(); - *bc = Some(BufferCallback(Box::new(callback))); + if input { + println!("Set input callback"); + bc[1] = Some(BufferCallback(Box::new(callback))); + }else{ + println!("Set output callback"); + bc[0] = Some(BufferCallback(Box::new(callback))); + } } /// Returns a list of all the ASIO drivers @@ -501,15 +516,15 @@ pub fn destroy_stream(stream: AsioStream) { pub fn play() { unsafe { - let result = ai::ASIOStart(); - println!("start result: {}", result); + let result = asio_start(); + println!("start result: {:?}", result); } } pub fn stop() { unsafe { - let result = ai::ASIOStop(); - println!("start result: {}", result); + let result = asio_stop(); + println!("stop result: {:?}", result); } } @@ -566,4 +581,14 @@ unsafe fn asio_dispose_buffers() -> Result<(), AsioError> { unsafe fn asio_exit() -> Result<(), AsioError> { let result = ai::ASIOExit(); asio_result!(result) +} + +unsafe fn asio_start() -> Result<(), AsioError> { + let result = ai::ASIOStart(); + asio_result!(result) +} + +unsafe fn asio_stop() -> Result<(), AsioError> { + let result = ai::ASIOStop(); + asio_result!(result) } \ No newline at end of file diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 1bff1b9..af64202 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -128,7 +128,15 @@ impl Iterator for Devices { // 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() { + // Remove + let d_name = driver_list.into_iter() + .filter(|d| d == "ASIO4ALL v2") + //.filter(|d| d.name() == "Dante Via (x64)") + //.filter(|d| d.name() == "Dante Virtual Soundcard (x64)") + .next(); + //match driver_list.pop() { + match d_name { + // end remove Some(name) => { sys::Drivers::load(&name) .or_else(|e| { @@ -144,7 +152,15 @@ pub fn default_input_device() -> Option { pub fn default_output_device() -> Option { let mut driver_list = sys::get_driver_list(); - match driver_list.pop() { + // Remove + let d_name = driver_list.into_iter() + .filter(|d| d == "ASIO4ALL v2") + //.filter(|d| d.name() == "Dante Via (x64)") + //.filter(|d| d.name() == "Dante Virtual Soundcard (x64)") + .next(); + //match driver_list.pop() { + match d_name { + // end remove Some(name) => { sys::Drivers::load(&name) .or_else(|e| { diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 43ac99f..3407d75 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -16,8 +16,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use SampleFormat; pub struct EventLoop { - asio_stream: Arc>>, - stream_count: Arc, + asio_streams: Arc>>>, + stream_count: AtomicUsize, callbacks: Arc>>, } @@ -55,8 +55,8 @@ struct Buffers { impl EventLoop { pub fn new() -> EventLoop { EventLoop { - asio_stream: Arc::new(Mutex::new(None)), - stream_count: Arc::new(AtomicUsize::new(0)), + asio_streams: Arc::new(Mutex::new(Vec::new())), + stream_count: AtomicUsize::new(0), callbacks: Arc::new(Mutex::new(Vec::new())), } } @@ -77,11 +77,11 @@ impl EventLoop { let cpal_num_samples = (stream.buffer_size as usize) * num_channels as usize; { - *self.asio_stream.lock().unwrap() = Some(stream); + self.asio_streams.lock().unwrap().push(Some(stream)); } let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_stream = self.asio_stream.clone(); + let asio_streams = self.asio_streams.clone(); let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); @@ -126,8 +126,8 @@ impl EventLoop { } }; - sys::set_callback(move |index| unsafe { - if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { + sys::set_callback(true, move |index| unsafe { + if let Some(ref asio_stream) = asio_streams.lock().unwrap()[count - 1] { // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); @@ -153,7 +153,6 @@ impl EventLoop { 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 [$AsioType] = std::slice::from_raw_parts( buff_ptr, @@ -244,11 +243,11 @@ pub fn build_output_stream( let cpal_num_samples = (stream.buffer_size as usize) * num_channels as usize; { - *self.asio_stream.lock().unwrap() = Some(stream); + self.asio_streams.lock().unwrap().push(Some(stream)); } let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_stream = self.asio_stream.clone(); + let asio_streams = self.asio_streams.clone(); let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); // Create buffers @@ -292,8 +291,8 @@ pub fn build_output_stream( } }; - sys::set_callback(move |index| unsafe { - if let Some(ref asio_stream) = *asio_stream.lock().unwrap() { + sys::set_callback(false, move |index| unsafe { + if let Some(ref asio_stream) = asio_streams.lock().unwrap()[count - 1] { // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); @@ -402,8 +401,8 @@ 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); + let mut asio_streams_lock = self.asio_streams.lock().unwrap(); + let old_stream = mem::replace(asio_streams_lock.get_mut(stream_id.0 - 1).expect("stream count out of bounds"), None); if let Some(old_stream) = old_stream { sys::destroy_stream(old_stream); } From 8193bc9f790777b8e316de640b1190869088a062 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 29 Oct 2018 22:57:42 +1100 Subject: [PATCH 21/75] sharing asio streams --- asio-sys/src/lib.rs | 198 +++++------ src/platform/windows/asio/stream.rs | 510 +++++++++++++++------------- 2 files changed, 363 insertions(+), 345 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index b2655c2..7d77ef6 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -34,7 +34,7 @@ pub struct CbArgs { struct BufferCallback(Box); lazy_static! { - static ref buffer_callback: Mutex<[Option; 2]> = Mutex::new([None, None]); + static ref buffer_callback: Mutex>> = Mutex::new(Vec::new()); } lazy_static! { @@ -62,6 +62,11 @@ struct DriverWrapper { pub drivers: ai::AsioDrivers, } +pub struct AsioStreams { + pub input: Option, + pub output: Option, +} + pub struct AsioStream { pub buffer_infos: Vec, pub buffer_size: i32, @@ -127,21 +132,13 @@ struct AsioCallbacks { direct_process: c_long, ) -> *mut ai::ASIOTime, } -extern "C" fn buffer_switch_output(double_buffer_index: c_long, direct_process: c_long) -> () { - let mut bc = buffer_callback.lock().unwrap(); - println!("output"); +extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) -> () { + let mut bcs = buffer_callback.lock().unwrap(); - if let Some(ref mut bc) = bc[0] { - bc.run(double_buffer_index); - } -} - -extern "C" fn buffer_switch_input(double_buffer_index: c_long, direct_process: c_long) -> () { - let mut bc = buffer_callback.lock().unwrap(); - println!("input"); - - if let Some(ref mut bc) = bc[1] { - bc.run(double_buffer_index); + for mut bc in bcs.iter_mut() { + if let Some(ref mut bc) = bc { + bc.run(double_buffer_index); + } } } @@ -258,7 +255,7 @@ impl Drivers { } } - pub fn prepare_input_stream(&self, num_channels: usize) -> Result { + pub fn prepare_input_stream(&self, output: Option, mut num_channels: usize) -> Result { let mut buffer_infos = vec![ AsioBufferInfo { is_input: 1, @@ -267,74 +264,13 @@ impl Drivers { }; num_channels ]; - - let mut callbacks = AsioCallbacks { - buffer_switch: buffer_switch_input, - 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 result = Err(AsioDriverError::NoResult("not implimented".to_owned())); - - unsafe { - asio_get_buffer_size( - &mut min_b_size, - &mut max_b_size, - &mut pref_b_size, - &mut grans, - ).expect("Failed getting buffers"); - result = if pref_b_size > 0 { - let mut buffer_info_convert: Vec = buffer_infos - .into_iter() - .map(|bi| mem::transmute::(bi)) - .collect(); - let mut callbacks_convert = - mem::transmute::(callbacks); - let buffer_result = asio_create_buffers( - buffer_info_convert.as_mut_ptr(), - num_channels as i32, - pref_b_size, - &mut callbacks_convert, - ); - if buffer_result.is_ok() { - let mut buffer_infos: Vec = buffer_info_convert - .into_iter() - .map(|bi| mem::transmute::(bi)) - .collect(); - for d in &buffer_infos { - println!("after {:?}", d); - } - println!("channels: {:?}", num_channels); - - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - return Ok(AsioStream { - buffer_infos: buffer_infos, - buffer_size: pref_b_size, - }); - } - Err(AsioDriverError::BufferError(format!( - "failed to create buffers, - error \ - code: {:?}", - buffer_result - ))) - } else { - Err(AsioDriverError::BufferError( - "Failed to get buffer size".to_owned(), - )) - }; - } - result + + let streams = AsioStreams{input: Some(AsioStream{buffer_infos, buffer_size: 0}), output}; + self.create_streams(streams) } /// Creates the output stream - pub fn prepare_output_stream(&self, num_channels: usize) -> Result { + pub fn prepare_output_stream(&self, input: Option, num_channels: usize) -> Result { // Initialize data for FFI let mut buffer_infos = vec![ AsioBufferInfo { @@ -344,9 +280,66 @@ impl Drivers { }; num_channels ]; + let streams = AsioStreams{output: Some(AsioStream{buffer_infos, buffer_size: 0}), input}; + self.create_streams(streams) + } + /// Creates the output stream + fn create_streams(&self, streams: AsioStreams) -> Result { + let AsioStreams { + input, + output, + } = streams; + match (input, output) { + (Some(input), Some(mut output)) => { + let split_point = input.buffer_infos.len(); + let mut bi = input.buffer_infos; + bi.append(&mut output.buffer_infos); + 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} + }) + }, + (Some(input), None) => { + self.create_buffers(input.buffer_infos) + .map(|(buffer_infos, buffer_size)| AsioStreams{ + input: Some(AsioStream{ + buffer_infos, + buffer_size, + }), + output: None, + }) + }, + (None, Some(output)) => { + self.create_buffers(output.buffer_infos) + .map(|(buffer_infos, buffer_size)| AsioStreams{ + output: Some(AsioStream{ + buffer_infos, + buffer_size, + }), + input: None, + }) + }, + (None, None) => panic!("Trying to create streams without preparing"), + } + + } + + fn create_buffers(&self, buffer_infos: Vec) + -> Result<(Vec, c_long), AsioDriverError>{ + let num_channels = buffer_infos.len(); let mut callbacks = AsioCallbacks { - buffer_switch: buffer_switch_output, + buffer_switch: buffer_switch, sample_rate_did_change: sample_rate_did_change, asio_message: asio_message, buffer_switch_time_info: buffer_switch_time_info, @@ -357,7 +350,7 @@ impl Drivers { let mut pref_b_size: c_long = 0; let mut grans: c_long = 0; - let mut result = Err(AsioDriverError::NoResult("not implimented".to_owned())); + //let mut result = Err(AsioDriverError::NoResult("not implimented".to_owned())); unsafe { // Get the buffer sizes @@ -371,20 +364,19 @@ impl Drivers { &mut pref_b_size, &mut grans, ).expect("Failed getting buffers"); - result = if pref_b_size > 0 { + if pref_b_size > 0 { let mut buffer_info_convert: Vec = buffer_infos .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); let mut callbacks_convert = mem::transmute::(callbacks); - let buffer_result = asio_create_buffers( + asio_create_buffers( buffer_info_convert.as_mut_ptr(), num_channels as i32, pref_b_size, &mut callbacks_convert, - ); - if buffer_result.is_ok() { + ).map(|_|{ let mut buffer_infos: Vec = buffer_info_convert .into_iter() .map(|bi| mem::transmute::(bi)) @@ -395,22 +387,17 @@ impl Drivers { println!("channels: {:?}", num_channels); STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - return Ok(AsioStream { - buffer_infos: buffer_infos, - buffer_size: pref_b_size, - }); - } - Err(AsioDriverError::BufferError(format!( - "failed to create buffers, error code: {:?}", - buffer_result - ))) + (buffer_infos, pref_b_size) + }).map_err(|e|{ + AsioDriverError::BufferError(format!( + "failed to create buffers, error code: {:?}", e)) + }) } else { Err(AsioDriverError::BufferError( - "Failed to get buffer size".to_owned(), + "bad buffer size".to_owned(), )) - }; + } } - result } } @@ -428,7 +415,7 @@ impl Drop for Drivers { } } } - +/* TODO this should be tied to cpal streams and not AsioStreams impl Drop for AsioStream { fn drop(&mut self) { println!("dropping stream"); @@ -443,6 +430,7 @@ impl Drop for AsioStream { } } } +*/ unsafe impl Send for DriverWrapper {} @@ -455,18 +443,12 @@ impl BufferCallback { unsafe impl Send for AsioStream {} -pub fn set_callback(input: bool, mut callback: F) -> () +pub fn set_callback(mut callback: F) -> () where F: FnMut(i32) + Send, { let mut bc = buffer_callback.lock().unwrap(); - if input { - println!("Set input callback"); - bc[1] = Some(BufferCallback(Box::new(callback))); - }else{ - println!("Set output callback"); - bc[0] = Some(BufferCallback(Box::new(callback))); - } + bc.push(Some(BufferCallback(Box::new(callback)))); } /// Returns a list of all the ASIO drivers diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 3407d75..c76c07f 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -16,7 +16,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use SampleFormat; pub struct EventLoop { - asio_streams: Arc>>>, + asio_streams: Arc>, stream_count: AtomicUsize, callbacks: Arc>>, } @@ -55,12 +55,58 @@ struct Buffers { impl EventLoop { pub fn new() -> EventLoop { EventLoop { - asio_streams: Arc::new(Mutex::new(Vec::new())), + asio_streams: Arc::new(Mutex::new(sys::AsioStreams{input: None, output: None})), stream_count: AtomicUsize::new(0), callbacks: Arc::new(Mutex::new(Vec::new())), } } + fn get_input_stream(&self, drivers: &sys::Drivers, num_channels: usize) -> Result { + let ref mut streams = *self.asio_streams.lock().unwrap(); + match streams.input { + Some(ref input) => Ok(input.buffer_size as usize), + None => { + let output = streams.output.take(); + drivers.prepare_input_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.input { + Some(ref inp) => inp.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }) + .map_err(|ref e| { + println!("Error preparing stream: {}", e); + CreationError::DeviceNotAvailable + }) + } + } + } + + fn get_output_stream(&self, drivers: &sys::Drivers, num_channels: usize) -> Result { + let ref mut streams = *self.asio_streams.lock().unwrap(); + match streams.output { + Some(ref output) => Ok(output.buffer_size as usize), + None => { + let input = streams.input.take(); + drivers.prepare_output_stream(input, num_channels) + .map(|new_streams| { + let bs = match new_streams.output { + Some(ref out) => out.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }) + .map_err(|ref e| { + println!("Error preparing stream: {}", e); + CreationError::DeviceNotAvailable + }) + }, + } + } + pub fn build_input_stream( &self, device: &Device, @@ -72,190 +118,20 @@ impl EventLoop { } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - match drivers.prepare_input_stream(num_channels as usize) { - Ok(stream) => { - let cpal_num_samples = - (stream.buffer_size as usize) * num_channels as usize; - { - self.asio_streams.lock().unwrap().push(Some(stream)); - } - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let callbacks = self.callbacks.clone(); - let bytes_per_channel = format.data_type.sample_size(); - - // Create buffers - let channel_len = cpal_num_samples - / num_channels as usize; - - - let mut buffers = match format.data_type{ - SampleFormat::I16 => { - Buffers{ - i16_buff: I16Buffer{ - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer::default(), - } - } - SampleFormat::U16 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer{ - cpal: vec![0 as u16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - f32_buff: F32Buffer::default(), - } - } - SampleFormat::F32 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer{ - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - } - } - }; - - sys::set_callback(true, move |index| unsafe { - if let Some(ref asio_stream) = asio_streams.lock().unwrap()[count - 1] { - // Number of samples needed total - 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, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident - ) => { - - // For each channel write the cpal data to - // the asio buffer - // Also need to check for Endian - - for (i, channel) in $Buffers.channel.iter_mut().enumerate(){ - let buff_ptr = asio_stream - .buffer_infos[i] - .buffers[index as usize] as *mut $AsioType; - let asio_buffer: &'static [$AsioType] = - std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize); - for asio_s in asio_buffer.iter(){ - channel.push( (*asio_s as i64 * - ::std::$SampleTypeIdent::MAX as i64 / - ::std::$AsioTypeIdent::MAX as i64) as $SampleType); - } - } - - - // interleave all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = $Buffers; - au::interleave(&channels, c_buffer); - for c in channels.iter_mut() { - c.clear(); - } - } - - - let buff = InputBuffer{ - buffer: &mut $Buffers.cpal, - }; - callback( - StreamId(count), - StreamData::Input{ - buffer: UnknownTypeInputBuffer::$SampleFormat( - ::InputBuffer{ - buffer: 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, - buffers.i16_buff, I16Buffer, I16Buffer); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16, - buffers.i16_buff, I16Buffer, I16Buffer); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32, - buffers.f32_buff, F32Buffer, F32Buffer); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64, - buffers.f32_buff, F32Buffer, F32Buffer); - } - _ => 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 Device { - drivers, - .. - } = device; - let num_channels = format.channels.clone(); - let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - match drivers.prepare_output_stream(num_channels as usize) { - Ok(stream) => { - let cpal_num_samples = - (stream.buffer_size as usize) * num_channels as usize; - { - self.asio_streams.lock().unwrap().push(Some(stream)); - } + self.get_input_stream(&drivers, num_channels as usize).map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); let callbacks = self.callbacks.clone(); let bytes_per_channel = format.data_type.sample_size(); + // Create buffers let channel_len = cpal_num_samples / num_channels as usize; - let mut re_buffers = match format.data_type{ + let mut buffers = match format.data_type{ SampleFormat::I16 => { Buffers{ i16_buff: I16Buffer{ @@ -291,8 +167,8 @@ pub fn build_output_stream( } }; - sys::set_callback(false, move |index| unsafe { - if let Some(ref asio_stream) = asio_streams.lock().unwrap()[count - 1] { + sys::set_callback(move |index| unsafe { + if let Some(ref asio_stream) = asio_streams.lock().unwrap().input { // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); @@ -301,81 +177,80 @@ pub fn build_output_stream( Some(callback) => { macro_rules! try_callback { ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident - ) => { - let mut my_buffers = $Buffers; - // 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 my_buffers.cpal - }; - callback( - StreamId(count), - StreamData::Output{ - buffer: UnknownTypeOutputBuffer::$SampleFormat( - ::OutputBuffer{ - target: Some(super::super::OutputBuffer::Asio(buff)) - }) - } - ); - } - // Deinter all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = my_buffers; - au::deinterleave(&c_buffer[..], channels); - } + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident + ) => { // For each channel write the cpal data to // the asio buffer // Also need to check for Endian - for (i, channel) in my_buffers.channel.iter().enumerate(){ - let buff_ptr = (asio_stream + + for (i, channel) in $Buffers.channel.iter_mut().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( + .buffers[index as usize] as *mut $AsioType; + let asio_buffer: &'static [$AsioType] = + std::slice::from_raw_parts( 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; - } - + for asio_s in asio_buffer.iter(){ + channel.push( (*asio_s as i64 * + ::std::$SampleTypeIdent::MAX as i64 / + ::std::$AsioTypeIdent::MAX as i64) as $SampleType); + } } - }; - } + + + // interleave all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = $Buffers; + au::interleave(&channels, c_buffer); + for c in channels.iter_mut() { + c.clear(); + } + } + + + let buff = InputBuffer{ + buffer: &mut $Buffers.cpal, + }; + callback( + StreamId(count), + StreamData::Input{ + buffer: UnknownTypeInputBuffer::$SampleFormat( + ::InputBuffer{ + buffer: 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, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!(I16, i16, i16, i16, i16, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + buffers.i16_buff, I16Buffer, I16Buffer); } sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + buffers.f32_buff, F32Buffer, F32Buffer); } sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + buffers.f32_buff, F32Buffer, F32Buffer); } _ => println!("unsupported format {:?}", stream_type), } @@ -384,13 +259,166 @@ pub fn build_output_stream( } } }); - Ok(StreamId(count)) - } - Err(ref e) => { - println!("Error preparing stream: {}", e); - Err(CreationError::DeviceNotAvailable) - } + StreamId(count) + }) } + +pub fn build_output_stream( + &self, + device: &Device, + format: &Format, + ) -> Result { + let Device { + drivers, + .. + } = device; + let num_channels = format.channels.clone(); + let stream_type = drivers.get_data_type().expect("Couldn't load data type"); + self.get_output_stream(&drivers, num_channels as usize).map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let callbacks = self.callbacks.clone(); + let bytes_per_channel = format.data_type.sample_size(); + // Create buffers + let channel_len = cpal_num_samples + / num_channels as usize; + + + let mut re_buffers = match format.data_type{ + SampleFormat::I16 => { + Buffers{ + i16_buff: I16Buffer{ + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer::default(), + } + } + SampleFormat::U16 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer{ + cpal: vec![0 as u16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + f32_buff: F32Buffer::default(), + } + } + SampleFormat::F32 => { + Buffers{ + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer{ + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect()}, + } + } + }; + + sys::set_callback(move |index| unsafe { + if let Some(ref asio_stream) = asio_streams.lock().unwrap().output { + // Number of samples needed total + 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, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident + ) => { + let mut my_buffers = $Buffers; + // 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 my_buffers.cpal + }; + callback( + StreamId(count), + StreamData::Output{ + buffer: UnknownTypeOutputBuffer::$SampleFormat( + ::OutputBuffer{ + target: Some(super::super::OutputBuffer::Asio(buff)) + }) + } + ); + } + // Deinter all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = my_buffers; + au::deinterleave(&c_buffer[..], channels); + } + + // For each channel write the cpal data to + // the asio buffer + // TODO need to check for Endian + for (i, channel) in my_buffers.channel.iter().enumerate(){ + let buff_ptr = (asio_stream + .buffer_infos[i] + .buffers[index as usize] as *mut $AsioType) + // I'm not sure if this is needed anymore + // Why should we offset the pointer? + .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, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + } + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!(I16, i16, i16, i16, i16, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!(F32, f32, f32, f32, f32, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!(F32, f32, f32, f64, f64, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + } + _ => println!("unsupported format {:?}", stream_type), + } + } + None => return (), + } + } + }); + StreamId(count) + }) } pub fn play_stream(&self, stream: StreamId) { @@ -400,12 +428,20 @@ pub fn play_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) { sys::stop(); } + +// TODO the logic for this is wrong +// We are not destroying AsioStreams but CPAL streams +// Asio Streams should only be destroyed if there are no +// CPAL streams left pub fn destroy_stream(&self, stream_id: StreamId) { + /* let mut asio_streams_lock = self.asio_streams.lock().unwrap(); let old_stream = mem::replace(asio_streams_lock.get_mut(stream_id.0 - 1).expect("stream count out of bounds"), None); if let Some(old_stream) = old_stream { sys::destroy_stream(old_stream); } + */ + unimplemented!() } pub fn run(&self, mut callback: F) -> ! where From 93f75940d6acd7d54b762ede395de7b0b9008508 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 29 Oct 2018 23:05:13 +1100 Subject: [PATCH 22/75] fixed offset bug --- src/platform/windows/asio/stream.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index c76c07f..37c58b2 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -371,12 +371,9 @@ pub fn build_output_stream( // the asio buffer // TODO need to check for Endian for (i, channel) in my_buffers.channel.iter().enumerate(){ - let buff_ptr = (asio_stream + let buff_ptr = asio_stream .buffer_infos[i] - .buffers[index as usize] as *mut $AsioType) - // I'm not sure if this is needed anymore - // Why should we offset the pointer? - .offset(asio_stream.buffer_size as isize * i as isize); + .buffers[index as usize] as *mut $AsioType; let asio_buffer: &'static mut [$AsioType] = std::slice::from_raw_parts_mut( buff_ptr, From ff7fd62a2dc23e9e320571106961205188d8d99a Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 30 Oct 2018 14:27:50 +1100 Subject: [PATCH 23/75] clean up --- asio-sys/src/lib.rs | 269 +++++++++++++++++++--------- src/platform/windows/asio/stream.rs | 35 ++-- 2 files changed, 210 insertions(+), 94 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 7d77ef6..1626fb9 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -21,6 +21,7 @@ use std::os::raw::c_long; use std::os::raw::c_void; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; +use std::sync::MutexGuard; use asio_import as ai; @@ -38,7 +39,10 @@ lazy_static! { } lazy_static! { - static ref ASIO_DRIVERS: Mutex> = Mutex::new(None); + static ref ASIO_DRIVERS: Mutex = Mutex::new(DriverWrapper{ + drivers: None, + state: AsioState::Offline, + }); } static STREAM_DRIVER_COUNT: AtomicUsize = AtomicUsize::new(0); @@ -59,7 +63,17 @@ pub struct Drivers; #[derive(Debug)] struct DriverWrapper { - pub drivers: ai::AsioDrivers, + drivers: Option, + state: AsioState, +} + +#[derive(Debug)] +enum AsioState { + Offline, + Loaded, + Initialized, + Prepared, + Running, } pub struct AsioStreams { @@ -156,15 +170,15 @@ extern "C" fn buffer_switch_time_info( params } +fn get_drivers() -> MutexGuard<'static, DriverWrapper> { + ASIO_DRIVERS.lock().unwrap() +} + impl Drivers { pub fn load(driver_name: &str) -> Result { - let mut drivers = ASIO_DRIVERS.lock().unwrap(); - match *drivers { - Some(_) => { - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers {}) - }, - None => { + let mut drivers = get_drivers(); + match drivers.state { + AsioState::Offline => { // 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"); @@ -175,21 +189,24 @@ impl Drivers { unsafe { let mut asio_drivers = ai::AsioDrivers::new(); let load_result = asio_drivers.loadDriver(raw); - let init_result = asio_init(&mut driver_info); + if load_result { drivers.state = AsioState::Loaded; } + let init_result = drivers.asio_init(&mut driver_info); // Take back ownership my_driver_name = CString::from_raw(raw); if load_result && init_result.is_ok() { println!("Creating drivers"); - *drivers = Some(DriverWrapper { - drivers: asio_drivers, - }); + drivers.drivers = Some(asio_drivers); STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers {}) + Ok(Drivers) } else { Err(AsioDriverError::DriverLoadError) } } }, + _ => { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + Ok(Drivers) + }, } } @@ -207,7 +224,7 @@ impl Drivers { let mut ins: c_long = 0; let mut outs: c_long = 0; unsafe { - asio_get_channels(&mut ins, &mut outs).expect("failed to get channels"); + get_drivers().asio_get_channels(&mut ins, &mut outs).expect("failed to get channels"); channel = Channel { ins: ins as i64, outs: outs as i64, @@ -224,7 +241,7 @@ impl Drivers { let mut rate: c_double = 0.0f64; unsafe { - asio_get_sample_rate(&mut rate).expect("failed to get sample rate"); + get_drivers().asio_get_sample_rate(&mut rate).expect("failed to get sample rate"); sample_rate = SampleRate { rate: rate as u32 }; } @@ -242,7 +259,7 @@ impl Drivers { name: [0 as c_char; 32], }; unsafe { - match asio_get_channel_info(&mut channel_info) { + 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)) @@ -312,22 +329,28 @@ impl Drivers { }, (Some(input), None) => { self.create_buffers(input.buffer_infos) - .map(|(buffer_infos, buffer_size)| AsioStreams{ - input: Some(AsioStream{ - buffer_infos, - buffer_size, - }), - output: None, + .map(|(buffer_infos, buffer_size)| { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + AsioStreams{ + input: Some(AsioStream{ + buffer_infos, + buffer_size, + }), + output: None, + } }) }, (None, Some(output)) => { self.create_buffers(output.buffer_infos) - .map(|(buffer_infos, buffer_size)| AsioStreams{ - output: Some(AsioStream{ - buffer_infos, - buffer_size, - }), - input: None, + .map(|(buffer_infos, buffer_size)| { + STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); + AsioStreams{ + output: Some(AsioStream{ + buffer_infos, + buffer_size, + }), + input: None, + } }) }, (None, None) => panic!("Trying to create streams without preparing"), @@ -350,7 +373,7 @@ impl Drivers { let mut pref_b_size: c_long = 0; let mut grans: c_long = 0; - //let mut result = Err(AsioDriverError::NoResult("not implimented".to_owned())); + let mut drivers = get_drivers(); unsafe { // Get the buffer sizes @@ -358,7 +381,7 @@ impl Drivers { // max possible size // preferred size // granularity - asio_get_buffer_size( + drivers.asio_get_buffer_size( &mut min_b_size, &mut max_b_size, &mut pref_b_size, @@ -371,7 +394,7 @@ impl Drivers { .collect(); let mut callbacks_convert = mem::transmute::(callbacks); - asio_create_buffers( + drivers.asio_create_buffers( buffer_info_convert.as_mut_ptr(), num_channels as i32, pref_b_size, @@ -386,7 +409,6 @@ impl Drivers { } println!("channels: {:?}", num_channels); - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); (buffer_infos, pref_b_size) }).map_err(|e|{ AsioDriverError::BufferError(format!( @@ -407,30 +429,10 @@ impl Drop for Drivers { let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); if count == 1 { println!("Destroying driver"); - unsafe { - if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { - ai::destruct_AsioDrivers(&mut asio_drivers.drivers); - } - } + clean_up(); } } } -/* TODO this should be tied to cpal streams and not AsioStreams -impl Drop for AsioStream { - fn drop(&mut self) { - println!("dropping stream"); - let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); - if count == 1 { - println!("Destroying driver"); - unsafe { - if let Some(mut asio_drivers) = (*ASIO_DRIVERS.lock().unwrap()).take() { - ai::destruct_AsioDrivers(&mut asio_drivers.drivers); - } - } - } - } -} -*/ unsafe impl Send for DriverWrapper {} @@ -489,63 +491,135 @@ pub fn get_driver_list() -> Vec { driver_list } -pub fn destroy_stream(stream: AsioStream) { - unsafe { - asio_dispose_buffers().expect("Failed to dispose buffers"); - asio_exit().expect("Failed to exit asio"); +pub fn clean_up() { + let mut drivers = get_drivers(); + match drivers.state { + AsioState::Offline => (), + AsioState::Loaded => { + unsafe { + let mut old_drivers = drivers.drivers.take().unwrap(); + ai::destruct_AsioDrivers(&mut old_drivers); + } + drivers.state = AsioState::Offline; + }, + AsioState::Initialized => { + unsafe { + drivers.asio_exit().expect("Failed to exit asio"); + let mut old_drivers = drivers.drivers.take().unwrap(); + ai::destruct_AsioDrivers(&mut old_drivers); + } + drivers.state = AsioState::Offline; + }, + AsioState::Prepared => { + unsafe { + drivers.asio_dispose_buffers().expect("Failed to dispose buffers"); + drivers.asio_exit().expect("Failed to exit asio"); + let mut old_drivers = drivers.drivers.take().unwrap(); + ai::destruct_AsioDrivers(&mut old_drivers); + } + drivers.state = AsioState::Offline; + }, + AsioState::Running => { + unsafe { + drivers.asio_stop(); + drivers.asio_dispose_buffers().expect("Failed to dispose buffers"); + drivers.asio_exit().expect("Failed to exit asio"); + let mut old_drivers = drivers.drivers.take().unwrap(); + ai::destruct_AsioDrivers(&mut old_drivers); + } + drivers.state = AsioState::Offline; + }, } } pub fn play() { unsafe { - let result = asio_start(); + let result = get_drivers().asio_start(); println!("start result: {:?}", result); } } pub fn stop() { unsafe { - let result = asio_stop(); + let result = get_drivers().asio_stop(); println!("stop result: {:?}", result); } } -unsafe fn asio_init(di: &mut ai::ASIODriverInfo) -> Result<(), AsioError> { - let result = ai::ASIOInit(di); - asio_result!(result) +impl DriverWrapper { +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(()) + } } -unsafe fn asio_get_channels(ins: &mut c_long, outs: &mut c_long) -> Result<(), AsioError> { - let result = ai::ASIOGetChannels(ins, outs); - asio_result!(result) +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) + } } -unsafe fn asio_get_sample_rate(rate: &mut c_double) -> Result<(), AsioError> { - let result = ai::get_sample_rate(rate); - asio_result!(result) +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) + } } -unsafe fn asio_get_channel_info(ci: &mut ai::ASIOChannelInfo) -> Result<(), AsioError> { - let result = ai::ASIOGetChannelInfo(ci); - asio_result!(result) +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) + } } 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> { - let result = ai::ASIOGetBufferSize( - min_b_size, - max_b_size, - pref_b_size, - grans, - ); - asio_result!(result) + 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) + } } 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(); + 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, @@ -553,24 +627,53 @@ unsafe fn asio_create_buffers( callbacks_convert, ); asio_result!(result) + .map(|_| self.state = AsioState::Prepared) } -unsafe fn asio_dispose_buffers() -> Result<(), AsioError> { +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) } -unsafe fn asio_exit() -> Result<(), AsioError> { +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::Loaded) } -unsafe fn asio_start() -> Result<(), AsioError> { +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) } -unsafe fn asio_stop() -> Result<(), AsioError> { +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) +} } \ No newline at end of file diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 37c58b2..e6fb083 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -17,6 +17,7 @@ use SampleFormat; pub struct EventLoop { asio_streams: Arc>, + cpal_streams: Arc>>>, stream_count: AtomicUsize, callbacks: Arc>>, } @@ -31,6 +32,11 @@ pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], } +enum Stream{ + Input, + Output, +} + #[derive(Default)] struct I16Buffer{ cpal: Vec, @@ -56,6 +62,7 @@ impl EventLoop { pub fn new() -> EventLoop { EventLoop { asio_streams: Arc::new(Mutex::new(sys::AsioStreams{input: None, output: None})), + cpal_streams: Arc::new(Mutex::new(Vec::new())), stream_count: AtomicUsize::new(0), callbacks: Arc::new(Mutex::new(Vec::new())), } @@ -259,6 +266,7 @@ impl EventLoop { } } }); + self.cpal_streams.lock().unwrap().push(Some(Stream::Input)); StreamId(count) }) } @@ -414,10 +422,12 @@ pub fn build_output_stream( } } }); + self.cpal_streams.lock().unwrap().push(Some(Stream::Output)); StreamId(count) }) } + pub fn play_stream(&self, stream: StreamId) { sys::play(); } @@ -426,20 +436,17 @@ pub fn pause_stream(&self, stream: StreamId) { sys::stop(); } -// TODO the logic for this is wrong -// We are not destroying AsioStreams but CPAL streams -// Asio Streams should only be destroyed if there are no -// CPAL streams left pub fn destroy_stream(&self, stream_id: StreamId) { - /* - let mut asio_streams_lock = self.asio_streams.lock().unwrap(); - let old_stream = mem::replace(asio_streams_lock.get_mut(stream_id.0 - 1).expect("stream count out of bounds"), None); - if let Some(old_stream) = old_stream { - sys::destroy_stream(old_stream); + let mut streams = self.cpal_streams.lock().unwrap(); + streams.get_mut(stream_id.0 - 1).take(); + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count - 1, Ordering::SeqCst); + if count == 1 { + *self.asio_streams.lock().unwrap() = sys::AsioStreams{ output: None, input: None }; + sys::clean_up(); } - */ - unimplemented!() } + pub fn run(&self, mut callback: F) -> ! where F: FnMut(StreamId, StreamData) + Send, @@ -456,6 +463,12 @@ F: FnMut(StreamId, StreamData) + Send, } } +impl Drop for EventLoop { + fn drop(&mut self) { + sys::clean_up(); + } +} + impl<'a, T> InputBuffer<'a, T> { pub fn buffer(&self) -> &[T] { &self.buffer From ed50bb99233394d62d2cc3c9b8e34a4bbfc812d4 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 30 Oct 2018 16:50:20 +1100 Subject: [PATCH 24/75] adding outputs --- asio-sys/src/lib.rs | 4 +- src/platform/windows/asio/device.rs | 8 +-- src/platform/windows/asio/stream.rs | 90 ++++++++++++++++++++++------- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 1626fb9..afefedf 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -19,7 +19,7 @@ use std::os::raw::c_char; use std::os::raw::c_double; use std::os::raw::c_long; use std::os::raw::c_void; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::sync::MutexGuard; @@ -46,6 +46,8 @@ lazy_static! { } static STREAM_DRIVER_COUNT: AtomicUsize = AtomicUsize::new(0); +pub static SILENCE_FIRST: AtomicBool = AtomicBool::new(false); +pub static SILENCE_SECOND: AtomicBool = AtomicBool::new(false); #[derive(Debug)] pub struct Channel { diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index af64202..67fff78 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -45,7 +45,7 @@ impl Device { FormatsEnumerationError> { match self.default_input_format() { Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), - Err(e) => Err(FormatsEnumerationError::DeviceNotAvailable), + Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), } } @@ -53,7 +53,7 @@ impl Device { FormatsEnumerationError> { match self.default_output_format() { Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), - Err(e) => Err(FormatsEnumerationError::DeviceNotAvailable), + Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), } } @@ -127,7 +127,7 @@ 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(); + let driver_list = sys::get_driver_list(); // Remove let d_name = driver_list.into_iter() .filter(|d| d == "ASIO4ALL v2") @@ -151,7 +151,7 @@ pub fn default_input_device() -> Option { } pub fn default_output_device() -> Option { - let mut driver_list = sys::get_driver_list(); + let driver_list = sys::get_driver_list(); // Remove let d_name = driver_list.into_iter() .filter(|d| d == "ASIO4ALL v2") diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index e6fb083..5392a96 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -5,11 +5,9 @@ use std; use Format; use CreationError; use StreamData; -use std::marker::PhantomData; use super::Device; -use std::cell::Cell; -use UnknownTypeOutputBuffer; use UnknownTypeInputBuffer; +use UnknownTypeOutputBuffer; use std::sync::{Arc, Mutex}; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -32,9 +30,8 @@ pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], } -enum Stream{ - Input, - Output, +struct Stream{ + playing: bool, } #[derive(Default)] @@ -68,6 +65,9 @@ impl EventLoop { } } + /// Create a new CPAL Input Stream + /// If there is no ASIO Input Stream + /// it will be created fn get_input_stream(&self, drivers: &sys::Drivers, num_channels: usize) -> Result { let ref mut streams = *self.asio_streams.lock().unwrap(); match streams.input { @@ -130,8 +130,8 @@ impl EventLoop { let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); let callbacks = self.callbacks.clone(); - let bytes_per_channel = format.data_type.sample_size(); // Create buffers let channel_len = cpal_num_samples @@ -175,11 +175,19 @@ impl EventLoop { }; sys::set_callback(move |index| unsafe { + //if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1){ + if let Some(s) = s{ + if !s.playing { return (); } + } + } + } if let Some(ref asio_stream) = asio_streams.lock().unwrap().input { // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); - // Assuming only one callback, probably needs to change + // Theres only a single callback because theres only one event loop match callbacks.first_mut() { Some(callback) => { macro_rules! try_callback { @@ -266,7 +274,7 @@ impl EventLoop { } } }); - self.cpal_streams.lock().unwrap().push(Some(Stream::Input)); + self.cpal_streams.lock().unwrap().push(Some(Stream{ playing: false })); StreamId(count) }) } @@ -287,8 +295,8 @@ pub fn build_output_stream( let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); let callbacks = self.callbacks.clone(); - let bytes_per_channel = format.data_type.sample_size(); // Create buffers let channel_len = cpal_num_samples / num_channels as usize; @@ -331,11 +339,19 @@ pub fn build_output_stream( }; sys::set_callback(move |index| unsafe { + //if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1){ + if let Some(s) = s{ + if !s.playing { return (); } + } + } + } if let Some(ref asio_stream) = asio_streams.lock().unwrap().output { // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); - // Assuming only one callback, probably needs to change + // Theres only a single callback because theres only one event loop match callbacks.first_mut() { Some(callback) => { macro_rules! try_callback { @@ -349,9 +365,6 @@ pub fn build_output_stream( $BuffersTypeIdent:ident ) => { let mut my_buffers = $Buffers; - // 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 my_buffers.cpal @@ -375,6 +388,25 @@ pub fn build_output_stream( au::deinterleave(&c_buffer[..], channels); } + let silence = match index { + 0 =>{ + if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { + sys::SILENCE_FIRST.store(true, Ordering::SeqCst); + sys::SILENCE_SECOND.store(false, Ordering::SeqCst); + true + }else{false} + }, + 1 =>{ + if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { + sys::SILENCE_SECOND.store(true, Ordering::SeqCst); + sys::SILENCE_FIRST.store(false, Ordering::SeqCst); + true + }else{false} + }, + _ => unreachable!(), + }; + + // For each channel write the cpal data to // the asio buffer // TODO need to check for Endian @@ -388,7 +420,8 @@ pub fn build_output_stream( asio_stream.buffer_size as usize); for (asio_s, cpal_s) in asio_buffer.iter_mut() .zip(channel){ - *asio_s = (*cpal_s as i64 * + if silence { *asio_s = 0.0 as $AsioType; } + *asio_s += (*cpal_s as i64 * ::std::$AsioTypeIdent::MAX as i64 / ::std::$SampleTypeIdent::MAX as i64) as $AsioType; } @@ -422,23 +455,40 @@ pub fn build_output_stream( } } }); - self.cpal_streams.lock().unwrap().push(Some(Stream::Output)); + self.cpal_streams.lock().unwrap().push(Some(Stream{ playing: false })); StreamId(count) }) } -pub fn play_stream(&self, stream: StreamId) { +pub fn play_stream(&self, stream_id: StreamId) { + let mut streams = self.cpal_streams.lock().unwrap(); + if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { + s.playing = true; + } + // Calling play when already playing is a no-op sys::play(); } -pub fn pause_stream(&self, stream: StreamId) { - sys::stop(); +pub fn pause_stream(&self, stream_id: StreamId) { + let mut streams = self.cpal_streams.lock().unwrap(); + if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { + s.playing = false; + } + let any_playing = streams + .iter() + .filter(|s| if let Some(s) = s { + s.playing + } else {false} ) + .next(); + if let None = any_playing { + sys::stop(); + } } pub fn destroy_stream(&self, stream_id: StreamId) { let mut streams = self.cpal_streams.lock().unwrap(); - streams.get_mut(stream_id.0 - 1).take(); + streams.get_mut(stream_id.0).take(); let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count - 1, Ordering::SeqCst); if count == 1 { From 91f2edb277b56087b25b855e357d95f3f1b18aa6 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 31 Oct 2018 10:34:14 +1100 Subject: [PATCH 25/75] utils as mod --- Cargo.toml | 1 - src/platform/windows/asio/asio_utils/Cargo.toml | 6 ------ src/platform/windows/asio/asio_utils/{src/lib.rs => mod.rs} | 6 ++++-- src/platform/windows/asio/asio_utils/{src => }/tests.rs | 0 src/platform/windows/asio/mod.rs | 1 + src/platform/windows/asio/stream.rs | 3 ++- 6 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 src/platform/windows/asio/asio_utils/Cargo.toml rename src/platform/windows/asio/asio_utils/{src/lib.rs => mod.rs} (95%) rename src/platform/windows/asio/asio_utils/{src => }/tests.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index e863431..d7f234d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ hound = "3.4" [target.'cfg(any(target_os = "windows" ))'.dependencies] asio-sys = { version = "0.1", path = "asio-sys" } -asio_utils = { version = "0.1", path = "src/platform/windows/asio/asio_utils" } [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/src/platform/windows/asio/asio_utils/Cargo.toml b/src/platform/windows/asio/asio_utils/Cargo.toml deleted file mode 100644 index e949bad..0000000 --- a/src/platform/windows/asio/asio_utils/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "asio_utils" -version = "0.1.0" -authors = ["Tom Gowan "] - -[dependencies] diff --git a/src/platform/windows/asio/asio_utils/src/lib.rs b/src/platform/windows/asio/asio_utils/mod.rs similarity index 95% rename from src/platform/windows/asio/asio_utils/src/lib.rs rename to src/platform/windows/asio/asio_utils/mod.rs index f9f78fe..c9b221f 100644 --- a/src/platform/windows/asio/asio_utils/src/lib.rs +++ b/src/platform/windows/asio/asio_utils/mod.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod tests; +use std::marker::Copy; + /// Interleave the buffer from asio to cpal /// asio: LLLLRRRR /// cpal: LRLRLRLR @@ -11,7 +13,7 @@ mod tests; /// this check is ommited for performance pub fn interleave(channels: &[Vec], target: &mut Vec) where - T: std::marker::Copy, + T: Copy, { assert!(!channels.is_empty()); target.clear(); @@ -28,7 +30,7 @@ where /// asio: 111122223333 pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) where - T: std::marker::Copy, + T: Copy, { for ch in asio_channels.iter_mut() { ch.clear(); diff --git a/src/platform/windows/asio/asio_utils/src/tests.rs b/src/platform/windows/asio/asio_utils/tests.rs similarity index 100% rename from src/platform/windows/asio/asio_utils/src/tests.rs rename to src/platform/windows/asio/asio_utils/tests.rs diff --git a/src/platform/windows/asio/mod.rs b/src/platform/windows/asio/mod.rs index 8f98594..1d49f42 100644 --- a/src/platform/windows/asio/mod.rs +++ b/src/platform/windows/asio/mod.rs @@ -6,3 +6,4 @@ pub use self::stream::{InputBuffer, OutputBuffer, EventLoop, StreamId}; mod device; mod stream; +mod asio_utils; \ No newline at end of file diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 5392a96..66ca056 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -1,5 +1,5 @@ extern crate asio_sys as sys; -extern crate asio_utils as au; + use std; use Format; @@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex}; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; use SampleFormat; +use super::asio_utils as au; pub struct EventLoop { asio_streams: Arc>, From 6a71f0a8015769ace14ea50102567f8518d3413a Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 31 Oct 2018 13:22:10 +1100 Subject: [PATCH 26/75] choose backend --- src/os/windows/mod.rs | 30 +++++++++++++++++++++++------ src/platform/windows/asio/device.rs | 24 ++++------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index ad447d8..17f029f 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -1,12 +1,30 @@ -pub enum Backend { +use std::sync::Mutex; + +#[derive(Clone)] +pub enum BackEnd { Wasapi, Asio, } -// TODO This needs to be set once at run time -// by the cpal user -static backend: Backend = Backend::Asio; +//static BACKEND: BackEnd = BackEnd::Asio; -pub fn which_backend() -> &'static Backend { - &backend +lazy_static! { + static ref BACK_END: Mutex = Mutex::new(BackEnd::Wasapi); } + +pub fn which_backend() -> BackEnd { + (*BACK_END.lock().unwrap()).clone() +} + +pub fn use_asio_backend() -> Result<(), BackEndError> { + *BACK_END.lock().unwrap() = BackEnd::Asio; + Ok(()) +} + +pub fn use_wasapi_backend() -> Result<(), BackEndError> { + *BACK_END.lock().unwrap() = BackEnd::Wasapi; + Ok(()) +} + +#[derive(Debug)] +pub struct BackEndError; \ No newline at end of file diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 67fff78..815e1f5 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -127,16 +127,8 @@ 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 driver_list = sys::get_driver_list(); - // Remove - let d_name = driver_list.into_iter() - .filter(|d| d == "ASIO4ALL v2") - //.filter(|d| d.name() == "Dante Via (x64)") - //.filter(|d| d.name() == "Dante Virtual Soundcard (x64)") - .next(); - //match driver_list.pop() { - match d_name { - // end remove + let mut driver_list = sys::get_driver_list(); + match driver_list.pop() { Some(name) => { sys::Drivers::load(&name) .or_else(|e| { @@ -151,16 +143,8 @@ pub fn default_input_device() -> Option { } pub fn default_output_device() -> Option { - let driver_list = sys::get_driver_list(); - // Remove - let d_name = driver_list.into_iter() - .filter(|d| d == "ASIO4ALL v2") - //.filter(|d| d.name() == "Dante Via (x64)") - //.filter(|d| d.name() == "Dante Virtual Soundcard (x64)") - .next(); - //match driver_list.pop() { - match d_name { - // end remove + let mut driver_list = sys::get_driver_list(); + match driver_list.pop() { Some(name) => { sys::Drivers::load(&name) .or_else(|e| { From 5ec6da3aad4e07f7cc3ea1d78d516080b7dab60c Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Thu, 1 Nov 2018 12:21:00 +1100 Subject: [PATCH 27/75] Fixed driver bug --- asio-sys/asio-link/helpers.cpp | 19 ++- asio-sys/asio-link/helpers.hpp | 6 +- asio-sys/build.rs | 4 +- asio-sys/src/lib.rs | 230 ++++++++++++++-------------- src/platform/windows/asio/device.rs | 7 +- 5 files changed, 141 insertions(+), 125 deletions(-) diff --git a/asio-sys/asio-link/helpers.cpp b/asio-sys/asio-link/helpers.cpp index ded6328..e5c8436 100644 --- a/asio-sys/asio-link/helpers.cpp +++ b/asio-sys/asio-link/helpers.cpp @@ -1,9 +1,20 @@ #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)); } + +extern AsioDrivers* asioDrivers; +bool loadAsioDriver(char *name); + +extern "C" bool load_asio_driver(char * name){ + return loadAsioDriver(name); +} + +extern "C" void remove_current_driver() { + asioDrivers->removeCurrentDriver(); +} +extern "C" long get_driver_names(char **names, long maxDrivers) { + AsioDrivers ad; + return ad.getDriverNames(names, maxDrivers); +} \ No newline at end of file diff --git a/asio-sys/asio-link/helpers.hpp b/asio-sys/asio-link/helpers.hpp index 16264e1..962047f 100644 --- a/asio-sys/asio-link/helpers.hpp +++ b/asio-sys/asio-link/helpers.hpp @@ -2,9 +2,9 @@ #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); +extern "C" bool load_asio_driver(char * name); +extern "C" void remove_current_driver(); +extern "C" long get_driver_names(char **names, long maxDrivers); \ No newline at end of file diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 0a004f3..8d895f0 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -163,7 +163,6 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .whitelist_type("ASIOCallbacks") .whitelist_type("ASIOSampleType") .whitelist_type("ASIOChannelInfo") - .whitelist_function("destruct_AsioDrivers") .whitelist_function("ASIOGetChannels") .whitelist_function("ASIOGetChannelInfo") .whitelist_function("ASIOGetBufferSize") @@ -174,6 +173,9 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .whitelist_function("ASIOStop") .whitelist_function("ASIODisposeBuffers") .whitelist_function("ASIOExit") + .whitelist_function("load_asio_driver") + .whitelist_function("remove_current_driver") + .whitelist_function("get_driver_names") // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index afefedf..98c83a5 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -15,17 +15,12 @@ use errors::{AsioError, AsioDriverError, AsioErrorWrapper}; use std::ffi::CStr; use std::ffi::CString; use std::mem; -use std::os::raw::c_char; -use std::os::raw::c_double; -use std::os::raw::c_long; -use std::os::raw::c_void; +use std::os::raw::{c_char, c_double, c_long, c_void}; use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use std::sync::MutexGuard; +use std::sync::{Mutex, MutexGuard}; use asio_import as ai; -const MAX_DRIVER: usize = 32; pub struct CbArgs { pub stream_id: S, @@ -39,8 +34,7 @@ lazy_static! { } lazy_static! { - static ref ASIO_DRIVERS: Mutex = Mutex::new(DriverWrapper{ - drivers: None, + static ref ASIO_DRIVERS: Mutex = Mutex::new(AsioWrapper{ state: AsioState::Offline, }); } @@ -64,8 +58,7 @@ pub struct SampleRate { pub struct Drivers; #[derive(Debug)] -struct DriverWrapper { - drivers: Option, +struct AsioWrapper { state: AsioState, } @@ -78,6 +71,7 @@ enum AsioState { Running, } + pub struct AsioStreams { pub input: Option, pub output: Option, @@ -132,7 +126,7 @@ pub enum AsioSampleType { pub struct AsioBufferInfo { pub is_input: c_long, pub channel_num: c_long, - pub buffers: [*mut std::os::raw::c_void; 2], + pub buffers: [*mut c_void; 2], } #[repr(C)] @@ -148,7 +142,7 @@ struct AsioCallbacks { direct_process: c_long, ) -> *mut ai::ASIOTime, } -extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) -> () { +extern "C" fn buffer_switch(double_buffer_index: c_long, _direct_process: c_long) -> () { let mut bcs = buffer_callback.lock().unwrap(); for mut bc in bcs.iter_mut() { @@ -158,57 +152,52 @@ extern "C" fn buffer_switch(double_buffer_index: c_long, direct_process: c_long) } } -extern "C" fn sample_rate_did_change(s_rate: c_double) -> () {} +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, + _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, + params: *mut ai::ASIOTime, _double_buffer_index: c_long, _direct_process: c_long, ) -> *mut ai::ASIOTime { params } -fn get_drivers() -> MutexGuard<'static, DriverWrapper> { +fn get_drivers() -> MutexGuard<'static, AsioWrapper> { ASIO_DRIVERS.lock().unwrap() } impl Drivers { + #[allow(unused_assignments)] pub fn load(driver_name: &str) -> Result { let mut drivers = get_drivers(); - match drivers.state { - AsioState::Offline => { - // 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 { - let mut asio_drivers = ai::AsioDrivers::new(); - let load_result = asio_drivers.loadDriver(raw); - if load_result { drivers.state = AsioState::Loaded; } - let init_result = drivers.asio_init(&mut driver_info); - // Take back ownership - my_driver_name = CString::from_raw(raw); - if load_result && init_result.is_ok() { - println!("Creating drivers"); - drivers.drivers = Some(asio_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 { + let load_result = drivers.load(raw); + // Take back ownership + my_driver_name = CString::from_raw(raw); + if load_result { + match drivers.asio_init(&mut driver_info) { + Ok(_) => { STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); Ok(Drivers) - } else { + }, + Err(_) => { Err(AsioDriverError::DriverLoadError) - } + }, } - }, - _ => { - STREAM_DRIVER_COUNT.fetch_add(1, Ordering::SeqCst); - Ok(Drivers) - }, + } else { + Err(AsioDriverError::DriverLoadError) + } } } @@ -274,8 +263,8 @@ impl Drivers { } } - pub fn prepare_input_stream(&self, output: Option, mut num_channels: usize) -> Result { - let mut buffer_infos = vec![ + pub fn prepare_input_stream(&self, output: Option, num_channels: usize) -> Result { + let buffer_infos = vec![ AsioBufferInfo { is_input: 1, channel_num: 0, @@ -291,7 +280,7 @@ impl Drivers { /// Creates the output stream pub fn prepare_output_stream(&self, input: Option, num_channels: usize) -> Result { // Initialize data for FFI - let mut buffer_infos = vec![ + let buffer_infos = vec![ AsioBufferInfo { is_input: 0, channel_num: 0, @@ -363,7 +352,7 @@ impl Drivers { fn create_buffers(&self, buffer_infos: Vec) -> Result<(Vec, c_long), AsioDriverError>{ let num_channels = buffer_infos.len(); - let mut callbacks = AsioCallbacks { + let callbacks = AsioCallbacks { buffer_switch: buffer_switch, sample_rate_did_change: sample_rate_did_change, asio_message: asio_message, @@ -402,7 +391,7 @@ impl Drivers { pref_b_size, &mut callbacks_convert, ).map(|_|{ - let mut buffer_infos: Vec = buffer_info_convert + let buffer_infos: Vec = buffer_info_convert .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); @@ -427,27 +416,25 @@ impl Drivers { impl Drop for Drivers { fn drop(&mut self) { - println!("dropping drivers"); let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); if count == 1 { - println!("Destroying driver"); clean_up(); } } } -unsafe impl Send for DriverWrapper {} +unsafe impl Send for AsioWrapper {} impl BufferCallback { fn run(&mut self, index: i32) { - let mut cb = &mut self.0; + let cb = &mut self.0; cb(index); } } unsafe impl Send for AsioStream {} -pub fn set_callback(mut callback: F) -> () +pub fn set_callback(callback: F) -> () where F: FnMut(i32) + Send, { @@ -456,82 +443,36 @@ where } /// Returns a list of all the ASIO drivers -//TODO this needs to not create and remove drivers +#[allow(unused_assignments)] pub fn get_driver_list() -> Vec { - let mut driver_list: Vec = Vec::new(); + const MAX_DRIVERS: usize = 100; + const CHAR_LEN: usize = 32; - 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]; + let mut driver_names: [[c_char; CHAR_LEN]; MAX_DRIVERS] = [[0; CHAR_LEN]; MAX_DRIVERS]; + let mut p_driver_name: [*mut i8; MAX_DRIVERS] = [0 as *mut i8; MAX_DRIVERS]; - for i in 0 .. MAX_DRIVER { + for i in 0 .. MAX_DRIVERS { p_driver_name[i] = driver_names[i].as_mut_ptr(); } + unsafe { - let mut asio_drivers = ai::AsioDrivers::new(); + let num_drivers = ai::get_driver_names(p_driver_name.as_mut_ptr(), MAX_DRIVERS as i32); - 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 { + (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(); - 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); + my_driver_name.into_string().expect("Failed to convert driver name") + }) + .collect() } - - driver_list } pub fn clean_up() { let mut drivers = get_drivers(); - match drivers.state { - AsioState::Offline => (), - AsioState::Loaded => { - unsafe { - let mut old_drivers = drivers.drivers.take().unwrap(); - ai::destruct_AsioDrivers(&mut old_drivers); - } - drivers.state = AsioState::Offline; - }, - AsioState::Initialized => { - unsafe { - drivers.asio_exit().expect("Failed to exit asio"); - let mut old_drivers = drivers.drivers.take().unwrap(); - ai::destruct_AsioDrivers(&mut old_drivers); - } - drivers.state = AsioState::Offline; - }, - AsioState::Prepared => { - unsafe { - drivers.asio_dispose_buffers().expect("Failed to dispose buffers"); - drivers.asio_exit().expect("Failed to exit asio"); - let mut old_drivers = drivers.drivers.take().unwrap(); - ai::destruct_AsioDrivers(&mut old_drivers); - } - drivers.state = AsioState::Offline; - }, - AsioState::Running => { - unsafe { - drivers.asio_stop(); - drivers.asio_dispose_buffers().expect("Failed to dispose buffers"); - drivers.asio_exit().expect("Failed to exit asio"); - let mut old_drivers = drivers.drivers.take().unwrap(); - ai::destruct_AsioDrivers(&mut old_drivers); - } - drivers.state = AsioState::Offline; - }, - } + drivers.clean_up(); } pub fn play() { @@ -548,12 +489,32 @@ pub fn stop() { } } -impl DriverWrapper { +impl AsioWrapper { + +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 + } +} + +unsafe fn unload(&mut self) { + ai::remove_current_driver(); +} + 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) + .map_err(|e| { + self.state = AsioState::Offline; + e + }) }else{ Ok(()) } @@ -612,7 +573,7 @@ unsafe fn asio_create_buffers( match self.state { Offline | Loaded => return Err(AsioError::NoDrivers), Running => { - self.asio_stop(); + self.asio_stop().expect("Asio failed to stop"); self.asio_dispose_buffers().expect("Failed to dispose buffers"); self.state = Initialized; }, @@ -652,7 +613,7 @@ unsafe fn asio_exit(&mut self) -> Result<(), AsioError> { } let result = ai::ASIOExit(); asio_result!(result) - .map(|_| self.state = AsioState::Loaded) + .map(|_| self.state = AsioState::Offline) } unsafe fn asio_start(&mut self) -> Result<(), AsioError> { @@ -678,4 +639,41 @@ unsafe fn asio_stop(&mut self) -> Result<(), AsioError> { asio_result!(result) .map(|_| self.state = AsioState::Prepared) } + +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; + }, + } +} } \ No newline at end of file diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 815e1f5..b354d57 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -96,7 +96,12 @@ impl Device { impl Default for Devices { fn default() -> Devices { - Devices{ drivers: sys::get_driver_list().into_iter() } + // Remove offline drivers + let driver_names: Vec = sys::get_driver_list() + .into_iter() + .filter(|name| sys::Drivers::load(&name).is_ok()) + .collect(); + Devices{ drivers: driver_names.into_iter() } } } From b0b0484d4b225fe4b53e7d34bd5a293d5c1df969 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Thu, 1 Nov 2018 17:58:50 +1100 Subject: [PATCH 28/75] endian conversion --- Cargo.toml | 1 + src/platform/windows/asio/device.rs | 2 +- src/platform/windows/asio/stream.rs | 105 ++++++++++++++++++++++++---- 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d7f234d..7b1f2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["audio", "sound"] [dependencies] failure = "0.1.5" lazy_static = "1.3" +num-traits = "0.2.6" [dev-dependencies] hound = "3.4" diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index b354d57..7b03eb2 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -62,9 +62,9 @@ impl Device { let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); match self.drivers.get_data_type() { Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTInt32MSB) => Ok(SampleFormat::I16), Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), - // TODO This should not be set to 16bit but is for testing Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), _ => Err(DefaultFormatError::StreamTypeNotSupported), diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 66ca056..0920a58 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -1,5 +1,5 @@ extern crate asio_sys as sys; - +extern crate num_traits; use std; use Format; @@ -13,6 +13,7 @@ use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; use SampleFormat; use super::asio_utils as au; +use self::num_traits::PrimInt; pub struct EventLoop { asio_streams: Arc>, @@ -56,6 +57,11 @@ struct Buffers { f32_buff: F32Buffer, } +enum Endian { + Little, + Big, +} + impl EventLoop { pub fn new() -> EventLoop { EventLoop { @@ -199,9 +205,12 @@ impl EventLoop { $AsioTypeIdent:ident, $Buffers:expr, $BuffersType:ty, - $BuffersTypeIdent:ident + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr ) => { + // For each channel write the cpal data to // the asio buffer // Also need to check for Endian @@ -215,9 +224,10 @@ impl EventLoop { buff_ptr, asio_stream.buffer_size as usize); for asio_s in asio_buffer.iter(){ - channel.push( (*asio_s as i64 * + channel.push( $ConvertEndian((*asio_s as i64 * ::std::$SampleTypeIdent::MAX as i64 / - ::std::$AsioTypeIdent::MAX as i64) as $SampleType); + ::std::$AsioTypeIdent::MAX as i64) as $SampleType, + $Endianness)); } } @@ -254,19 +264,43 @@ impl EventLoop { match stream_type { sys::AsioSampleType::ASIOSTInt32LSB => { try_callback!(I16, i16, i16, i32, i32, - buffers.i16_buff, I16Buffer, I16Buffer); + buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Little, convert_endian_to); } sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!(I16, i16, i16, i16, i16, - buffers.i16_buff, I16Buffer, I16Buffer); + buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Little, convert_endian_to); + } + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!(I16, i16, i16, i32, i32, + buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Big, convert_endian_to); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!(I16, i16, i16, i16, i16, + buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Big, convert_endian_to); } sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32, - buffers.f32_buff, F32Buffer, F32Buffer); + buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Little, |a, _| a); } sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64, - buffers.f32_buff, F32Buffer, F32Buffer); + buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Little, |a, _| a); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!(F32, f32, f32, f32, f32, + buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Big, |a, _| a); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!(F32, f32, f32, f64, f64, + buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Big, |a, _| a); } _ => println!("unsupported format {:?}", stream_type), } @@ -363,7 +397,9 @@ pub fn build_output_stream( $AsioTypeIdent:ident, $Buffers:expr, $BuffersType:ty, - $BuffersTypeIdent:ident + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr ) => { let mut my_buffers = $Buffers; { @@ -422,9 +458,10 @@ pub fn build_output_stream( for (asio_s, cpal_s) in asio_buffer.iter_mut() .zip(channel){ if silence { *asio_s = 0.0 as $AsioType; } - *asio_s += (*cpal_s as i64 * + *asio_s += $ConvertEndian((*cpal_s as i64 * ::std::$AsioTypeIdent::MAX as i64 / - ::std::$SampleTypeIdent::MAX as i64) as $AsioType; + ::std::$SampleTypeIdent::MAX as i64) as $AsioType, + $Endianness); } } @@ -435,19 +472,43 @@ pub fn build_output_stream( match stream_type { sys::AsioSampleType::ASIOSTInt32LSB => { try_callback!(I16, i16, i16, i32, i32, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + &mut re_buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Little, convert_endian_from); } sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!(I16, i16, i16, i16, i16, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer); + &mut re_buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Little, convert_endian_from); + } + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!(I16, i16, i16, i32, i32, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Big, convert_endian_from); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!(I16, i16, i16, i16, i16, + &mut re_buffers.i16_buff, I16Buffer, I16Buffer, + Endian::Big, convert_endian_from); } sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!(F32, f32, f32, f32, f32, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + &mut re_buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Little, |a, _| a); } sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!(F32, f32, f32, f64, f64, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer); + &mut re_buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Little, |a, _| a); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!(F32, f32, f32, f32, f32, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Big, |a, _| a); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!(F32, f32, f32, f64, f64, + &mut re_buffers.f32_buff, F32Buffer, F32Buffer, + Endian::Big, |a, _| a); } _ => println!("unsupported format {:?}", stream_type), } @@ -539,3 +600,17 @@ impl<'a, T> OutputBuffer<'a, T> { pub fn finish(self) {} } + +fn convert_endian_to(sample: T, endian: Endian) -> T { + match endian { + Endian::Big => sample.to_be(), + Endian::Little => sample.to_le(), + } +} + +fn convert_endian_from(sample: T, endian: Endian) -> T { + match endian { + Endian::Big => T::from_be(sample), + Endian::Little => T::from_le(sample), + } +} \ No newline at end of file From 6e0eb073d1190811b3a6b161ae904c267ceadab1 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Fri, 2 Nov 2018 22:06:08 +1100 Subject: [PATCH 29/75] sample rate support --- asio-sys/asio-link/helpers.cpp | 8 +++ asio-sys/asio-link/helpers.hpp | 6 ++ asio-sys/build.rs | 2 + asio-sys/src/lib.rs | 36 ++++++++++++ src/host/wasapi/device.rs | 1 - src/platform/windows/asio/device.rs | 28 +++++++++- src/platform/windows/asio/stream.rs | 86 ++++++++++++++++++++++++----- 7 files changed, 150 insertions(+), 17 deletions(-) diff --git a/asio-sys/asio-link/helpers.cpp b/asio-sys/asio-link/helpers.cpp index e5c8436..39c703d 100644 --- a/asio-sys/asio-link/helpers.cpp +++ b/asio-sys/asio-link/helpers.cpp @@ -4,6 +4,14 @@ extern "C" ASIOError get_sample_rate(double * rate){ return ASIOGetSampleRate(reinterpret_cast(rate)); } +extern "C" ASIOError set_sample_rate(double rate){ + return ASIOSetSampleRate(rate); +} + +extern "C" ASIOError can_sample_rate(double rate){ + return ASIOCanSampleRate(rate); +} + extern AsioDrivers* asioDrivers; bool loadAsioDriver(char *name); diff --git a/asio-sys/asio-link/helpers.hpp b/asio-sys/asio-link/helpers.hpp index 962047f..d63f968 100644 --- a/asio-sys/asio-link/helpers.hpp +++ b/asio-sys/asio-link/helpers.hpp @@ -5,6 +5,12 @@ // Helper function to wrap confusing preprocessor extern "C" ASIOError get_sample_rate(double * rate); +// Helper function to wrap confusing preprocessor +extern "C" ASIOError set_sample_rate(double rate); + +// Helper function to wrap confusing preprocessor +extern "C" ASIOError can_sample_rate(double rate); + extern "C" bool load_asio_driver(char * name); extern "C" void remove_current_driver(); extern "C" long get_driver_names(char **names, long maxDrivers); \ No newline at end of file diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 8d895f0..8d2be50 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -167,6 +167,8 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .whitelist_function("ASIOGetChannelInfo") .whitelist_function("ASIOGetBufferSize") .whitelist_function("get_sample_rate") + .whitelist_function("set_sample_rate") + .whitelist_function("can_sample_rate") .whitelist_function("ASIOInit") .whitelist_function("ASIOCreateBuffers") .whitelist_function("ASIOStart") diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 98c83a5..c68fb8c 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -238,6 +238,24 @@ impl Drivers { sample_rate } + + 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) + } + } + + pub fn can_sample_rate(&self, sample_rate: u32) -> bool { + // Initialize memory for calls + let rate: c_double = c_double::from(sample_rate); + + unsafe { + get_drivers().asio_can_sample_rate(rate).is_ok() + } + } pub fn get_data_type(&self) -> Result { // Initialize memory for calls @@ -538,6 +556,24 @@ unsafe fn asio_get_sample_rate(&mut self, rate: &mut c_double) -> Result<(), Asi } } +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) + } +} + +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) + } +} + unsafe fn asio_get_channel_info(&mut self, ci: &mut ai::ASIOChannelInfo) -> Result<(), AsioError> { if let AsioState::Offline = self.state { Err(AsioError::NoDrivers) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index a70b591..f57ba04 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -490,7 +490,6 @@ impl Device { format.sample_rate = SampleRate(rate as _); supported_formats.push(SupportedFormat::from(format.clone())); } - Ok(supported_formats.into_iter()) } } diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index 7b03eb2..d1e46ea 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -44,7 +44,19 @@ impl Device { pub fn supported_input_formats(&self) -> Result { match self.default_input_format() { - Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), + Ok(f) => { + let supported_formats: Vec = [44100, 48000] + .into_iter() + .filter(|rate| self.drivers.can_sample_rate(**rate as u32) ) + .map(|rate| { + let mut format = f.clone(); + format.sample_rate = SampleRate(*rate); + SupportedFormat::from(format) + }) + .collect(); + //Ok(vec![SupportedFormat::from(f)].into_iter()) + Ok(supported_formats.into_iter()) + }, Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), } } @@ -52,7 +64,19 @@ impl Device { pub fn supported_output_formats(&self) -> Result { match self.default_output_format() { - Ok(f) => Ok(vec![SupportedFormat::from(f)].into_iter()), + Ok(f) => { + let supported_formats: Vec = [44100, 48000] + .into_iter() + .filter(|rate| self.drivers.can_sample_rate(**rate as u32) ) + .map(|rate| { + let mut format = f.clone(); + format.sample_rate = SampleRate(*rate); + SupportedFormat::from(format) + }) + .collect(); + //Ok(vec![SupportedFormat::from(f)].into_iter()) + Ok(supported_formats.into_iter()) + }, Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), } } diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 0920a58..abb5e2f 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -75,8 +75,15 @@ impl EventLoop { /// Create a new CPAL Input Stream /// If there is no ASIO Input Stream /// it will be created - fn get_input_stream(&self, drivers: &sys::Drivers, num_channels: usize) -> Result { + fn get_input_stream(&self, drivers: &sys::Drivers, num_channels: usize, sample_rate: u32) -> Result { let ref mut streams = *self.asio_streams.lock().unwrap(); + if sample_rate != drivers.get_sample_rate().rate { + if drivers.can_sample_rate(sample_rate) { + drivers.set_sample_rate(sample_rate).expect("Unsupported sample rate"); + } else { + panic!("This sample rate {:?} is not supported", sample_rate); + } + } match streams.input { Some(ref input) => Ok(input.buffer_size as usize), None => { @@ -98,8 +105,15 @@ impl EventLoop { } } - fn get_output_stream(&self, drivers: &sys::Drivers, num_channels: usize) -> Result { + fn get_output_stream(&self, drivers: &sys::Drivers, num_channels: usize, sample_rate: u32) -> Result { let ref mut streams = *self.asio_streams.lock().unwrap(); + if sample_rate != drivers.get_sample_rate().rate { + if drivers.can_sample_rate(sample_rate) { + drivers.set_sample_rate(sample_rate).expect("Unsupported sample rate"); + } else { + panic!("This sample rate {:?} is not supported", sample_rate); + } + } match streams.output { Some(ref output) => Ok(output.buffer_size as usize), None => { @@ -132,7 +146,8 @@ impl EventLoop { } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_input_stream(&drivers, num_channels as usize).map(|stream_buffer_size| { + let sample_rate = format.sample_rate.0; + self.get_input_stream(&drivers, num_channels as usize, sample_rate).map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); @@ -197,6 +212,26 @@ impl EventLoop { // Theres only a single callback because theres only one event loop match callbacks.first_mut() { Some(callback) => { + macro_rules! convert_sample { + ($AsioTypeIdent:ident, + u16, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + ((*$Sample as f64 + $AsioTypeIdent::MAX as f64) / + (::std::u16::MAX as f64 / + ::std::AsioTypeIdent::MAX as f64)) as u16 + }; + ($AsioTypeIdent:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * + ::std::$SampleTypeIdent::MAX as i64 / + ::std::$AsioTypeIdent::MAX as i64) as $SampleType + }; + }; macro_rules! try_callback { ($SampleFormat:ident, $SampleType:ty, @@ -224,10 +259,11 @@ impl EventLoop { buff_ptr, asio_stream.buffer_size as usize); for asio_s in asio_buffer.iter(){ - channel.push( $ConvertEndian((*asio_s as i64 * - ::std::$SampleTypeIdent::MAX as i64 / - ::std::$AsioTypeIdent::MAX as i64) as $SampleType, - $Endianness)); + channel.push( $ConvertEndian(convert_sample!( + $AsioTypeIdent, + $SampleType, + $SampleTypeIdent, + asio_s), $Endianness)); } } @@ -325,7 +361,8 @@ pub fn build_output_stream( } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_output_stream(&drivers, num_channels as usize).map(|stream_buffer_size| { + let sample_rate = format.sample_rate.0; + self.get_output_stream(&drivers, num_channels as usize, sample_rate).map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); @@ -336,7 +373,6 @@ pub fn build_output_stream( let channel_len = cpal_num_samples / num_channels as usize; - let mut re_buffers = match format.data_type{ SampleFormat::I16 => { Buffers{ @@ -386,6 +422,27 @@ pub fn build_output_stream( // Number of samples needed total let mut callbacks = callbacks.lock().unwrap(); + macro_rules! convert_sample { + ($AsioTypeIdent:ident, + $AsioType:ty, + u16, + $Sample:expr + ) => { + ((*$Sample as i64 * + ::std::$AsioTypeIdent::MAX as i64 / + ::std::u16::MAX as i64) - $AsioTypeIdent::MAX as i64) as $AsioType + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * + ::std::$AsioTypeIdent::MAX as i64 / + ::std::$SampleTypeIdent::MAX as i64) as $AsioType + }; + }; + // Theres only a single callback because theres only one event loop match callbacks.first_mut() { Some(callback) => { @@ -446,7 +503,6 @@ pub fn build_output_stream( // For each channel write the cpal data to // the asio buffer - // TODO need to check for Endian for (i, channel) in my_buffers.channel.iter().enumerate(){ let buff_ptr = asio_stream .buffer_infos[i] @@ -458,9 +514,12 @@ pub fn build_output_stream( for (asio_s, cpal_s) in asio_buffer.iter_mut() .zip(channel){ if silence { *asio_s = 0.0 as $AsioType; } - *asio_s += $ConvertEndian((*cpal_s as i64 * - ::std::$AsioTypeIdent::MAX as i64 / - ::std::$SampleTypeIdent::MAX as i64) as $AsioType, + *asio_s += $ConvertEndian(convert_sample!( + $AsioTypeIdent, + $AsioType, + $SampleTypeIdent, + cpal_s + ), $Endianness); } @@ -468,7 +527,6 @@ pub fn build_output_stream( }; } // Generic over types - // TODO check for endianess match stream_type { sys::AsioSampleType::ASIOSTInt32LSB => { try_callback!(I16, i16, i16, i32, i32, From ffcbc02e52c9832ddb2edc1880dbf8f337812e91 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Sun, 4 Nov 2018 21:23:24 +1100 Subject: [PATCH 30/75] multi channels --- asio-sys/asio-link/helpers.cpp | 1 + asio-sys/src/lib.rs | 43 +++++++++++++++-------------- src/platform/windows/asio/stream.rs | 24 ++++++++++++---- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/asio-sys/asio-link/helpers.cpp b/asio-sys/asio-link/helpers.cpp index 39c703d..79ec8bb 100644 --- a/asio-sys/asio-link/helpers.cpp +++ b/asio-sys/asio-link/helpers.cpp @@ -1,4 +1,5 @@ #include "helpers.hpp" +#include extern "C" ASIOError get_sample_rate(double * rate){ return ASIOGetSampleRate(reinterpret_cast(rate)); diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index c68fb8c..a9859b8 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -71,7 +71,6 @@ enum AsioState { Running, } - pub struct AsioStreams { pub input: Option, pub output: Option, @@ -282,14 +281,15 @@ impl Drivers { } pub fn prepare_input_stream(&self, output: Option, num_channels: usize) -> Result { - let buffer_infos = vec![ - AsioBufferInfo { - is_input: 1, - channel_num: 0, - buffers: [std::ptr::null_mut(); 2], - }; - num_channels - ]; + let buffer_infos = (0..num_channels) + .map(|i| { + AsioBufferInfo { + is_input: 1, + channel_num: i as c_long, + buffers: [std::ptr::null_mut(); 2], + } + }) + .collect(); let streams = AsioStreams{input: Some(AsioStream{buffer_infos, buffer_size: 0}), output}; self.create_streams(streams) @@ -298,14 +298,15 @@ impl Drivers { /// Creates the output stream pub fn prepare_output_stream(&self, input: Option, num_channels: usize) -> Result { // Initialize data for FFI - let buffer_infos = vec![ - AsioBufferInfo { - is_input: 0, - channel_num: 0, - buffers: [std::ptr::null_mut(); 2], - }; - num_channels - ]; + let buffer_infos = (0..num_channels) + .map(|i| { + AsioBufferInfo { + is_input: 0, + channel_num: i as c_long, + buffers: [std::ptr::null_mut(); 2], + } + }) + .collect(); let streams = AsioStreams{output: Some(AsioStream{buffer_infos, buffer_size: 0}), input}; self.create_streams(streams) } @@ -397,10 +398,13 @@ impl Drivers { &mut grans, ).expect("Failed getting buffers"); if pref_b_size > 0 { + /* let mut buffer_info_convert: Vec = buffer_infos .into_iter() .map(|bi| mem::transmute::(bi)) .collect(); + */ + let mut buffer_info_convert = mem::transmute::, Vec>(buffer_infos); let mut callbacks_convert = mem::transmute::(callbacks); drivers.asio_create_buffers( @@ -409,10 +413,7 @@ impl Drivers { pref_b_size, &mut callbacks_convert, ).map(|_|{ - let buffer_infos: Vec = buffer_info_convert - .into_iter() - .map(|bi| mem::transmute::(bi)) - .collect(); + let buffer_infos = mem::transmute::, Vec>(buffer_info_convert); for d in &buffer_infos { println!("after {:?}", d); } diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index abb5e2f..2b86482 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -75,7 +75,14 @@ impl EventLoop { /// Create a new CPAL Input Stream /// If there is no ASIO Input Stream /// it will be created - fn get_input_stream(&self, drivers: &sys::Drivers, num_channels: usize, sample_rate: u32) -> Result { + fn get_input_stream(&self, drivers: &sys::Drivers, format: &Format) -> Result { + let Format { + channels, + sample_rate, + .. + } = format; + let num_channels = *channels as usize; + let sample_rate = sample_rate.0; let ref mut streams = *self.asio_streams.lock().unwrap(); if sample_rate != drivers.get_sample_rate().rate { if drivers.can_sample_rate(sample_rate) { @@ -105,7 +112,14 @@ impl EventLoop { } } - fn get_output_stream(&self, drivers: &sys::Drivers, num_channels: usize, sample_rate: u32) -> Result { + fn get_output_stream(&self, drivers: &sys::Drivers, format: &Format) -> Result { + let Format { + channels, + sample_rate, + .. + } = format; + let num_channels = *channels as usize; + let sample_rate = sample_rate.0; let ref mut streams = *self.asio_streams.lock().unwrap(); if sample_rate != drivers.get_sample_rate().rate { if drivers.can_sample_rate(sample_rate) { @@ -146,8 +160,7 @@ impl EventLoop { } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let sample_rate = format.sample_rate.0; - self.get_input_stream(&drivers, num_channels as usize, sample_rate).map(|stream_buffer_size| { + self.get_input_stream(&drivers, format).map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); @@ -361,8 +374,7 @@ pub fn build_output_stream( } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let sample_rate = format.sample_rate.0; - self.get_output_stream(&drivers, num_channels as usize, sample_rate).map(|stream_buffer_size| { + self.get_output_stream(&drivers, format).map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.load(Ordering::SeqCst); self.stream_count.store(count + 1, Ordering::SeqCst); From f6d1a74be294ca8765c0361e185b63fbfe616dc9 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 5 Nov 2018 11:28:59 +1100 Subject: [PATCH 31/75] asio sys lib comments and rust fmt --- asio-sys/.Cargo.toml.swp | Bin 12288 -> 0 bytes asio-sys/.build.rs.swp | Bin 16384 -> 0 bytes asio-sys/src/lib.rs | 810 ++++++++++++++++++++++++--------------- 3 files changed, 493 insertions(+), 317 deletions(-) delete mode 100644 asio-sys/.Cargo.toml.swp delete mode 100644 asio-sys/.build.rs.swp diff --git a/asio-sys/.Cargo.toml.swp b/asio-sys/.Cargo.toml.swp deleted file mode 100644 index e0dc7b55894817c47370ea9b7025b3dcffcb4316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&O>WdM7zW_4EZ86wL@$tC}g)?vfR&3$(bdX@tvS##JdNs-CAH~lu6HoG2&tD|_GAC{~h>pUiH}Ue>H6mSm zZO%+(TjOHwqW1E2`uOm0l=m+hu&R&lXUE<+pZ&RT_QV!lZJOY-dU0Bn6Q32WsxswE zoBD;%M#@dC47RTRu{^pV0D(&dHfhlB<+sFkvUT(L(vj$d00bZa0SG_<0uX=z1lCU= zv>Wu3w{?ZXul%&J3jd%%00Izz00bZa0SG_<0uX=z1R$^`0!kBo*(CbTiQoU@e*izO z6Mf@+<$UIRJO$uAL zw&9cIiPAcW#nij^U0KXhPM+GiYLWv#xHG)h+rRsWH_7vZbafe|(VbM?Louc^ eK4D+l)dTlrE~rtpiA(=JQqMbRRWVajGp1iRfsZBt diff --git a/asio-sys/.build.rs.swp b/asio-sys/.build.rs.swp deleted file mode 100644 index e6df5f97633c97f90baeee0f3ec6b39593363ff5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeHOU2Ggz6&}h@2|p&)&^WGdnZP zPwcJjqCSvB;)%i&tq`a$MZ8p1DitUaYAR7tl^4VV1PNYCbI+?e<6q|3cHYIJ+1>uf{~JFwh_6*>My z)QcJ$iQ~46Fgiea)m$-9F>n9_hqbB66UUF%#@LaMuN@#;HCGH&3{(tM3{(tM3{(tM z3{(tM4E(<_5C?Z?xA9PS01oh;6z|37N#K$6Z+ui&#X!YC#X!YC#X!YC#X!YC#X!YC z#X!YC#X!Zt|0n}iOVdo){%I-z;Qc?<0N%Ju(|!xw0A2#F15W{0fNucHz!Gp2`0HU! zdmgwBJOlW^6mSpl_m67YpMaad4d6xKr@-UD4lo5w0*8P2VMn!3490G049OA z@mqvH051Yh0Z#%C1D^!m{19{lPXgC~Q^03{w+?CAw}CFO3VZ^1`-7VH8{j42I`Bi_ z8Q@Xi9PoMIZs7Kvnsy8L9&jEw1l;_9racQh0c-;g1M9#AU>R5f?grj?zoz{N=mA^6 zS%3le0Jq-<8-U*d&jD9})4*Zibrf7)2EGq`3z!Bz3%rSW`8{wG_!-~sMP$Mrk z^qO5=TryX`vTB}Ln4Mo(VKb+g74=KMdm+a+@dFLB!>tw%*$i9tyIjXFIl`V1SaC-GA$HgN2saZvd#)pf1d?y$Dwa?`WAoNa^m!nHeH`jc9Oxmkxgv6KC^ z*VJ>q$4SKYz1Z;*UY2lqoOcEl(wY(%nQri6c`Y;Ua8KWfc34C-MR5p*U^Q8$t^r0b zU6Kq7bA|r0A=Gst40Tfo3^!JEEgM=GyzPffN;n={x7_SKF#Ff3_{0?U2jaU*;aVbZ-z+}+Y_$iUBW*b zGf`~Cjy*$TDgFEOh`1Hnotf?3+ITiz%ZUQl>S1L`^Y-aZ1GakfFa1VqYI1}T^De&2 zWe-8NWJBr!g$0U5OcX5a@B3@VcgM(WgP1CeqO;CMFutD(6Cw z*645-KUIrJS*=gh@oYC~!2}w=EZrF{jeUTfjQQE#Z3tpZtKZ1z9cQ(UAH|KpuGNPO z{0>xsuCDL>Cp3{!MLo9NY@Vjy)5*=8n~KdVerLzvmBj92n5ujhX)~J^En!_6M_J$;tO-K|ZiYmnCzDNN3BU&nNC?4YXe9>JyMRQ*XbYS5$&~HL4a0WsxBqK*_V0 zqnxJ1`#*;(CQ0S0a}xXe!%vllEe4dt+YGHhMqCj}*i0Ia#e#2zm*IS9ofn*ZQNM_V zH7S80x2+W$)%X2FAW01fdweVA9`>9!de|dPLuPT5@JHPb3V|d;zIy*KNK*TRLhJz( zb5D!CZgj0Jv&93XJtvAJ(;)e7Kq%NL^crVkNj2q0y1?x(En|tDPTHq-Fa;V7PnX<6 za^dBECSNG|mP}=o1Ib0y&MGJ=x?mNya!s(b?CVtM(u7I)NA;1`jV`ZD8NEm5VVbPS z5ftFh)RRq1xng`_MP}9@gZQ3kq#jAhVT2)(nuY_>zci~Q_zvw7JH z_1e2 z7^Ni<7hVE709S#>0ea5Ifa{Mf5* zi=2j3<(ZI(xh$)u2!+{sKTY1Khsp#=U{Biij#5E6K3f+LOXqN99o5a~?_+5`*-#C5 z8c}sL^0NXd$p~EYtPPh_cf@)w@&nbipF<>NYvK$kMq1%f;-U8L}5{}`Bih_C3c^@I!p)5evwshsw)Rb30 z8qZ_1J6?d|n(WOsk=TYg?);F$g6~6 zucP*z_0R1g5nm}A7yB0jMXcv1X}vHBm>fv|`XQXVIIIp38y|Ei>~q$lTRwF|HRmF!*8IFUNMkxv!|asJsU~@h zENB5;OCFBAstCwX$7uTr+@MaSFvrtUp%;OxVHkMxH;8~~#3>Hv#Ma7STmM9pA z@TOivW${Aa-Vl0~samMM;5<)*J24J$H3j+c}u zFBAp!f=m-d92C8C^TZ87U*4%}*`aTS30_-_^)Fc}T`1*!vzFag?Ix9R^Z^vhYD%f< PL81dT16KCIZ)E=klM_y3 diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index a9859b8..baaea0b 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -11,57 +11,85 @@ mod asio_import; #[macro_use] pub mod errors; -use errors::{AsioError, AsioDriverError, AsioErrorWrapper}; +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::{AtomicUsize, AtomicBool, Ordering}; +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{ + 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, @@ -71,18 +99,27 @@ enum AsioState { 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, } -// This is a direct copy of the ASIOSampleType -// inside ASIO SDK. +/// 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 { @@ -120,14 +157,20 @@ pub enum AsioSampleType { 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) -> (), @@ -141,7 +184,14 @@ struct AsioCallbacks { 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() { @@ -151,48 +201,61 @@ extern "C" fn buffer_switch(double_buffer_index: c_long, _direct_process: c_long } } +/// 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 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 { + 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) - }, + Err(_) => Err(AsioDriverError::DriverLoadError), } } else { Err(AsioDriverError::DriverLoadError) @@ -200,13 +263,8 @@ impl Drivers { } } - /// 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 + /// Returns the number of input and output + /// channels for the active drivers pub fn get_channels(&self) -> Channel { let channel: Channel; @@ -214,7 +272,11 @@ impl Drivers { let mut ins: c_long = 0; let mut outs: c_long = 0; unsafe { - get_drivers().asio_get_channels(&mut ins, &mut outs).expect("failed to get channels"); + 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, @@ -224,6 +286,7 @@ impl Drivers { channel } + /// Get current sample rate of the active drivers pub fn get_sample_rate(&self) -> SampleRate { let sample_rate: SampleRate; @@ -231,118 +294,173 @@ impl Drivers { let mut rate: c_double = 0.0f64; unsafe { - get_drivers().asio_get_sample_rate(&mut rate).expect("failed to get sample rate"); + 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 } - - pub fn set_sample_rate(&self, sample_rate: u32) -> Result<(), AsioError>{ + + /// 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) - } + 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); - unsafe { - get_drivers().asio_can_sample_rate(rate).is_ok() - } + // 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)) - }, + 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) }, - } + } } } - pub fn prepare_input_stream(&self, output: Option, num_channels: usize) -> Result { - let buffer_infos = (0..num_channels) - .map(|i| { - AsioBufferInfo { - is_input: 1, - channel_num: i as c_long, - buffers: [std::ptr::null_mut(); 2], - } - }) - .collect(); - - let streams = AsioStreams{input: Some(AsioStream{buffer_infos, buffer_size: 0}), output}; - self.create_streams(streams) - } + /// 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(); - /// Creates the output stream - 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 { - is_input: 0, - channel_num: i as c_long, - buffers: [std::ptr::null_mut(); 2], - } - }) - .collect(); - let streams = AsioStreams{output: Some(AsioStream{buffer_infos, buffer_size: 0}), input}; - self.create_streams(streams) - } - - /// Creates the output stream - fn create_streams(&self, streams: AsioStreams) -> Result { - let AsioStreams { - input, + // Create the streams + let streams = AsioStreams { + input: Some(AsioStream { + buffer_infos, + buffer_size: 0, + }), output, - } = streams; + }; + 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); - 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} - }) + // 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{ + AsioStreams { + input: Some(AsioStream { buffer_infos, buffer_size, }), @@ -350,12 +468,13 @@ impl Drivers { } }) }, + // 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{ + AsioStreams { + output: Some(AsioStream { buffer_infos, buffer_size, }), @@ -363,13 +482,19 @@ impl Drivers { } }) }, + // Impossible (None, None) => panic!("Trying to create streams without preparing"), } - } - fn create_buffers(&self, buffer_infos: Vec) - -> Result<(Vec, c_long), AsioDriverError>{ + /// 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, @@ -391,48 +516,46 @@ impl Drivers { // 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"); + 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 { - /* - let mut buffer_info_convert: Vec = buffer_infos - .into_iter() - .map(|bi| mem::transmute::(bi)) - .collect(); - */ - let mut buffer_info_convert = mem::transmute::, Vec>(buffer_infos); + // 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>(buffer_info_convert); - for d in &buffer_infos { - println!("after {:?}", d); - } - println!("channels: {:?}", num_channels); - - (buffer_infos, pref_b_size) - }).map_err(|e|{ - AsioDriverError::BufferError(format!( - "failed to create buffers, error code: {:?}", e)) - }) + 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(), - )) + 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); @@ -442,17 +565,21 @@ impl Drop for Drivers { } } +/// 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); } } -unsafe impl Send for AsioStream {} +/// Adds a callback to the list of active callbacks pub fn set_callback(callback: F) -> () where F: FnMut(i32) + Send, @@ -461,256 +588,305 @@ where bc.push(Some(BufferCallback(Box::new(callback)))); } -/// Returns a list of all the ASIO drivers +/// 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|{ + (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() + 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 { - -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 + /// 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 + } } -} -unsafe fn unload(&mut self) { - ai::remove_current_driver(); -} - -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) - .map_err(|e| { - self.state = AsioState::Offline; - e - }) - }else{ - Ok(()) + /// Unloads the current driver from ASIO + unsafe fn unload(&mut self) { + ai::remove_current_driver(); } -} -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) + /// 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(()) + } } -} -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) + /// 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) + } } -} -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) + /// 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) + } } -} -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) + /// 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) + } } -} -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) + /// 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) + } } -} -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) + /// 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) + } } -} - -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; - }, - _ => (), + + /// 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) + } } - let result = ai::ASIOCreateBuffers( - buffer_info_convert, - num_channels, - pref_b_size, - callbacks_convert, - ); - asio_result!(result) - .map(|_| self.state = AsioState::Prepared) -} -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) -} - -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) -} - -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) -} - -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) -} - -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 { + /// 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.asio_exit().expect("Failed to exit asio"); - self.unload(); - } - self.state = AsioState::Offline; - }, + 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; + }, + } } } -} \ No newline at end of file From 72c5e9a8c139099e53d994c93b8d91fa79b1d4fe Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 5 Nov 2018 11:29:28 +1100 Subject: [PATCH 32/75] asio sys lib comments and rust fmt --- asio-sys/src/lib.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index baaea0b..412d40e 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -43,7 +43,7 @@ lazy_static! { } /// Globally available state of the ASIO driver. -/// This allows all calls the the driver to ensure +/// 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! { @@ -53,14 +53,14 @@ lazy_static! { } /// Count of active device and streams. -/// Used to clean up the driver connection +/// 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 +/// Amount of input and output /// channels available. #[derive(Debug)] pub struct Channel { @@ -80,15 +80,15 @@ pub struct SampleRate { #[derive(Debug, Clone)] pub struct Drivers; -/// Tracks the current state of the +/// Tracks the current state of the /// ASIO drivers. #[derive(Debug)] struct AsioWrapper { state: AsioState, } -/// All possible states of the -/// ASIO driver. Mapped to the +/// All possible states of the +/// ASIO driver. Mapped to the /// FSM in the ASIO SDK docs. #[derive(Debug)] enum AsioState { @@ -119,7 +119,7 @@ pub struct AsioStream { /// All the possible types from ASIO. /// This is a direct copy of the ASIOSampleType -/// inside ASIO SDK. +/// inside ASIO SDK. #[derive(Debug, FromPrimitive)] #[repr(C)] pub enum AsioSampleType { @@ -190,7 +190,7 @@ struct AsioCallbacks { /// 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 + // This lock is probably unavoidable // but locks in the audio stream is not great let mut bcs = buffer_callback.lock().unwrap(); @@ -263,7 +263,7 @@ impl Drivers { } } - /// Returns the number of input and output + /// Returns the number of input and output /// channels for the active drivers pub fn get_channels(&self) -> Channel { let channel: Channel; @@ -359,7 +359,7 @@ impl Drivers { /// 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 + /// 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 @@ -393,7 +393,7 @@ impl Drivers { /// 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 + /// 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 @@ -489,7 +489,7 @@ impl Drivers { /// Ask ASIO to allocate the buffers /// and give the callback pointers. - /// This will destroy any already allocated + /// This will destroy any already allocated /// buffers. /// The prefered buffer size from ASIO is used. fn create_buffers( @@ -578,7 +578,6 @@ impl BufferCallback { } } - /// Adds a callback to the list of active callbacks pub fn set_callback(callback: F) -> () where @@ -683,8 +682,7 @@ impl AsioWrapper { 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) + asio_result!(result).map(|_| self.state = AsioState::Initialized) } else { Ok(()) } @@ -748,7 +746,7 @@ impl AsioWrapper { asio_result!(result) } } - + /// Gets the buffer sizes. /// Needs to be atleast Loaded. unsafe fn asio_get_buffer_size( From 5248455cb7ce7a44a782f842318fe698cdd08f0b Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 5 Nov 2018 12:41:15 +1100 Subject: [PATCH 33/75] commets and rustfmt for stream --- src/platform/windows/asio/stream.rs | 1248 ++++++++++++++++----------- 1 file changed, 745 insertions(+), 503 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 2b86482..db90da7 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -1,27 +1,36 @@ extern crate asio_sys as sys; extern crate num_traits; -use std; -use Format; -use CreationError; -use StreamData; +use self::num_traits::PrimInt; +use super::asio_utils as au; use super::Device; -use UnknownTypeInputBuffer; -use UnknownTypeOutputBuffer; -use std::sync::{Arc, Mutex}; +use std; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use CreationError; +use Format; use SampleFormat; -use super::asio_utils as au; -use self::num_traits::PrimInt; +use StreamData; +use UnknownTypeInputBuffer; +use UnknownTypeOutputBuffer; +/// Controls all streams pub struct EventLoop { + /// The input and output ASIO streams asio_streams: Arc>, + /// List of all CPAL streams cpal_streams: Arc>>>, + /// Total stream count stream_count: AtomicUsize, + /// The CPAL callback that the user gives to fill the buffers. + /// TODO This should probably not be in a Vec as there can only be one callbacks: Arc>>, } +/// Id for each stream. +/// Created depending on the number they are created. +/// Starting at one! not zero. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamId(usize); @@ -32,22 +41,30 @@ pub struct OutputBuffer<'a, T: 'a> { buffer: &'a mut [T], } -struct Stream{ +/// CPAL stream. +/// This decouples the many cpal streams +/// from the single input and single output +/// ASIO streams. +/// Each stream can be playing or paused. +struct Stream { playing: bool, } #[derive(Default)] -struct I16Buffer{ +struct I16Buffer { cpal: Vec, channel: Vec>, } +// TODO This never gets used as there is +// no usigned variants of ASIO buffers +// and can probably be removed. #[derive(Default)] -struct U16Buffer{ +struct U16Buffer { cpal: Vec, channel: Vec>, } #[derive(Default)] -struct F32Buffer{ +struct F32Buffer { cpal: Vec, channel: Vec>, } @@ -65,188 +82,221 @@ enum Endian { impl EventLoop { pub fn new() -> EventLoop { EventLoop { - asio_streams: Arc::new(Mutex::new(sys::AsioStreams{input: None, output: None})), + asio_streams: Arc::new(Mutex::new(sys::AsioStreams { + input: None, + output: None, + })), cpal_streams: Arc::new(Mutex::new(Vec::new())), + // This is why the Id's count from one not zero + // because at this point there is no streams stream_count: AtomicUsize::new(0), callbacks: Arc::new(Mutex::new(Vec::new())), } } - /// Create a new CPAL Input Stream - /// If there is no ASIO Input Stream - /// it will be created - fn get_input_stream(&self, drivers: &sys::Drivers, format: &Format) -> Result { + /// Create a new CPAL Input Stream. + /// If there is no ASIO Input Stream + /// it will be created. + fn get_input_stream( + &self, drivers: &sys::Drivers, format: &Format, + ) -> Result { let Format { channels, sample_rate, .. } = format; let num_channels = *channels as usize; + // Try and set the sample rate to what the user selected. + // If they try and use and unavailable rate then panic let sample_rate = sample_rate.0; - let ref mut streams = *self.asio_streams.lock().unwrap(); - if sample_rate != drivers.get_sample_rate().rate { - if drivers.can_sample_rate(sample_rate) { - drivers.set_sample_rate(sample_rate).expect("Unsupported sample rate"); - } else { - panic!("This sample rate {:?} is not supported", sample_rate); - } - } - match streams.input { - Some(ref input) => Ok(input.buffer_size as usize), - None => { - let output = streams.output.take(); - drivers.prepare_input_stream(output, num_channels) - .map(|new_streams| { - let bs = match new_streams.input { - Some(ref inp) => inp.buffer_size as usize, - None => unreachable!(), - }; - *streams = new_streams; - bs - }) - .map_err(|ref e| { - println!("Error preparing stream: {}", e); - CreationError::DeviceNotAvailable - }) - } - } - } - - fn get_output_stream(&self, drivers: &sys::Drivers, format: &Format) -> Result { - let Format { - channels, - sample_rate, - .. - } = format; - let num_channels = *channels as usize; - let sample_rate = sample_rate.0; - let ref mut streams = *self.asio_streams.lock().unwrap(); - if sample_rate != drivers.get_sample_rate().rate { - if drivers.can_sample_rate(sample_rate) { - drivers.set_sample_rate(sample_rate).expect("Unsupported sample rate"); - } else { - panic!("This sample rate {:?} is not supported", sample_rate); - } - } - match streams.output { - Some(ref output) => Ok(output.buffer_size as usize), - None => { - let input = streams.input.take(); - drivers.prepare_output_stream(input, num_channels) - .map(|new_streams| { - let bs = match new_streams.output { - Some(ref out) => out.buffer_size as usize, - None => unreachable!(), - }; - *streams = new_streams; - bs - }) - .map_err(|ref e| { - println!("Error preparing stream: {}", e); - CreationError::DeviceNotAvailable - }) - }, + let ref mut streams = *self.asio_streams.lock().unwrap(); + if sample_rate != drivers.get_sample_rate().rate { + if drivers.can_sample_rate(sample_rate) { + drivers + .set_sample_rate(sample_rate) + .expect("Unsupported sample rate"); + } else { + panic!("This sample rate {:?} is not supported", sample_rate); } + } + // Either create a stream if thers none or had back the + // size of the current one. + match streams.input { + Some(ref input) => Ok(input.buffer_size as usize), + None => { + let output = streams.output.take(); + drivers + .prepare_input_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.input { + Some(ref inp) => inp.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { + println!("Error preparing stream: {}", e); + CreationError::DeviceNotAvailable + }) + }, + } } + /// Create a new CPAL Output Stream. + /// If there is no ASIO Output Stream + /// it will be created. + fn get_output_stream( + &self, drivers: &sys::Drivers, format: &Format, + ) -> Result { + let Format { + channels, + sample_rate, + .. + } = format; + let num_channels = *channels as usize; + // Try and set the sample rate to what the user selected. + // If they try and use and unavailable rate then panic + // TODO factor this into a function as it happens for both input and output + let sample_rate = sample_rate.0; + let ref mut streams = *self.asio_streams.lock().unwrap(); + if sample_rate != drivers.get_sample_rate().rate { + if drivers.can_sample_rate(sample_rate) { + drivers + .set_sample_rate(sample_rate) + .expect("Unsupported sample rate"); + } else { + panic!("This sample rate {:?} is not supported", sample_rate); + } + } + // Either create a stream if thers none or had back the + // size of the current one. + match streams.output { + Some(ref output) => Ok(output.buffer_size as usize), + None => { + let input = streams.input.take(); + drivers + .prepare_output_stream(input, num_channels) + .map(|new_streams| { + let bs = match new_streams.output { + Some(ref out) => out.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { + println!("Error preparing stream: {}", e); + CreationError::DeviceNotAvailable + }) + }, + } + } + + /// Builds a new cpal input stream pub fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result { - let Device { - drivers, - .. - } = device; + &self, device: &Device, format: &Format, + ) -> Result { + let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_input_stream(&drivers, format).map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); - - // Create buffers - let channel_len = cpal_num_samples - / num_channels as usize; - - - let mut buffers = match format.data_type{ - SampleFormat::I16 => { - Buffers{ - i16_buff: I16Buffer{ - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, + self.get_input_stream(&drivers, format) + .map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); + + let channel_len = cpal_num_samples / num_channels as usize; + + // Create buffers depending on data type + // TODO the naming of cpal and channel is confusing. + // change it to: + // cpal -> interleaved + // channels -> per_channel + let mut buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, u16_buff: U16Buffer::default(), f32_buff: F32Buffer::default(), - } - } - SampleFormat::U16 => { - Buffers{ + }, + SampleFormat::U16 => Buffers { i16_buff: I16Buffer::default(), - u16_buff: U16Buffer{ - cpal: vec![0 as u16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, + u16_buff: U16Buffer { + cpal: vec![0 as u16; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, f32_buff: F32Buffer::default(), - } - } - SampleFormat::F32 => { - Buffers{ + }, + SampleFormat::F32 => Buffers { i16_buff: I16Buffer::default(), u16_buff: U16Buffer::default(), - f32_buff: F32Buffer{ - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - } - } - }; + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, + }, + }; - sys::set_callback(move |index| unsafe { - //if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1){ - if let Some(s) = s{ - if !s.playing { return (); } + // Set the input callback. + // This is most performance critical part of the ASIO bindings. + sys::set_callback(move |index| unsafe { + // if not playing return early + // TODO is this lock necessary + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } } - } - } - if let Some(ref asio_stream) = asio_streams.lock().unwrap().input { - // Number of samples needed total - let mut callbacks = callbacks.lock().unwrap(); + // Get the stream + // TODO is this lock necessary + if let Some(ref asio_stream) = asio_streams.lock().unwrap().input { + // Get the callback + // TODO is this lock necessary + let mut callbacks = callbacks.lock().unwrap(); - // Theres only a single callback because theres only one event loop - match callbacks.first_mut() { - Some(callback) => { - macro_rules! convert_sample { - ($AsioTypeIdent:ident, + // Theres only a single callback because theres only one event loop + // TODO is 64bit necessary. Might be using more memory then needed + match callbacks.first_mut() { + Some(callback) => { + // Macro to convert sample from ASIO to CPAL type + macro_rules! convert_sample { + // Unsigned types required different conversion + ($AsioTypeIdent:ident, u16, $SampleTypeIdent:ident, $Sample:expr ) => { - ((*$Sample as f64 + $AsioTypeIdent::MAX as f64) / - (::std::u16::MAX as f64 / - ::std::AsioTypeIdent::MAX as f64)) as u16 - }; - ($AsioTypeIdent:ident, + ((*$Sample as f64 + $AsioTypeIdent::MAX as f64) + / (::std::u16::MAX as f64 + / ::std::AsioTypeIdent::MAX as f64)) as u16 + }; + ($AsioTypeIdent:ident, $SampleType:ty, $SampleTypeIdent:ident, $Sample:expr ) => { - (*$Sample as i64 * - ::std::$SampleTypeIdent::MAX as i64 / - ::std::$AsioTypeIdent::MAX as i64) as $SampleType + (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 + / ::std::$AsioTypeIdent::MAX as i64) as $SampleType + }; }; - }; - macro_rules! try_callback { - ($SampleFormat:ident, + // This creates gets the buffer and interleaves it. + // It allows it to be done based on the sample type. + macro_rules! try_callback { + ($SampleFormat:ident, $SampleType:ty, $SampleTypeIdent:ident, $AsioType:ty, @@ -257,209 +307,289 @@ impl EventLoop { $Endianness:expr, $ConvertEndian:expr ) => { + // For each channel write the asio buffer to + // the cpal buffer - - // For each channel write the cpal data to - // the asio buffer - // Also need to check for Endian - - for (i, channel) in $Buffers.channel.iter_mut().enumerate(){ - let buff_ptr = asio_stream - .buffer_infos[i] - .buffers[index as usize] as *mut $AsioType; - let asio_buffer: &'static [$AsioType] = - std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize); - for asio_s in asio_buffer.iter(){ - channel.push( $ConvertEndian(convert_sample!( - $AsioTypeIdent, - $SampleType, - $SampleTypeIdent, - asio_s), $Endianness)); + for (i, channel) in $Buffers.channel.iter_mut().enumerate() + { + let buff_ptr = asio_stream.buffer_infos[i].buffers + [index as usize] + as *mut $AsioType; + let asio_buffer: &'static [$AsioType] = + std::slice::from_raw_parts( + buff_ptr, + asio_stream.buffer_size as usize, + ); + for asio_s in asio_buffer.iter() { + channel.push($ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $SampleType, + $SampleTypeIdent, + asio_s + ), + $Endianness, + )); + } } - } - - // interleave all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = $Buffers; - au::interleave(&channels, c_buffer); - for c in channels.iter_mut() { - c.clear(); + // interleave all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = $Buffers; + au::interleave(&channels, c_buffer); + // Clear the per channel buffers + for c in channels.iter_mut() { + c.clear(); + } } - } - - let buff = InputBuffer{ - buffer: &mut $Buffers.cpal, - }; - callback( - StreamId(count), - StreamData::Input{ - buffer: UnknownTypeInputBuffer::$SampleFormat( - ::InputBuffer{ - buffer: Some(super::super::InputBuffer::Asio(buff)) - }) - } + // Wrap the buffer in the CPAL type + let buff = InputBuffer { + buffer: &mut $Buffers.cpal, + }; + // Call the users callback with the buffer + callback( + StreamId(count), + StreamData::Input { + buffer: UnknownTypeInputBuffer::$SampleFormat( + ::InputBuffer { + buffer: Some( + super::super::InputBuffer::Asio(buff), + ), + }, + ), + }, ); + }; + }; + // Call the right buffer handler depending on types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); + }, + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); + }, + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + }, + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + }, + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + }, + _ => println!("unsupported format {:?}", stream_type), } - }; - // Generic over types - // TODO check for endianess - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!(I16, i16, i16, i32, i32, - buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Little, convert_endian_to); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16, - buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Little, convert_endian_to); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!(I16, i16, i16, i32, i32, - buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Big, convert_endian_to); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!(I16, i16, i16, i16, i16, - buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Big, convert_endian_to); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32, - buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Little, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64, - buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Little, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!(F32, f32, f32, f32, f32, - buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Big, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!(F32, f32, f32, f64, f64, - buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Big, |a, _| a); - } - _ => println!("unsupported format {:?}", stream_type), - } + }, + None => return (), } - None => return (), } - } - }); - self.cpal_streams.lock().unwrap().push(Some(Stream{ playing: false })); - StreamId(count) - }) + }); + // Create stream and set to paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { playing: false })); + StreamId(count) + }) } -pub fn build_output_stream( - &self, - device: &Device, - format: &Format, + /// Create the an output cpal stream. + pub fn build_output_stream( + &self, device: &Device, format: &Format, ) -> Result { - let Device { - drivers, - .. - } = device; - let num_channels = format.channels.clone(); - let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_output_stream(&drivers, format).map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); - // Create buffers - let channel_len = cpal_num_samples - / num_channels as usize; - - let mut re_buffers = match format.data_type{ - SampleFormat::I16 => { - Buffers{ - i16_buff: I16Buffer{ - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer::default(), - } - } - SampleFormat::U16 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer{ - cpal: vec![0 as u16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - f32_buff: F32Buffer::default(), - } - } - SampleFormat::F32 => { - Buffers{ - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer{ - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect()}, - } - } - }; + let Device { drivers, .. } = device; + let num_channels = format.channels.clone(); + let stream_type = drivers.get_data_type().expect("Couldn't load data type"); + self.get_output_stream(&drivers, format) + .map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count + 1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); + let channel_len = cpal_num_samples / num_channels as usize; - sys::set_callback(move |index| unsafe { - //if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1){ - if let Some(s) = s{ - if !s.playing { return (); } - } - } - } - if let Some(ref asio_stream) = asio_streams.lock().unwrap().output { - // Number of samples needed total - let mut callbacks = callbacks.lock().unwrap(); + // Create buffers depending on data type + let mut re_buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer::default(), + }, + SampleFormat::U16 => Buffers { + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer { + cpal: vec![0 as u16; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, + f32_buff: F32Buffer::default(), + }, + SampleFormat::F32 => Buffers { + i16_buff: I16Buffer::default(), + u16_buff: U16Buffer::default(), + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0 .. num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), + }, + }, + }; - macro_rules! convert_sample { - ($AsioTypeIdent:ident, + sys::set_callback(move |index| unsafe { + // if not playing return early + // TODO is this lock necessary + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } + } + // Get the output stream + // TODO is this lock necessary + if let Some(ref asio_stream) = asio_streams.lock().unwrap().output { + // Number of samples needed total + let mut callbacks = callbacks.lock().unwrap(); + + // Convert sample depending on the sample type + macro_rules! convert_sample { + ($AsioTypeIdent:ident, $AsioType:ty, u16, $Sample:expr ) => { - ((*$Sample as i64 * - ::std::$AsioTypeIdent::MAX as i64 / - ::std::u16::MAX as i64) - $AsioTypeIdent::MAX as i64) as $AsioType - }; - ($AsioTypeIdent:ident, + ((*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 + / ::std::u16::MAX as i64) + - $AsioTypeIdent::MAX as i64) as $AsioType + }; + ($AsioTypeIdent:ident, $AsioType:ty, $SampleTypeIdent:ident, $Sample:expr ) => { - (*$Sample as i64 * - ::std::$AsioTypeIdent::MAX as i64 / - ::std::$SampleTypeIdent::MAX as i64) as $AsioType - }; - }; + (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 + / ::std::$SampleTypeIdent::MAX as i64) as $AsioType + }; + }; - // Theres only a single callback because theres only one event loop - match callbacks.first_mut() { - Some(callback) => { - macro_rules! try_callback { - ($SampleFormat:ident, + // Theres only a single callback because theres only one event loop + match callbacks.first_mut() { + Some(callback) => { + macro_rules! try_callback { + ($SampleFormat:ident, $SampleType:ty, $SampleTypeIdent:ident, $AsioType:ty, @@ -470,181 +600,292 @@ pub fn build_output_stream( $Endianness:expr, $ConvertEndian:expr ) => { - let mut my_buffers = $Buffers; - { - let buff = OutputBuffer{ - buffer: &mut my_buffers.cpal + let mut my_buffers = $Buffers; + { + // Wrap the cpal buffer + let buff = OutputBuffer { + buffer: &mut my_buffers.cpal, + }; + // call the callback to fill the buffer with + // users data + callback( + StreamId(count), + StreamData::Output { + buffer: UnknownTypeOutputBuffer::$SampleFormat( + ::OutputBuffer { + target: Some( + super::super::OutputBuffer::Asio( + buff, + ), + ), + }, + ), + }, + ); + } + // Deinter all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = my_buffers; + au::deinterleave(&c_buffer[..], channels); + } + + // Silence the buffer that is about to be used + let silence = match index { + 0 => { + if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { + sys::SILENCE_FIRST + .store(true, Ordering::SeqCst); + sys::SILENCE_SECOND + .store(false, Ordering::SeqCst); + true + } else { + false + } + }, + 1 => { + if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { + sys::SILENCE_SECOND + .store(true, Ordering::SeqCst); + sys::SILENCE_FIRST + .store(false, Ordering::SeqCst); + true + } else { + false + } + }, + _ => unreachable!(), + }; + + // For each channel write the cpal data to + // the asio buffer + for (i, channel) in my_buffers.channel.iter().enumerate() { + let buff_ptr = asio_stream.buffer_infos[i].buffers + [index as usize] + as *mut $AsioType; + 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) + { + if silence { + *asio_s = 0.0 as $AsioType; + } + *asio_s += $ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $AsioType, + $SampleTypeIdent, + cpal_s + ), + $Endianness, + ); + } + } }; - callback( - StreamId(count), - StreamData::Output{ - buffer: UnknownTypeOutputBuffer::$SampleFormat( - ::OutputBuffer{ - target: Some(super::super::OutputBuffer::Asio(buff)) - }) - } + } + // Choose the buffer conversions based on the sample types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from ); - } - // Deinter all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = my_buffers; - au::deinterleave(&c_buffer[..], channels); - } - - let silence = match index { - 0 =>{ - if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { - sys::SILENCE_FIRST.store(true, Ordering::SeqCst); - sys::SILENCE_SECOND.store(false, Ordering::SeqCst); - true - }else{false} }, - 1 =>{ - if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { - sys::SILENCE_SECOND.store(true, Ordering::SeqCst); - sys::SILENCE_FIRST.store(false, Ordering::SeqCst); - true - }else{false} + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from + ); }, - _ => unreachable!(), - }; - - - // For each channel write the cpal data to - // the asio buffer - for (i, channel) in my_buffers.channel.iter().enumerate(){ - let buff_ptr = asio_stream - .buffer_infos[i] - .buffers[index as usize] as *mut $AsioType; - 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){ - if silence { *asio_s = 0.0 as $AsioType; } - *asio_s += $ConvertEndian(convert_sample!( - $AsioTypeIdent, - $AsioType, - $SampleTypeIdent, - cpal_s - ), - $Endianness); - } - + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + }, + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + }, + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + }, + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + }, + _ => println!("unsupported format {:?}", stream_type), } - }; - } - // Generic over types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!(I16, i16, i16, i32, i32, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Little, convert_endian_from); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!(I16, i16, i16, i16, i16, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Little, convert_endian_from); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!(I16, i16, i16, i32, i32, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Big, convert_endian_from); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!(I16, i16, i16, i16, i16, - &mut re_buffers.i16_buff, I16Buffer, I16Buffer, - Endian::Big, convert_endian_from); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!(F32, f32, f32, f32, f32, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Little, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!(F32, f32, f32, f64, f64, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Little, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!(F32, f32, f32, f32, f32, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Big, |a, _| a); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!(F32, f32, f32, f64, f64, - &mut re_buffers.f32_buff, F32Buffer, F32Buffer, - Endian::Big, |a, _| a); - } - _ => println!("unsupported format {:?}", stream_type), + }, + None => return (), } } - None => return (), - } - } - }); - self.cpal_streams.lock().unwrap().push(Some(Stream{ playing: false })); - StreamId(count) - }) -} - - -pub fn play_stream(&self, stream_id: StreamId) { - let mut streams = self.cpal_streams.lock().unwrap(); - if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { - s.playing = true; + }); + // Create the stream paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { playing: false })); + // Give the ID based on the stream count + StreamId(count) + }) } - // Calling play when already playing is a no-op - sys::play(); -} -pub fn pause_stream(&self, stream_id: StreamId) { - let mut streams = self.cpal_streams.lock().unwrap(); - if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { - s.playing = false; + /// Play the cpal stream for the given ID. + /// Also play The ASIO streams if they are not already. + pub fn play_stream(&self, stream_id: StreamId) { + let mut streams = self.cpal_streams.lock().unwrap(); + if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { + s.playing = true; + } + // Calling play when already playing is a no-op + sys::play(); } - let any_playing = streams - .iter() - .filter(|s| if let Some(s) = s { - s.playing - } else {false} ) - .next(); - if let None = any_playing { - sys::stop(); + + /// Pause the cpal stream for the given ID. + /// Pause the ASIO streams if there are no CPAL streams palying. + pub fn pause_stream(&self, stream_id: StreamId) { + let mut streams = self.cpal_streams.lock().unwrap(); + if let Some(s) = streams + .get_mut(stream_id.0) + .expect("Bad pause stream index") + { + s.playing = false; + } + let any_playing = streams + .iter() + .filter(|s| if let Some(s) = s { s.playing } else { false }) + .next(); + if let None = any_playing { + sys::stop(); + } + } + + /// Destroy the cpal stream based on the ID. + /// If no cpal streams exist then destory the + /// ASIO streams and clean up + pub fn destroy_stream(&self, stream_id: StreamId) { + let mut streams = self.cpal_streams.lock().unwrap(); + streams.get_mut(stream_id.0).take(); + let count = self.stream_count.load(Ordering::SeqCst); + self.stream_count.store(count - 1, Ordering::SeqCst); + if count == 1 { + *self.asio_streams.lock().unwrap() = sys::AsioStreams { + output: None, + input: None, + }; + sys::clean_up(); + } + } + + /// Run the cpal callbacks + 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 + } } } -pub fn destroy_stream(&self, stream_id: StreamId) { - let mut streams = self.cpal_streams.lock().unwrap(); - streams.get_mut(stream_id.0).take(); - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count - 1, Ordering::SeqCst); - if count == 1 { - *self.asio_streams.lock().unwrap() = sys::AsioStreams{ output: None, input: None }; - sys::clean_up(); - } -} - -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 - } -} -} - +/// Clean up if event loop is dropped. +/// Currently event loop is never dropped. impl Drop for EventLoop { fn drop(&mut self) { sys::clean_up(); @@ -655,8 +896,7 @@ impl<'a, T> InputBuffer<'a, T> { pub fn buffer(&self) -> &[T] { &self.buffer } - pub fn finish(self) { - } + pub fn finish(self) {} } impl<'a, T> OutputBuffer<'a, T> { @@ -671,6 +911,7 @@ impl<'a, T> OutputBuffer<'a, T> { pub fn finish(self) {} } +/// Helper function to convert to system endianness fn convert_endian_to(sample: T, endian: Endian) -> T { match endian { Endian::Big => sample.to_be(), @@ -678,9 +919,10 @@ fn convert_endian_to(sample: T, endian: Endian) -> T { } } +/// Helper function to convert from system endianness fn convert_endian_from(sample: T, endian: Endian) -> T { match endian { Endian::Big => T::from_be(sample), Endian::Little => T::from_le(sample), } -} \ No newline at end of file +} From 1f24c76e6aec05732ded960d4be0b4477f89b9af Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Mon, 5 Nov 2018 12:51:55 +1100 Subject: [PATCH 34/75] commenting and rustfmt for device --- src/platform/windows/asio/device.rs | 154 +++++++++++++++------------- 1 file changed, 81 insertions(+), 73 deletions(-) diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index d1e46ea..968081c 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -3,21 +3,25 @@ use std; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; +use std::hash::{Hash, Hasher}; +use DefaultFormatError; use Format; use FormatsEnumerationError; -use DefaultFormatError; -use SupportedFormat; use SampleFormat; use SampleRate; -use std::hash::{Hash, Hasher}; +use SupportedFormat; +/// A ASIO Device #[derive(Debug, Clone)] -pub struct Device{ +pub struct Device { + /// The drivers for this device pub drivers: sys::Drivers, + /// The name of this device pub name: String, } -pub struct Devices{ +/// All available devices +pub struct Devices { drivers: std::vec::IntoIter, } @@ -25,7 +29,7 @@ impl PartialEq for Device { fn eq(&self, other: &Self) -> bool { self.name == other.name } -} +} impl Eq for Device {} @@ -39,41 +43,47 @@ impl Device { pub fn name(&self) -> String { self.name.clone() } - - // Just supporting default for now - pub fn supported_input_formats(&self) -> Result { - match self.default_input_format() { - Ok(f) => { + + /// Gets the supported input formats. + /// TODO currently only supports the default. + /// Need to find all possible formats. + pub fn supported_input_formats( + &self, + ) -> Result { + match self.default_input_format() { + Ok(f) => { + // Can this device support both 44100 and 48000 let supported_formats: Vec = [44100, 48000] .into_iter() - .filter(|rate| self.drivers.can_sample_rate(**rate as u32) ) + .filter(|rate| self.drivers.can_sample_rate(**rate as u32)) .map(|rate| { let mut format = f.clone(); format.sample_rate = SampleRate(*rate); SupportedFormat::from(format) - }) - .collect(); - //Ok(vec![SupportedFormat::from(f)].into_iter()) + }).collect(); Ok(supported_formats.into_iter()) - }, - Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), - } + }, + Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), + } } - pub fn supported_output_formats(&self) -> Result { + /// Gets the supported output formats. + /// TODO currently only supports the default. + /// Need to find all possible formats. + pub fn supported_output_formats( + &self, + ) -> Result { match self.default_output_format() { Ok(f) => { + // Can this device support both 44100 and 48000 let supported_formats: Vec = [44100, 48000] .into_iter() - .filter(|rate| self.drivers.can_sample_rate(**rate as u32) ) + .filter(|rate| self.drivers.can_sample_rate(**rate as u32)) .map(|rate| { let mut format = f.clone(); format.sample_rate = SampleRate(*rate); SupportedFormat::from(format) - }) - .collect(); + }).collect(); //Ok(vec![SupportedFormat::from(f)].into_iter()) Ok(supported_formats.into_iter()) }, @@ -81,39 +91,42 @@ impl Device { } } + /// Returns the default input format pub fn default_input_format(&self) -> Result { let channels = self.drivers.get_channels().ins as u16; let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); + // Map th ASIO sample type to a CPAL sample type match self.drivers.get_data_type() { - Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTInt32MSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), - Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), - _ => Err(DefaultFormatError::StreamTypeNotSupported), - }.map(|dt| { - Format{channels, - sample_rate, - data_type: dt } + Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTInt32MSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), + Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), + _ => Err(DefaultFormatError::StreamTypeNotSupported), + }.map(|dt| Format { + channels, + sample_rate, + data_type: dt, }) } + /// Returns the default output format pub fn default_output_format(&self) -> Result { let channels = self.drivers.get_channels().outs as u16; let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); match self.drivers.get_data_type() { + // Map th ASIO sample type to a CPAL sample type Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), - // TODO This should not be set to 16bit but is for testing - Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), + Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), + Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), _ => Err(DefaultFormatError::StreamTypeNotSupported), - }.map(|dt|{ - Format{channels, - sample_rate, - data_type: dt } + }.map(|dt| Format { + channels, + sample_rate, + data_type: dt, }) } } @@ -125,25 +138,24 @@ impl Default for Devices { .into_iter() .filter(|name| sys::Drivers::load(&name).is_ok()) .collect(); - Devices{ drivers: driver_names.into_iter() } - + Devices { + drivers: driver_names.into_iter(), + } } } impl Iterator for Devices { type Item = Device; + /// Load drivers and return device fn next(&mut self) -> Option { match self.drivers.next() { - Some(name) => { - sys::Drivers::load(&name) - .or_else(|e| { - eprintln!("{}", e); - Err(e) - }) - .ok() - .map(|drivers| Device{drivers, name} ) - }, + Some(name) => sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }).ok() + .map(|drivers| Device { drivers, name }), None => None, } } @@ -153,36 +165,32 @@ impl Iterator for Devices { } } -// Asio doesn't have a concept of default -// so returning first in list as default +/// 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(name) => { - sys::Drivers::load(&name) - .or_else(|e| { - eprintln!("{}", e); - Err(e) - }) - .ok() - .map(|drivers| Device{drivers, name} ) - }, + Some(name) => sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }).ok() + .map(|drivers| Device { drivers, name }), None => None, } } +/// 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} ) - }, + Some(name) => sys::Drivers::load(&name) + .or_else(|e| { + eprintln!("{}", e); + Err(e) + }).ok() + .map(|drivers| Device { drivers, name }), None => None, } } From 616974353b5d17446d185cd7fd8acb8161b0ee83 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 6 Nov 2018 12:15:17 +1100 Subject: [PATCH 35/75] 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 From 523d25ad49ecfe71fc75230a6454c9d7b7cebf5d Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Tue, 6 Nov 2018 12:33:07 +1100 Subject: [PATCH 36/75] remove u16 --- src/platform/windows/asio/stream.rs | 30 +++++------------------------ 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index db90da7..16686db 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -58,11 +58,13 @@ struct I16Buffer { // TODO This never gets used as there is // no usigned variants of ASIO buffers // and can probably be removed. +/* #[derive(Default)] struct U16Buffer { cpal: Vec, channel: Vec>, } +*/ #[derive(Default)] struct F32Buffer { cpal: Vec, @@ -70,7 +72,7 @@ struct F32Buffer { } struct Buffers { i16_buff: I16Buffer, - u16_buff: U16Buffer, + //u16_buff: U16Buffer, f32_buff: F32Buffer, } @@ -222,22 +224,10 @@ impl EventLoop { .map(|_| Vec::with_capacity(channel_len)) .collect(), }, - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer::default(), - }, - SampleFormat::U16 => Buffers { - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer { - cpal: vec![0 as u16; cpal_num_samples], - channel: (0 .. num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, f32_buff: F32Buffer::default(), }, SampleFormat::F32 => Buffers { i16_buff: I16Buffer::default(), - u16_buff: U16Buffer::default(), f32_buff: F32Buffer { cpal: vec![0 as f32; cpal_num_samples], channel: (0 .. num_channels) @@ -245,6 +235,7 @@ impl EventLoop { .collect(), }, }, + _ => unimplemented!(), }; // Set the input callback. @@ -521,22 +512,10 @@ impl EventLoop { .map(|_| Vec::with_capacity(channel_len)) .collect(), }, - u16_buff: U16Buffer::default(), - f32_buff: F32Buffer::default(), - }, - SampleFormat::U16 => Buffers { - i16_buff: I16Buffer::default(), - u16_buff: U16Buffer { - cpal: vec![0 as u16; cpal_num_samples], - channel: (0 .. num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, f32_buff: F32Buffer::default(), }, SampleFormat::F32 => Buffers { i16_buff: I16Buffer::default(), - u16_buff: U16Buffer::default(), f32_buff: F32Buffer { cpal: vec![0 as f32; cpal_num_samples], channel: (0 .. num_channels) @@ -544,6 +523,7 @@ impl EventLoop { .collect(), }, }, + _ => unimplemented!(), }; sys::set_callback(move |index| unsafe { From 25070464f9b9247784c070785e476eb389272745 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 12:57:22 +1100 Subject: [PATCH 37/75] git ignore, temp files, fetch_add, os cfg --- .gitignore | 9 +-------- asio-sys/.gitignore | 4 ++++ asio-sys/examples/.test.rs.swp | Bin 12288 -> 0 bytes src/os/mod.rs | 1 + src/platform/windows/asio/stream.rs | 20 ++++---------------- 5 files changed, 10 insertions(+), 24 deletions(-) create mode 100644 asio-sys/.gitignore delete mode 100644 asio-sys/examples/.test.rs.swp diff --git a/.gitignore b/.gitignore index 555191e..6d067b3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,4 @@ /Cargo.lock .cargo/ .DS_Store -recorded.wav -asio-sys/target -asio-sys/Cargo.lock -asio-sys/.cargo/ -asio-sys/.DS_Store -*~ -*.swap -*.swo +recorded.wav \ No newline at end of file diff --git a/asio-sys/.gitignore b/asio-sys/.gitignore new file mode 100644 index 0000000..3375044 --- /dev/null +++ b/asio-sys/.gitignore @@ -0,0 +1,4 @@ +/target +/Cargo.lock +.cargo/ +.DS_Store \ No newline at end of file diff --git a/asio-sys/examples/.test.rs.swp b/asio-sys/examples/.test.rs.swp deleted file mode 100644 index d041a32118a297802fd2a215b14b539b97514601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2&u-H|5XQG~L<;|2U>c;#mfF~qOXaAoM2b`nh)Spwfgp07Nn>F<*6tFus&ef^ z@Br`{yaW#b55W~?9S8doA+FIk(r53^&dl!oj`p61ub;o5yrI1oOLCH, channel: Vec>, } -// TODO This never gets used as there is -// no usigned variants of ASIO buffers -// and can probably be removed. -/* -#[derive(Default)] -struct U16Buffer { - cpal: Vec, - channel: Vec>, -} -*/ + #[derive(Default)] struct F32Buffer { cpal: Vec, @@ -203,8 +194,7 @@ impl EventLoop { self.get_input_stream(&drivers, format) .map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); let cpal_streams = self.cpal_streams.clone(); let callbacks = self.callbacks.clone(); @@ -496,8 +486,7 @@ impl EventLoop { self.get_output_stream(&drivers, format) .map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count + 1, Ordering::SeqCst); + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); let cpal_streams = self.cpal_streams.clone(); let callbacks = self.callbacks.clone(); @@ -836,8 +825,7 @@ impl EventLoop { pub fn destroy_stream(&self, stream_id: StreamId) { let mut streams = self.cpal_streams.lock().unwrap(); streams.get_mut(stream_id.0).take(); - let count = self.stream_count.load(Ordering::SeqCst); - self.stream_count.store(count - 1, Ordering::SeqCst); + let count = self.stream_count.fetch_sub(1, Ordering::SeqCst); if count == 1 { *self.asio_streams.lock().unwrap() = sys::AsioStreams { output: None, From 062205160c3bf80316091c44eea6e1c050e3505e Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 14:12:59 +1100 Subject: [PATCH 38/75] remove build.bat --- asio-sys/build.bat | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 asio-sys/build.bat diff --git a/asio-sys/build.bat b/asio-sys/build.bat deleted file mode 100644 index d55f1d1..0000000 --- a/asio-sys/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -@CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" %* -@start powershell From 7950045240775a459288995f032165b246fab43d Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 14:59:38 +1100 Subject: [PATCH 39/75] minor fixes --- asio-sys/src/bindings/errors.rs | 20 ++++++++++---------- asio-sys/src/bindings/mod.rs | 16 +++------------- src/platform/windows/asio/stream.rs | 10 ++++++---- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/asio-sys/src/bindings/errors.rs b/asio-sys/src/bindings/errors.rs index 0c92d02..43f41bb 100644 --- a/asio-sys/src/bindings/errors.rs +++ b/asio-sys/src/bindings/errors.rs @@ -11,14 +11,14 @@ pub enum AsioDriverError { #[derive(Debug)] pub enum AsioError { - NoDrivers, - HardwareMalfunction, - InvalidInput, - BadMode, - HardwareStuck, - NoRate, - ASE_NoMemory, - UnknownError, + NoDrivers, + HardwareMalfunction, + InvalidInput, + BadMode, + HardwareStuck, + NoRate, + ASE_NoMemory, + UnknownError, } #[derive(Debug)] @@ -62,7 +62,7 @@ impl fmt::Display for AsioError { match *self { AsioError::NoDrivers => { write!(f, "hardware input or output is not present or available") - }, + } AsioError::HardwareMalfunction => write!( f, "hardware is malfunctioning (can be returned by any ASIO function)" @@ -89,7 +89,7 @@ impl Error for AsioError { AsioError::NoDrivers => "hardware input or output is not present or available", AsioError::HardwareMalfunction => { "hardware is malfunctioning (can be returned by any ASIO function)" - }, + } AsioError::InvalidInput => "input parameter invalid", AsioError::BadMode => "hardware is in a bad mode or used in a bad mode", AsioError::HardwareStuck => "hardware is not running when sample position is inquired", diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index fada89c..e4e0376 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -15,14 +15,6 @@ 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); @@ -231,16 +223,14 @@ impl Drivers { 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 my_driver_name = CString::new(driver_name).expect("Can't go from str to CString"); 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); + //let load_result = drivers.load(raw); + let load_result = drivers.load(my_driver_name.as_ptr() as *mut i8); if load_result { // Initialize ASIO match drivers.asio_init(&mut driver_info) { diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index b439bf5..8420d52 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -14,6 +14,8 @@ use SampleFormat; use StreamData; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; +use std::thread; +use std::time::Duration; /// Controls all streams pub struct EventLoop { @@ -812,9 +814,8 @@ impl EventLoop { } let any_playing = streams .iter() - .filter(|s| if let Some(s) = s { s.playing } else { false }) - .next(); - if let None = any_playing { + .any(|s| if let Some(s) = s { s.playing } else { false }); + if any_playing { sys::stop(); } } @@ -846,8 +847,9 @@ impl EventLoop { .unwrap() .push(unsafe { mem::transmute(callback) }); loop { - // Might need a sleep here to prevent the loop being + // A sleep here to prevent the loop being // removed in --release + thread::sleep(Duration::new(1u64, 0u32)); } } } From ab3b76ad75d1b679f67ff1bf1a2d41f787e6af32 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 18:38:33 +1100 Subject: [PATCH 40/75] fixes, remove transmute --- asio-sys/src/bindings/mod.rs | 33 ++-- src/os/mod.rs | 5 + src/os/windows/mod.rs | 9 +- src/platform/windows/asio/asio_utils/mod.rs | 8 + src/platform/windows/asio/asio_utils/tests.rs | 10 +- src/platform/windows/asio/stream.rs | 165 ++++++++++-------- 6 files changed, 131 insertions(+), 99 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index e4e0376..596b1ca 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -5,7 +5,6 @@ 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}; @@ -478,15 +477,17 @@ impl Drivers { /// buffers. /// The prefered buffer size from ASIO is used. fn create_buffers( - &self, buffer_infos: Vec, + &self, mut buffer_infos: Vec, ) -> Result<(Vec, c_long), AsioDriverError> { let num_channels = buffer_infos.len(); - let callbacks = AsioCallbacks { + 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, }; + // To pass as ai::ASIOCallbacks + let callbacks: *mut _ = &mut callbacks; let mut min_b_size: c_long = 0; let mut max_b_size: c_long = 0; @@ -509,22 +510,14 @@ impl Drivers { &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(), + //buffer_info_convert.as_mut_ptr(), + buffer_infos.as_mut_ptr() as *mut _, num_channels as i32, pref_b_size, - &mut callbacks_convert, + callbacks as *mut _, ).map(|_| { - let buffer_infos = mem::transmute::< - Vec, - Vec, - >(buffer_info_convert); (buffer_infos, pref_b_size) }).map_err(|e| { AsioDriverError::BufferError(format!( @@ -616,18 +609,14 @@ pub fn 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); + get_drivers().asio_start().expect("Failed to start asio playing"); } } /// 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); + get_drivers().asio_stop().expect("Failed to stop asio"); } } @@ -643,7 +632,7 @@ impl AsioWrapper { /// Load the driver. /// Unloads the previous driver. /// Sets state to Loaded on success. - unsafe fn load(&mut self, raw: *mut i8) -> bool { + unsafe fn load(&mut self, raw: *mut c_char) -> bool { use self::AsioState::*; self.clean_up(); if ai::load_asio_driver(raw) { @@ -752,7 +741,7 @@ impl AsioWrapper { /// 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, + pref_b_size: c_long, callbacks_convert: *mut ai::ASIOCallbacks, ) -> Result<(), AsioError> { use self::AsioState::*; match self.state { diff --git a/src/os/mod.rs b/src/os/mod.rs index 581af77..3716caa 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -1,2 +1,7 @@ +/// The cpal::os module provides operating-system-specific +/// functionality. If you are using this module within a +/// cross-platform project, you may wish to use +/// cfg(target_os = "") to ensure that you only +/// use the OS-specific items when compiling for that OS. #[cfg(target_os = "windows")] pub mod windows; diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 8c40f02..e148c81 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -1,3 +1,7 @@ +/// This allows you to choose either Wasapi or ASIO +/// as your back end. Wasapi is the default. +/// The CPAL_ASIO_DIR must be set to the ASIO SDK +/// directory for use_asio_backend to be available. use std::sync::Mutex; #[derive(Clone)] @@ -6,22 +10,23 @@ pub enum BackEnd { Asio, } -//static BACKEND: BackEnd = BackEnd::Asio; - lazy_static! { static ref BACK_END: Mutex = Mutex::new(BackEnd::Wasapi); } +/// See which beackend is currently set. pub fn which_backend() -> BackEnd { (*BACK_END.lock().unwrap()).clone() } #[cfg(asio)] +/// Choose ASIO as the backend pub fn use_asio_backend() -> Result<(), BackEndError> { *BACK_END.lock().unwrap() = BackEnd::Asio; Ok(()) } +/// Choose Wasapi as the backend pub fn use_wasapi_backend() -> Result<(), BackEndError> { *BACK_END.lock().unwrap() = BackEnd::Wasapi; Ok(()) diff --git a/src/platform/windows/asio/asio_utils/mod.rs b/src/platform/windows/asio/asio_utils/mod.rs index c9b221f..017454c 100644 --- a/src/platform/windows/asio/asio_utils/mod.rs +++ b/src/platform/windows/asio/asio_utils/mod.rs @@ -15,6 +15,10 @@ pub fn interleave(channels: &[Vec], target: &mut Vec) where T: Copy, { + assert!( + target.len() % channels.len() == 0, + "the length of the interleaved buffer must be a multiple of the expected number of channels" + ); assert!(!channels.is_empty()); target.clear(); let frames = channels[0].len(); @@ -32,6 +36,10 @@ pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) where T: Copy, { + assert!( + cpal_buffer.len() % asio_channels.len() == 0, + "the length of the interleaved buffer must be a multiple of the expected number of channels" + ); for ch in asio_channels.iter_mut() { ch.clear(); } diff --git a/src/platform/windows/asio/asio_utils/tests.rs b/src/platform/windows/asio/asio_utils/tests.rs index c0d9ffd..c43fe5f 100644 --- a/src/platform/windows/asio/asio_utils/tests.rs +++ b/src/platform/windows/asio/asio_utils/tests.rs @@ -1,4 +1,4 @@ -use super::{deinterleave, deinterleave_index, interleave}; +use super::{deinterleave, interleave}; #[test] fn interleave_two() { @@ -6,7 +6,7 @@ fn interleave_two() { let goal = vec![1, 2, 1, 2, 1, 2, 1, 2]; let mut result = vec![0; 8]; - interleave(&a[..], &mut result[..]); + interleave(&a[..], &mut result); assert_eq!(goal, result); } @@ -17,7 +17,7 @@ fn interleave_three() { let goal = vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]; let mut result = vec![0; 12]; - interleave(&a[..], &mut result[..]); + interleave(&a[..], &mut result); assert_eq!(goal, result); } @@ -28,7 +28,7 @@ fn interleave_none() { let goal = Vec::::new(); let mut result = Vec::::new(); - interleave(&a[..], &mut result[..]); + interleave(&a[..], &mut result); assert_eq!(goal, result); } @@ -39,7 +39,7 @@ fn interleave_two_diff() { let goal = vec![1, 5, 2, 6, 3, 7, 4, 8]; let mut result = vec![0; 8]; - interleave(&a[..], &mut result[..]); + interleave(&a[..], &mut result); assert_eq!(goal, result); } diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 8420d52..53443d0 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -8,14 +8,14 @@ use std; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; use CreationError; use Format; use SampleFormat; use StreamData; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; -use std::thread; -use std::time::Duration; /// Controls all streams pub struct EventLoop { @@ -26,8 +26,7 @@ pub struct EventLoop { /// Total stream count stream_count: AtomicUsize, /// The CPAL callback that the user gives to fill the buffers. - /// TODO This should probably not be in a Vec as there can only be one - callbacks: Arc>>, + callbacks: Arc>>, } /// Id for each stream. @@ -85,7 +84,7 @@ impl EventLoop { // This is why the Id's count from one not zero // because at this point there is no streams stream_count: AtomicUsize::new(0), - callbacks: Arc::new(Mutex::new(Vec::new())), + callbacks: Arc::new(Mutex::new(None)), } } @@ -93,7 +92,9 @@ impl EventLoop { /// If there is no ASIO Input Stream /// it will be created. fn get_input_stream( - &self, drivers: &sys::Drivers, format: &Format, + &self, + drivers: &sys::Drivers, + format: &Format, ) -> Result { let Format { channels, @@ -111,7 +112,7 @@ impl EventLoop { .set_sample_rate(sample_rate) .expect("Unsupported sample rate"); } else { - panic!("This sample rate {:?} is not supported", sample_rate); + return Err(CreationError::FormatNotSupported); } } // Either create a stream if thers none or had back the @@ -133,7 +134,7 @@ impl EventLoop { println!("Error preparing stream: {}", e); CreationError::DeviceNotAvailable }) - }, + } } } @@ -141,7 +142,9 @@ impl EventLoop { /// If there is no ASIO Output Stream /// it will be created. fn get_output_stream( - &self, drivers: &sys::Drivers, format: &Format, + &self, + drivers: &sys::Drivers, + format: &Format, ) -> Result { let Format { channels, @@ -182,13 +185,15 @@ impl EventLoop { println!("Error preparing stream: {}", e); CreationError::DeviceNotAvailable }) - }, + } } } /// Builds a new cpal input stream pub fn build_input_stream( - &self, device: &Device, format: &Format, + &self, + device: &Device, + format: &Format, ) -> Result { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); @@ -212,7 +217,7 @@ impl EventLoop { SampleFormat::I16 => Buffers { i16_buff: I16Buffer { cpal: vec![0 as i16; cpal_num_samples], - channel: (0 .. num_channels) + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) .collect(), }, @@ -222,7 +227,7 @@ impl EventLoop { i16_buff: I16Buffer::default(), f32_buff: F32Buffer { cpal: vec![0 as f32; cpal_num_samples], - channel: (0 .. num_channels) + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) .collect(), }, @@ -253,27 +258,35 @@ impl EventLoop { // Theres only a single callback because theres only one event loop // TODO is 64bit necessary. Might be using more memory then needed - match callbacks.first_mut() { + match callbacks.as_mut() { Some(callback) => { // Macro to convert sample from ASIO to CPAL type macro_rules! convert_sample { - // Unsigned types required different conversion + // floats types required different conversion ($AsioTypeIdent:ident, - u16, + f32, $SampleTypeIdent:ident, $Sample:expr - ) => { - ((*$Sample as f64 + $AsioTypeIdent::MAX as f64) - / (::std::u16::MAX as f64 - / ::std::AsioTypeIdent::MAX as f64)) as u16 + ) => { + (*$Sample as f64 + / ::std::$SampleTypeIdent::MAX as f64) as f32 + }; + ($AsioTypeIdent:ident, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 + / ::std::$SampleTypeIdent::MAX as f64 }; ($AsioTypeIdent:ident, $SampleType:ty, $SampleTypeIdent:ident, $Sample:expr - ) => { + ) => { (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - / ::std::$AsioTypeIdent::MAX as i64) as $SampleType + / ::std::$AsioTypeIdent::MAX as i64) + as $SampleType }; }; // This creates gets the buffer and interleaves it. @@ -293,7 +306,8 @@ impl EventLoop { // For each channel write the asio buffer to // the cpal buffer - for (i, channel) in $Buffers.channel.iter_mut().enumerate() + for (i, channel) in + $Buffers.channel.iter_mut().enumerate() { let buff_ptr = asio_stream.buffer_infos[i].buffers [index as usize] @@ -340,7 +354,9 @@ impl EventLoop { buffer: UnknownTypeInputBuffer::$SampleFormat( ::InputBuffer { buffer: Some( - super::super::InputBuffer::Asio(buff), + super::super::InputBuffer::Asio( + buff, + ), ), }, ), @@ -363,7 +379,7 @@ impl EventLoop { Endian::Little, convert_endian_to ); - }, + } sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!( I16, @@ -377,7 +393,7 @@ impl EventLoop { Endian::Little, convert_endian_to ); - }, + } sys::AsioSampleType::ASIOSTInt32MSB => { try_callback!( I16, @@ -391,7 +407,7 @@ impl EventLoop { Endian::Big, convert_endian_to ); - }, + } sys::AsioSampleType::ASIOSTInt16MSB => { try_callback!( I16, @@ -405,7 +421,7 @@ impl EventLoop { Endian::Big, convert_endian_to ); - }, + } sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!( F32, @@ -419,7 +435,7 @@ impl EventLoop { Endian::Little, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!( F32, @@ -433,7 +449,7 @@ impl EventLoop { Endian::Little, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat32MSB => { try_callback!( F32, @@ -447,7 +463,7 @@ impl EventLoop { Endian::Big, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat64MSB => { try_callback!( F32, @@ -461,10 +477,10 @@ impl EventLoop { Endian::Big, |a, _| a ); - }, + } _ => println!("unsupported format {:?}", stream_type), } - }, + } None => return (), } } @@ -480,7 +496,9 @@ impl EventLoop { /// Create the an output cpal stream. pub fn build_output_stream( - &self, device: &Device, format: &Format, + &self, + device: &Device, + format: &Format, ) -> Result { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); @@ -499,7 +517,7 @@ impl EventLoop { SampleFormat::I16 => Buffers { i16_buff: I16Buffer { cpal: vec![0 as i16; cpal_num_samples], - channel: (0 .. num_channels) + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) .collect(), }, @@ -509,7 +527,7 @@ impl EventLoop { i16_buff: I16Buffer::default(), f32_buff: F32Buffer { cpal: vec![0 as f32; cpal_num_samples], - channel: (0 .. num_channels) + channel: (0..num_channels) .map(|_| Vec::with_capacity(channel_len)) .collect(), }, @@ -538,39 +556,46 @@ impl EventLoop { // Convert sample depending on the sample type macro_rules! convert_sample { ($AsioTypeIdent:ident, - $AsioType:ty, - u16, - $Sample:expr - ) => { - ((*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 - / ::std::u16::MAX as i64) - - $AsioTypeIdent::MAX as i64) as $AsioType + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 + / ::std::$SampleTypeIdent::MAX as f64) as f32 }; ($AsioTypeIdent:ident, - $AsioType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 + / ::std::$SampleTypeIdent::MAX as f64 + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 / ::std::$SampleTypeIdent::MAX as i64) as $AsioType }; }; // Theres only a single callback because theres only one event loop - match callbacks.first_mut() { + match callbacks.as_mut() { Some(callback) => { macro_rules! try_callback { ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr + ) => { let mut my_buffers = $Buffers; { // Wrap the cpal buffer @@ -675,7 +700,7 @@ impl EventLoop { Endian::Little, convert_endian_from ); - }, + } sys::AsioSampleType::ASIOSTInt16LSB => { try_callback!( I16, @@ -689,7 +714,7 @@ impl EventLoop { Endian::Little, convert_endian_from ); - }, + } sys::AsioSampleType::ASIOSTInt32MSB => { try_callback!( I16, @@ -703,7 +728,7 @@ impl EventLoop { Endian::Big, convert_endian_from ); - }, + } sys::AsioSampleType::ASIOSTInt16MSB => { try_callback!( I16, @@ -717,7 +742,7 @@ impl EventLoop { Endian::Big, convert_endian_from ); - }, + } sys::AsioSampleType::ASIOSTFloat32LSB => { try_callback!( F32, @@ -731,7 +756,7 @@ impl EventLoop { Endian::Little, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat64LSB => { try_callback!( F32, @@ -745,7 +770,7 @@ impl EventLoop { Endian::Little, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat32MSB => { try_callback!( F32, @@ -759,7 +784,7 @@ impl EventLoop { Endian::Big, |a, _| a ); - }, + } sys::AsioSampleType::ASIOSTFloat64MSB => { try_callback!( F32, @@ -773,10 +798,10 @@ impl EventLoop { Endian::Big, |a, _| a ); - }, + } _ => println!("unsupported format {:?}", stream_type), } - }, + } None => return (), } } @@ -842,10 +867,10 @@ impl EventLoop { F: FnMut(StreamId, StreamData) + Send, { let callback: &mut (FnMut(StreamId, StreamData) + Send) = &mut callback; - self.callbacks + // Transmute needed to convince the compiler that the callback has a static lifetime + *self.callbacks .lock() - .unwrap() - .push(unsafe { mem::transmute(callback) }); + .unwrap() = Some(unsafe { mem::transmute(callback) }); loop { // A sleep here to prevent the loop being // removed in --release From 0ff0b65647a2356267c2f4a188996e43076ca4b6 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Wed, 7 Nov 2018 15:02:35 +0700 Subject: [PATCH 41/75] Add ASIO guide to README --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 705ad39..2d7a9ae 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This library currently supports the following: Currently supported backends include: - Linux (via ALSA) -- Windows +- Windows (via WASAPI by default, see ASIO instructions below) - macOS (via CoreAudio) - iOS (via CoreAudio) - Emscripten @@ -24,3 +24,64 @@ Currently supported backends include: Note that on Linux, the ALSA development files are required. These are provided as part of the `libasound2-dev` package on Debian and Ubuntu distributions and `alsa-lib-devel` on Fedora. + +## ASIO on Windows + +[ASIO](https://en.wikipedia.org/wiki/Audio_Stream_Input/Output) is an audio +driver protocol by Steinberg. While it is available on multiple operating +systems, it is most commonly used on Windows to work around limitations of +WASAPI including access to large numbers of channels and lower-latency audio +processing. + +CPAL allows for using the ASIO SDK as the audio backend on Windows instead of +WASAPI. To do so, follow these steps: + +1. **Download the ASIO SDK** `.zip` from [this + link](https://www.steinberg.net/en/company/developers.html). The version as + of writing this is 2.3.1. +2. Extract the files and place the `ASIOSDK2.3.1` directory somewhere you are + happy for it to stay (e.g. `~/.asio`). +3. Assign the full path of the `ASIOSDK2.3.1` directory to the `CPAL_ASIO_DIR` + environment variable. [How to set persisting Environment Variables on + Windows](https://gist.github.com/mitchmindtree/92c8e37fa80c8dddee5b94fc88d1288b#file-windows_environment_variables-md). +4. **Download and install LLVM** from + [here](http://releases.llvm.org/download.html) under the "Pre-Built Binaries" + section. The version as of writing this is 7.0.0. +5. Add the LLVM `bin` directory to a `LIBCLANG_PATH` environment variable. If + you installed LLVM to the default directory, this should work in the command + prompt: + ``` + setx LIBCLANG_PATH "C:\Program Files\LLVM\bin" + ``` +6. If you don't have any ASIO devices or drivers availabe, you can [**download + and install ASIO4ALL**](http://www.asio4all.org/). Be sure to enable the + "offline" feature during installation despite what the installer says about + it being useless. +7. **Use the correct command prompt** to build cpal and run examples. In my + case, I had to run a specific command prompt, otherwise rust-bindgen would + fail to find some of the necessary build tools. To do this, I went to the + `Start Menu > Visual C++ Build Tools > Visual C++ 2015 x64 Native Build Tools + Command Prompt` and ran this prompt. The exact prompt you need might differ + based on your machine's architecture and how you installed your Visual C++ + tools. There must be an easier solution to this (especially as not everyone + wants to build projects from the command line). +8. Select ASIO as the backend at the start of our program with the following: + + ```rust + #[cfg(target_os = "windows")] + { + cpal::os::windows::use_asio_backend().expect("Failed to select ASIO backend"); + } + ``` + + If you run into this error: + + ``` + cpal::os::windows::use_asio_backend().expect("Failed to use asio"); + ^^^^^^^^^^^^^^^^ did you mean `use_wasapi_backend`? + ``` + + Make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. + +In the future we would like to work on automating this process to make it +easier, but we are not familiar enough with the ASIO license to do so yet. From b70e27ed87024cd8d412d555010d3fa15330984f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Wed, 7 Nov 2018 15:34:13 +0700 Subject: [PATCH 42/75] Change step 7. to refer to VCVARS. Also adds a note about the `CPAL_ASIO_SDK` environment variable determining whether or not cpal will attempt to build ASIO and generate bindings to it. --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2d7a9ae..26df079 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ WASAPI. To do so, follow these steps: 3. Assign the full path of the `ASIOSDK2.3.1` directory to the `CPAL_ASIO_DIR` environment variable. [How to set persisting Environment Variables on Windows](https://gist.github.com/mitchmindtree/92c8e37fa80c8dddee5b94fc88d1288b#file-windows_environment_variables-md). + The existence of the `CPAL_ASIO_DIR` environment variable determines whether + or not CPAL will attempt to build the ASIO SDK and generate bindings to it. 4. **Download and install LLVM** from [here](http://releases.llvm.org/download.html) under the "Pre-Built Binaries" section. The version as of writing this is 7.0.0. @@ -57,15 +59,19 @@ WASAPI. To do so, follow these steps: and install ASIO4ALL**](http://www.asio4all.org/). Be sure to enable the "offline" feature during installation despite what the installer says about it being useless. -7. **Use the correct command prompt** to build cpal and run examples. In my - case, I had to run a specific command prompt, otherwise rust-bindgen would - fail to find some of the necessary build tools. To do this, I went to the - `Start Menu > Visual C++ Build Tools > Visual C++ 2015 x64 Native Build Tools - Command Prompt` and ran this prompt. The exact prompt you need might differ - based on your machine's architecture and how you installed your Visual C++ - tools. There must be an easier solution to this (especially as not everyone - wants to build projects from the command line). -8. Select ASIO as the backend at the start of our program with the following: +7. **Loading VCVARS**. `rust-bindgen` uses the C++ tool-chain when generating + bindings to the ASIO SDK. As a result, it is necessary to load some + environment variables in the command prompt that we use to build our project. + On 64-bit machines run: + ``` + "C:\Program Files (x86)Microsoft Visual Studio 14.0VC\vcvarsall" amd64 + ``` + On 32-bit machines run: + ``` + "C:\Program Files (x86)Microsoft Visual Studio 14.0VC\vcvarsall" x86 + ``` +8. Select ASIO as the backend at the start of our program with the following + code: ```rust #[cfg(target_os = "windows")] From fd808b95e9c60c8c650ae0cd1eba79713c09de1b Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 19:26:39 +1100 Subject: [PATCH 43/75] indentation and no clean up on destroy stream --- src/platform/windows/asio/stream.rs | 1158 +++++++++++++-------------- 1 file changed, 564 insertions(+), 594 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 53443d0..5f1b12b 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -112,7 +112,7 @@ impl EventLoop { .set_sample_rate(sample_rate) .expect("Unsupported sample rate"); } else { - return Err(CreationError::FormatNotSupported); + return Err(CreationError::FormatNotSupported); } } // Either create a stream if thers none or had back the @@ -198,300 +198,287 @@ impl EventLoop { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_input_stream(&drivers, format) - .map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); + let input_stream = self.get_input_stream(&drivers, format); + input_stream.map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; + let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - // TODO the naming of cpal and channel is confusing. - // change it to: - // cpal -> interleaved - // channels -> per_channel - let mut buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), + // Create buffers depending on data type + // TODO the naming of cpal and channel is confusing. + // change it to: + // cpal -> interleaved + // channels -> per_channel + let mut buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, + f32_buff: F32Buffer::default(), + }, + SampleFormat::F32 => Buffers { + i16_buff: I16Buffer::default(), + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + + // Set the input callback. + // This is most performance critical part of the ASIO bindings. + sys::set_callback(move |index| unsafe { + // if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } + } + // Get the stream + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return (), }; - // Set the input callback. - // This is most performance critical part of the ASIO bindings. - sys::set_callback(move |index| unsafe { - // if not playing return early - // TODO is this lock necessary - { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { - if let Some(s) = s { - if !s.playing { - return (); - } + // Get the callback + let mut callbacks = callbacks.lock().unwrap(); + + // Theres only a single callback because theres only one event loop + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return (), + }; + + // Macro to convert sample from ASIO to CPAL type + macro_rules! convert_sample { + // floats types required different conversion + ($AsioTypeIdent:ident, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 + }; + ($AsioTypeIdent:ident, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 + }; + ($AsioTypeIdent:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 + / ::std::$AsioTypeIdent::MAX as i64) as $SampleType + }; + }; + // This creates gets the buffer and interleaves it. + // It allows it to be done based on the sample type. + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr + ) => { + // For each channel write the asio buffer to + // the cpal buffer + + for (i, channel) in $Buffers.channel.iter_mut().enumerate() { + let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] + as *mut $AsioType; + let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( + buff_ptr, + asio_stream.buffer_size as usize, + ); + for asio_s in asio_buffer.iter() { + channel.push($ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $SampleType, + $SampleTypeIdent, + asio_s + ), + $Endianness, + )); } } - } - // Get the stream - // TODO is this lock necessary - if let Some(ref asio_stream) = asio_streams.lock().unwrap().input { - // Get the callback - // TODO is this lock necessary - let mut callbacks = callbacks.lock().unwrap(); - // Theres only a single callback because theres only one event loop - // TODO is 64bit necessary. Might be using more memory then needed - match callbacks.as_mut() { - Some(callback) => { - // Macro to convert sample from ASIO to CPAL type - macro_rules! convert_sample { - // floats types required different conversion - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 - / ::std::$SampleTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 - / ::std::$SampleTypeIdent::MAX as f64 - }; - ($AsioTypeIdent:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - / ::std::$AsioTypeIdent::MAX as i64) - as $SampleType - }; - }; - // This creates gets the buffer and interleaves it. - // It allows it to be done based on the sample type. - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - // For each channel write the asio buffer to - // the cpal buffer - - for (i, channel) in - $Buffers.channel.iter_mut().enumerate() - { - let buff_ptr = asio_stream.buffer_infos[i].buffers - [index as usize] - as *mut $AsioType; - let asio_buffer: &'static [$AsioType] = - std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize, - ); - for asio_s in asio_buffer.iter() { - channel.push($ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $SampleType, - $SampleTypeIdent, - asio_s - ), - $Endianness, - )); - } - } - - // interleave all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = $Buffers; - au::interleave(&channels, c_buffer); - // Clear the per channel buffers - for c in channels.iter_mut() { - c.clear(); - } - } - - // Wrap the buffer in the CPAL type - let buff = InputBuffer { - buffer: &mut $Buffers.cpal, - }; - // Call the users callback with the buffer - callback( - StreamId(count), - StreamData::Input { - buffer: UnknownTypeInputBuffer::$SampleFormat( - ::InputBuffer { - buffer: Some( - super::super::InputBuffer::Asio( - buff, - ), - ), - }, - ), - }, - ); - }; - }; - // Call the right buffer handler depending on types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), - } + // interleave all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = $Buffers; + au::interleave(&channels, c_buffer); + // Clear the per channel buffers + for c in channels.iter_mut() { + c.clear(); } - None => return (), } + + // Wrap the buffer in the CPAL type + let buff = InputBuffer { + buffer: &mut $Buffers.cpal, + }; + // Call the users callback with the buffer + callback( + StreamId(count), + StreamData::Input { + buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { + buffer: Some(super::super::InputBuffer::Asio(buff)), + }), + }, + ); + }; + }; + // Call the right buffer handler depending on types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); } - }); - // Create stream and set to paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { playing: false })); - StreamId(count) - }) + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); + } + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + _ => println!("unsupported format {:?}", stream_type), + } + }); + // Create stream and set to paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { playing: false })); + StreamId(count) + }) } /// Create the an output cpal stream. @@ -503,317 +490,308 @@ impl EventLoop { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - self.get_output_stream(&drivers, format) - .map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; + let output_stream = self.get_output_stream(&drivers, format); + output_stream.map(|stream_buffer_size| { + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); + let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - let mut re_buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), + // Create buffers depending on data type + let mut re_buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, + f32_buff: F32Buffer::default(), + }, + SampleFormat::F32 => Buffers { + i16_buff: I16Buffer::default(), + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + + sys::set_callback(move |index| unsafe { + // if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } + } + // Get the stream + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.output { + Some(ref asio_stream) => asio_stream, + None => return (), }; - sys::set_callback(move |index| unsafe { - // if not playing return early - // TODO is this lock necessary - { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { - if let Some(s) = s { - if !s.playing { - return (); + // Get the callback + let mut callbacks = callbacks.lock().unwrap(); + + // Theres only a single callback because theres only one event loop + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return (), + }; + + // Convert sample depending on the sample type + macro_rules! convert_sample { + ($AsioTypeIdent:ident, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 + }; + ($AsioTypeIdent:ident, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 + / ::std::$SampleTypeIdent::MAX as i64) as $AsioType + }; + }; + + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr + ) => { + let mut my_buffers = $Buffers; + { + // Wrap the cpal buffer + let buff = OutputBuffer { + buffer: &mut my_buffers.cpal, + }; + // call the callback to fill the buffer with + // users data + callback( + StreamId(count), + StreamData::Output { + buffer: UnknownTypeOutputBuffer::$SampleFormat( + ::OutputBuffer { + target: Some(super::super::OutputBuffer::Asio( + buff, + )), + }, + ), + }, + ); + } + // Deinter all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = my_buffers; + au::deinterleave(&c_buffer[..], channels); + } + + // Silence the buffer that is about to be used + let silence = match index { + 0 => { + if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { + sys::SILENCE_FIRST.store(true, Ordering::SeqCst); + sys::SILENCE_SECOND.store(false, Ordering::SeqCst); + true + } else { + false } } - } - } - // Get the output stream - // TODO is this lock necessary - if let Some(ref asio_stream) = asio_streams.lock().unwrap().output { - // Number of samples needed total - let mut callbacks = callbacks.lock().unwrap(); - - // Convert sample depending on the sample type - macro_rules! convert_sample { - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 - / ::std::$SampleTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 - / ::std::$SampleTypeIdent::MAX as f64 - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 - / ::std::$SampleTypeIdent::MAX as i64) as $AsioType - }; + 1 => { + if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { + sys::SILENCE_SECOND.store(true, Ordering::SeqCst); + sys::SILENCE_FIRST.store(false, Ordering::SeqCst); + true + } else { + false + } + } + _ => unreachable!(), }; - // Theres only a single callback because theres only one event loop - match callbacks.as_mut() { - Some(callback) => { - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - let mut my_buffers = $Buffers; - { - // Wrap the cpal buffer - let buff = OutputBuffer { - buffer: &mut my_buffers.cpal, - }; - // call the callback to fill the buffer with - // users data - callback( - StreamId(count), - StreamData::Output { - buffer: UnknownTypeOutputBuffer::$SampleFormat( - ::OutputBuffer { - target: Some( - super::super::OutputBuffer::Asio( - buff, - ), - ), - }, - ), - }, - ); - } - // Deinter all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = my_buffers; - au::deinterleave(&c_buffer[..], channels); - } - - // Silence the buffer that is about to be used - let silence = match index { - 0 => { - if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { - sys::SILENCE_FIRST - .store(true, Ordering::SeqCst); - sys::SILENCE_SECOND - .store(false, Ordering::SeqCst); - true - } else { - false - } - }, - 1 => { - if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { - sys::SILENCE_SECOND - .store(true, Ordering::SeqCst); - sys::SILENCE_FIRST - .store(false, Ordering::SeqCst); - true - } else { - false - } - }, - _ => unreachable!(), - }; - - // For each channel write the cpal data to - // the asio buffer - for (i, channel) in my_buffers.channel.iter().enumerate() { - let buff_ptr = asio_stream.buffer_infos[i].buffers - [index as usize] - as *mut $AsioType; - 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) - { - if silence { - *asio_s = 0.0 as $AsioType; - } - *asio_s += $ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $AsioType, - $SampleTypeIdent, - cpal_s - ), - $Endianness, - ); - } - } - }; - } - // Choose the buffer conversions based on the sample types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), + // For each channel write the cpal data to + // the asio buffer + for (i, channel) in my_buffers.channel.iter().enumerate() { + let buff_ptr = asio_stream.buffer_infos[i].buffers + [index as usize] as *mut $AsioType; + 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) { + if silence { + *asio_s = 0.0 as $AsioType; } + *asio_s += $ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $AsioType, + $SampleTypeIdent, + cpal_s + ), + $Endianness, + ); } - None => return (), } + }; + } + // Choose the buffer conversions based on the sample types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from + ); } - }); - // Create the stream paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { playing: false })); - // Give the ID based on the stream count - StreamId(count) - }) + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + _ => println!("unsupported format {:?}", stream_type), + } + }); + // Create the stream paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { playing: false })); + // Give the ID based on the stream count + StreamId(count) + }) } /// Play the cpal stream for the given ID. @@ -846,19 +824,9 @@ impl EventLoop { } /// Destroy the cpal stream based on the ID. - /// If no cpal streams exist then destory the - /// ASIO streams and clean up pub fn destroy_stream(&self, stream_id: StreamId) { let mut streams = self.cpal_streams.lock().unwrap(); streams.get_mut(stream_id.0).take(); - let count = self.stream_count.fetch_sub(1, Ordering::SeqCst); - if count == 1 { - *self.asio_streams.lock().unwrap() = sys::AsioStreams { - output: None, - input: None, - }; - sys::clean_up(); - } } /// Run the cpal callbacks @@ -868,9 +836,7 @@ impl EventLoop { { let callback: &mut (FnMut(StreamId, StreamData) + Send) = &mut callback; // Transmute needed to convince the compiler that the callback has a static lifetime - *self.callbacks - .lock() - .unwrap() = Some(unsafe { mem::transmute(callback) }); + *self.callbacks.lock().unwrap() = Some(unsafe { mem::transmute(callback) }); loop { // A sleep here to prevent the loop being // removed in --release @@ -883,6 +849,10 @@ impl EventLoop { /// Currently event loop is never dropped. impl Drop for EventLoop { fn drop(&mut self) { + *self.asio_streams.lock().unwrap() = sys::AsioStreams { + output: None, + input: None, + }; sys::clean_up(); } } From c255da24bf1e706935d78ee6bc0dc96fc2b25b61 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 20:02:27 +1100 Subject: [PATCH 44/75] stream id bug --- src/platform/windows/asio/stream.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 5f1b12b..d94bc25 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -163,7 +163,7 @@ impl EventLoop { .set_sample_rate(sample_rate) .expect("Unsupported sample rate"); } else { - panic!("This sample rate {:?} is not supported", sample_rate); + return Err(CreationError::FormatNotSupported); } } // Either create a stream if thers none or had back the @@ -240,7 +240,7 @@ impl EventLoop { sys::set_callback(move |index| unsafe { // if not playing return early { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = cpal_streams.lock().unwrap().get(count) { if let Some(s) = s { if !s.playing { return (); @@ -525,7 +525,7 @@ impl EventLoop { sys::set_callback(move |index| unsafe { // if not playing return early { - if let Some(s) = cpal_streams.lock().unwrap().get(count - 1) { + if let Some(s) = cpal_streams.lock().unwrap().get(count) { if let Some(s) = s { if !s.playing { return (); From a3defde208d83e3f767de1145e04fd206ed4bf31 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 20:42:00 +1100 Subject: [PATCH 45/75] Checks formats --- src/platform/windows/asio/stream.rs | 74 +++++++++++++++++------------ 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index d94bc25..bddf875 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -88,24 +88,19 @@ impl EventLoop { } } - /// Create a new CPAL Input Stream. - /// If there is no ASIO Input Stream - /// it will be created. - fn get_input_stream( + fn check_format( &self, drivers: &sys::Drivers, format: &Format, - ) -> Result { + num_asio_channels: u16, + ) -> Result<(), CreationError> { let Format { channels, sample_rate, - .. + data_type, } = format; - let num_channels = *channels as usize; // Try and set the sample rate to what the user selected. - // If they try and use and unavailable rate then panic let sample_rate = sample_rate.0; - let ref mut streams = *self.asio_streams.lock().unwrap(); if sample_rate != drivers.get_sample_rate().rate { if drivers.can_sample_rate(sample_rate) { drivers @@ -115,6 +110,35 @@ impl EventLoop { return Err(CreationError::FormatNotSupported); } } + // unsigned formats are not supported by asio + match data_type { + SampleFormat::I16 | SampleFormat::F32 => (), + SampleFormat::U16 => return Err(CreationError::FormatNotSupported), + } + if *channels > num_asio_channels { + return Err(CreationError::FormatNotSupported); + } + Ok(()) + } + + /// Create a new CPAL Input Stream. + /// If there is no ASIO Input Stream + /// it will be created. + fn get_input_stream( + &self, + drivers: &sys::Drivers, + format: &Format, + device: &Device, + ) -> Result { + match device.default_input_format() { + Ok(f) => { + let num_asio_channels = f.channels; + self.check_format(drivers, format, num_asio_channels) + }, + Err(_) => Err(CreationError::FormatNotSupported), + }?; + let num_channels = format.channels as usize; + let ref mut streams = *self.asio_streams.lock().unwrap(); // Either create a stream if thers none or had back the // size of the current one. match streams.input { @@ -145,27 +169,17 @@ impl EventLoop { &self, drivers: &sys::Drivers, format: &Format, + device: &Device, ) -> Result { - let Format { - channels, - sample_rate, - .. - } = format; - let num_channels = *channels as usize; - // Try and set the sample rate to what the user selected. - // If they try and use and unavailable rate then panic - // TODO factor this into a function as it happens for both input and output - let sample_rate = sample_rate.0; + match device.default_output_format() { + Ok(f) => { + let num_asio_channels = f.channels; + self.check_format(drivers, format, num_asio_channels) + }, + Err(_) => Err(CreationError::FormatNotSupported), + }?; + let num_channels = format.channels as usize; let ref mut streams = *self.asio_streams.lock().unwrap(); - if sample_rate != drivers.get_sample_rate().rate { - if drivers.can_sample_rate(sample_rate) { - drivers - .set_sample_rate(sample_rate) - .expect("Unsupported sample rate"); - } else { - return Err(CreationError::FormatNotSupported); - } - } // Either create a stream if thers none or had back the // size of the current one. match streams.output { @@ -198,7 +212,7 @@ impl EventLoop { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let input_stream = self.get_input_stream(&drivers, format); + let input_stream = self.get_input_stream(&drivers, format, device); input_stream.map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); @@ -490,7 +504,7 @@ impl EventLoop { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let output_stream = self.get_output_stream(&drivers, format); + let output_stream = self.get_output_stream(&drivers, format, device); output_stream.map(|stream_buffer_size| { let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); From 5eb311d8beb397278a9a674e8c043195e88bbdb3 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 21:54:27 +1100 Subject: [PATCH 46/75] float conversion fix --- src/platform/windows/asio/stream.rs | 88 ++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index bddf875..9ee121b 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -281,19 +281,61 @@ impl EventLoop { // Macro to convert sample from ASIO to CPAL type macro_rules! convert_sample { // floats types required different conversion + (f32, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample + }; + (f64, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample + }; + (f64, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f32 + }; + (f32, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 + }; ($AsioTypeIdent:ident, f32, $SampleTypeIdent:ident, $Sample:expr ) => { - (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 + (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 }; ($AsioTypeIdent:ident, f64, $SampleTypeIdent:ident, $Sample:expr ) => { - *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 + *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 + }; + (f32, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + }; + (f64, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType }; ($AsioTypeIdent:ident, $SampleType:ty, @@ -565,6 +607,48 @@ impl EventLoop { // Convert sample depending on the sample type macro_rules! convert_sample { + ($AsioTypeIdent:ident, + f64, + f64, + $Sample:expr + ) => { + *$Sample + }; + ($AsioTypeIdent:ident, + f32, + f32, + $Sample:expr + ) => { + *$Sample + }; + ($AsioTypeIdent:ident, + f64, + f32, + $Sample:expr + ) => { + *$Sample as f64 + }; + ($AsioTypeIdent:ident, + f32, + f64, + $Sample:expr + ) => { + *$Sample as f32 + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + f32, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + f64, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + }; ($AsioTypeIdent:ident, f32, $SampleTypeIdent:ident, From f2c80998105d6563f7cefbfa6e6c58fb71f9881f Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 21:54:27 +1100 Subject: [PATCH 47/75] bug fix and comment --- src/platform/windows/asio/stream.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 9ee121b..4e97b4d 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -715,7 +715,10 @@ impl EventLoop { au::deinterleave(&c_buffer[..], channels); } - // Silence the buffer that is about to be used + // Silence the buffer that is about to be used. + // This checks if any other callbacks have already + // silenced this buffer. If not it will silence it + // and set the opposite buffer half to unsilenced. let silence = match index { 0 => { if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { From 49bc3ee367fc375d2d5b3764fe2e9f9e50fd838a Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Wed, 7 Nov 2018 23:14:34 +1100 Subject: [PATCH 48/75] remove release symbols --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c191d4..5fa15b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,6 @@ 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" From bc94d167ae2e8c4f42e4e4262df9eb5b98311849 Mon Sep 17 00:00:00 2001 From: Tom Gowan Date: Thu, 8 Nov 2018 18:18:08 +1100 Subject: [PATCH 49/75] fix endianess --- src/platform/windows/asio/stream.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/windows/asio/stream.rs b/src/platform/windows/asio/stream.rs index 4e97b4d..d852d0a 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/platform/windows/asio/stream.rs @@ -424,7 +424,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Little, - convert_endian_to + convert_endian_from ); } sys::AsioSampleType::ASIOSTInt16LSB => { @@ -438,7 +438,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Little, - convert_endian_to + convert_endian_from ); } sys::AsioSampleType::ASIOSTInt32MSB => { @@ -452,7 +452,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Big, - convert_endian_to + convert_endian_from ); } sys::AsioSampleType::ASIOSTInt16MSB => { @@ -466,7 +466,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Big, - convert_endian_to + convert_endian_from ); } sys::AsioSampleType::ASIOSTFloat32LSB => { @@ -781,7 +781,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Little, - convert_endian_from + convert_endian_to ); } sys::AsioSampleType::ASIOSTInt16LSB => { @@ -795,7 +795,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Little, - convert_endian_from + convert_endian_to ); } sys::AsioSampleType::ASIOSTInt32MSB => { @@ -809,7 +809,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Big, - convert_endian_from + convert_endian_to ); } sys::AsioSampleType::ASIOSTInt16MSB => { @@ -823,7 +823,7 @@ impl EventLoop { I16Buffer, I16Buffer, Endian::Big, - convert_endian_from + convert_endian_to ); } sys::AsioSampleType::ASIOSTFloat32LSB => { From 2bc9f859708f737d3914fbc693a3744f797d1942 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 8 Nov 2018 12:54:32 +0700 Subject: [PATCH 50/75] Update supported_formats methods for all sample rates and channel combos Closes #32. --- src/platform/windows/asio/device.rs | 69 ++++++++++++++++------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/src/platform/windows/asio/device.rs b/src/platform/windows/asio/device.rs index c8c07ca..e31d9de 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/platform/windows/asio/device.rs @@ -50,21 +50,26 @@ impl Device { pub fn supported_input_formats( &self, ) -> Result { - match self.default_input_format() { - Ok(f) => { - // Can this device support both 44100 and 48000 - let supported_formats: Vec = [44100, 48000] - .into_iter() - .filter(|rate| self.drivers.can_sample_rate(**rate as u32)) - .map(|rate| { - let mut format = f.clone(); - format.sample_rate = SampleRate(*rate); - SupportedFormat::from(format) - }).collect(); - Ok(supported_formats.into_iter()) - }, - Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), + // Retrieve the default format for the total supported channels and supported sample + // format. + let mut f = match self.default_input_format() { + Err(_) => return Err(FormatsEnumerationError::DeviceNotAvailable), + Ok(f) => f, + }; + + // Collect a format for every combination of supported sample rate and number of channels. + let mut supported_formats = vec![]; + for &rate in ::COMMON_SAMPLE_RATES { + if !self.drivers.can_sample_rate(rate.0 as u32) { + continue; + } + for channels in 1..f.channels + 1 { + f.channels = channels; + f.sample_rate = rate; + supported_formats.push(SupportedFormat::from(f.clone())); + } } + Ok(supported_formats.into_iter()) } /// Gets the supported output formats. @@ -73,22 +78,26 @@ impl Device { pub fn supported_output_formats( &self, ) -> Result { - match self.default_output_format() { - Ok(f) => { - // Can this device support both 44100 and 48000 - let supported_formats: Vec = [44100, 48000] - .into_iter() - .filter(|rate| self.drivers.can_sample_rate(**rate as u32)) - .map(|rate| { - let mut format = f.clone(); - format.sample_rate = SampleRate(*rate); - SupportedFormat::from(format) - }).collect(); - //Ok(vec![SupportedFormat::from(f)].into_iter()) - Ok(supported_formats.into_iter()) - }, - Err(_) => Err(FormatsEnumerationError::DeviceNotAvailable), + // Retrieve the default format for the total supported channels and supported sample + // format. + let mut f = match self.default_output_format() { + Err(_) => return Err(FormatsEnumerationError::DeviceNotAvailable), + Ok(f) => f, + }; + + // Collect a format for every combination of supported sample rate and number of channels. + let mut supported_formats = vec![]; + for &rate in ::COMMON_SAMPLE_RATES { + if !self.drivers.can_sample_rate(rate.0 as u32) { + continue; + } + for channels in 1..f.channels + 1 { + f.channels = channels; + f.sample_rate = rate; + supported_formats.push(SupportedFormat::from(f.clone())); + } } + Ok(supported_formats.into_iter()) } /// Returns the default input format @@ -192,4 +201,4 @@ fn online_devices() -> Vec { .into_iter() .filter(|name| sys::Drivers::load(&name).is_ok()) .collect() -} \ No newline at end of file +} From 09fd5562be50515623d49d271377933e73b3af8c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Wed, 26 Jun 2019 16:24:23 +0200 Subject: [PATCH 51/75] Update ASIO implementation for new error handling and Host API This is currently untested and will almost certainly not build. I'm about to move into a Windows VM to get everything running properly again. --- .../windows => host}/asio/asio_utils/mod.rs | 0 .../windows => host}/asio/asio_utils/tests.rs | 0 src/{platform/windows => host}/asio/device.rs | 23 ++-- src/host/asio/mod.rs | 127 ++++++++++++++++++ src/{platform/windows => host}/asio/stream.rs | 97 +++++-------- src/host/mod.rs | 2 + src/os/mod.rs | 7 - src/os/windows/mod.rs | 36 ----- src/platform/mod.rs | 11 +- src/platform/windows/asio/mod.rs | 9 -- src/platform/windows/mod.rs | 2 - 11 files changed, 185 insertions(+), 129 deletions(-) rename src/{platform/windows => host}/asio/asio_utils/mod.rs (100%) rename src/{platform/windows => host}/asio/asio_utils/tests.rs (100%) rename src/{platform/windows => host}/asio/device.rs (92%) create mode 100644 src/host/asio/mod.rs rename src/{platform/windows => host}/asio/stream.rs (93%) delete mode 100644 src/os/mod.rs delete mode 100644 src/os/windows/mod.rs delete mode 100644 src/platform/windows/asio/mod.rs delete mode 100644 src/platform/windows/mod.rs diff --git a/src/platform/windows/asio/asio_utils/mod.rs b/src/host/asio/asio_utils/mod.rs similarity index 100% rename from src/platform/windows/asio/asio_utils/mod.rs rename to src/host/asio/asio_utils/mod.rs diff --git a/src/platform/windows/asio/asio_utils/tests.rs b/src/host/asio/asio_utils/tests.rs similarity index 100% rename from src/platform/windows/asio/asio_utils/tests.rs rename to src/host/asio/asio_utils/tests.rs diff --git a/src/platform/windows/asio/device.rs b/src/host/asio/device.rs similarity index 92% rename from src/platform/windows/asio/device.rs rename to src/host/asio/device.rs index e31d9de..c6c085b 100644 --- a/src/platform/windows/asio/device.rs +++ b/src/host/asio/device.rs @@ -4,11 +4,12 @@ pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; use DefaultFormatError; +use DeviceNameError; use Format; -use FormatsEnumerationError; use SampleFormat; use SampleRate; use SupportedFormat; +use SupportedFormatsError; use super::sys; /// A ASIO Device @@ -40,8 +41,8 @@ impl Hash for Device { } impl Device { - pub fn name(&self) -> String { - self.name.clone() + pub fn name(&self) -> Result { + Ok(self.name.clone()) } /// Gets the supported input formats. @@ -49,11 +50,11 @@ impl Device { /// Need to find all possible formats. pub fn supported_input_formats( &self, - ) -> Result { + ) -> Result { // Retrieve the default format for the total supported channels and supported sample // format. let mut f = match self.default_input_format() { - Err(_) => return Err(FormatsEnumerationError::DeviceNotAvailable), + Err(_) => return Err(SupportedFormatsError::DeviceNotAvailable), Ok(f) => f, }; @@ -77,11 +78,11 @@ impl Device { /// Need to find all possible formats. pub fn supported_output_formats( &self, - ) -> Result { + ) -> Result { // Retrieve the default format for the total supported channels and supported sample // format. let mut f = match self.default_output_format() { - Err(_) => return Err(FormatsEnumerationError::DeviceNotAvailable), + Err(_) => return Err(SupportedFormatsError::DeviceNotAvailable), Ok(f) => f, }; @@ -140,12 +141,12 @@ impl Device { } } -impl Default for Devices { - fn default() -> Devices { +impl Devices { + pub fn new() -> Result { let driver_names = online_devices(); - Devices { + Ok(Devices { drivers: driver_names.into_iter(), - } + }) } } diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs new file mode 100644 index 0000000..f1b22dc --- /dev/null +++ b/src/host/asio/mod.rs @@ -0,0 +1,127 @@ +extern crate asio_sys as sys; + +use { + BuildStreamError, + DefaultFormatError, + Device as DeviceTrait, + DeviceNameError, + DevicesError, + EventLoop as EventLoopTrait, + Host as HostTrait, + PauseStreamError, + PlayStreamError, + StreamDataResult, + StreamId as StreamIdTrait, + SupportedFormatsError, +} + +pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; +pub use self::stream::{EventLoop, StreamId}; + +mod device; +mod stream; +mod asio_utils; + +/// The host for ASIO. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + unimplemented!("asio as an initialisation and termination process that needs to be impld"); + //Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + unimplemented!("check how to do this using asio-sys") + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} diff --git a/src/platform/windows/asio/stream.rs b/src/host/asio/stream.rs similarity index 93% rename from src/platform/windows/asio/stream.rs rename to src/host/asio/stream.rs index d852d0a..2beb37d 100644 --- a/src/platform/windows/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -10,10 +10,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use CreationError; +use BuildStreamError; use Format; +use PauseStreamError; +use PlayStreamError; use SampleFormat; use StreamData; +use StreamDataResult; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; @@ -26,7 +29,7 @@ pub struct EventLoop { /// Total stream count stream_count: AtomicUsize, /// The CPAL callback that the user gives to fill the buffers. - callbacks: Arc>>, + callbacks: Arc>>, } /// Id for each stream. @@ -35,13 +38,6 @@ pub struct EventLoop { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamId(usize); -pub struct InputBuffer<'a, T: 'a> { - buffer: &'a [T], -} -pub struct OutputBuffer<'a, T: 'a> { - buffer: &'a mut [T], -} - /// CPAL stream. /// This decouples the many cpal streams /// from the single input and single output @@ -92,8 +88,8 @@ impl EventLoop { &self, drivers: &sys::Drivers, format: &Format, - num_asio_channels: u16, - ) -> Result<(), CreationError> { + num_asio_channels: u16, + ) -> Result<(), BuildStreamError> { let Format { channels, sample_rate, @@ -107,16 +103,16 @@ impl EventLoop { .set_sample_rate(sample_rate) .expect("Unsupported sample rate"); } else { - return Err(CreationError::FormatNotSupported); + return Err(BuildStreamError::FormatNotSupported); } } // unsigned formats are not supported by asio match data_type { SampleFormat::I16 | SampleFormat::F32 => (), - SampleFormat::U16 => return Err(CreationError::FormatNotSupported), + SampleFormat::U16 => return Err(BuildStreamError::FormatNotSupported), } if *channels > num_asio_channels { - return Err(CreationError::FormatNotSupported); + return Err(BuildStreamError::FormatNotSupported); } Ok(()) } @@ -129,13 +125,13 @@ impl EventLoop { drivers: &sys::Drivers, format: &Format, device: &Device, - ) -> Result { + ) -> Result { match device.default_input_format() { Ok(f) => { let num_asio_channels = f.channels; self.check_format(drivers, format, num_asio_channels) }, - Err(_) => Err(CreationError::FormatNotSupported), + Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; let ref mut streams = *self.asio_streams.lock().unwrap(); @@ -156,7 +152,7 @@ impl EventLoop { bs }).map_err(|ref e| { println!("Error preparing stream: {}", e); - CreationError::DeviceNotAvailable + BuildStreamError::DeviceNotAvailable }) } } @@ -170,13 +166,13 @@ impl EventLoop { drivers: &sys::Drivers, format: &Format, device: &Device, - ) -> Result { + ) -> Result { match device.default_output_format() { Ok(f) => { let num_asio_channels = f.channels; self.check_format(drivers, format, num_asio_channels) }, - Err(_) => Err(CreationError::FormatNotSupported), + Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; let ref mut streams = *self.asio_streams.lock().unwrap(); @@ -197,18 +193,18 @@ impl EventLoop { bs }).map_err(|ref e| { println!("Error preparing stream: {}", e); - CreationError::DeviceNotAvailable + BuildStreamError::DeviceNotAvailable }) } } } - /// Builds a new cpal input stream + /// Builds a new cpal input stream pub fn build_input_stream( &self, device: &Device, format: &Format, - ) -> Result { + ) -> Result { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); @@ -328,7 +324,7 @@ impl EventLoop { $SampleTypeIdent:ident, $Sample:expr ) => { - (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType }; (f64, $SampleType:ty, @@ -396,18 +392,14 @@ impl EventLoop { } } - // Wrap the buffer in the CPAL type - let buff = InputBuffer { - buffer: &mut $Buffers.cpal, - }; // Call the users callback with the buffer callback( StreamId(count), - StreamData::Input { + Ok(StreamData::Input { buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { - buffer: Some(super::super::InputBuffer::Asio(buff)), + buffer: &$Buffers.cpal, }), - }, + }), ); }; }; @@ -542,7 +534,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result { + ) -> Result { let Device { drivers, .. } = device; let num_channels = format.channels.clone(); let stream_type = drivers.get_data_type().expect("Couldn't load data type"); @@ -626,28 +618,28 @@ impl EventLoop { f32, $Sample:expr ) => { - *$Sample as f64 + *$Sample as f64 }; ($AsioTypeIdent:ident, f32, f64, $Sample:expr ) => { - *$Sample as f32 + *$Sample as f32 }; ($AsioTypeIdent:ident, $AsioType:ty, f32, $Sample:expr ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType }; ($AsioTypeIdent:ident, $AsioType:ty, f64, $Sample:expr ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType }; ($AsioTypeIdent:ident, f32, @@ -695,15 +687,13 @@ impl EventLoop { // users data callback( StreamId(count), - StreamData::Output { + Ok(StreamData::Output { buffer: UnknownTypeOutputBuffer::$SampleFormat( ::OutputBuffer { - target: Some(super::super::OutputBuffer::Asio( - buff, - )), + buffer: &mut my_buffers.cpal, }, ), - }, + }), ); } // Deinter all the channels @@ -897,7 +887,7 @@ impl EventLoop { /// Play the cpal stream for the given ID. /// Also play The ASIO streams if they are not already. - pub fn play_stream(&self, stream_id: StreamId) { + pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { s.playing = true; @@ -908,7 +898,7 @@ impl EventLoop { /// Pause the cpal stream for the given ID. /// Pause the ASIO streams if there are no CPAL streams palying. - pub fn pause_stream(&self, stream_id: StreamId) { + pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); if let Some(s) = streams .get_mut(stream_id.0) @@ -933,9 +923,9 @@ impl EventLoop { /// Run the cpal callbacks pub fn run(&self, mut callback: F) -> ! where - F: FnMut(StreamId, StreamData) + Send, + F: FnMut(StreamId, StreamDataResult) + Send, { - let callback: &mut (FnMut(StreamId, StreamData) + Send) = &mut callback; + let callback: &mut (FnMut(StreamId, StreamDataResult) + Send) = &mut callback; // Transmute needed to convince the compiler that the callback has a static lifetime *self.callbacks.lock().unwrap() = Some(unsafe { mem::transmute(callback) }); loop { @@ -958,25 +948,6 @@ impl Drop for EventLoop { } } -impl<'a, T> InputBuffer<'a, T> { - pub fn buffer(&self) -> &[T] { - &self.buffer - } - pub fn finish(self) {} -} - -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) {} -} - /// Helper function to convert to system endianness fn convert_endian_to(sample: T, endian: Endian) -> T { match endian { diff --git a/src/host/mod.rs b/src/host/mod.rs index 0c3155e..2bc9b2e 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -1,5 +1,7 @@ #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) mod alsa; +#[cfg(windows)] +pub(crate) mod asio; #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) mod coreaudio; #[cfg(target_os = "emscripten")] diff --git a/src/os/mod.rs b/src/os/mod.rs deleted file mode 100644 index 3716caa..0000000 --- a/src/os/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// The cpal::os module provides operating-system-specific -/// functionality. If you are using this module within a -/// cross-platform project, you may wish to use -/// cfg(target_os = "") to ensure that you only -/// use the OS-specific items when compiling for that OS. -#[cfg(target_os = "windows")] -pub mod windows; diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs deleted file mode 100644 index e148c81..0000000 --- a/src/os/windows/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -/// This allows you to choose either Wasapi or ASIO -/// as your back end. Wasapi is the default. -/// The CPAL_ASIO_DIR must be set to the ASIO SDK -/// directory for use_asio_backend to be available. -use std::sync::Mutex; - -#[derive(Clone)] -pub enum BackEnd { - Wasapi, - Asio, -} - -lazy_static! { - static ref BACK_END: Mutex = Mutex::new(BackEnd::Wasapi); -} - -/// See which beackend is currently set. -pub fn which_backend() -> BackEnd { - (*BACK_END.lock().unwrap()).clone() -} - -#[cfg(asio)] -/// Choose ASIO as the backend -pub fn use_asio_backend() -> Result<(), BackEndError> { - *BACK_END.lock().unwrap() = BackEnd::Asio; - Ok(()) -} - -/// Choose Wasapi as the backend -pub fn use_wasapi_backend() -> Result<(), BackEndError> { - *BACK_END.lock().unwrap() = BackEnd::Wasapi; - Ok(()) -} - -#[derive(Debug)] -pub struct BackEndError; \ No newline at end of file diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 74030a5..f97697e 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -516,6 +516,15 @@ mod platform_impl { // TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] mod platform_impl { + pub use crate::host::asio::{ + Device as AsioDevice, + Devices as AsioDevices, + EventLoop as AsioEventLoop, + Host as AsioHost, + StreamId as AsioStreamId, + SupportedInputFormats as AsioSupportedInputFormats, + SupportedOutputFormats as AsioSupportedOutputFormats, + }; pub use crate::host::wasapi::{ Device as WasapiDevice, Devices as WasapiDevices, @@ -526,7 +535,7 @@ mod platform_impl { SupportedOutputFormats as WasapiSupportedOutputFormats, }; - impl_platform_host!(Wasapi wasapi); + impl_platform_host!(Asio asio, Wasapi wasapi); /// The default host for the current compilation target platform. pub fn default_host() -> Host { diff --git a/src/platform/windows/asio/mod.rs b/src/platform/windows/asio/mod.rs deleted file mode 100644 index 1d49f42..0000000 --- a/src/platform/windows/asio/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -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; -mod asio_utils; \ No newline at end of file diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs deleted file mode 100644 index b274ec0..0000000 --- a/src/platform/windows/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(windows)] -mod asio; From 747caeee38adbe974f4d8f7aa1dc8a3cbf430b9d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 27 Jun 2019 21:29:06 +1000 Subject: [PATCH 52/75] Get asio-sys examples working with latest API updates --- asio-sys/examples/enumerate.rs | 47 +++++++++++----------------------- asio-sys/examples/test.rs | 9 +++---- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/asio-sys/examples/enumerate.rs b/asio-sys/examples/enumerate.rs index df29ce4..bcbc95b 100644 --- a/asio-sys/examples/enumerate.rs +++ b/asio-sys/examples/enumerate.rs @@ -34,39 +34,22 @@ pub struct Format { } 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 - } + for name in sys::get_driver_list() { + println!("Driver: {:?}", name); + let driver = sys::Drivers::load(&name).expect("failed to load driver"); + let channels = driver.get_channels(); + let sample_rate = driver.get_sample_rate(); + let in_fmt = Format { + channels: channels.ins as _, + sample_rate: SampleRate(sample_rate.rate as _), + data_type: SampleFormat::F32, }; - - 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 - } + let out_fmt = Format { + channels: channels.outs as _, + sample_rate: SampleRate(sample_rate.rate as _), + data_type: SampleFormat::F32, }; - - println!("Format {:?}", format); + println!(" Input {:?}", in_fmt); + println!(" Output {:?}", out_fmt); } } diff --git a/asio-sys/examples/test.rs b/asio-sys/examples/test.rs index 88d532e..a9d77d9 100644 --- a/asio-sys/examples/test.rs +++ b/asio-sys/examples/test.rs @@ -5,12 +5,9 @@ fn main() { 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), - } + let driver = sys::Drivers::load(driver).expect("failed to load drivers"); + println!(" Channels: {:?}", driver.get_channels()); + println!(" Sample rate: {:?}", driver.get_sample_rate()); } } From 05f8b7dc43b6b45258e4d04d0e2eece5a308744e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 27 Jun 2019 21:49:27 +1000 Subject: [PATCH 53/75] Fix remaining compile errors for ASIO host Now runs the beep and enumerate examples nicely! Time to do a proper code review of the ASIO stuff and see how to best take advantage of the new `Host` API. --- src/host/asio/device.rs | 1 + src/host/asio/mod.rs | 10 ++++++---- src/host/asio/stream.rs | 6 ++---- src/platform/mod.rs | 5 +++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index c6c085b..a0741e3 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -5,6 +5,7 @@ pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; use DefaultFormatError; use DeviceNameError; +use DevicesError; use Format; use SampleFormat; use SampleRate; diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index f1b22dc..028c919 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -7,13 +7,14 @@ use { DeviceNameError, DevicesError, EventLoop as EventLoopTrait, + Format, Host as HostTrait, PauseStreamError, PlayStreamError, StreamDataResult, StreamId as StreamIdTrait, SupportedFormatsError, -} +}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; pub use self::stream::{EventLoop, StreamId}; @@ -28,8 +29,8 @@ pub struct Host; impl Host { pub fn new() -> Result { - unimplemented!("asio as an initialisation and termination process that needs to be impld"); - //Ok(Host) + //unimplemented!("asio as an initialisation and termination process that needs to be impld"); + Ok(Host) } } @@ -39,7 +40,8 @@ impl HostTrait for Host { type EventLoop = EventLoop; fn is_available() -> bool { - unimplemented!("check how to do this using asio-sys") + true + //unimplemented!("check how to do this using asio-sys") } fn devices(&self) -> Result { diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 2beb37d..6b484c9 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -679,10 +679,6 @@ impl EventLoop { ) => { let mut my_buffers = $Buffers; { - // Wrap the cpal buffer - let buff = OutputBuffer { - buffer: &mut my_buffers.cpal, - }; // call the callback to fill the buffer with // users data callback( @@ -894,6 +890,7 @@ impl EventLoop { } // Calling play when already playing is a no-op sys::play(); + Ok(()) } /// Pause the cpal stream for the given ID. @@ -912,6 +909,7 @@ impl EventLoop { if any_playing { sys::stop(); } + Ok(()) } /// Destroy the cpal stream based on the ID. diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f97697e..7eb2081 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -269,6 +269,7 @@ macro_rules! impl_platform_host { .map(StreamId) } )* + _ => panic!("tried to build a stream with a device from another host"), } } @@ -285,6 +286,7 @@ macro_rules! impl_platform_host { .map(StreamId) } )* + _ => panic!("tried to build a stream with a device from another host"), } } @@ -295,6 +297,7 @@ macro_rules! impl_platform_host { e.play_stream(s.clone()) } )* + _ => panic!("tried to play a stream with an ID associated with another host"), } } @@ -305,6 +308,7 @@ macro_rules! impl_platform_host { e.pause_stream(s.clone()) } )* + _ => panic!("tried to pause a stream with an ID associated with another host"), } } @@ -315,6 +319,7 @@ macro_rules! impl_platform_host { e.destroy_stream(s.clone()) } )* + _ => panic!("tried to destroy a stream with an ID associated with another host"), } } From efe683133ca984782471f215bfcc574d33421211 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 28 Jun 2019 03:00:52 +1000 Subject: [PATCH 54/75] Refactor asio-sys to use less global state and more RAII The asio-sys examples have been tested and still work nicely. The CPAL asio host has not yet been updated for these changes. --- asio-sys/examples/enumerate.rs | 13 +- asio-sys/examples/test.rs | 12 +- asio-sys/src/bindings/errors.rs | 98 ++- asio-sys/src/bindings/mod.rs | 1069 +++++++++++++------------------ 4 files changed, 486 insertions(+), 706 deletions(-) diff --git a/asio-sys/examples/enumerate.rs b/asio-sys/examples/enumerate.rs index bcbc95b..edc2623 100644 --- a/asio-sys/examples/enumerate.rs +++ b/asio-sys/examples/enumerate.rs @@ -34,19 +34,20 @@ pub struct Format { } fn main() { - for name in sys::get_driver_list() { + let asio = sys::Asio::new(); + for name in asio.driver_names() { println!("Driver: {:?}", name); - let driver = sys::Drivers::load(&name).expect("failed to load driver"); - let channels = driver.get_channels(); - let sample_rate = driver.get_sample_rate(); + let driver = asio.load_driver(&name).expect("failed to load driver"); + let channels = driver.channels().expect("failed to retrieve channel counts"); + let sample_rate = driver.sample_rate().expect("failed to retrieve sample rate"); let in_fmt = Format { channels: channels.ins as _, - sample_rate: SampleRate(sample_rate.rate as _), + sample_rate: SampleRate(sample_rate as _), data_type: SampleFormat::F32, }; let out_fmt = Format { channels: channels.outs as _, - sample_rate: SampleRate(sample_rate.rate as _), + sample_rate: SampleRate(sample_rate as _), data_type: SampleFormat::F32, }; println!(" Input {:?}", in_fmt); diff --git a/asio-sys/examples/test.rs b/asio-sys/examples/test.rs index a9d77d9..3aa5869 100644 --- a/asio-sys/examples/test.rs +++ b/asio-sys/examples/test.rs @@ -1,13 +1,11 @@ extern crate asio_sys as sys; fn main() { - let driver_list = sys::get_driver_list(); - - for driver in &driver_list { + let asio = sys::Asio::new(); + for driver in asio.driver_names() { println!("Driver: {}", driver); - - let driver = sys::Drivers::load(driver).expect("failed to load drivers"); - println!(" Channels: {:?}", driver.get_channels()); - println!(" Sample rate: {:?}", driver.get_sample_rate()); + let driver = asio.load_driver(&driver).expect("failed to load drivers"); + println!(" Channels: {:?}", driver.channels().expect("failed to get channels")); + println!(" Sample rate: {:?}", driver.sample_rate().expect("failed to get sample rate")); } } diff --git a/asio-sys/src/bindings/errors.rs b/asio-sys/src/bindings/errors.rs index 43f41bb..4c770e6 100644 --- a/asio-sys/src/bindings/errors.rs +++ b/asio-sys/src/bindings/errors.rs @@ -1,14 +1,15 @@ use std::error::Error; use std::fmt; +/// Errors that might occur during `Asio::load_driver`. #[derive(Debug)] -pub enum AsioDriverError { - NoResult(String), - BufferError(String), - DriverLoadError, - TypeError, +pub enum LoadDriverError { + LoadDriverFailed, + DriverAlreadyExists, + InitializationFailed(AsioError), } +/// General errors returned by ASIO. #[derive(Debug)] pub enum AsioError { NoDrivers, @@ -35,25 +36,9 @@ pub enum AsioErrorWrapper { Invalid, } -impl fmt::Display for AsioDriverError { +impl fmt::Display for LoadDriverError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - AsioDriverError::NoResult(ref e) => write!(f, "Driver {} not found", e), - AsioDriverError::BufferError(ref e) => write!(f, "Buffer Error: {}", e), - AsioDriverError::DriverLoadError => write!(f, "Couldn't load the driver"), - AsioDriverError::TypeError => write!(f, "Couldn't convert sample type"), - } - } -} - -impl Error for AsioDriverError { - fn description(&self) -> &str { - match *self { - AsioDriverError::NoResult(_) => "Couln't find driver", - AsioDriverError::BufferError(_) => "Error creating the buffer", - AsioDriverError::DriverLoadError => "Error loading the driver", - AsioDriverError::TypeError => "Error getting sample type", - } + write!(f, "{}", self.description()) } } @@ -83,6 +68,20 @@ impl fmt::Display for AsioError { } } +impl Error for LoadDriverError { + fn description(&self) -> &str { + match *self { + LoadDriverError::LoadDriverFailed => { + "ASIO `loadDriver` function returned `false` indicating failure" + } + LoadDriverError::InitializationFailed(ref err) => err.description(), + LoadDriverError::DriverAlreadyExists => { + "ASIO only supports loading one driver at a time" + } + } + } +} + impl Error for AsioError { fn description(&self) -> &str { match *self { @@ -99,42 +98,27 @@ impl Error for AsioError { } } } -macro_rules! asio_error_helper { - ($x:expr, $ae:ident{ $($v:ident),+ }, $inval:ident) => { - match $x { - $(_ if $x == $ae::$v as i32 => $ae::$v,)+ - _ => $ae::$inval, - } - }; + +impl From for LoadDriverError { + fn from(err: AsioError) -> Self { + LoadDriverError::InitializationFailed(err) + } } macro_rules! asio_result { - ($result:expr) => { - match asio_error_helper!( - $result, - AsioErrorWrapper { - ASE_OK, - ASE_SUCCESS, - ASE_NotPresent, - ASE_HWMalfunction, - ASE_InvalidParameter, - ASE_InvalidMode, - ASE_SPNotAdvancing, - ASE_NoClock, - ASE_NoMemory - }, - Invalid - ) { - AsioErrorWrapper::ASE_OK => Ok(()), - AsioErrorWrapper::ASE_SUCCESS => Ok(()), - AsioErrorWrapper::ASE_NotPresent => Err(AsioError::NoDrivers), - AsioErrorWrapper::ASE_HWMalfunction => Err(AsioError::HardwareMalfunction), - AsioErrorWrapper::ASE_InvalidParameter => Err(AsioError::InvalidInput), - AsioErrorWrapper::ASE_InvalidMode => Err(AsioError::BadMode), - AsioErrorWrapper::ASE_SPNotAdvancing => Err(AsioError::HardwareStuck), - AsioErrorWrapper::ASE_NoClock => Err(AsioError::NoRate), - AsioErrorWrapper::ASE_NoMemory => Err(AsioError::ASE_NoMemory), - AsioErrorWrapper::Invalid => Err(AsioError::UnknownError), + ($e:expr) => {{ + let res = { $e }; + match res { + r if r == AsioErrorWrapper::ASE_OK as i32 => Ok(()), + r if r == AsioErrorWrapper::ASE_SUCCESS as i32 => Ok(()), + r if r == AsioErrorWrapper::ASE_NotPresent as i32 => Err(AsioError::NoDrivers), + r if r == AsioErrorWrapper::ASE_HWMalfunction as i32 => Err(AsioError::HardwareMalfunction), + r if r == AsioErrorWrapper::ASE_InvalidParameter as i32 => Err(AsioError::InvalidInput), + r if r == AsioErrorWrapper::ASE_InvalidMode as i32 => Err(AsioError::BadMode), + r if r == AsioErrorWrapper::ASE_SPNotAdvancing as i32 => Err(AsioError::HardwareStuck), + r if r == AsioErrorWrapper::ASE_NoClock as i32 => Err(AsioError::NoRate), + r if r == AsioErrorWrapper::ASE_NoMemory as i32 => Err(AsioError::ASE_NoMemory), + _ => Err(AsioError::UnknownError), } - }; + }}; } diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 596b1ca..4c3b8e5 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -2,99 +2,81 @@ mod asio_import; #[macro_use] pub mod errors; -use self::errors::{AsioDriverError, AsioError, AsioErrorWrapper}; +use self::errors::{AsioError, AsioErrorWrapper, LoadDriverError}; use std::ffi::CStr; use std::ffi::CString; 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::sync::atomic::{self, AtomicBool}; +use std::sync::{Arc, Mutex}; use std; -use num; // Bindings import use self::asio_import as ai; -/// 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. +/// A handle to the ASIO API. +/// +/// There should only be one instance of this type at any point in time. #[derive(Debug)] -pub struct Channel { - pub ins: i64, - pub outs: i64, +pub struct Asio { + // Keeps track of whether or not a driver is already loaded. + // + // This is necessary as ASIO only supports one `Driver` at a time. + driver_loaded: Arc, } -/// Sample rate of the ASIO device. +/// A handle to a single ASIO driver. +/// +/// Creating an instance of this type loads and initialises the driver. +/// +/// Dropping the instance will dispose of any resources and de-initialise the driver. #[derive(Debug)] -pub struct SampleRate { - pub rate: u32, +pub struct Driver { + state: Mutex, + // A flag that is set to `false` when the `Driver` is dropped. + // + // This lets the `Asio` handle know that a new driver can be loaded. + loaded: Arc, } -/// 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, +/// All possible states of an ASIO `Driver` instance. +/// +/// Mapped to the finite state machine in the ASIO SDK docs. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum DriverState { Initialized, Prepared, Running, } +/// Amount of input and output +/// channels available. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Channels { + pub ins: c_long, + pub outs: c_long, +} + +/// Sample rate of the ASIO driver. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SampleRate { + pub rate: u32, +} + +/// Holds the pointer to the callbacks that come from cpal +struct BufferCallback(Box); + /// Input and Output streams. -/// There is only ever max one -/// input and one output. Only one -/// is required. +/// +/// 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 @@ -171,150 +153,159 @@ struct AsioCallbacks { ) -> *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(); +lazy_static! { + /// 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. + static ref BUFFER_CALLBACK: Mutex>> = Mutex::new(Vec::new()); +} - for mut bc in bcs.iter_mut() { - if let Some(ref mut bc) = bc { - bc.run(double_buffer_index); +impl Asio { + /// Initialise the ASIO API. + pub fn new() -> Self { + let driver_loaded = Arc::new(AtomicBool::new(false)); + Asio { driver_loaded } + } + + /// Returns the name for each available driver. + /// + /// This is used at the start to allow the user to choose which driver they want. + pub fn driver_names(&self) -> Vec { + // The most drivers 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 name = CStr::from_ptr(p_driver_name[i as usize]); + let my_driver_name = name.to_owned(); + my_driver_name + .into_string() + .expect("Failed to convert driver name") + }).collect() + } + } + + /// Whether or not a driver has already been loaded by this process. + /// + /// This can be useful to check before calling `load_driver` as ASIO only supports loading a + /// single driver at a time. + /// + /// Uses the given atomic ordering to access the atomic boolean used to track driver loading. + pub fn is_driver_loaded(&self, ord: atomic::Ordering) -> bool { + self.driver_loaded.load(ord) + } + + /// Load a driver from the given name. + /// + /// Driver names compatible with this method can be produced via the `asio.driver_names()` + /// method. + /// + /// NOTE: Despite many requests from users, ASIO only supports loading a single driver at a + /// time. Calling this method while a previously loaded `Driver` instance exists will result in + /// an error. + pub fn load_driver(&self, driver_name: &str) -> Result { + if self.driver_loaded.load(atomic::Ordering::SeqCst) { + return Err(LoadDriverError::DriverAlreadyExists); + } + + // Make owned CString to send to load driver + let my_driver_name = CString::new(driver_name) + .expect("failed to create `CString` from driver name"); + let mut driver_info = ai::ASIODriverInfo { + _bindgen_opaque_blob: [0u32; 43], + }; + + unsafe { + // TODO: Check that a driver of the same name does not already exist? + match ai::load_asio_driver(my_driver_name.as_ptr() as *mut i8) { + false => Err(LoadDriverError::LoadDriverFailed), + true => { + // Initialize ASIO. + asio_result!(ai::ASIOInit(&mut driver_info))?; + self.driver_loaded.store(true, atomic::Ordering::SeqCst); + let loaded = self.driver_loaded.clone(); + let state = Mutex::new(DriverState::Initialized); + let driver = Driver { state, loaded }; + Ok(driver) + } + } } } } -/// 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 +impl BufferCallback { + /// Calls the inner callback + fn run(&mut self, index: i32) { + let cb = &mut self.0; + cb(index); + } } -/// 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 my_driver_name = CString::new(driver_name).expect("Can't go from str to CString"); - let mut driver_info = ai::ASIODriverInfo { - _bindgen_opaque_blob: [0u32; 43], - }; +impl Driver { + /// Returns the number of input and output channels available on the driver. + pub fn channels(&self) -> Result { + let mut ins: c_long = 0; + let mut outs: c_long = 0; unsafe { - // Destroy old drivers and load new drivers. - //let load_result = drivers.load(raw); - let load_result = drivers.load(my_driver_name.as_ptr() as *mut i8); - 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) + asio_result!(ai::ASIOGetChannels(&mut ins, &mut outs))?; + } + let channel = Channels { ins, outs }; + Ok(channel) + } + + /// Get current sample rate of the driver. + pub fn sample_rate(&self) -> Result { + let mut rate: c_double = 0.0; + unsafe { + asio_result!(ai::get_sample_rate(&mut rate))?; + } + Ok(rate) + } + + /// Can the driver accept the given sample rate. + pub fn can_sample_rate(&self, sample_rate: c_double) -> Result { + unsafe { + match asio_result!(ai::can_sample_rate(sample_rate)) { + Ok(()) => Ok(true), + Err(AsioError::NoRate) => Ok(false), + Err(err) => Err(err), } } } - /// 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; + /// Set the sample rate for the driver. + pub fn set_sample_rate(&self, sample_rate: c_double) -> Result<(), AsioError> { 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, - }; + asio_result!(ai::set_sample_rate(sample_rate))?; } - - channel + Ok(()) } - /// 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 + /// Get the current data type of the driver. + /// + /// This queries a single channel's type assuming all channels have the same sample type. + /// + /// 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. + pub fn data_type(&self) -> Result { let mut channel_info = ai::ASIOChannelInfo { // Which channel we are querying channel: 0, @@ -328,29 +319,146 @@ impl Drivers { 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) - }, + asio_result!(ai::ASIOGetChannelInfo(&mut channel_info))?; + Ok(num::FromPrimitive::from_i32(channel_info.type_).expect("failed to cast sample type")) + } + } + + /// 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: &mut [AsioBufferInfo]) -> Result { + let num_channels = buffer_infos.len(); + 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, + }; + // To pass as ai::ASIOCallbacks + let callbacks: *mut _ = &mut callbacks; + 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; + + unsafe { + // Get the buffer sizes + // min possilbe size + // max possible size + // preferred size + // granularity + asio_result!(ai::ASIOGetBufferSize( + &mut min_b_size, + &mut max_b_size, + &mut pref_b_size, + &mut grans, + ))?; + + if pref_b_size <= 0 { + panic!( + "`ASIOGetBufferSize` produced unusable preferred buffer size of {}", + pref_b_size, + ); } + + if let DriverState::Running = self.state() { + self.stop()?; + } + if let DriverState::Prepared = self.state() { + self.dispose_buffers()?; + } + + asio_result!(ai::ASIOCreateBuffers( + buffer_infos.as_mut_ptr() as *mut _, + num_channels as i32, + pref_b_size, + callbacks as *mut _, + ))?; + } + self.set_state(DriverState::Prepared); + Ok(pref_b_size) + } + + /// 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(&mut bi).map(|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(mut input), None) => { + self.create_buffers(&mut input.buffer_infos) + .map(|buffer_size| { + AsioStreams { + input: Some(AsioStream { + buffer_infos: input.buffer_infos, + buffer_size, + }), + output: None, + } + }) + }, + // Just output + (None, Some(mut output)) => { + self.create_buffers(&mut output.buffer_infos) + .map(|buffer_size| { + AsioStreams { + output: Some(AsioStream { + buffer_infos: output.buffer_infos, + buffer_size, + }), + input: None, + } + }) + }, + // Impossible + (None, None) => panic!("Trying to create streams without preparing"), } } /// 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. + /// + /// 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 desired 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 { + &self, + output: Option, + num_channels: usize, + ) -> Result { let buffer_infos = (0 .. num_channels) .map(|i| AsioBufferInfo { // These are output channels @@ -383,8 +491,10 @@ impl Drivers { /// 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 { + &self, + input: Option, + num_channels: usize, + ) -> Result { // Initialize data for FFI let buffer_infos = (0 .. num_channels) .map(|i| AsioBufferInfo { @@ -408,456 +518,143 @@ impl Drivers { 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"), - } + fn state(&self) -> DriverState { + *self.state.lock().expect("failed to lock `DriverState`") } - /// 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, mut buffer_infos: Vec, - ) -> Result<(Vec, c_long), AsioDriverError> { - let num_channels = buffer_infos.len(); - 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, - }; - // To pass as ai::ASIOCallbacks - let callbacks: *mut _ = &mut callbacks; - - 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(); + fn set_state(&self, state: DriverState) { + *self.state.lock().expect("failed to lock `DriverState`") = state; + } + /// Releases buffers allocations. + /// + /// This will `stop` the stream if the driver is `Running`. + /// + /// No-op if no buffers are allocated. + pub fn dispose_buffers(&self) -> Result<(), AsioError> { + if let DriverState::Initialized = self.state() { + return Ok(()); + } + if let DriverState::Running = self.state() { + self.stop()?; + } 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 { - drivers - .asio_create_buffers( - //buffer_info_convert.as_mut_ptr(), - buffer_infos.as_mut_ptr() as *mut _, - num_channels as i32, - pref_b_size, - callbacks as *mut _, - ).map(|_| { - (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())) + asio_result!(ai::ASIODisposeBuffers())?; + } + self.set_state(DriverState::Initialized); + Ok(()) + } + + /// Starts ASIO streams playing. + /// + /// The driver must be in the `Prepared` state + /// + /// If called successfully, the driver will be in the `Running` state. + /// + /// No-op if already `Running`. + pub fn start(&self) -> Result<(), AsioError> { + if let DriverState::Running = self.state() { + return Ok(()); + } + unsafe { + asio_result!(ai::ASIOStart())?; + } + self.set_state(DriverState::Running); + Ok(()) + } + + /// Stops ASIO streams playing. + /// + /// No-op if the state is not `Running`. + /// + /// If the state was `Running` and the stream is stopped successfully, the driver will be in + /// the `Prepared` state. + pub fn stop(&self) -> Result<(), AsioError> { + if let DriverState::Running = self.state() { + unsafe { + asio_result!(ai::ASIOStop())?; } + self.set_state(DriverState::Prepared); } + Ok(()) + } + + /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing + /// any associated resources. + pub fn destroy(mut self) -> Result<(), AsioError> { + self.destroy_inner() + } + + fn destroy_inner(&mut self) -> Result<(), AsioError> { + // Drop back through the driver state machine one state at a time. + if let DriverState::Running = self.state() { + self.stop().expect("failed to stop ASIO driver"); + } + if let DriverState::Prepared = self.state() { + self.dispose_buffers().expect("failed to dispose buffers of ASIO driver"); + } + unsafe { + asio_result!(ai::ASIOExit())?; + ai::remove_current_driver(); + } + self.loaded.store(false, atomic::Ordering::SeqCst); + Ok(()) } } -/// If all drivers and streams are gone -/// clean up drivers -impl Drop for Drivers { +impl Drop for Driver { fn drop(&mut self) { - let count = STREAM_DRIVER_COUNT.fetch_sub(1, Ordering::SeqCst); - if count == 1 { - clean_up(); + if self.loaded.load(atomic::Ordering::SeqCst) { + // We probably shouldn't `panic!` in the destructor? We also shouldn't ignore errors + // though either. + self.destroy_inner().ok(); } } } -/// 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(); + 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; +/// 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) -> () {} - // 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() - } +/// 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 } -/// Cleans up the drivers and -/// any allocations -pub fn clean_up() { - let mut drivers = get_drivers(); - drivers.clean_up(); +/// 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 } -/// Starts input and output streams playing -pub fn play() { - unsafe { - get_drivers().asio_start().expect("Failed to start asio playing"); +/// 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); + } } } - -/// Stops input and output streams playing -pub fn stop() { - unsafe { - get_drivers().asio_stop().expect("Failed to stop asio"); - } -} - -/// 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 c_char) -> 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 From d739a5b79d1f29b1bc6b5beb188b9b7fd0af7fcd Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 28 Jun 2019 04:43:58 +1000 Subject: [PATCH 55/75] Majority of ASIO host update following refactor Currently not compiling - still need to address some global items within asio-sys, including the `set_callback` function and the double buffer globals. --- asio-sys/src/lib.rs | 4 +- src/host/asio/device.rs | 128 ++-- src/host/asio/mod.rs | 20 +- src/host/asio/stream.rs | 1371 ++++++++++++++++++++------------------- src/lib.rs | 6 + 5 files changed, 788 insertions(+), 741 deletions(-) diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 53be5fd..9a2acc6 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -12,4 +12,6 @@ extern crate num_derive; #[cfg(asio)] pub mod bindings; #[cfg(asio)] -pub use bindings::*; \ No newline at end of file +pub use bindings::*; +#[cfg(asio)] +pub use bindings::errors::{AsioError, LoadDriverError}; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index a0741e3..c8566e0 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -3,6 +3,8 @@ pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; +use std::sync::Arc; +use BackendSpecificError; use DefaultFormatError; use DeviceNameError; use DevicesError; @@ -14,16 +16,17 @@ use SupportedFormatsError; use super::sys; /// A ASIO Device -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Device { /// The drivers for this device - pub drivers: sys::Drivers, + pub driver: Arc, /// The name of this device pub name: String, } /// All available devices pub struct Devices { + asio: Arc, drivers: std::vec::IntoIter, } @@ -62,7 +65,7 @@ impl Device { // Collect a format for every combination of supported sample rate and number of channels. let mut supported_formats = vec![]; for &rate in ::COMMON_SAMPLE_RATES { - if !self.drivers.can_sample_rate(rate.0 as u32) { + if !self.driver.can_sample_rate(rate.0.into()).ok().unwrap_or(false) { continue; } for channels in 1..f.channels + 1 { @@ -90,7 +93,7 @@ impl Device { // Collect a format for every combination of supported sample rate and number of channels. let mut supported_formats = vec![]; for &rate in ::COMMON_SAMPLE_RATES { - if !self.drivers.can_sample_rate(rate.0 as u32) { + if !self.driver.can_sample_rate(rate.0.into()).ok().unwrap_or(false) { continue; } for channels in 1..f.channels + 1 { @@ -104,50 +107,36 @@ impl Device { /// Returns the default input format pub fn default_input_format(&self) -> Result { - let channels = self.drivers.get_channels().ins as u16; - let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); + let channels = self.driver.channels().map_err(default_format_err)?.ins as u16; + let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); // Map th ASIO sample type to a CPAL sample type - match self.drivers.get_data_type() { - Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTInt32MSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), - Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), - _ => Err(DefaultFormatError::StreamTypeNotSupported), - }.map(|dt| Format { + let data_type = self.driver.data_type().map_err(default_format_err)?; + let data_type = convert_data_type(data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; + Ok(Format { channels, sample_rate, - data_type: dt, + data_type, }) } /// Returns the default output format pub fn default_output_format(&self) -> Result { - let channels = self.drivers.get_channels().outs as u16; - let sample_rate = SampleRate(self.drivers.get_sample_rate().rate); - match self.drivers.get_data_type() { - // Map th ASIO sample type to a CPAL sample type - Ok(sys::AsioSampleType::ASIOSTInt16MSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32MSB) => Ok(SampleFormat::F32), - Ok(sys::AsioSampleType::ASIOSTInt16LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTInt32LSB) => Ok(SampleFormat::I16), - Ok(sys::AsioSampleType::ASIOSTFloat32LSB) => Ok(SampleFormat::F32), - _ => Err(DefaultFormatError::StreamTypeNotSupported), - }.map(|dt| Format { + let channels = self.driver.channels().map_err(default_format_err)?.outs as u16; + let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); + let data_type = self.driver.data_type().map_err(default_format_err)?; + let data_type = convert_data_type(data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; + Ok(Format { channels, sample_rate, - data_type: dt, + data_type, }) } } impl Devices { - pub fn new() -> Result { - let driver_names = online_devices(); - Ok(Devices { - drivers: driver_names.into_iter(), - }) + pub fn new(asio: Arc) -> Result { + let drivers = asio.driver_names().into_iter(); + Ok(Devices { asio, drivers }) } } @@ -156,14 +145,14 @@ impl Iterator for Devices { /// Load drivers and return device fn next(&mut self) -> Option { - match self.drivers.next() { - Some(name) => sys::Drivers::load(&name) - .or_else(|e| { - eprintln!("{}", e); - Err(e) - }).ok() - .map(|drivers| Device { drivers, name }), - None => None, + loop { + match self.drivers.next() { + Some(name) => match self.asio.load_driver(&name) { + Ok(driver) => return Some(Device { driver: Arc::new(driver), name }), + Err(_) => continue, + } + None => return None, + } } } @@ -172,35 +161,40 @@ 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 { - first_device() +fn convert_data_type(ty: sys::AsioSampleType) -> Option { + let fmt = match ty { + sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32, + sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32, + _ => return None, + }; + Some(fmt) } -/// 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| { - eprintln!("{}", e); - Err(e) - }).ok() - .map(|drivers| Device { drivers, name }), - None => None, +fn default_format_err(e: sys::AsioError) -> DefaultFormatError { + match e { + sys::AsioError::NoDrivers | + sys::AsioError::HardwareMalfunction => DefaultFormatError::DeviceNotAvailable, + sys::AsioError::NoRate => DefaultFormatError::StreamTypeNotSupported, + err => { + let description = format!("{}", err); + BackendSpecificError { description }.into() + } } } -/// Remove offline drivers -fn online_devices() -> Vec { - sys::get_driver_list() - .into_iter() - .filter(|name| sys::Drivers::load(&name).is_ok()) - .collect() +fn supported_formats_err(e: sys::AsioError) -> SupportedFormatsError { + match e { + sys::AsioError::NoDrivers | + sys::AsioError::HardwareMalfunction => SupportedFormatsError::DeviceNotAvailable, + sys::AsioError::InvalidInput | + sys::AsioError::BadMode => SupportedFormatsError::InvalidArgument, + err => { + let description = format!("{}", err); + BackendSpecificError { description }.into() + } + } } diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 028c919..7e5172c 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -16,8 +16,9 @@ use { SupportedFormatsError, }; -pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; +pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats}; pub use self::stream::{EventLoop, StreamId}; +use std::sync::Arc; mod device; mod stream; @@ -25,12 +26,15 @@ mod asio_utils; /// The host for ASIO. #[derive(Debug)] -pub struct Host; +pub struct Host { + asio: Arc, +} impl Host { pub fn new() -> Result { - //unimplemented!("asio as an initialisation and termination process that needs to be impld"); - Ok(Host) + let asio = Arc::new(sys::Asio::new()); + let host = Host { asio }; + Ok(host) } } @@ -45,15 +49,17 @@ impl HostTrait for Host { } fn devices(&self) -> Result { - Devices::new() + Devices::new(self.asio.clone()) } fn default_input_device(&self) -> Option { - default_input_device() + // ASIO has no concept of a default device, so just use the first. + self.input_devices().ok().and_then(|mut ds| ds.next()) } fn default_output_device(&self) -> Option { - default_output_device() + // ASIO has no concept of a default device, so just use the first. + self.output_devices().ok().and_then(|mut ds| ds.next()) } fn event_loop(&self) -> Self::EventLoop { diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 6b484c9..6344dcf 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; +use BackendSpecificError; use BuildStreamError; use Format; use PauseStreamError; @@ -45,6 +46,8 @@ pub struct StreamId(usize); /// Each stream can be playing or paused. struct Stream { playing: bool, + // The driver associated with this stream. + driver: Arc, } #[derive(Default)] @@ -86,7 +89,7 @@ impl EventLoop { fn check_format( &self, - drivers: &sys::Drivers, + driver: &sys::Driver, format: &Format, num_asio_channels: u16, ) -> Result<(), BuildStreamError> { @@ -96,12 +99,12 @@ impl EventLoop { data_type, } = format; // Try and set the sample rate to what the user selected. - let sample_rate = sample_rate.0; - if sample_rate != drivers.get_sample_rate().rate { - if drivers.can_sample_rate(sample_rate) { - drivers + let sample_rate = sample_rate.0.into(); + if sample_rate != driver.sample_rate().map_err(build_stream_err)? { + if driver.can_sample_rate(sample_rate).map_err(build_stream_err)? { + driver .set_sample_rate(sample_rate) - .expect("Unsupported sample rate"); + .map_err(build_stream_err)?; } else { return Err(BuildStreamError::FormatNotSupported); } @@ -122,14 +125,14 @@ impl EventLoop { /// it will be created. fn get_input_stream( &self, - drivers: &sys::Drivers, + driver: &sys::Driver, format: &Format, device: &Device, ) -> Result { match device.default_input_format() { Ok(f) => { let num_asio_channels = f.channels; - self.check_format(drivers, format, num_asio_channels) + self.check_format(driver, format, num_asio_channels) }, Err(_) => Err(BuildStreamError::FormatNotSupported), }?; @@ -141,7 +144,7 @@ impl EventLoop { Some(ref input) => Ok(input.buffer_size as usize), None => { let output = streams.output.take(); - drivers + driver .prepare_input_stream(output, num_channels) .map(|new_streams| { let bs = match new_streams.input { @@ -163,14 +166,14 @@ impl EventLoop { /// it will be created. fn get_output_stream( &self, - drivers: &sys::Drivers, + driver: &sys::Driver, format: &Format, device: &Device, ) -> Result { match device.default_output_format() { Ok(f) => { let num_asio_channels = f.channels; - self.check_format(drivers, format, num_asio_channels) + self.check_format(driver, format, num_asio_channels) }, Err(_) => Err(BuildStreamError::FormatNotSupported), }?; @@ -182,7 +185,7 @@ impl EventLoop { Some(ref output) => Ok(output.buffer_size as usize), None => { let input = streams.input.take(); - drivers + driver .prepare_output_stream(input, num_channels) .map(|new_streams| { let bs = match new_streams.output { @@ -205,328 +208,327 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - let Device { drivers, .. } = device; + let Device { driver, .. } = device; let num_channels = format.channels.clone(); - let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let input_stream = self.get_input_stream(&drivers, format, device); - input_stream.map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); + let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_buffer_size = self.get_input_stream(&driver, format, device)?; + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; + let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - // TODO the naming of cpal and channel is confusing. - // change it to: - // cpal -> interleaved - // channels -> per_channel - let mut buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), + // Create buffers depending on data type + // TODO the naming of cpal and channel is confusing. + // change it to: + // cpal -> interleaved + // channels -> per_channel + let mut buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, + f32_buff: F32Buffer::default(), + }, + SampleFormat::F32 => Buffers { + i16_buff: I16Buffer::default(), + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + + // Set the input callback. + // This is most performance critical part of the ASIO bindings. + sys::set_callback(move |index| unsafe { + // if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } + } + // Get the stream + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return (), }; - // Set the input callback. - // This is most performance critical part of the ASIO bindings. - sys::set_callback(move |index| unsafe { - // if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return (); - } + // Get the callback + let mut callbacks = callbacks.lock().unwrap(); + + // Theres only a single callback because theres only one event loop + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return (), + }; + + // Macro to convert sample from ASIO to CPAL type + macro_rules! convert_sample { + // floats types required different conversion + (f32, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample + }; + (f64, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample + }; + (f64, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f32 + }; + (f32, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 + }; + ($AsioTypeIdent:ident, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 + }; + ($AsioTypeIdent:ident, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 + }; + (f32, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + }; + (f64, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + }; + ($AsioTypeIdent:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 + / ::std::$AsioTypeIdent::MAX as i64) as $SampleType + }; + }; + // This creates gets the buffer and interleaves it. + // It allows it to be done based on the sample type. + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr + ) => { + // For each channel write the asio buffer to + // the cpal buffer + + for (i, channel) in $Buffers.channel.iter_mut().enumerate() { + let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] + as *mut $AsioType; + let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( + buff_ptr, + asio_stream.buffer_size as usize, + ); + for asio_s in asio_buffer.iter() { + channel.push($ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $SampleType, + $SampleTypeIdent, + asio_s + ), + $Endianness, + )); } } - } - // Get the stream - let stream_lock = asio_streams.lock().unwrap(); - let ref asio_stream = match stream_lock.input { - Some(ref asio_stream) => asio_stream, - None => return (), - }; - // Get the callback - let mut callbacks = callbacks.lock().unwrap(); - - // Theres only a single callback because theres only one event loop - let callback = match callbacks.as_mut() { - Some(callback) => callback, - None => return (), - }; - - // Macro to convert sample from ASIO to CPAL type - macro_rules! convert_sample { - // floats types required different conversion - (f32, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample - }; - (f64, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample - }; - (f64, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f32 - }; - (f32, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 - }; - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 - }; - (f32, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - }; - (f64, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - }; - ($AsioTypeIdent:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - / ::std::$AsioTypeIdent::MAX as i64) as $SampleType - }; - }; - // This creates gets the buffer and interleaves it. - // It allows it to be done based on the sample type. - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - // For each channel write the asio buffer to - // the cpal buffer - - for (i, channel) in $Buffers.channel.iter_mut().enumerate() { - let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] - as *mut $AsioType; - let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize, - ); - for asio_s in asio_buffer.iter() { - channel.push($ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $SampleType, - $SampleTypeIdent, - asio_s - ), - $Endianness, - )); - } + // interleave all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = $Buffers; + au::interleave(&channels, c_buffer); + // Clear the per channel buffers + for c in channels.iter_mut() { + c.clear(); } + } - // interleave all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = $Buffers; - au::interleave(&channels, c_buffer); - // Clear the per channel buffers - for c in channels.iter_mut() { - c.clear(); - } - } - - // Call the users callback with the buffer - callback( - StreamId(count), - Ok(StreamData::Input { - buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { - buffer: &$Buffers.cpal, - }), + // Call the users callback with the buffer + callback( + StreamId(count), + Ok(StreamData::Input { + buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { + buffer: &$Buffers.cpal, }), - ); - }; + }), + ); }; - // Call the right buffer handler depending on types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), + }; + // Call the right buffer handler depending on types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from + ); } - }); - // Create stream and set to paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { playing: false })); - StreamId(count) - }) + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_from + ); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + _ => println!("unsupported format {:?}", stream_type), + } + }); + // Create stream and set to paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { driver: driver.clone(), playing: false })); + + Ok(StreamId(count)) } /// Create the an output cpal stream. @@ -535,385 +537,388 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - let Device { drivers, .. } = device; + let Device { driver, .. } = device; let num_channels = format.channels.clone(); - let stream_type = drivers.get_data_type().expect("Couldn't load data type"); - let output_stream = self.get_output_stream(&drivers, format, device); - output_stream.map(|stream_buffer_size| { - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; + let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_buffer_size = self.get_output_stream(&driver, format, device)?; + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); + let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - let mut re_buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), + // Create buffers depending on data type + let mut re_buffers = match format.data_type { + SampleFormat::I16 => Buffers { + i16_buff: I16Buffer { + cpal: vec![0 as i16; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, + f32_buff: F32Buffer::default(), + }, + SampleFormat::F32 => Buffers { + i16_buff: I16Buffer::default(), + f32_buff: F32Buffer { + cpal: vec![0 as f32; cpal_num_samples], + channel: (0..num_channels) + .map(|_| Vec::with_capacity(channel_len)) + .collect(), }, - _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + + sys::set_callback(move |index| unsafe { + // if not playing return early + { + if let Some(s) = cpal_streams.lock().unwrap().get(count) { + if let Some(s) = s { + if !s.playing { + return (); + } + } + } + } + // Get the stream + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.output { + Some(ref asio_stream) => asio_stream, + None => return (), }; - sys::set_callback(move |index| unsafe { - // if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return (); + // Get the callback + let mut callbacks = callbacks.lock().unwrap(); + + // Theres only a single callback because theres only one event loop + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return (), + }; + + // Convert sample depending on the sample type + macro_rules! convert_sample { + ($AsioTypeIdent:ident, + f64, + f64, + $Sample:expr + ) => { + *$Sample + }; + ($AsioTypeIdent:ident, + f32, + f32, + $Sample:expr + ) => { + *$Sample + }; + ($AsioTypeIdent:ident, + f64, + f32, + $Sample:expr + ) => { + *$Sample as f64 + }; + ($AsioTypeIdent:ident, + f32, + f64, + $Sample:expr + ) => { + *$Sample as f32 + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + f32, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + f64, + $Sample:expr + ) => { + (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType + }; + ($AsioTypeIdent:ident, + f32, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 + }; + ($AsioTypeIdent:ident, + f64, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 + }; + ($AsioTypeIdent:ident, + $AsioType:ty, + $SampleTypeIdent:ident, + $Sample:expr + ) => { + (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 + / ::std::$SampleTypeIdent::MAX as i64) as $AsioType + }; + }; + + macro_rules! try_callback { + ($SampleFormat:ident, + $SampleType:ty, + $SampleTypeIdent:ident, + $AsioType:ty, + $AsioTypeIdent:ident, + $Buffers:expr, + $BuffersType:ty, + $BuffersTypeIdent:ident, + $Endianness:expr, + $ConvertEndian:expr + ) => { + let mut my_buffers = $Buffers; + { + // call the callback to fill the buffer with + // users data + callback( + StreamId(count), + Ok(StreamData::Output { + buffer: UnknownTypeOutputBuffer::$SampleFormat( + ::OutputBuffer { + buffer: &mut my_buffers.cpal, + }, + ), + }), + ); + } + // Deinter all the channels + { + let $BuffersTypeIdent { + cpal: ref mut c_buffer, + channel: ref mut channels, + } = my_buffers; + au::deinterleave(&c_buffer[..], channels); + } + + // Silence the buffer that is about to be used. + // This checks if any other callbacks have already + // silenced this buffer. If not it will silence it + // and set the opposite buffer half to unsilenced. + let silence = match index { + 0 => { + if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { + sys::SILENCE_FIRST.store(true, Ordering::SeqCst); + sys::SILENCE_SECOND.store(false, Ordering::SeqCst); + true + } else { + false } } - } - } - // Get the stream - let stream_lock = asio_streams.lock().unwrap(); - let ref asio_stream = match stream_lock.output { - Some(ref asio_stream) => asio_stream, - None => return (), - }; + 1 => { + if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { + sys::SILENCE_SECOND.store(true, Ordering::SeqCst); + sys::SILENCE_FIRST.store(false, Ordering::SeqCst); + true + } else { + false + } + } + _ => unreachable!(), + }; - // Get the callback - let mut callbacks = callbacks.lock().unwrap(); - - // Theres only a single callback because theres only one event loop - let callback = match callbacks.as_mut() { - Some(callback) => callback, - None => return (), - }; - - // Convert sample depending on the sample type - macro_rules! convert_sample { - ($AsioTypeIdent:ident, - f64, - f64, - $Sample:expr - ) => { - *$Sample - }; - ($AsioTypeIdent:ident, - f32, - f32, - $Sample:expr - ) => { - *$Sample - }; - ($AsioTypeIdent:ident, - f64, - f32, - $Sample:expr - ) => { - *$Sample as f64 - }; - ($AsioTypeIdent:ident, - f32, - f64, - $Sample:expr - ) => { - *$Sample as f32 - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - f32, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - f64, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType - }; - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 - / ::std::$SampleTypeIdent::MAX as i64) as $AsioType - }; - }; - - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - let mut my_buffers = $Buffers; - { - // call the callback to fill the buffer with - // users data - callback( - StreamId(count), - Ok(StreamData::Output { - buffer: UnknownTypeOutputBuffer::$SampleFormat( - ::OutputBuffer { - buffer: &mut my_buffers.cpal, - }, - ), - }), + // For each channel write the cpal data to + // the asio buffer + for (i, channel) in my_buffers.channel.iter().enumerate() { + let buff_ptr = asio_stream.buffer_infos[i].buffers + [index as usize] as *mut $AsioType; + 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) { + if silence { + *asio_s = 0.0 as $AsioType; + } + *asio_s += $ConvertEndian( + convert_sample!( + $AsioTypeIdent, + $AsioType, + $SampleTypeIdent, + cpal_s + ), + $Endianness, ); } - // Deinter all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = my_buffers; - au::deinterleave(&c_buffer[..], channels); - } - - // Silence the buffer that is about to be used. - // This checks if any other callbacks have already - // silenced this buffer. If not it will silence it - // and set the opposite buffer half to unsilenced. - let silence = match index { - 0 => { - if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { - sys::SILENCE_FIRST.store(true, Ordering::SeqCst); - sys::SILENCE_SECOND.store(false, Ordering::SeqCst); - true - } else { - false - } - } - 1 => { - if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { - sys::SILENCE_SECOND.store(true, Ordering::SeqCst); - sys::SILENCE_FIRST.store(false, Ordering::SeqCst); - true - } else { - false - } - } - _ => unreachable!(), - }; - - // For each channel write the cpal data to - // the asio buffer - for (i, channel) in my_buffers.channel.iter().enumerate() { - let buff_ptr = asio_stream.buffer_infos[i].buffers - [index as usize] as *mut $AsioType; - 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) { - if silence { - *asio_s = 0.0 as $AsioType; - } - *asio_s += $ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $AsioType, - $SampleTypeIdent, - cpal_s - ), - $Endianness, - ); - } - } - }; + } + }; + } + // Choose the buffer conversions based on the sample types + match stream_type { + sys::AsioSampleType::ASIOSTInt32LSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); } - // Choose the buffer conversions based on the sample types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to - ); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), + sys::AsioSampleType::ASIOSTInt16LSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Little, + convert_endian_to + ); } - }); - // Create the stream paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { playing: false })); - // Give the ID based on the stream count - StreamId(count) - }) + sys::AsioSampleType::ASIOSTInt32MSB => { + try_callback!( + I16, + i16, + i16, + i32, + i32, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + } + sys::AsioSampleType::ASIOSTInt16MSB => { + try_callback!( + I16, + i16, + i16, + i16, + i16, + &mut re_buffers.i16_buff, + I16Buffer, + I16Buffer, + Endian::Big, + convert_endian_to + ); + } + sys::AsioSampleType::ASIOSTFloat32LSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64LSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Little, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat32MSB => { + try_callback!( + F32, + f32, + f32, + f32, + f32, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + sys::AsioSampleType::ASIOSTFloat64MSB => { + try_callback!( + F32, + f32, + f32, + f64, + f64, + &mut re_buffers.f32_buff, + F32Buffer, + F32Buffer, + Endian::Big, + |a, _| a + ); + } + _ => println!("unsupported format {:?}", stream_type), + } + }); + // Create the stream paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { driver: driver.clone(), playing: false })); + + // Give the ID based on the stream count + Ok(StreamId(count)) } /// Play the cpal stream for the given ID. - /// Also play The ASIO streams if they are not already. + /// + /// TODO: This will also play all other paused streams... need a nicer way of addressing ASIO's + /// limitation of only being able to start/stop the entire driver. pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { s.playing = true; + // Calling play when already playing is a no-op + s.driver.start().map_err(play_stream_err)?; } - // Calling play when already playing is a no-op - sys::play(); Ok(()) } /// Pause the cpal stream for the given ID. - /// Pause the ASIO streams if there are no CPAL streams palying. + /// + /// Pause the ASIO streams if there are no other CPAL streams playing, as ASIO only allows + /// stopping the entire driver. + /// + /// TODO: Come up with a nicer solution for this. pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); - if let Some(s) = streams - .get_mut(stream_id.0) - .expect("Bad pause stream index") - { - s.playing = false; - } - let any_playing = streams - .iter() - .any(|s| if let Some(s) = s { s.playing } else { false }); - if any_playing { - sys::stop(); + let streams_playing = streams.iter() + .filter(|s| s.map(|s| s.playing).unwrap_or(false)) + .count(); + if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { + if streams_playing <= 1 { + s.driver.stop().map_err(pause_stream_err)?; + s.playing = false; + } } Ok(()) } /// Destroy the cpal stream based on the ID. pub fn destroy_stream(&self, stream_id: StreamId) { + // TODO: Should we not also remove an ASIO stream here? let mut streams = self.cpal_streams.lock().unwrap(); streams.get_mut(stream_id.0).take(); } @@ -942,7 +947,6 @@ impl Drop for EventLoop { output: None, input: None, }; - sys::clean_up(); } } @@ -961,3 +965,38 @@ fn convert_endian_from(sample: T, endian: Endian) -> T { Endian::Little => T::from_le(sample), } } + +fn build_stream_err(e: sys::AsioError) -> BuildStreamError { + match e { + sys::AsioError::NoDrivers | + sys::AsioError::HardwareMalfunction => BuildStreamError::DeviceNotAvailable, + sys::AsioError::InvalidInput | + sys::AsioError::BadMode => BuildStreamError::InvalidArgument, + err => { + let description = format!("{}", err); + BackendSpecificError { description }.into() + } + } +} + +fn pause_stream_err(e: sys::AsioError) -> PauseStreamError { + match e { + sys::AsioError::NoDrivers | + sys::AsioError::HardwareMalfunction => PauseStreamError::DeviceNotAvailable, + err => { + let description = format!("{}", err); + BackendSpecificError { description }.into() + } + } +} + +fn play_stream_err(e: sys::AsioError) -> PlayStreamError { + match e { + sys::AsioError::NoDrivers | + sys::AsioError::HardwareMalfunction => PlayStreamError::DeviceNotAvailable, + err => { + let description = format!("{}", err); + BackendSpecificError { description }.into() + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8257051..244be18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -377,6 +377,9 @@ pub enum BuildStreamError { /// them immediately. #[derive(Debug, Fail)] pub enum PlayStreamError { + /// The device associated with the stream is no longer available. + #[fail(display = "the device associated with the stream is no longer available")] + DeviceNotAvailable, /// See the `BackendSpecificError` docs for more information about this error variant. #[fail(display = "{}", err)] BackendSpecific { @@ -392,6 +395,9 @@ pub enum PlayStreamError { /// them immediately. #[derive(Debug, Fail)] pub enum PauseStreamError { + /// The device associated with the stream is no longer available. + #[fail(display = "the device associated with the stream is no longer available")] + DeviceNotAvailable, /// See the `BackendSpecificError` docs for more information about this error variant. #[fail(display = "{}", err)] BackendSpecific { From 0f510a95129384db8f7d225cca35f7f26d9efd9f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 02:58:48 +1000 Subject: [PATCH 56/75] Update README for Host API tweaks. Fix typos in steps. --- README.md | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 26df079..14e31ab 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ systems, it is most commonly used on Windows to work around limitations of WASAPI including access to large numbers of channels and lower-latency audio processing. -CPAL allows for using the ASIO SDK as the audio backend on Windows instead of +CPAL allows for using the ASIO SDK as the audio host on Windows instead of WASAPI. To do so, follow these steps: 1. **Download the ASIO SDK** `.zip` from [this @@ -55,7 +55,7 @@ WASAPI. To do so, follow these steps: ``` setx LIBCLANG_PATH "C:\Program Files\LLVM\bin" ``` -6. If you don't have any ASIO devices or drivers availabe, you can [**download +6. If you don't have any ASIO devices or drivers available, you can [**download and install ASIO4ALL**](http://www.asio4all.org/). Be sure to enable the "offline" feature during installation despite what the installer says about it being useless. @@ -64,30 +64,24 @@ WASAPI. To do so, follow these steps: environment variables in the command prompt that we use to build our project. On 64-bit machines run: ``` - "C:\Program Files (x86)Microsoft Visual Studio 14.0VC\vcvarsall" amd64 + "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 ``` On 32-bit machines run: ``` - "C:\Program Files (x86)Microsoft Visual Studio 14.0VC\vcvarsall" x86 + "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 ``` -8. Select ASIO as the backend at the start of our program with the following - code: - +8. Select the ASIO host at the start of our program with the following code: + ```rust + let host; #[cfg(target_os = "windows")] { - cpal::os::windows::use_asio_backend().expect("Failed to select ASIO backend"); + host = cpal::host_from_id(cpal::HostId::Asio).expect("failed to initialise ASIO host"); } ``` - If you run into this error: - - ``` - cpal::os::windows::use_asio_backend().expect("Failed to use asio"); - ^^^^^^^^^^^^^^^^ did you mean `use_wasapi_backend`? - ``` - - Make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. + If you run into an error along the lines of "no variant `Asio` in `HostId`", + make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. In the future we would like to work on automating this process to make it easier, but we are not familiar enough with the ASIO license to do so yet. From b6181d4362839b5d0ed7d5617096027ba3e05451 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 02:59:37 +1000 Subject: [PATCH 57/75] Add impl Send for AsioStream --- asio-sys/src/bindings/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 4c3b8e5..a07343a 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -612,6 +612,8 @@ impl Drop for Driver { } } +unsafe impl Send for AsioStream {} + /// Adds a callback to the list of active callbacks pub fn set_callback(callback: F) -> () where From 50aa0585be1cc9afaf00c0d40170730960200ad5 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 03:00:13 +1000 Subject: [PATCH 58/75] Remove unwieldy macro from output stream callback Instead, we use some traits and generics in an attempt to retain a little readability. See the remaining TODOs in this section for required future work. --- src/host/asio/stream.rs | 1198 +++++++++++++++++++-------------------- 1 file changed, 570 insertions(+), 628 deletions(-) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 6344dcf..61b1dfb 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -21,6 +21,19 @@ use StreamDataResult; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; +/// Sample types whose constant silent value is known. +trait Silence { + const SILENCE: Self; +} + +/// Constraints on the interleaved sample buffer format required by the CPAL API. +trait InterleavedSample: Clone + Copy + Silence { + fn unknown_type_output_buffer(&mut [Self]) -> UnknownTypeOutputBuffer; +} + +/// Constraints on the ASIO sample types. +trait AsioSample: Clone + Copy + Silence + std::ops::Add {} + /// Controls all streams pub struct EventLoop { /// The input and output ASIO streams @@ -36,7 +49,7 @@ pub struct EventLoop { /// Id for each stream. /// Created depending on the number they are created. /// Starting at one! not zero. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct StreamId(usize); /// CPAL stream. @@ -50,21 +63,9 @@ struct Stream { driver: Arc, } -#[derive(Default)] -struct I16Buffer { - cpal: Vec, - channel: Vec>, -} - -#[derive(Default)] -struct F32Buffer { - cpal: Vec, - channel: Vec>, -} struct Buffers { - i16_buff: I16Buffer, - //u16_buff: U16Buffer, - f32_buff: F32Buffer, + interleaved: Vec, + non_interleaved: Vec, } enum Endian { @@ -72,6 +73,14 @@ enum Endian { Big, } +// Used to keep track of whether or not the current current asio stream buffer requires +// being silencing before summing audio. +#[derive(Default)] +struct SilenceAsioBuffer { + first: bool, + second: bool, +} + impl EventLoop { pub fn new() -> EventLoop { EventLoop { @@ -208,327 +217,328 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - let Device { driver, .. } = device; - let num_channels = format.channels.clone(); - let stream_type = driver.data_type().map_err(build_stream_err)?; - let stream_buffer_size = self.get_input_stream(&driver, format, device)?; - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); + unimplemented!() + // let Device { driver, .. } = device; + // let num_channels = format.channels.clone(); + // let stream_type = driver.data_type().map_err(build_stream_err)?; + // let stream_buffer_size = self.get_input_stream(&driver, format, device)?; + // let cpal_num_samples = stream_buffer_size * num_channels as usize; + // let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + // let asio_streams = self.asio_streams.clone(); + // let cpal_streams = self.cpal_streams.clone(); + // let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; + // let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - // TODO the naming of cpal and channel is confusing. - // change it to: - // cpal -> interleaved - // channels -> per_channel - let mut buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), - }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - }, - _ => unimplemented!(), - }; + // // Create buffers depending on data type + // // TODO the naming of cpal and channel is confusing. + // // change it to: + // // cpal -> interleaved + // // channels -> per_channel + // let mut buffers = match format.data_type { + // SampleFormat::I16 => Buffers { + // i16_buff: I16Buffer { + // cpal: vec![0 as i16; cpal_num_samples], + // channel: (0..num_channels) + // .map(|_| Vec::with_capacity(channel_len)) + // .collect(), + // }, + // f32_buff: F32Buffer::default(), + // }, + // SampleFormat::F32 => Buffers { + // i16_buff: I16Buffer::default(), + // f32_buff: F32Buffer { + // cpal: vec![0 as f32; cpal_num_samples], + // channel: (0..num_channels) + // .map(|_| Vec::with_capacity(channel_len)) + // .collect(), + // }, + // }, + // _ => unimplemented!(), + // }; - // Set the input callback. - // This is most performance critical part of the ASIO bindings. - sys::set_callback(move |index| unsafe { - // if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return (); - } - } - } - } - // Get the stream - let stream_lock = asio_streams.lock().unwrap(); - let ref asio_stream = match stream_lock.input { - Some(ref asio_stream) => asio_stream, - None => return (), - }; + // // Set the input callback. + // // This is most performance critical part of the ASIO bindings. + // sys::set_callback(move |index| unsafe { + // // if not playing return early + // { + // if let Some(s) = cpal_streams.lock().unwrap().get(count) { + // if let Some(s) = s { + // if !s.playing { + // return (); + // } + // } + // } + // } + // // Get the stream + // let stream_lock = asio_streams.lock().unwrap(); + // let ref asio_stream = match stream_lock.input { + // Some(ref asio_stream) => asio_stream, + // None => return (), + // }; - // Get the callback - let mut callbacks = callbacks.lock().unwrap(); + // // Get the callback + // let mut callbacks = callbacks.lock().unwrap(); - // Theres only a single callback because theres only one event loop - let callback = match callbacks.as_mut() { - Some(callback) => callback, - None => return (), - }; + // // Theres only a single callback because theres only one event loop + // let callback = match callbacks.as_mut() { + // Some(callback) => callback, + // None => return (), + // }; - // Macro to convert sample from ASIO to CPAL type - macro_rules! convert_sample { - // floats types required different conversion - (f32, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample - }; - (f64, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample - }; - (f64, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f32 - }; - (f32, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 - }; - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 - }; - (f32, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - }; - (f64, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - }; - ($AsioTypeIdent:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - / ::std::$AsioTypeIdent::MAX as i64) as $SampleType - }; - }; - // This creates gets the buffer and interleaves it. - // It allows it to be done based on the sample type. - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - // For each channel write the asio buffer to - // the cpal buffer + // // Macro to convert sample from ASIO to CPAL type + // macro_rules! convert_sample { + // // floats types required different conversion + // (f32, + // f32, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // *$Sample + // }; + // (f64, + // f64, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // *$Sample + // }; + // (f64, + // f32, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // *$Sample as f32 + // }; + // (f32, + // f64, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // *$Sample as f64 + // }; + // ($AsioTypeIdent:ident, + // f32, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 + // }; + // ($AsioTypeIdent:ident, + // f64, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 + // }; + // (f32, + // $SampleType:ty, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + // }; + // (f64, + // $SampleType:ty, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType + // }; + // ($AsioTypeIdent:ident, + // $SampleType:ty, + // $SampleTypeIdent:ident, + // $Sample:expr + // ) => { + // (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 + // / ::std::$AsioTypeIdent::MAX as i64) as $SampleType + // }; + // }; + // // This creates gets the buffer and interleaves it. + // // It allows it to be done based on the sample type. + // macro_rules! try_callback { + // ($SampleFormat:ident, + // $SampleType:ty, + // $SampleTypeIdent:ident, + // $AsioType:ty, + // $AsioTypeIdent:ident, + // $Buffers:expr, + // $BuffersType:ty, + // $BuffersTypeIdent:ident, + // $Endianness:expr, + // $ConvertEndian:expr + // ) => { + // // For each channel write the asio buffer to + // // the cpal buffer - for (i, channel) in $Buffers.channel.iter_mut().enumerate() { - let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] - as *mut $AsioType; - let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( - buff_ptr, - asio_stream.buffer_size as usize, - ); - for asio_s in asio_buffer.iter() { - channel.push($ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $SampleType, - $SampleTypeIdent, - asio_s - ), - $Endianness, - )); - } - } + // for (i, channel) in $Buffers.channel.iter_mut().enumerate() { + // let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] + // as *mut $AsioType; + // let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( + // buff_ptr, + // asio_stream.buffer_size as usize, + // ); + // for asio_s in asio_buffer.iter() { + // channel.push($ConvertEndian( + // convert_sample!( + // $AsioTypeIdent, + // $SampleType, + // $SampleTypeIdent, + // asio_s + // ), + // $Endianness, + // )); + // } + // } - // interleave all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = $Buffers; - au::interleave(&channels, c_buffer); - // Clear the per channel buffers - for c in channels.iter_mut() { - c.clear(); - } - } + // // interleave all the channels + // { + // let $BuffersTypeIdent { + // cpal: ref mut c_buffer, + // channel: ref mut channels, + // } = $Buffers; + // au::interleave(&channels, c_buffer); + // // Clear the per channel buffers + // for c in channels.iter_mut() { + // c.clear(); + // } + // } - // Call the users callback with the buffer - callback( - StreamId(count), - Ok(StreamData::Input { - buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { - buffer: &$Buffers.cpal, - }), - }), - ); - }; - }; - // Call the right buffer handler depending on types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_from - ); - } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), - } - }); - // Create stream and set to paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { driver: driver.clone(), playing: false })); + // // Call the users callback with the buffer + // callback( + // StreamId(count), + // Ok(StreamData::Input { + // buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { + // buffer: &$Buffers.cpal, + // }), + // }), + // ); + // }; + // }; + // // Call the right buffer handler depending on types + // match stream_type { + // sys::AsioSampleType::ASIOSTInt32LSB => { + // try_callback!( + // I16, + // i16, + // i16, + // i32, + // i32, + // buffers.i16_buff, + // I16Buffer, + // I16Buffer, + // Endian::Little, + // convert_endian_from + // ); + // } + // sys::AsioSampleType::ASIOSTInt16LSB => { + // try_callback!( + // I16, + // i16, + // i16, + // i16, + // i16, + // buffers.i16_buff, + // I16Buffer, + // I16Buffer, + // Endian::Little, + // convert_endian_from + // ); + // } + // sys::AsioSampleType::ASIOSTInt32MSB => { + // try_callback!( + // I16, + // i16, + // i16, + // i32, + // i32, + // buffers.i16_buff, + // I16Buffer, + // I16Buffer, + // Endian::Big, + // convert_endian_from + // ); + // } + // sys::AsioSampleType::ASIOSTInt16MSB => { + // try_callback!( + // I16, + // i16, + // i16, + // i16, + // i16, + // buffers.i16_buff, + // I16Buffer, + // I16Buffer, + // Endian::Big, + // convert_endian_from + // ); + // } + // sys::AsioSampleType::ASIOSTFloat32LSB => { + // try_callback!( + // F32, + // f32, + // f32, + // f32, + // f32, + // buffers.f32_buff, + // F32Buffer, + // F32Buffer, + // Endian::Little, + // |a, _| a + // ); + // } + // sys::AsioSampleType::ASIOSTFloat64LSB => { + // try_callback!( + // F32, + // f32, + // f32, + // f64, + // f64, + // buffers.f32_buff, + // F32Buffer, + // F32Buffer, + // Endian::Little, + // |a, _| a + // ); + // } + // sys::AsioSampleType::ASIOSTFloat32MSB => { + // try_callback!( + // F32, + // f32, + // f32, + // f32, + // f32, + // buffers.f32_buff, + // F32Buffer, + // F32Buffer, + // Endian::Big, + // |a, _| a + // ); + // } + // sys::AsioSampleType::ASIOSTFloat64MSB => { + // try_callback!( + // F32, + // f32, + // f32, + // f64, + // f64, + // buffers.f32_buff, + // F32Buffer, + // F32Buffer, + // Endian::Big, + // |a, _| a + // ); + // } + // _ => println!("unsupported format {:?}", stream_type), + // } + // }); + // // Create stream and set to paused + // self.cpal_streams + // .lock() + // .unwrap() + // .push(Some(Stream { driver: driver.clone(), playing: false })); - Ok(StreamId(count)) + // Ok(StreamId(count)) } /// Create the an output cpal stream. @@ -541,337 +551,203 @@ impl EventLoop { let num_channels = format.channels.clone(); let stream_type = driver.data_type().map_err(build_stream_err)?; let stream_buffer_size = self.get_output_stream(&driver, format, device)?; + let channel_len = stream_buffer_size as usize; let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); let cpal_streams = self.cpal_streams.clone(); let callbacks = self.callbacks.clone(); - let channel_len = cpal_num_samples / num_channels as usize; - // Create buffers depending on data type - let mut re_buffers = match format.data_type { - SampleFormat::I16 => Buffers { - i16_buff: I16Buffer { - cpal: vec![0 as i16; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - f32_buff: F32Buffer::default(), - }, - SampleFormat::F32 => Buffers { - i16_buff: I16Buffer::default(), - f32_buff: F32Buffer { - cpal: vec![0 as f32; cpal_num_samples], - channel: (0..num_channels) - .map(|_| Vec::with_capacity(channel_len)) - .collect(), - }, - }, - _ => unimplemented!(), - }; + // Create buffers depending on data type. + let stream_id = StreamId(count); + let data_type = format.data_type; + let len_bytes = cpal_num_samples * data_type.sample_size(); + let mut interleaved = vec![0u8; len_bytes]; + let mut silence_asio_buffer = SilenceAsioBuffer::default(); - sys::set_callback(move |index| unsafe { - // if not playing return early - { - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return (); - } + sys::set_callback(move |buffer_index| unsafe { + // If not playing, return early. + if let Some(s) = cpal_streams.lock().unwrap().get(count) { + if let Some(s) = s { + if !s.playing { + return (); } } } - // Get the stream + + // Acquire the stream and callback. let stream_lock = asio_streams.lock().unwrap(); let ref asio_stream = match stream_lock.output { Some(ref asio_stream) => asio_stream, None => return (), }; - - // Get the callback let mut callbacks = callbacks.lock().unwrap(); - - // Theres only a single callback because theres only one event loop let callback = match callbacks.as_mut() { Some(callback) => callback, None => return (), }; - // Convert sample depending on the sample type - macro_rules! convert_sample { - ($AsioTypeIdent:ident, - f64, - f64, - $Sample:expr - ) => { - *$Sample - }; - ($AsioTypeIdent:ident, - f32, - f32, - $Sample:expr - ) => { - *$Sample - }; - ($AsioTypeIdent:ident, - f64, - f32, - $Sample:expr - ) => { - *$Sample as f64 - }; - ($AsioTypeIdent:ident, - f32, - f64, - $Sample:expr - ) => { - *$Sample as f32 - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - f32, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - f64, - $Sample:expr - ) => { - (*$Sample as f64 * ::std::$AsioTypeIdent::MAX as f64) as $AsioType - }; - ($AsioTypeIdent:ident, - f32, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64) as f32 - }; - ($AsioTypeIdent:ident, - f64, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - *$Sample as f64 / ::std::$SampleTypeIdent::MAX as f64 - }; - ($AsioTypeIdent:ident, - $AsioType:ty, - $SampleTypeIdent:ident, - $Sample:expr - ) => { - (*$Sample as i64 * ::std::$AsioTypeIdent::MAX as i64 - / ::std::$SampleTypeIdent::MAX as i64) as $AsioType - }; + // Silence the ASIO buffer that is about to be used. + // + // This checks if any other callbacks have already silenced the buffer associated with + // the current `buffer_index`. + // + // If not, we will silence it and set the opposite buffer half to unsilenced. + let silence = match buffer_index { + 0 if !silence_asio_buffer.first => { + silence_asio_buffer.first = true; + silence_asio_buffer.second = false; + true + } + 0 => false, + 1 if !silence_asio_buffer.second => { + silence_asio_buffer.second = true; + silence_asio_buffer.first = false; + true + } + 1 => false, + _ => unreachable!("ASIO uses a double-buffer so there should only be 2"), }; - macro_rules! try_callback { - ($SampleFormat:ident, - $SampleType:ty, - $SampleTypeIdent:ident, - $AsioType:ty, - $AsioTypeIdent:ident, - $Buffers:expr, - $BuffersType:ty, - $BuffersTypeIdent:ident, - $Endianness:expr, - $ConvertEndian:expr - ) => { - let mut my_buffers = $Buffers; - { - // call the callback to fill the buffer with - // users data - callback( - StreamId(count), - Ok(StreamData::Output { - buffer: UnknownTypeOutputBuffer::$SampleFormat( - ::OutputBuffer { - buffer: &mut my_buffers.cpal, - }, - ), - }), - ); - } - // Deinter all the channels - { - let $BuffersTypeIdent { - cpal: ref mut c_buffer, - channel: ref mut channels, - } = my_buffers; - au::deinterleave(&c_buffer[..], channels); - } + /// 1. Render the given callback to the given buffer of interleaved samples. + /// 2. If required, silence the ASIO buffer. + /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer, + /// performing endianness conversions as necessary. + unsafe fn process_output_callback( + stream_id: StreamId, + callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send), + interleaved: &mut [u8], + silence_asio_buffer: bool, + asio_stream: &sys::AsioStream, + buffer_index: usize, + to_asio_sample: F, + to_endianness: G, + ) + where + A: InterleavedSample, + B: AsioSample, + F: Fn(A) -> B, + G: Fn(B) -> B, + { + // 1. Render interleaved buffer from callback. + let interleaved: &mut [A] = cast_slice_mut(interleaved); + callback( + stream_id, + Ok(StreamData::Output { buffer: A::unknown_type_output_buffer(interleaved) }), + ); - // Silence the buffer that is about to be used. - // This checks if any other callbacks have already - // silenced this buffer. If not it will silence it - // and set the opposite buffer half to unsilenced. - let silence = match index { - 0 => { - if !sys::SILENCE_FIRST.load(Ordering::SeqCst) { - sys::SILENCE_FIRST.store(true, Ordering::SeqCst); - sys::SILENCE_SECOND.store(false, Ordering::SeqCst); - true - } else { - false - } - } - 1 => { - if !sys::SILENCE_SECOND.load(Ordering::SeqCst) { - sys::SILENCE_SECOND.store(true, Ordering::SeqCst); - sys::SILENCE_FIRST.store(false, Ordering::SeqCst); - true - } else { - false - } - } - _ => unreachable!(), - }; - - // For each channel write the cpal data to - // the asio buffer - for (i, channel) in my_buffers.channel.iter().enumerate() { - let buff_ptr = asio_stream.buffer_infos[i].buffers - [index as usize] as *mut $AsioType; - 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) { - if silence { - *asio_s = 0.0 as $AsioType; - } - *asio_s += $ConvertEndian( - convert_sample!( - $AsioTypeIdent, - $AsioType, - $SampleTypeIdent, - cpal_s - ), - $Endianness, - ); - } + // 2. Silence ASIO channels if necessary. + let n_channels = interleaved.len() / asio_stream.buffer_size as usize; + if silence_asio_buffer { + for ch_ix in 0..n_channels { + let asio_channel = + asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix); + asio_channel.iter_mut().for_each(|s| *s = to_endianness(B::SILENCE)); } - }; + } + + // 3. Write interleaved samples to ASIO channels, one channel at a time. + for ch_ix in 0..n_channels { + let asio_channel = + asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix); + for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) { + *s_asio = *s_asio + to_endianness(to_asio_sample(frame[ch_ix])); + } + } } - // Choose the buffer conversions based on the sample types - match stream_type { - sys::AsioSampleType::ASIOSTInt32LSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to + + match (data_type, &stream_type) { + (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + std::convert::identity::, + to_le, ); } - sys::AsioSampleType::ASIOSTInt16LSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Little, - convert_endian_to + (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + std::convert::identity::, + to_be, ); } - sys::AsioSampleType::ASIOSTInt32MSB => { - try_callback!( - I16, - i16, - i16, - i32, - i32, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to + + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) | + (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + std::convert::identity::, + std::convert::identity::, ); } - sys::AsioSampleType::ASIOSTInt16MSB => { - try_callback!( - I16, - i16, - i16, - i16, - i16, - &mut re_buffers.i16_buff, - I16Buffer, - I16Buffer, - Endian::Big, - convert_endian_to + + // TODO: Add support for the following sample formats to CPAL and simplify the + // `process_output_callback` function above by removing the unnecessary sample + // conversion function. + (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + |s| (s as i32) << 16, + to_le, ); } - sys::AsioSampleType::ASIOSTFloat32LSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a + (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + |s| (s as i32) << 16, + to_be, ); } - sys::AsioSampleType::ASIOSTFloat64LSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Little, - |a, _| a + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64LSB) | + (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64MSB) => { + process_output_callback::( + stream_id, + callback, + &mut interleaved, + silence, + asio_stream, + buffer_index as usize, + |s| s as f64, + std::convert::identity::, ); } - sys::AsioSampleType::ASIOSTFloat32MSB => { - try_callback!( - F32, - f32, - f32, - f32, - f32, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); + + unsupported_format_pair => { + unreachable!("`build_output_stream` should have returned with unsupported \ + format {:?}", unsupported_format_pair) } - sys::AsioSampleType::ASIOSTFloat64MSB => { - try_callback!( - F32, - f32, - f32, - f64, - f64, - &mut re_buffers.f32_buff, - F32Buffer, - F32Buffer, - Endian::Big, - |a, _| a - ); - } - _ => println!("unsupported format {:?}", stream_type), } }); + // Create the stream paused self.cpal_streams .lock() @@ -883,9 +759,6 @@ impl EventLoop { } /// Play the cpal stream for the given ID. - /// - /// TODO: This will also play all other paused streams... need a nicer way of addressing ASIO's - /// limitation of only being able to start/stop the entire driver. pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { @@ -900,18 +773,16 @@ impl EventLoop { /// /// Pause the ASIO streams if there are no other CPAL streams playing, as ASIO only allows /// stopping the entire driver. - /// - /// TODO: Come up with a nicer solution for this. pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let mut streams = self.cpal_streams.lock().unwrap(); let streams_playing = streams.iter() - .filter(|s| s.map(|s| s.playing).unwrap_or(false)) + .filter(|s| s.as_ref().map(|s| s.playing).unwrap_or(false)) .count(); if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { if streams_playing <= 1 { s.driver.stop().map_err(pause_stream_err)?; - s.playing = false; } + s.playing = false; } Ok(()) } @@ -950,14 +821,85 @@ impl Drop for EventLoop { } } -/// Helper function to convert to system endianness -fn convert_endian_to(sample: T, endian: Endian) -> T { - match endian { - Endian::Big => sample.to_be(), - Endian::Little => sample.to_le(), +impl Silence for i16 { + const SILENCE: Self = 0; +} + +impl Silence for i32 { + const SILENCE: Self = 0; +} + +impl Silence for f32 { + const SILENCE: Self = 0.0; +} + +impl Silence for f64 { + const SILENCE: Self = 0.0; +} + +impl InterleavedSample for i16 { + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { + UnknownTypeOutputBuffer::I16(::OutputBuffer { buffer }) } } +impl InterleavedSample for f32 { + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { + UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer }) + } +} + +impl AsioSample for i16 {} + +impl AsioSample for i32 {} + +impl AsioSample for f32 {} + +impl AsioSample for f64 {} + +/// Cast a byte slice into a (immutable) slice of desired type. +/// +/// Safety: it's up to the caller to ensure that the input slice has valid bit representations. +unsafe fn cast_slice(v: &[u8]) -> &[T] { + debug_assert!(v.len() % std::mem::size_of::() == 0); + std::slice::from_raw_parts(v.as_ptr() as *const T, v.len() / std::mem::size_of::()) +} + +/// Cast a byte slice into a mutable slice of desired type. +/// +/// Safety: it's up to the caller to ensure that the input slice has valid bit representations. +unsafe fn cast_slice_mut(v: &mut [u8]) -> &mut [T] { + debug_assert!(v.len() % std::mem::size_of::() == 0); + std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) +} + +/// Helper function to convert to system endianness +fn to_le(t: T) -> T { + t.to_le() +} + +/// Helper function to convert from system endianness +fn to_be(t: T) -> T { + t.to_be() +} + +/// Shorthand for retrieving the asio buffer slice associated with a channel. +/// +/// Safety: it's up to the user to ensure the slice is not used beyond the lifetime of +/// the stream and that this function is not called multiple times for the same +/// channel. +unsafe fn asio_channel_slice_mut( + asio_stream: &sys::AsioStream, + buffer_index: usize, + channel_index: usize, +) -> &mut [T] { + let buff_ptr: *mut T = asio_stream + .buffer_infos[channel_index] + .buffers[buffer_index as usize] + as *mut _; + std::slice::from_raw_parts_mut(buff_ptr, asio_stream.buffer_size as usize) +} + /// Helper function to convert from system endianness fn convert_endian_from(sample: T, endian: Endian) -> T { match endian { From dc6c5c5a7c70f76d66506c5e0ef2f13a64edd978 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 03:44:51 +1000 Subject: [PATCH 59/75] Remove unnecessary asio_utils mod --- src/host/asio/asio_utils/mod.rs | 51 ------------------ src/host/asio/asio_utils/tests.rs | 89 ------------------------------- src/host/asio/mod.rs | 1 - 3 files changed, 141 deletions(-) delete mode 100644 src/host/asio/asio_utils/mod.rs delete mode 100644 src/host/asio/asio_utils/tests.rs diff --git a/src/host/asio/asio_utils/mod.rs b/src/host/asio/asio_utils/mod.rs deleted file mode 100644 index 017454c..0000000 --- a/src/host/asio/asio_utils/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -#[cfg(test)] -mod tests; - -use std::marker::Copy; - -/// Interleave the buffer from asio to cpal -/// asio: LLLLRRRR -/// cpal: LRLRLRLR -/// More then stereo: -/// asio: 111122223333 -/// cpal: 123123123123 -/// cpal buffer must have a length of exactly sum( all asio channel lengths ) -/// this check is ommited for performance -pub fn interleave(channels: &[Vec], target: &mut Vec) -where - T: Copy, -{ - assert!( - target.len() % channels.len() == 0, - "the length of the interleaved buffer must be a multiple of the expected number of channels" - ); - assert!(!channels.is_empty()); - target.clear(); - let frames = channels[0].len(); - target.extend((0 .. frames).flat_map(|f| channels.iter().map(move |ch| ch[f]))); -} - -/// Function for deinterleaving because -/// cpal writes to buffer interleaved -/// cpal: LRLRLRLR -/// asio: LLLLRRRR -/// More then stereo: -/// cpal: 123123123123 -/// asio: 111122223333 -pub fn deinterleave(cpal_buffer: &[T], asio_channels: &mut [Vec]) -where - T: Copy, -{ - assert!( - cpal_buffer.len() % asio_channels.len() == 0, - "the length of the interleaved buffer must be a multiple of the expected number of channels" - ); - for ch in asio_channels.iter_mut() { - ch.clear(); - } - let num_channels = asio_channels.len(); - let mut ch = (0 .. num_channels).cycle(); - for &sample in cpal_buffer.iter() { - asio_channels[ch.next().unwrap()].push(sample); - } -} \ No newline at end of file diff --git a/src/host/asio/asio_utils/tests.rs b/src/host/asio/asio_utils/tests.rs deleted file mode 100644 index c43fe5f..0000000 --- a/src/host/asio/asio_utils/tests.rs +++ /dev/null @@ -1,89 +0,0 @@ -use super::{deinterleave, interleave}; - -#[test] -fn interleave_two() { - let a = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2]]; - let goal = vec![1, 2, 1, 2, 1, 2, 1, 2]; - let mut result = vec![0; 8]; - - interleave(&a[..], &mut result); - - assert_eq!(goal, result); -} - -#[test] -fn interleave_three() { - let a = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2], vec![3, 3, 3, 3]]; - let goal = vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]; - let mut result = vec![0; 12]; - - interleave(&a[..], &mut result); - - assert_eq!(goal, result); -} - -#[test] -fn interleave_none() { - let a = vec![Vec::::new()]; - let goal = Vec::::new(); - let mut result = Vec::::new(); - - interleave(&a[..], &mut result); - - assert_eq!(goal, result); -} - -#[test] -fn interleave_two_diff() { - let a = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; - let goal = vec![1, 5, 2, 6, 3, 7, 4, 8]; - let mut result = vec![0; 8]; - - interleave(&a[..], &mut result); - - assert_eq!(goal, result); -} - -#[test] -fn deinterleave_two() { - let goal = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2]]; - let a = vec![1, 2, 1, 2, 1, 2, 1, 2]; - let mut result = vec![vec![0; 4]; 2]; - - deinterleave(&a[..], &mut result[..]); - - assert_eq!(goal, result); -} - -#[test] -fn deinterleave_three() { - let goal = vec![vec![1, 1, 1, 1], vec![2, 2, 2, 2], vec![3, 3, 3, 3]]; - let a = vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]; - let mut result = vec![vec![0; 4]; 3]; - - deinterleave(&a[..], &mut result[..]); - - assert_eq!(goal, result); -} - -#[test] -fn deinterleave_none() { - let goal = vec![Vec::::new()]; - let a = Vec::::new(); - let mut result = vec![Vec::::new()]; - - deinterleave(&a[..], &mut result[..]); - - assert_eq!(goal, result); -} - -#[test] -fn deinterleave_two_diff() { - let goal = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; - let a = vec![1, 5, 2, 6, 3, 7, 4, 8]; - let mut result = vec![vec![0; 4]; 2]; - - deinterleave(&a[..], &mut result[..]); - - assert_eq!(goal, result); -} diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 7e5172c..9f985b6 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -22,7 +22,6 @@ use std::sync::Arc; mod device; mod stream; -mod asio_utils; /// The host for ASIO. #[derive(Debug)] From cc70a10c121e4120dafde5022eeae0271320fadc Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 03:45:16 +1000 Subject: [PATCH 60/75] Remove unused supported_formats_err function --- src/host/asio/device.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index c8566e0..9acf425 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -185,16 +185,3 @@ fn default_format_err(e: sys::AsioError) -> DefaultFormatError { } } } - -fn supported_formats_err(e: sys::AsioError) -> SupportedFormatsError { - match e { - sys::AsioError::NoDrivers | - sys::AsioError::HardwareMalfunction => SupportedFormatsError::DeviceNotAvailable, - sys::AsioError::InvalidInput | - sys::AsioError::BadMode => SupportedFormatsError::InvalidArgument, - err => { - let description = format!("{}", err); - BackendSpecificError { description }.into() - } - } -} From 4dafb212fb742f277e6878a25cda1042cfd1d8a9 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 03:45:31 +1000 Subject: [PATCH 61/75] Refactor build_input_stream callback to, like recent output refactor --- src/host/asio/stream.rs | 543 +++++++++++++++------------------------- 1 file changed, 197 insertions(+), 346 deletions(-) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 61b1dfb..c356aa9 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -2,7 +2,6 @@ extern crate asio_sys as sys; extern crate num_traits; use self::num_traits::PrimInt; -use super::asio_utils as au; use super::Device; use std; use std::mem; @@ -28,6 +27,7 @@ trait Silence { /// Constraints on the interleaved sample buffer format required by the CPAL API. trait InterleavedSample: Clone + Copy + Silence { + fn unknown_type_input_buffer(&[Self]) -> UnknownTypeInputBuffer; fn unknown_type_output_buffer(&mut [Self]) -> UnknownTypeOutputBuffer; } @@ -40,7 +40,7 @@ pub struct EventLoop { asio_streams: Arc>, /// List of all CPAL streams cpal_streams: Arc>>>, - /// Total stream count + /// Total stream count. stream_count: AtomicUsize, /// The CPAL callback that the user gives to fill the buffers. callbacks: Arc>>, @@ -63,16 +63,6 @@ struct Stream { driver: Arc, } -struct Buffers { - interleaved: Vec, - non_interleaved: Vec, -} - -enum Endian { - Little, - Big, -} - // Used to keep track of whether or not the current current asio stream buffer requires // being silencing before summing audio. #[derive(Default)] @@ -217,328 +207,174 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - unimplemented!() - // let Device { driver, .. } = device; - // let num_channels = format.channels.clone(); - // let stream_type = driver.data_type().map_err(build_stream_err)?; - // let stream_buffer_size = self.get_input_stream(&driver, format, device)?; - // let cpal_num_samples = stream_buffer_size * num_channels as usize; - // let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - // let asio_streams = self.asio_streams.clone(); - // let cpal_streams = self.cpal_streams.clone(); - // let callbacks = self.callbacks.clone(); + let Device { driver, .. } = device; + let num_channels = format.channels.clone(); + let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_buffer_size = self.get_input_stream(&driver, format, device)?; + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); - // let channel_len = cpal_num_samples / num_channels as usize; + // Create the buffer depending on the size of the data type. + let stream_id = StreamId(count); + let data_type = format.data_type; + let len_bytes = cpal_num_samples * data_type.sample_size(); + let mut interleaved = vec![0u8; len_bytes]; - // // Create buffers depending on data type - // // TODO the naming of cpal and channel is confusing. - // // change it to: - // // cpal -> interleaved - // // channels -> per_channel - // let mut buffers = match format.data_type { - // SampleFormat::I16 => Buffers { - // i16_buff: I16Buffer { - // cpal: vec![0 as i16; cpal_num_samples], - // channel: (0..num_channels) - // .map(|_| Vec::with_capacity(channel_len)) - // .collect(), - // }, - // f32_buff: F32Buffer::default(), - // }, - // SampleFormat::F32 => Buffers { - // i16_buff: I16Buffer::default(), - // f32_buff: F32Buffer { - // cpal: vec![0 as f32; cpal_num_samples], - // channel: (0..num_channels) - // .map(|_| Vec::with_capacity(channel_len)) - // .collect(), - // }, - // }, - // _ => unimplemented!(), - // }; + // Set the input callback. + // This is most performance critical part of the ASIO bindings. + sys::set_callback(move |buffer_index| unsafe { + // If not playing return early. + // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. + if let Some(s) = cpal_streams.lock().unwrap().get(count) { + if let Some(s) = s { + if !s.playing { + return; + } + } + } - // // Set the input callback. - // // This is most performance critical part of the ASIO bindings. - // sys::set_callback(move |index| unsafe { - // // if not playing return early - // { - // if let Some(s) = cpal_streams.lock().unwrap().get(count) { - // if let Some(s) = s { - // if !s.playing { - // return (); - // } - // } - // } - // } - // // Get the stream - // let stream_lock = asio_streams.lock().unwrap(); - // let ref asio_stream = match stream_lock.input { - // Some(ref asio_stream) => asio_stream, - // None => return (), - // }; + // Acquire the stream and callback. + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return, + }; + let mut callbacks = callbacks.lock().unwrap(); + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return, + }; - // // Get the callback - // let mut callbacks = callbacks.lock().unwrap(); + /// 1. Write from the ASIO buffer to the interleaved CPAL buffer. + /// 2. Deliver the CPAL buffer to the user callback. + unsafe fn process_input_callback( + stream_id: StreamId, + callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send), + interleaved: &mut [u8], + asio_stream: &sys::AsioStream, + buffer_index: usize, + from_endianness: F, + to_cpal_sample: G, + ) + where + A: AsioSample, + B: InterleavedSample, + F: Fn(A) -> A, + G: Fn(A) -> B, + { + // 1. Write the ASIO channels to the CPAL buffer. + let interleaved: &mut [B] = cast_slice_mut(interleaved); + let n_channels = interleaved.len() / asio_stream.buffer_size as usize; + for ch_ix in 0..n_channels { + let asio_channel = asio_channel_slice::(asio_stream, buffer_index, ch_ix); + for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) { + frame[ch_ix] = to_cpal_sample(from_endianness(*s_asio)); + } + } - // // Theres only a single callback because theres only one event loop - // let callback = match callbacks.as_mut() { - // Some(callback) => callback, - // None => return (), - // }; + // 2. Deliver the interleaved buffer to the callback. + callback( + stream_id, + Ok(StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }), + ); + } - // // Macro to convert sample from ASIO to CPAL type - // macro_rules! convert_sample { - // // floats types required different conversion - // (f32, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample - // }; - // (f64, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample - // }; - // (f64, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f32 - // }; - // (f32, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f64 - // }; - // ($AsioTypeIdent:ident, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 - // }; - // ($AsioTypeIdent:ident, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 - // }; - // (f32, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - // }; - // (f64, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - // }; - // ($AsioTypeIdent:ident, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - // / ::std::$AsioTypeIdent::MAX as i64) as $SampleType - // }; - // }; - // // This creates gets the buffer and interleaves it. - // // It allows it to be done based on the sample type. - // macro_rules! try_callback { - // ($SampleFormat:ident, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $AsioType:ty, - // $AsioTypeIdent:ident, - // $Buffers:expr, - // $BuffersType:ty, - // $BuffersTypeIdent:ident, - // $Endianness:expr, - // $ConvertEndian:expr - // ) => { - // // For each channel write the asio buffer to - // // the cpal buffer + match (&stream_type, data_type) { + (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_le, + std::convert::identity::, + ); + } + (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_be, + std::convert::identity::, + ); + } - // for (i, channel) in $Buffers.channel.iter_mut().enumerate() { - // let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] - // as *mut $AsioType; - // let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( - // buff_ptr, - // asio_stream.buffer_size as usize, - // ); - // for asio_s in asio_buffer.iter() { - // channel.push($ConvertEndian( - // convert_sample!( - // $AsioTypeIdent, - // $SampleType, - // $SampleTypeIdent, - // asio_s - // ), - // $Endianness, - // )); - // } - // } + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) | + (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + std::convert::identity::, + std::convert::identity::, + ); + } - // // interleave all the channels - // { - // let $BuffersTypeIdent { - // cpal: ref mut c_buffer, - // channel: ref mut channels, - // } = $Buffers; - // au::interleave(&channels, c_buffer); - // // Clear the per channel buffers - // for c in channels.iter_mut() { - // c.clear(); - // } - // } + // TODO: Add support for the following sample formats to CPAL and simplify the + // `process_output_callback` function above by removing the unnecessary sample + // conversion function. + (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_le, + |s| (s >> 16) as i16, + ); + } + (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_be, + |s| (s >> 16) as i16, + ); + } + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) | + (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + std::convert::identity::, + |s| s as f32, + ); + } - // // Call the users callback with the buffer - // callback( - // StreamId(count), - // Ok(StreamData::Input { - // buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { - // buffer: &$Buffers.cpal, - // }), - // }), - // ); - // }; - // }; - // // Call the right buffer handler depending on types - // match stream_type { - // sys::AsioSampleType::ASIOSTInt32LSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i32, - // i32, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Little, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt16LSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i16, - // i16, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Little, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt32MSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i32, - // i32, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Big, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt16MSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i16, - // i16, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Big, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTFloat32LSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f32, - // f32, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Little, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat64LSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f64, - // f64, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Little, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat32MSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f32, - // f32, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Big, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat64MSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f64, - // f64, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Big, - // |a, _| a - // ); - // } - // _ => println!("unsupported format {:?}", stream_type), - // } - // }); - // // Create stream and set to paused - // self.cpal_streams - // .lock() - // .unwrap() - // .push(Some(Stream { driver: driver.clone(), playing: false })); + unsupported_format_pair => { + unreachable!("`build_input_stream` should have returned with unsupported \ + format {:?}", unsupported_format_pair) + } + } + }); - // Ok(StreamId(count)) + // Create stream and set to paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { driver: driver.clone(), playing: false })); + + Ok(StreamId(count)) } /// Create the an output cpal stream. @@ -551,7 +387,6 @@ impl EventLoop { let num_channels = format.channels.clone(); let stream_type = driver.data_type().map_err(build_stream_err)?; let stream_buffer_size = self.get_output_stream(&driver, format, device)?; - let channel_len = stream_buffer_size as usize; let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); @@ -567,6 +402,7 @@ impl EventLoop { sys::set_callback(move |buffer_index| unsafe { // If not playing, return early. + // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. if let Some(s) = cpal_streams.lock().unwrap().get(count) { if let Some(s) = s { if !s.playing { @@ -790,6 +626,8 @@ impl EventLoop { /// Destroy the cpal stream based on the ID. pub fn destroy_stream(&self, stream_id: StreamId) { // TODO: Should we not also remove an ASIO stream here? + // Yes, and we should update the logic in the callbacks to search for the stream with + // the matching ID, rather than assuming the index associated with the ID is valid. let mut streams = self.cpal_streams.lock().unwrap(); streams.get_mut(stream_id.0).take(); } @@ -838,12 +676,20 @@ impl Silence for f64 { } impl InterleavedSample for i16 { + fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { + UnknownTypeInputBuffer::I16(::InputBuffer { buffer }) + } + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { UnknownTypeOutputBuffer::I16(::OutputBuffer { buffer }) } } impl InterleavedSample for f32 { + fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { + UnknownTypeInputBuffer::F32(::InputBuffer { buffer }) + } + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer }) } @@ -857,14 +703,6 @@ impl AsioSample for f32 {} impl AsioSample for f64 {} -/// Cast a byte slice into a (immutable) slice of desired type. -/// -/// Safety: it's up to the caller to ensure that the input slice has valid bit representations. -unsafe fn cast_slice(v: &[u8]) -> &[T] { - debug_assert!(v.len() % std::mem::size_of::() == 0); - std::slice::from_raw_parts(v.as_ptr() as *const T, v.len() / std::mem::size_of::()) -} - /// Cast a byte slice into a mutable slice of desired type. /// /// Safety: it's up to the caller to ensure that the input slice has valid bit representations. @@ -873,21 +711,42 @@ unsafe fn cast_slice_mut(v: &mut [u8]) -> &mut [T] { std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) } -/// Helper function to convert to system endianness +/// Helper function to convert to little endianness. fn to_le(t: T) -> T { t.to_le() } -/// Helper function to convert from system endianness +/// Helper function to convert to big endianness. fn to_be(t: T) -> T { t.to_be() } +/// Helper function to convert from little endianness. +fn from_le(t: T) -> T { + T::from_le(t) +} + +/// Helper function to convert from little endianness. +fn from_be(t: T) -> T { + T::from_be(t) +} + /// Shorthand for retrieving the asio buffer slice associated with a channel. /// -/// Safety: it's up to the user to ensure the slice is not used beyond the lifetime of -/// the stream and that this function is not called multiple times for the same -/// channel. +/// Safety: it's up to the user to ensure that this function is not called multiple times for the +/// same channel. +unsafe fn asio_channel_slice( + asio_stream: &sys::AsioStream, + buffer_index: usize, + channel_index: usize, +) -> &[T] { + asio_channel_slice_mut(asio_stream, buffer_index, channel_index) +} + +/// Shorthand for retrieving the asio buffer slice associated with a channel. +/// +/// Safety: it's up to the user to ensure that this function is not called multiple times for the +/// same channel. unsafe fn asio_channel_slice_mut( asio_stream: &sys::AsioStream, buffer_index: usize, @@ -900,14 +759,6 @@ unsafe fn asio_channel_slice_mut( std::slice::from_raw_parts_mut(buff_ptr, asio_stream.buffer_size as usize) } -/// Helper function to convert from system endianness -fn convert_endian_from(sample: T, endian: Endian) -> T { - match endian { - Endian::Big => T::from_be(sample), - Endian::Little => T::from_le(sample), - } -} - fn build_stream_err(e: sys::AsioError) -> BuildStreamError { match e { sys::AsioError::NoDrivers | From 191b90909a390fa179649224dd3705f8c0a38a1e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 05:47:58 +1000 Subject: [PATCH 62/75] Make Driver type responsible for managing user callbacks --- asio-sys/src/bindings/mod.rs | 28 +++++++++++++++++++--------- src/host/asio/stream.rs | 4 ++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index a07343a..38617af 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -579,6 +579,17 @@ impl Driver { Ok(()) } + /// Adds a callback to the list of active callbacks. + /// + /// The given function receives the index of the buffer currently ready for processing. + pub fn set_callback(&self, callback: F) + where + F: 'static + FnMut(i32) + Send, + { + let mut bc = BUFFER_CALLBACK.lock().unwrap(); + bc.push(Some(BufferCallback(Box::new(callback)))); + } + /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing /// any associated resources. pub fn destroy(mut self) -> Result<(), AsioError> { @@ -597,7 +608,15 @@ impl Driver { asio_result!(ai::ASIOExit())?; ai::remove_current_driver(); } + + // Clear any existing stream callbacks. + if let Ok(mut bcs) = BUFFER_CALLBACK.lock() { + bcs.clear(); + } + + // Indicate to the self.loaded.store(false, atomic::Ordering::SeqCst); + Ok(()) } } @@ -614,15 +633,6 @@ impl Drop for Driver { unsafe impl Send for AsioStream {} -/// 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)))); -} - /// Idicates the sample rate has changed /// TODO Change the sample rate when this /// is called. diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index c356aa9..99e9c55 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -225,7 +225,7 @@ impl EventLoop { // Set the input callback. // This is most performance critical part of the ASIO bindings. - sys::set_callback(move |buffer_index| unsafe { + driver.set_callback(move |buffer_index| unsafe { // If not playing return early. // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. if let Some(s) = cpal_streams.lock().unwrap().get(count) { @@ -400,7 +400,7 @@ impl EventLoop { let mut interleaved = vec![0u8; len_bytes]; let mut silence_asio_buffer = SilenceAsioBuffer::default(); - sys::set_callback(move |buffer_index| unsafe { + driver.set_callback(move |buffer_index| unsafe { // If not playing, return early. // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. if let Some(s) = cpal_streams.lock().unwrap().get(count) { From 76fab4f98230a4d0edb68497a25b1831701a3d5d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 05:48:18 +1000 Subject: [PATCH 63/75] Update README for ASIO v2.3.3 --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 14e31ab..c32cb0b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This library currently supports the following: - Get the current default input and output stream formats for a device. - Build and run input and output PCM streams on a chosen device with a given stream format. -Currently supported backends include: +Currently supported hosts include: - Linux (via ALSA) - Windows (via WASAPI by default, see ASIO instructions below) @@ -39,10 +39,11 @@ WASAPI. To do so, follow these steps: 1. **Download the ASIO SDK** `.zip` from [this link](https://www.steinberg.net/en/company/developers.html). The version as of writing this is 2.3.1. -2. Extract the files and place the `ASIOSDK2.3.1` directory somewhere you are - happy for it to stay (e.g. `~/.asio`). -3. Assign the full path of the `ASIOSDK2.3.1` directory to the `CPAL_ASIO_DIR` - environment variable. [How to set persisting Environment Variables on +2. Extract the files and place the directory somewhere you are happy for it to stay + (e.g. `~/.asio`). +3. Assign the full path of the directory (that contains the `readme`, `changes`, + `ASIO SDK 2.3` pdf, etc) to the `CPAL_ASIO_DIR` environment variable. [How to + set persisting Environment Variables on Windows](https://gist.github.com/mitchmindtree/92c8e37fa80c8dddee5b94fc88d1288b#file-windows_environment_variables-md). The existence of the `CPAL_ASIO_DIR` environment variable determines whether or not CPAL will attempt to build the ASIO SDK and generate bindings to it. @@ -83,5 +84,8 @@ WASAPI. To do so, follow these steps: If you run into an error along the lines of "no variant `Asio` in `HostId`", make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. + In the future we would like to work on automating this process to make it easier, but we are not familiar enough with the ASIO license to do so yet. + +*Updated as of ASIO version 2.3.3.* From 8f95042b8d667ad730c131b17414960d78632ddf Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 06:08:35 +1000 Subject: [PATCH 64/75] Add `asio` cargo feature for optional ASIO support ASIO introduces quite a few extra crates and requires a fairly complicated setup process (see the readme), so this feature is disabled by default. --- Cargo.toml | 5 ++++- README.md | 11 +++++++++++ src/host/mod.rs | 2 +- src/platform/mod.rs | 7 +++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fa15b6..37c72c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ documentation = "https://docs.rs/cpal" license = "Apache-2.0" keywords = ["audio", "sound"] +[features] +asio = ["asio-sys"] # Only available on Windows. See README for setup instructions. + [dependencies] failure = "0.1.5" lazy_static = "1.3" @@ -18,7 +21,7 @@ hound = "3.4" [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" } +asio-sys = { version = "0.1", path = "asio-sys", optional = true } [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/README.md b/README.md index c32cb0b..d3a0f55 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,18 @@ WASAPI. To do so, follow these steps: If you run into an error along the lines of "no variant `Asio` in `HostId`", make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. +9. Make sure to enable the `asio` feature when building CPAL: + ``` + cargo build --features "asio" + ``` + + or if you are using CPAL as a dependency in a downstream project, enable the + feature like this: + + ```toml + cpal = { version = "*", features = ["asio"] } + ``` In the future we would like to work on automating this process to make it easier, but we are not familiar enough with the ASIO license to do so yet. diff --git a/src/host/mod.rs b/src/host/mod.rs index 2bc9b2e..3b1b61a 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -1,6 +1,6 @@ #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) mod alsa; -#[cfg(windows)] +#[cfg(all(windows, feature = "asio"))] pub(crate) mod asio; #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) mod coreaudio; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 7eb2081..976acd5 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -521,6 +521,9 @@ mod platform_impl { // TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] mod platform_impl { + #![allow(unreachable_patterns)] + + #[cfg(feature = "asio")] pub use crate::host::asio::{ Device as AsioDevice, Devices as AsioDevices, @@ -540,8 +543,12 @@ mod platform_impl { SupportedOutputFormats as WasapiSupportedOutputFormats, }; + #[cfg(feature = "asio")] impl_platform_host!(Asio asio, Wasapi wasapi); + #[cfg(not(feature = "asio"))] + impl_platform_host!(Wasapi wasapi); + /// The default host for the current compilation target platform. pub fn default_host() -> Host { WasapiHost::new() From c2b7c7f648f0e331b1168319ac5001852e507bdf Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 06:32:02 +1000 Subject: [PATCH 65/75] Remove .DS_Store from asio-sys gitignore --- asio-sys/.gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/asio-sys/.gitignore b/asio-sys/.gitignore index 3375044..96ef6c0 100644 --- a/asio-sys/.gitignore +++ b/asio-sys/.gitignore @@ -1,4 +1,2 @@ /target -/Cargo.lock -.cargo/ -.DS_Store \ No newline at end of file +Cargo.lock From 86e3366063d40d85f7d70d6ef2cb71bda7b882d6 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 06:46:07 +1000 Subject: [PATCH 66/75] Remove unnecessary num crate from asio-sys --- asio-sys/Cargo.toml | 5 ++--- asio-sys/src/bindings/mod.rs | 3 ++- asio-sys/src/lib.rs | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/asio-sys/Cargo.toml b/asio-sys/Cargo.toml index 34f598a..b1208d5 100644 --- a/asio-sys/Cargo.toml +++ b/asio-sys/Cargo.toml @@ -11,6 +11,5 @@ cc = "1.0.25" [dependencies] lazy_static = "1.0.0" -num = "0.1.42" -num-traits = "0.2.2" -num-derive = "0.2.0" +num-derive = "0.2" +num-traits = "0.2" diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 38617af..1ff86af 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -2,6 +2,7 @@ mod asio_import; #[macro_use] pub mod errors; +use num_traits::FromPrimitive; use self::errors::{AsioError, AsioErrorWrapper, LoadDriverError}; use std::ffi::CStr; use std::ffi::CString; @@ -320,7 +321,7 @@ impl Driver { }; unsafe { asio_result!(ai::ASIOGetChannelInfo(&mut channel_info))?; - Ok(num::FromPrimitive::from_i32(channel_info.type_).expect("failed to cast sample type")) + Ok(FromPrimitive::from_i32(channel_info.type_).expect("failed to cast sample type")) } } diff --git a/asio-sys/src/lib.rs b/asio-sys/src/lib.rs index 9a2acc6..a071e7b 100644 --- a/asio-sys/src/lib.rs +++ b/asio-sys/src/lib.rs @@ -4,10 +4,11 @@ #[macro_use] extern crate lazy_static; -extern crate num; #[allow(unused_imports)] #[macro_use] extern crate num_derive; +#[allow(unused_imports)] +extern crate num_traits; #[cfg(asio)] pub mod bindings; From 7d0f9c7265ff7dbe99395c63ccd89043e0c37209 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 28 Jun 2019 23:03:35 +0200 Subject: [PATCH 67/75] Remove unreachable_patterns gate onto relevant fns so all platforms benefit --- src/platform/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 976acd5..d64f1d2 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -256,6 +256,7 @@ macro_rules! impl_platform_host { type StreamId = StreamId; type Device = Device; + #[allow(unreachable_patterns)] fn build_input_stream( &self, device: &Self::Device, @@ -273,6 +274,7 @@ macro_rules! impl_platform_host { } } + #[allow(unreachable_patterns)] fn build_output_stream( &self, device: &Self::Device, @@ -290,6 +292,7 @@ macro_rules! impl_platform_host { } } + #[allow(unreachable_patterns)] fn play_stream(&self, stream: Self::StreamId) -> Result<(), crate::PlayStreamError> { match (&self.0, stream.0) { $( @@ -301,6 +304,7 @@ macro_rules! impl_platform_host { } } + #[allow(unreachable_patterns)] fn pause_stream(&self, stream: Self::StreamId) -> Result<(), crate::PauseStreamError> { match (&self.0, stream.0) { $( @@ -312,6 +316,7 @@ macro_rules! impl_platform_host { } } + #[allow(unreachable_patterns)] fn destroy_stream(&self, stream: Self::StreamId) { match (&self.0, stream.0) { $( @@ -521,8 +526,6 @@ mod platform_impl { // TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] mod platform_impl { - #![allow(unreachable_patterns)] - #[cfg(feature = "asio")] pub use crate::host::asio::{ Device as AsioDevice, From 8b4ebeffffd5544a2385801fd755b60666243eeb Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 1 Jul 2019 15:23:45 +0200 Subject: [PATCH 68/75] Address README suggestions by @ishitatsuyuki --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d3a0f55..d68b357 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,10 @@ WASAPI. To do so, follow these steps: 2. Extract the files and place the directory somewhere you are happy for it to stay (e.g. `~/.asio`). 3. Assign the full path of the directory (that contains the `readme`, `changes`, - `ASIO SDK 2.3` pdf, etc) to the `CPAL_ASIO_DIR` environment variable. [How to - set persisting Environment Variables on - Windows](https://gist.github.com/mitchmindtree/92c8e37fa80c8dddee5b94fc88d1288b#file-windows_environment_variables-md). - The existence of the `CPAL_ASIO_DIR` environment variable determines whether - or not CPAL will attempt to build the ASIO SDK and generate bindings to it. -4. **Download and install LLVM** from + `ASIO SDK 2.3` pdf, etc) to the `CPAL_ASIO_DIR` environment variable. This is + necessary for the `asio-sys` build script to build and bind to the SDK. +4. `bindgen`, the library used to generate bindings to the C++ SDK, requires + clang. **Download and install LLVM** from [here](http://releases.llvm.org/download.html) under the "Pre-Built Binaries" section. The version as of writing this is 7.0.0. 5. Add the LLVM `bin` directory to a `LIBCLANG_PATH` environment variable. If @@ -71,6 +69,8 @@ WASAPI. To do so, follow these steps: ``` "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 ``` + Note that, depending on your version of Visual Studio, this script might be + in a slightly different location. 8. Select the ASIO host at the start of our program with the following code: ```rust @@ -81,8 +81,8 @@ WASAPI. To do so, follow these steps: } ``` - If you run into an error along the lines of "no variant `Asio` in `HostId`", - make sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. + If you run into compilations errors produced by `asio-sys` or `bindgen`, make + sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. 9. Make sure to enable the `asio` feature when building CPAL: ``` From c432f2b18d087a3007b0f31680bf9278cb22a831 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 1 Jul 2019 16:13:14 +0200 Subject: [PATCH 69/75] Update asio-sys to allow for having multiple handles to the same driver ASIO has a limitation where it only supports loading a single audio driver at a time. This fixes a common error where CPAL users would request both the default input device and output device in separate `load_driver` calls. Now, `load_driver` will return another handle to the existing driver if the existing driver has the same name. --- asio-sys/src/bindings/mod.rs | 189 +++++++++++++++++++++++------------ src/host/asio/device.rs | 10 +- 2 files changed, 130 insertions(+), 69 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 1ff86af..93f0e59 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -7,9 +7,7 @@ use self::errors::{AsioError, AsioErrorWrapper, LoadDriverError}; use std::ffi::CStr; use std::ffi::CString; use std::os::raw::{c_char, c_double, c_long, c_void}; -use std::sync::atomic::{self, AtomicBool}; -use std::sync::{Arc, Mutex}; -use std; +use std::sync::{Arc, Mutex, Weak}; // Bindings import use self::asio_import as ai; @@ -22,21 +20,44 @@ pub struct Asio { // Keeps track of whether or not a driver is already loaded. // // This is necessary as ASIO only supports one `Driver` at a time. - driver_loaded: Arc, + loaded_driver: Mutex>, } /// A handle to a single ASIO driver. /// /// Creating an instance of this type loads and initialises the driver. /// -/// Dropping the instance will dispose of any resources and de-initialise the driver. -#[derive(Debug)] +/// Dropping all `Driver` instances will automatically dispose of any resources and de-initialise +/// the driver. +#[derive(Clone, Debug)] pub struct Driver { + inner: Arc, +} + +// Contains the state associated with a `Driver`. +// +// This state may be shared between multiple `Driver` handles representing the same underlying +// driver. Only when the last `Driver` is dropped will the `Drop` implementation for this type run +// and the necessary driver resources will be de-allocated and unloaded. +// +// The same could be achieved by returning an `Arc` from the `Host::load_driver` API, +// however the `DriverInner` abstraction is required in order to allow for the `Driver::destroy` +// method to exist safely. By wrapping the `Arc` in the `Driver` type, we can make +// sure the user doesn't `try_unwrap` the `Arc` and invalidate the `Asio` instance's weak pointer. +// This would allow for instantiation of a separate driver before the existing one is destroyed, +// which is disallowed by ASIO. +#[derive(Debug)] +struct DriverInner { state: Mutex, - // A flag that is set to `false` when the `Driver` is dropped. + // The unique name associated with this driver. + name: String, + // Track whether or not the driver has been destroyed. // - // This lets the `Asio` handle know that a new driver can be loaded. - loaded: Arc, + // This allows for the user to manually destroy the driver and handle any errors if they wish. + // + // In the case that the driver has been manually destroyed this flag will be set to `true` + // indicating to the `drop` implementation that there is nothing to be done. + destroyed: bool, } /// All possible states of an ASIO `Driver` instance. @@ -168,8 +189,8 @@ lazy_static! { impl Asio { /// Initialise the ASIO API. pub fn new() -> Self { - let driver_loaded = Arc::new(AtomicBool::new(false)); - Asio { driver_loaded } + let loaded_driver = Mutex::new(Weak::new()); + Asio { loaded_driver } } /// Returns the name for each available driver. @@ -204,14 +225,18 @@ impl Asio { } } - /// Whether or not a driver has already been loaded by this process. + /// If a driver has already been loaded, this will return that driver. + /// + /// Returns `None` if no driver is currently loaded. /// /// This can be useful to check before calling `load_driver` as ASIO only supports loading a /// single driver at a time. - /// - /// Uses the given atomic ordering to access the atomic boolean used to track driver loading. - pub fn is_driver_loaded(&self, ord: atomic::Ordering) -> bool { - self.driver_loaded.load(ord) + pub fn loaded_driver(&self) -> Option { + self.loaded_driver + .lock() + .expect("failed to acquire loaded driver lock") + .upgrade() + .map(|inner| Driver { inner }) } /// Load a driver from the given name. @@ -221,14 +246,20 @@ impl Asio { /// /// NOTE: Despite many requests from users, ASIO only supports loading a single driver at a /// time. Calling this method while a previously loaded `Driver` instance exists will result in - /// an error. + /// an error. That said, if this method is called with the name of a driver that has already + /// been loaded, that driver will be returned successfully. pub fn load_driver(&self, driver_name: &str) -> Result { - if self.driver_loaded.load(atomic::Ordering::SeqCst) { - return Err(LoadDriverError::DriverAlreadyExists); + // Check whether or not a driver is already loaded. + if let Some(driver) = self.loaded_driver() { + if driver.name() == driver_name { + return Ok(driver); + } else { + return Err(LoadDriverError::DriverAlreadyExists); + } } // Make owned CString to send to load driver - let my_driver_name = CString::new(driver_name) + let driver_name_cstring = CString::new(driver_name) .expect("failed to create `CString` from driver name"); let mut driver_info = ai::ASIODriverInfo { _bindgen_opaque_blob: [0u32; 43], @@ -236,15 +267,18 @@ impl Asio { unsafe { // TODO: Check that a driver of the same name does not already exist? - match ai::load_asio_driver(my_driver_name.as_ptr() as *mut i8) { + match ai::load_asio_driver(driver_name_cstring.as_ptr() as *mut i8) { false => Err(LoadDriverError::LoadDriverFailed), true => { // Initialize ASIO. asio_result!(ai::ASIOInit(&mut driver_info))?; - self.driver_loaded.store(true, atomic::Ordering::SeqCst); - let loaded = self.driver_loaded.clone(); let state = Mutex::new(DriverState::Initialized); - let driver = Driver { state, loaded }; + let name = driver_name.to_string(); + let destroyed = false; + let inner = Arc::new(DriverInner { name, state, destroyed }); + *self.loaded_driver.lock().expect("failed to acquire loaded driver lock") = + Arc::downgrade(&inner); + let driver = Driver { inner }; Ok(driver) } } @@ -261,6 +295,11 @@ impl BufferCallback { } impl Driver { + /// The name used to uniquely identify this driver. + pub fn name(&self) -> &str { + &self.inner.name + } + /// Returns the number of input and output channels available on the driver. pub fn channels(&self) -> Result { let mut ins: c_long = 0; @@ -365,10 +404,10 @@ impl Driver { ); } - if let DriverState::Running = self.state() { + if let DriverState::Running = self.inner.state() { self.stop()?; } - if let DriverState::Prepared = self.state() { + if let DriverState::Prepared = self.inner.state() { self.dispose_buffers()?; } @@ -379,7 +418,7 @@ impl Driver { callbacks as *mut _, ))?; } - self.set_state(DriverState::Prepared); + self.inner.set_state(DriverState::Prepared); Ok(pref_b_size) } @@ -519,31 +558,13 @@ impl Driver { self.create_streams(streams) } - fn state(&self) -> DriverState { - *self.state.lock().expect("failed to lock `DriverState`") - } - - fn set_state(&self, state: DriverState) { - *self.state.lock().expect("failed to lock `DriverState`") = state; - } - /// Releases buffers allocations. /// /// This will `stop` the stream if the driver is `Running`. /// /// No-op if no buffers are allocated. pub fn dispose_buffers(&self) -> Result<(), AsioError> { - if let DriverState::Initialized = self.state() { - return Ok(()); - } - if let DriverState::Running = self.state() { - self.stop()?; - } - unsafe { - asio_result!(ai::ASIODisposeBuffers())?; - } - self.set_state(DriverState::Initialized); - Ok(()) + self.inner.dispose_buffers_inner() } /// Starts ASIO streams playing. @@ -554,13 +575,13 @@ impl Driver { /// /// No-op if already `Running`. pub fn start(&self) -> Result<(), AsioError> { - if let DriverState::Running = self.state() { + if let DriverState::Running = self.inner.state() { return Ok(()); } unsafe { asio_result!(ai::ASIOStart())?; } - self.set_state(DriverState::Running); + self.inner.set_state(DriverState::Running); Ok(()) } @@ -571,13 +592,7 @@ impl Driver { /// If the state was `Running` and the stream is stopped successfully, the driver will be in /// the `Prepared` state. pub fn stop(&self) -> Result<(), AsioError> { - if let DriverState::Running = self.state() { - unsafe { - asio_result!(ai::ASIOStop())?; - } - self.set_state(DriverState::Prepared); - } - Ok(()) + self.inner.stop_inner() } /// Adds a callback to the list of active callbacks. @@ -593,17 +608,65 @@ impl Driver { /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing /// any associated resources. - pub fn destroy(mut self) -> Result<(), AsioError> { - self.destroy_inner() + /// + /// Returns `Ok(true)` if the driver was successfully destroyed. + /// + /// Returns `Ok(false)` if the driver was not destroyed because another handle to the driver + /// still exists. + /// + /// Returns `Err` if some switching driver states failed or if ASIO returned an error on exit. + pub fn destroy(self) -> Result { + let Driver { inner } = self; + match Arc::try_unwrap(inner) { + Err(_) => Ok(false), + Ok(mut inner) => { + inner.destroy_inner()?; + Ok(true) + } + } + } +} + +impl DriverInner { + fn state(&self) -> DriverState { + *self.state.lock().expect("failed to lock `DriverState`") + } + + fn set_state(&self, state: DriverState) { + *self.state.lock().expect("failed to lock `DriverState`") = state; + } + + fn stop_inner(&self) -> Result<(), AsioError> { + if let DriverState::Running = self.state() { + unsafe { + asio_result!(ai::ASIOStop())?; + } + self.set_state(DriverState::Prepared); + } + Ok(()) + } + + fn dispose_buffers_inner(&self) -> Result<(), AsioError> { + if let DriverState::Initialized = self.state() { + return Ok(()); + } + if let DriverState::Running = self.state() { + self.stop_inner()?; + } + unsafe { + asio_result!(ai::ASIODisposeBuffers())?; + } + self.set_state(DriverState::Initialized); + Ok(()) } fn destroy_inner(&mut self) -> Result<(), AsioError> { // Drop back through the driver state machine one state at a time. if let DriverState::Running = self.state() { - self.stop().expect("failed to stop ASIO driver"); + self.stop_inner()?; } if let DriverState::Prepared = self.state() { - self.dispose_buffers().expect("failed to dispose buffers of ASIO driver"); + self.dispose_buffers_inner()?; } unsafe { asio_result!(ai::ASIOExit())?; @@ -615,16 +678,16 @@ impl Driver { bcs.clear(); } - // Indicate to the - self.loaded.store(false, atomic::Ordering::SeqCst); + // Signal that the driver has been destroyed. + self.destroyed = true; Ok(()) } } -impl Drop for Driver { +impl Drop for DriverInner { fn drop(&mut self) { - if self.loaded.load(atomic::Ordering::SeqCst) { + if self.destroyed { // We probably shouldn't `panic!` in the destructor? We also shouldn't ignore errors // though either. self.destroy_inner().ok(); diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index 9acf425..7dea0d6 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -20,8 +20,6 @@ use super::sys; pub struct Device { /// The drivers for this device pub driver: Arc, - /// The name of this device - pub name: String, } /// All available devices @@ -32,7 +30,7 @@ pub struct Devices { impl PartialEq for Device { fn eq(&self, other: &Self) -> bool { - self.name == other.name + self.driver.name() == other.driver.name() } } @@ -40,13 +38,13 @@ impl Eq for Device {} impl Hash for Device { fn hash(&self, state: &mut H) { - self.name.hash(state); + self.driver.name().hash(state); } } impl Device { pub fn name(&self) -> Result { - Ok(self.name.clone()) + Ok(self.driver.name().to_string()) } /// Gets the supported input formats. @@ -148,7 +146,7 @@ impl Iterator for Devices { loop { match self.drivers.next() { Some(name) => match self.asio.load_driver(&name) { - Ok(driver) => return Some(Device { driver: Arc::new(driver), name }), + Ok(driver) => return Some(Device { driver: Arc::new(driver) }), Err(_) => continue, } None => return None, From 9182f00e208dfca0338dabd7bf357ac64a073975 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 2 Jul 2019 02:30:30 +1000 Subject: [PATCH 70/75] Check that sample format is supported by ASIO when building streams --- src/host/asio/device.rs | 25 +++++---- src/host/asio/stream.rs | 117 +++++++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index 7dea0d6..6e39251 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -18,11 +18,11 @@ use super::sys; /// A ASIO Device #[derive(Debug)] pub struct Device { - /// The drivers for this device + /// The driver represented by this device. pub driver: Arc, } -/// All available devices +/// All available devices. pub struct Devices { asio: Arc, drivers: std::vec::IntoIter, @@ -109,7 +109,8 @@ impl Device { let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); // Map th ASIO sample type to a CPAL sample type let data_type = self.driver.data_type().map_err(default_format_err)?; - let data_type = convert_data_type(data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; + let data_type = convert_data_type(&data_type) + .ok_or(DefaultFormatError::StreamTypeNotSupported)?; Ok(Format { channels, sample_rate, @@ -122,7 +123,8 @@ impl Device { let channels = self.driver.channels().map_err(default_format_err)?.outs as u16; let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); let data_type = self.driver.data_type().map_err(default_format_err)?; - let data_type = convert_data_type(data_type).ok_or(DefaultFormatError::StreamTypeNotSupported)?; + let data_type = convert_data_type(&data_type) + .ok_or(DefaultFormatError::StreamTypeNotSupported)?; Ok(Format { channels, sample_rate, @@ -159,14 +161,19 @@ impl Iterator for Devices { } } -fn convert_data_type(ty: sys::AsioSampleType) -> Option { - let fmt = match ty { +pub(crate) fn convert_data_type(ty: &sys::AsioSampleType) -> Option { + let fmt = match *ty { sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16, - sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I16, - sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32, sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16, - sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32, sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32, + // NOTE: While ASIO does not support these formats directly, the stream callback created by + // CPAL supports converting back and forth between the following. This is because many ASIO + // drivers only support `Int32` formats, while CPAL does not support this format at all. We + // allow for this implicit conversion temporarily until CPAL gets support for an `I32` + // format. + sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I16, + sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I16, _ => return None, }; Some(fmt) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 99e9c55..092f7b2 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -86,43 +86,12 @@ impl EventLoop { } } - fn check_format( - &self, - driver: &sys::Driver, - format: &Format, - num_asio_channels: u16, - ) -> Result<(), BuildStreamError> { - let Format { - channels, - sample_rate, - data_type, - } = format; - // Try and set the sample rate to what the user selected. - let sample_rate = sample_rate.0.into(); - if sample_rate != driver.sample_rate().map_err(build_stream_err)? { - if driver.can_sample_rate(sample_rate).map_err(build_stream_err)? { - driver - .set_sample_rate(sample_rate) - .map_err(build_stream_err)?; - } else { - return Err(BuildStreamError::FormatNotSupported); - } - } - // unsigned formats are not supported by asio - match data_type { - SampleFormat::I16 | SampleFormat::F32 => (), - SampleFormat::U16 => return Err(BuildStreamError::FormatNotSupported), - } - if *channels > num_asio_channels { - return Err(BuildStreamError::FormatNotSupported); - } - Ok(()) - } - /// Create a new CPAL Input Stream. - /// If there is no ASIO Input Stream - /// it will be created. - fn get_input_stream( + /// + /// If there is no existing ASIO Input Stream it will be created. + /// + /// On success, the buffer size of the stream is returned. + fn get_or_create_input_stream( &self, driver: &sys::Driver, format: &Format, @@ -131,7 +100,7 @@ impl EventLoop { match device.default_input_format() { Ok(f) => { let num_asio_channels = f.channels; - self.check_format(driver, format, num_asio_channels) + check_format(driver, format, num_asio_channels) }, Err(_) => Err(BuildStreamError::FormatNotSupported), }?; @@ -161,9 +130,11 @@ impl EventLoop { } /// Create a new CPAL Output Stream. - /// If there is no ASIO Output Stream - /// it will be created. - fn get_output_stream( + /// + /// If there is no existing ASIO Output Stream it will be created. + /// + /// On success, the buffer size of the stream is returned. + fn get_or_create_output_stream( &self, driver: &sys::Driver, format: &Format, @@ -172,14 +143,13 @@ impl EventLoop { match device.default_output_format() { Ok(f) => { let num_asio_channels = f.channels; - self.check_format(driver, format, num_asio_channels) + check_format(driver, format, num_asio_channels) }, Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; let ref mut streams = *self.asio_streams.lock().unwrap(); - // Either create a stream if thers none or had back the - // size of the current one. + // Either create a stream if there's none or return the size of the current one. match streams.output { Some(ref output) => Ok(output.buffer_size as usize), None => { @@ -208,9 +178,17 @@ impl EventLoop { format: &Format, ) -> Result { let Device { driver, .. } = device; - let num_channels = format.channels.clone(); let stream_type = driver.data_type().map_err(build_stream_err)?; - let stream_buffer_size = self.get_input_stream(&driver, format, device)?; + + // Ensure that the desired sample type is supported. + let data_type = super::device::convert_data_type(&stream_type) + .ok_or(BuildStreamError::FormatNotSupported)?; + if format.data_type != data_type { + return Err(BuildStreamError::FormatNotSupported); + } + + let num_channels = format.channels.clone(); + let stream_buffer_size = self.get_or_create_input_stream(&driver, format, device)?; let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); @@ -219,7 +197,6 @@ impl EventLoop { // Create the buffer depending on the size of the data type. let stream_id = StreamId(count); - let data_type = format.data_type; let len_bytes = cpal_num_samples * data_type.sample_size(); let mut interleaved = vec![0u8; len_bytes]; @@ -384,9 +361,17 @@ impl EventLoop { format: &Format, ) -> Result { let Device { driver, .. } = device; - let num_channels = format.channels.clone(); let stream_type = driver.data_type().map_err(build_stream_err)?; - let stream_buffer_size = self.get_output_stream(&driver, format, device)?; + + // Ensure that the desired sample type is supported. + let data_type = super::device::convert_data_type(&stream_type) + .ok_or(BuildStreamError::FormatNotSupported)?; + if format.data_type != data_type { + return Err(BuildStreamError::FormatNotSupported); + } + + let num_channels = format.channels.clone(); + let stream_buffer_size = self.get_or_create_output_stream(&driver, format, device)?; let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); @@ -395,7 +380,6 @@ impl EventLoop { // Create buffers depending on data type. let stream_id = StreamId(count); - let data_type = format.data_type; let len_bytes = cpal_num_samples * data_type.sample_size(); let mut interleaved = vec![0u8; len_bytes]; let mut silence_asio_buffer = SilenceAsioBuffer::default(); @@ -703,6 +687,41 @@ impl AsioSample for f32 {} impl AsioSample for f64 {} +/// Check whether or not the desired format is supported by the stream. +/// +/// Checks sample rate, data type and then finally the number of channels. +fn check_format( + driver: &sys::Driver, + format: &Format, + num_asio_channels: u16, +) -> Result<(), BuildStreamError> { + let Format { + channels, + sample_rate, + data_type, + } = format; + // Try and set the sample rate to what the user selected. + let sample_rate = sample_rate.0.into(); + if sample_rate != driver.sample_rate().map_err(build_stream_err)? { + if driver.can_sample_rate(sample_rate).map_err(build_stream_err)? { + driver + .set_sample_rate(sample_rate) + .map_err(build_stream_err)?; + } else { + return Err(BuildStreamError::FormatNotSupported); + } + } + // unsigned formats are not supported by asio + match data_type { + SampleFormat::I16 | SampleFormat::F32 => (), + SampleFormat::U16 => return Err(BuildStreamError::FormatNotSupported), + } + if *channels > num_asio_channels { + return Err(BuildStreamError::FormatNotSupported); + } + Ok(()) +} + /// Cast a byte slice into a mutable slice of desired type. /// /// Safety: it's up to the caller to ensure that the input slice has valid bit representations. From 91c5aa86b35c83f46d62ebdddd5f0868d18b9fc7 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 2 Jul 2019 03:11:37 +1000 Subject: [PATCH 71/75] Fix Driver destructor behaviour in asio-sys --- asio-sys/src/bindings/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 93f0e59..201ece4 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -479,7 +479,7 @@ impl Driver { }) }, // Impossible - (None, None) => panic!("Trying to create streams without preparing"), + (None, None) => unreachable!("Trying to create streams without preparing"), } } @@ -687,7 +687,7 @@ impl DriverInner { impl Drop for DriverInner { fn drop(&mut self) { - if self.destroyed { + if !self.destroyed { // We probably shouldn't `panic!` in the destructor? We also shouldn't ignore errors // though either. self.destroy_inner().ok(); From cc5b0555c2952747c45c0193cf184b45280d0299 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Wed, 3 Jul 2019 06:12:07 +1000 Subject: [PATCH 72/75] Refactor of asio-sys while reviewing for code correctness Most of this is an attempt at improving readability and modularity of the asio-sys crate while attempting to review it for correctness. Still unsure why glitching is occasionally occuring on output, but recorded input sounds perfectly clean. --- asio-sys/src/bindings/mod.rs | 380 ++++++++++++++++++----------------- src/host/asio/device.rs | 4 +- src/host/asio/stream.rs | 24 +-- 3 files changed, 215 insertions(+), 193 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 201ece4..a4c31cd 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -1,4 +1,4 @@ -mod asio_import; +pub mod asio_import; #[macro_use] pub mod errors; @@ -175,6 +175,15 @@ struct AsioCallbacks { ) -> *mut ai::ASIOTime, } +// A helper type to simplify retrieval of available buffer sizes. +#[derive(Default)] +struct BufferSizes { + min: c_long, + max: c_long, + pref: c_long, + grans: c_long, +} + lazy_static! { /// A global way to access all the callbacks. /// This is required because of how ASIO @@ -200,28 +209,22 @@ impl Asio { // The most drivers we can take const MAX_DRIVERS: usize = 100; // Max length for divers name - const CHAR_LEN: usize = 32; + const MAX_DRIVER_NAME_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(); + // 2D array of driver names set to 0. + let mut driver_names: [[c_char; MAX_DRIVER_NAME_LEN]; MAX_DRIVERS] = + [[0; MAX_DRIVER_NAME_LEN]; MAX_DRIVERS]; + // Pointer to each driver name. + let mut driver_name_ptrs: [*mut i8; MAX_DRIVERS] = [0 as *mut i8; MAX_DRIVERS]; + for (ptr, name) in driver_name_ptrs.iter_mut().zip(&mut driver_names[..]) { + *ptr = (*name).as_mut_ptr(); } unsafe { - let num_drivers = ai::get_driver_names(p_driver_name.as_mut_ptr(), MAX_DRIVERS as i32); - + let num_drivers = ai::get_driver_names(driver_name_ptrs.as_mut_ptr(), MAX_DRIVERS as i32); (0 .. num_drivers) - .map(|i| { - let name = CStr::from_ptr(p_driver_name[i as usize]); - let my_driver_name = name.to_owned(); - my_driver_name - .into_string() - .expect("Failed to convert driver name") - }).collect() + .map(|i| driver_name_to_utf8(&driver_names[i as usize]).to_string()) + .collect() } } @@ -339,29 +342,18 @@ impl Driver { Ok(()) } - /// Get the current data type of the driver. + /// Get the current data type of the driver's input stream. /// /// This queries a single channel's type assuming all channels have the same sample type. + pub fn input_data_type(&self) -> Result { + stream_data_type(true) + } + + /// Get the current data type of the driver's output stream. /// - /// 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. - pub fn data_type(&self) -> Result { - 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 { - asio_result!(ai::ASIOGetChannelInfo(&mut channel_info))?; - Ok(FromPrimitive::from_i32(channel_info.type_).expect("failed to cast sample type")) - } + /// This queries a single channel's type assuming all channels have the same sample type. + pub fn output_data_type(&self) -> Result { + stream_data_type(false) } /// Ask ASIO to allocate the buffers and give the callback pointers. @@ -371,116 +363,94 @@ impl Driver { /// The prefered buffer size from ASIO is used. fn create_buffers(&self, buffer_infos: &mut [AsioBufferInfo]) -> Result { let num_channels = buffer_infos.len(); - 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, - }; + // To pass as ai::ASIOCallbacks - let callbacks: *mut _ = &mut callbacks; - 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 callbacks = create_asio_callbacks(); + + // Retrieve the available buffer sizes. + let buffer_sizes = asio_get_buffer_sizes()?; + if buffer_sizes.pref <= 0 { + panic!( + "`ASIOGetBufferSize` produced unusable preferred buffer size of {}", + buffer_sizes.pref, + ); + } + + // Ensure the driver is in the `Initialized` state. + if let DriverState::Running = self.inner.state() { + self.stop()?; + } + if let DriverState::Prepared = self.inner.state() { + self.dispose_buffers()?; + } unsafe { - // Get the buffer sizes - // min possilbe size - // max possible size - // preferred size - // granularity - asio_result!(ai::ASIOGetBufferSize( - &mut min_b_size, - &mut max_b_size, - &mut pref_b_size, - &mut grans, - ))?; - - if pref_b_size <= 0 { - panic!( - "`ASIOGetBufferSize` produced unusable preferred buffer size of {}", - pref_b_size, - ); - } - - if let DriverState::Running = self.inner.state() { - self.stop()?; - } - if let DriverState::Prepared = self.inner.state() { - self.dispose_buffers()?; - } - asio_result!(ai::ASIOCreateBuffers( buffer_infos.as_mut_ptr() as *mut _, num_channels as i32, - pref_b_size, - callbacks as *mut _, + buffer_sizes.pref, + &mut callbacks as *mut _ as *mut _, ))?; } + self.inner.set_state(DriverState::Prepared); - Ok(pref_b_size) + Ok(buffer_sizes.pref) } /// 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) { + fn create_streams( + &self, + mut input_buffer_infos: Vec, + mut output_buffer_infos: Vec, + ) -> Result { + let (input, output) = match (input_buffer_infos.is_empty(), output_buffer_infos.is_empty()) { // 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(&mut bi).map(|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 } - }) + (false, false) => { + // Create one continuous slice of buffers. + let split_point = input_buffer_infos.len(); + let mut all_buffer_infos = input_buffer_infos; + all_buffer_infos.append(&mut output_buffer_infos); + // Create the buffers. On success, split the output and input again. + let buffer_size = self.create_buffers(&mut all_buffer_infos)?; + let output_buffer_infos = all_buffer_infos.split_off(split_point); + let input_buffer_infos = all_buffer_infos; + let input = Some(AsioStream { + buffer_infos: input_buffer_infos, + buffer_size, + }); + let output = Some(AsioStream { + buffer_infos: output_buffer_infos, + buffer_size, + }); + (input, output) }, // Just input - (Some(mut input), None) => { - self.create_buffers(&mut input.buffer_infos) - .map(|buffer_size| { - AsioStreams { - input: Some(AsioStream { - buffer_infos: input.buffer_infos, - buffer_size, - }), - output: None, - } - }) + (false, true) => { + let buffer_size = self.create_buffers(&mut input_buffer_infos)?; + let input = Some(AsioStream { + buffer_infos: input_buffer_infos, + buffer_size, + }); + let output = None; + (input, output) }, // Just output - (None, Some(mut output)) => { - self.create_buffers(&mut output.buffer_infos) - .map(|buffer_size| { - AsioStreams { - output: Some(AsioStream { - buffer_infos: output.buffer_infos, - buffer_size, - }), - input: None, - } - }) + (true, false) => { + let buffer_size = self.create_buffers(&mut output_buffer_infos)?; + let input = None; + let output = Some(AsioStream { + buffer_infos: output_buffer_infos, + buffer_size, + }); + (input, output) }, // Impossible - (None, None) => unreachable!("Trying to create streams without preparing"), - } + (true, true) => unreachable!("Trying to create streams without preparing"), + }; + Ok(AsioStreams { input, output }) } /// Prepare the input stream. @@ -492,70 +462,39 @@ impl Driver { /// /// `num_channels` is the desired number of input channels. /// - /// This returns a full AsioStreams with both input - /// and output if output was active. + /// 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) + let input_buffer_infos = prepare_buffer_infos(true, num_channels); + let output_buffer_infos = output + .map(|output| output.buffer_infos) + .unwrap_or_else(Vec::new); + self.create_streams(input_buffer_infos, output_buffer_infos) } /// 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. + /// + /// 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 desired 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) + let input_buffer_infos = input + .map(|input| input.buffer_infos) + .unwrap_or_else(Vec::new); + let output_buffer_infos = prepare_buffer_infos(false, num_channels); + self.create_streams(input_buffer_infos, output_buffer_infos) } /// Releases buffers allocations. @@ -697,16 +636,100 @@ impl Drop for DriverInner { unsafe impl Send for AsioStream {} +/// Used by the input and output stream creation process. +fn prepare_buffer_infos(is_input: bool, n_channels: usize) -> Vec { + let is_input = if is_input { 1 } else { 0 }; + (0..n_channels) + .map(|ch| { + let channel_num = ch as c_long; + // To be filled by ASIOCreateBuffers. + let buffers = [std::ptr::null_mut(); 2]; + AsioBufferInfo { is_input, channel_num, buffers } + }) + .collect() +} + +/// The set of callbacks passed to `ASIOCreateBuffers`. +fn create_asio_callbacks() -> AsioCallbacks { + 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, + } +} + +/// Retrieve the minimum, maximum and preferred buffer sizes along with the available +/// buffer size granularity. +fn asio_get_buffer_sizes() -> Result { + let mut b = BufferSizes::default(); + unsafe { + let res = ai::ASIOGetBufferSize(&mut b.min, &mut b.max, &mut b.pref, &mut b.grans); + asio_result!(res)?; + } + Ok(b) +} + +/// Retrieve the `ASIOChannelInfo` associated with the channel at the given index on either the +/// input or output stream (`true` for input). +fn asio_channel_info(channel: c_long, is_input: bool) -> Result { + let mut channel_info = ai::ASIOChannelInfo { + // Which channel we are querying + channel, + // Was it input or output + isInput: if is_input { 1 } else { 0 }, + // Was it active + isActive: 0, + channelGroup: 0, + // The sample type + type_: 0, + name: [0 as c_char; 32], + }; + unsafe { + asio_result!(ai::ASIOGetChannelInfo(&mut channel_info))?; + Ok(channel_info) + } +} + +/// Retrieve the data type of either the input or output stream. +/// +/// If `is_input` is true, this will be queried on the input stream. +fn stream_data_type(is_input: bool) -> Result { + let channel_info = asio_channel_info(0, is_input)?; + Ok(FromPrimitive::from_i32(channel_info.type_).expect("unkown `ASIOSampletype` value")) +} + +/// ASIO uses null terminated c strings for driver names. +/// +/// This converts to utf8. +fn driver_name_to_utf8(bytes: &[c_char]) -> std::borrow::Cow { + unsafe { + CStr::from_ptr(bytes.as_ptr()).to_string_lossy() + } +} + +/// ASIO uses null terminated c strings for channel names. +/// +/// This converts to utf8. +fn _channel_name_to_utf8(bytes: &[c_char]) -> std::borrow::Cow { + unsafe { + CStr::from_ptr(bytes.as_ptr()).to_string_lossy() + } +} + /// 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) -> () {} +extern "C" fn sample_rate_did_change(_s_rate: c_double) -> () { + println!("sample rate changed"); +} /// 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 { + println!("selector: {}, vaule: {}", _selector, _value); // TODO Impliment this to give proper responses 4 as c_long } @@ -727,7 +750,6 @@ 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); diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index 6e39251..ad6159d 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -108,7 +108,7 @@ impl Device { let channels = self.driver.channels().map_err(default_format_err)?.ins as u16; let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); // Map th ASIO sample type to a CPAL sample type - let data_type = self.driver.data_type().map_err(default_format_err)?; + let data_type = self.driver.input_data_type().map_err(default_format_err)?; let data_type = convert_data_type(&data_type) .ok_or(DefaultFormatError::StreamTypeNotSupported)?; Ok(Format { @@ -122,7 +122,7 @@ impl Device { pub fn default_output_format(&self) -> Result { let channels = self.driver.channels().map_err(default_format_err)?.outs as u16; let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_format_err)? as _); - let data_type = self.driver.data_type().map_err(default_format_err)?; + let data_type = self.driver.output_data_type().map_err(default_format_err)?; let data_type = convert_data_type(&data_type) .ok_or(DefaultFormatError::StreamTypeNotSupported)?; Ok(Format { diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 092f7b2..661620a 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -178,7 +178,7 @@ impl EventLoop { format: &Format, ) -> Result { let Device { driver, .. } = device; - let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_type = driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let data_type = super::device::convert_data_type(&stream_type) @@ -361,7 +361,7 @@ impl EventLoop { format: &Format, ) -> Result { let Device { driver, .. } = device; - let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_type = driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let data_type = super::device::convert_data_type(&stream_type) @@ -399,13 +399,10 @@ impl EventLoop { let stream_lock = asio_streams.lock().unwrap(); let ref asio_stream = match stream_lock.output { Some(ref asio_stream) => asio_stream, - None => return (), + None => return, }; let mut callbacks = callbacks.lock().unwrap(); - let callback = match callbacks.as_mut() { - Some(callback) => callback, - None => return (), - }; + let callback = callbacks.as_mut(); // Silence the ASIO buffer that is about to be used. // @@ -435,7 +432,7 @@ impl EventLoop { /// performing endianness conversions as necessary. unsafe fn process_output_callback( stream_id: StreamId, - callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send), + callback: Option<&mut &mut (dyn FnMut(StreamId, StreamDataResult) + Send)>, interleaved: &mut [u8], silence_asio_buffer: bool, asio_stream: &sys::AsioStream, @@ -451,10 +448,13 @@ impl EventLoop { { // 1. Render interleaved buffer from callback. let interleaved: &mut [A] = cast_slice_mut(interleaved); - callback( - stream_id, - Ok(StreamData::Output { buffer: A::unknown_type_output_buffer(interleaved) }), - ); + match callback { + None => interleaved.iter_mut().for_each(|s| *s = A::SILENCE), + Some(callback) => { + let buffer = A::unknown_type_output_buffer(interleaved); + callback(stream_id, Ok(StreamData::Output { buffer })); + } + } // 2. Silence ASIO channels if necessary. let n_channels = interleaved.len() / asio_stream.buffer_size as usize; From b7d40d29937a150e4fa362b5a4711430eb64b5a0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 5 Jul 2019 04:21:23 +1000 Subject: [PATCH 73/75] Handle timed buffer switch and messaging callbacks in asio-sys This adds an implementation of ASIO message handling in a manner that matches that demonstrated in the SDK's `hostsample.cpp` file. Also refactors the code to ensure that both `buffer_switch` and `buffer_switch_time_info` callbacks are handled. --- asio-sys/build.rs | 23 ++++ asio-sys/src/bindings/mod.rs | 210 ++++++++++++++++++++++++++++++----- 2 files changed, 203 insertions(+), 30 deletions(-) diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 0a56362..1ef7e1f 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -159,14 +159,35 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .whitelist_type("AsioDrivers") .whitelist_type("AsioDriver") .whitelist_type("ASIOTime") + .whitelist_type("ASIOTimeInfo") .whitelist_type("ASIODriverInfo") .whitelist_type("ASIOBufferInfo") .whitelist_type("ASIOCallbacks") + .whitelist_type("ASIOSamples") .whitelist_type("ASIOSampleType") + .whitelist_type("ASIOSampleRate") .whitelist_type("ASIOChannelInfo") + .whitelist_type("AsioTimeInfoFlags") + .whitelist_type("ASIOTimeCodeFlags") + .whitelist_var("kAsioSelectorSupported") + .whitelist_var("kAsioEngineVersion") + .whitelist_var("kAsioResetRequest") + .whitelist_var("kAsioBufferSizeChange") + .whitelist_var("kAsioResyncRequest") + .whitelist_var("kAsioLatenciesChanged") + .whitelist_var("kAsioSupportsTimeInfo") + .whitelist_var("kAsioSupportsTimeCode") + .whitelist_var("kAsioMMCCommand") + .whitelist_var("kAsioSupportsInputMonitor") + .whitelist_var("kAsioSupportsInputGain") + .whitelist_var("kAsioSupportsInputMeter") + .whitelist_var("kAsioSupportsOutputGain") + .whitelist_var("kAsioSupportsOutputMeter") + .whitelist_var("kAsioOverload") .whitelist_function("ASIOGetChannels") .whitelist_function("ASIOGetChannelInfo") .whitelist_function("ASIOGetBufferSize") + .whitelist_function("ASIOGetSamplePosition") .whitelist_function("get_sample_rate") .whitelist_function("set_sample_rate") .whitelist_function("can_sample_rate") @@ -179,6 +200,8 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .whitelist_function("load_asio_driver") .whitelist_function("remove_current_driver") .whitelist_function("get_driver_names") + .bitfield_enum("AsioTimeInfoFlags") + .bitfield_enum("ASIOTimeCodeFlags") // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index a4c31cd..ddc07d2 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -175,6 +175,57 @@ struct AsioCallbacks { ) -> *mut ai::ASIOTime, } +/// A rust-usable version of the `ASIOTime` type that does not contain a binary blob for fields. +#[repr(C)] +pub struct AsioTime { + /// Must be `0`. + pub reserved: [c_long; 4], + /// Required. + pub time_info: AsioTimeInfo, + /// Optional, evaluated if (time_code.flags & ktcValid). + pub time_code: AsioTimeCode, +} + +/// A rust-compatible version of the `ASIOTimeInfo` type that does not contain a binary blob for +/// fields. +#[repr(C)] +pub struct AsioTimeInfo { + /// Absolute speed (1. = nominal). + pub speed: c_double, + /// System time related to sample_position, in nanoseconds. + /// + /// On Windows, must be derived from timeGetTime(). + pub system_time: ai::ASIOTimeStamp, + /// Sample position since `ASIOStart()`. + pub sample_position: ai::ASIOSamples, + /// Current rate, unsigned. + pub sample_rate: AsioSampleRate, + /// See `AsioTimeInfoFlags`. + pub flags: c_long, + /// Must be `0`. + pub reserved: [c_char; 12], +} + +/// A rust-compatible version of the `ASIOTimeCode` type that does not use a binary blob for its +/// fields. +#[repr(C)] +pub struct AsioTimeCode { + /// Speed relation (fraction of nominal speed) optional. + /// + /// Set to 0. or 1. if not supported. + pub speed: c_double, + /// Time in samples unsigned. + pub time_code_samples: ai::ASIOSamples, + /// See `ASIOTimeCodeFlags`. + pub flags: c_long, + /// Set to `0`. + pub future: [c_char; 64], +} + +/// A rust-compatible version of the `ASIOSampleRate` type that does not use a binary blob for its +/// fields. +pub type AsioSampleRate = f64; + // A helper type to simplify retrieval of available buffer sizes. #[derive(Default)] struct BufferSizes { @@ -186,12 +237,13 @@ struct BufferSizes { lazy_static! { /// 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. + /// + /// This is required because of how ASIO calls the `buffer_switch` function with no data + /// parameters. + /// + /// Options are used so that when a callback is removed we don't change the Vec indices. + /// + /// The indices are how we match a callback with a stream. static ref BUFFER_CALLBACK: Mutex>> = Mutex::new(Vec::new()); } @@ -290,7 +342,7 @@ impl Asio { } impl BufferCallback { - /// Calls the inner callback + /// Calls the inner callback. fn run(&mut self, index: i32) { let cb = &mut self.0; cb(index); @@ -360,7 +412,7 @@ impl Driver { /// /// This will destroy any already allocated buffers. /// - /// The prefered buffer size from ASIO is used. + /// The preferred buffer size from ASIO is used. fn create_buffers(&self, buffer_infos: &mut [AsioBufferInfo]) -> Result { let num_channels = buffer_infos.len(); @@ -717,42 +769,140 @@ fn _channel_name_to_utf8(bytes: &[c_char]) -> std::borrow::Cow { } } -/// 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) -> () { - println!("sample rate changed"); +/// Indicates the stream sample rate has changed. +/// +/// TODO: Provide some way of allowing CPAL to handle this. +extern "C" fn sample_rate_did_change(s_rate: c_double) -> () { + eprintln!("unhandled sample rate change to {}", s_rate); } -/// Messages for ASIO -/// This is not currently used +/// Message callback for ASIO to notify of certain events. extern "C" fn asio_message( - _selector: c_long, _value: c_long, _message: *mut (), _opt: *mut c_double, + selector: c_long, + value: c_long, + _message: *mut (), + _opt: *mut c_double, ) -> c_long { - println!("selector: {}, vaule: {}", _selector, _value); - // TODO Impliment this to give proper responses - 4 as c_long + let mut ret = 0; + match selector { + ai::kAsioSelectorSupported => { + // Indicate what message selectors are supported. + match value { + | ai::kAsioResetRequest + | ai::kAsioEngineVersion + | ai::kAsioResyncRequest + | ai::kAsioLatenciesChanged + // Following added in ASIO 2.0. + | ai::kAsioSupportsTimeInfo + | ai::kAsioSupportsTimeCode + | ai::kAsioSupportsInputMonitor => { + ret = 1; + } + _ => (), + } + } + + ai::kAsioResetRequest => { + // Defer the task and perform the reset of the driver during the next "safe" situation + // You cannot reset the driver right now, as this code is called from the driver. Reset + // the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), + // Destruction. Afterwards you initialize the driver again. + // TODO: Handle this. + ret = 1; + } + + ai::kAsioResyncRequest => { + // This informs the application, that the driver encountered some non fatal data loss. + // It is used for synchronization purposes of different media. Added mainly to work + // around the Win16Mutex problems in Windows 95/98 with the Windows Multimedia system, + // which could loose data because the Mutex was hold too long by another thread. + // However a driver can issue it in other situations, too. + // TODO: Handle this. + ret = 1; + } + + ai::kAsioLatenciesChanged => { + // This will inform the host application that the drivers were latencies changed. + // Beware, it this does not mean that the buffer sizes have changed! You might need to + // update internal delay data. + // TODO: Handle this. + ret = 1; + } + + ai::kAsioEngineVersion => { + // Return the supported ASIO version of the host application If a host applications + // does not implement this selector, ASIO 1.0 is assumed by the driver + ret = 2; + } + + ai::kAsioSupportsTimeInfo => { + // Informs the driver whether the asioCallbacks.bufferSwitchTimeInfo() callback is + // supported. For compatibility with ASIO 1.0 drivers the host application should + // always support the "old" bufferSwitch method, too, which we do. + ret = 1; + } + + ai::kAsioSupportsTimeCode => { + // Informs the driver whether the application is interested in time code info. If an + // application does not need to know about time code, the driver has less work to do. + // TODO: Provide an option for this? + ret = 0; + } + + _ => (), // Unknown/unhandled message type. + } + + ret } /// Similar to buffer switch but with time info /// Not currently used +/// +/// TODO: Provide some access to `ai::ASIOTime` once CPAL gains support for time stamps. extern "C" fn buffer_switch_time_info( - params: *mut ai::ASIOTime, _double_buffer_index: c_long, _direct_process: c_long, + time: *mut ai::ASIOTime, + double_buffer_index: c_long, + _direct_process: c_long, ) -> *mut ai::ASIOTime { - params -} - -/// 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 + // This lock is probably unavoidable, but locks in the audio stream are 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); } } + time +} + +/// 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) -> () { + // Emulate the time info provided by the `buffer_switch_time_info` callback. + // This is an attempt at matching the behaviour in `hostsample.cpp` from the SDK. + let mut time = unsafe { + let mut time: AsioTime = std::mem::zeroed(); + let res = ai::ASIOGetSamplePosition( + &mut time.time_info.sample_position, + &mut time.time_info.system_time, + ); + if let Ok(()) = asio_result!(res) { + time.time_info.flags = + (ai::AsioTimeInfoFlags::kSystemTimeValid | ai::AsioTimeInfoFlags::kSamplePositionValid).0; + } + time + }; + + // Actual processing happens within the `buffer_switch_time_info` callback. + let asio_time_ptr = &mut time as *mut AsioTime as *mut ai::ASIOTime; + buffer_switch_time_info(asio_time_ptr, double_buffer_index, direct_process); +} + +#[test] +fn check_type_sizes() { + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); } From 64dc6accf97528abd15a8d72ca7316d4fc067920 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 5 Jul 2019 06:03:33 +1000 Subject: [PATCH 74/75] Rebase for the trait refactor in #295. --- src/host/asio/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 9f985b6..f752cd9 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -3,18 +3,20 @@ extern crate asio_sys as sys; use { BuildStreamError, DefaultFormatError, - Device as DeviceTrait, DeviceNameError, DevicesError, - EventLoop as EventLoopTrait, Format, - Host as HostTrait, PauseStreamError, PlayStreamError, StreamDataResult, - StreamId as StreamIdTrait, SupportedFormatsError, }; +use traits::{ + DeviceTrait, + EventLoopTrait, + HostTrait, + StreamIdTrait, +}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats}; pub use self::stream::{EventLoop, StreamId}; From 714dab7270a838dcc2bbbfe3df4591e9a81237b1 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 5 Jul 2019 13:19:42 +0200 Subject: [PATCH 75/75] Refactor asio message to use a match expr. Fix some typos. --- asio-sys/src/bindings/mod.rs | 31 ++++++++++++++----------------- src/platform/mod.rs | 1 - 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index ddc07d2..6896b60 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -783,7 +783,6 @@ extern "C" fn asio_message( _message: *mut (), _opt: *mut c_double, ) -> c_long { - let mut ret = 0; match selector { ai::kAsioSelectorSupported => { // Indicate what message selectors are supported. @@ -795,20 +794,18 @@ extern "C" fn asio_message( // Following added in ASIO 2.0. | ai::kAsioSupportsTimeInfo | ai::kAsioSupportsTimeCode - | ai::kAsioSupportsInputMonitor => { - ret = 1; - } - _ => (), + | ai::kAsioSupportsInputMonitor => 1, + _ => 0, } } ai::kAsioResetRequest => { // Defer the task and perform the reset of the driver during the next "safe" situation // You cannot reset the driver right now, as this code is called from the driver. Reset - // the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), + // the driver is done by completely destruct it. I.e. ASIOStop(), ASIODisposeBuffers(), // Destruction. Afterwards you initialize the driver again. // TODO: Handle this. - ret = 1; + 1 } ai::kAsioResyncRequest => { @@ -818,7 +815,7 @@ extern "C" fn asio_message( // which could loose data because the Mutex was hold too long by another thread. // However a driver can issue it in other situations, too. // TODO: Handle this. - ret = 1; + 1 } ai::kAsioLatenciesChanged => { @@ -826,37 +823,37 @@ extern "C" fn asio_message( // Beware, it this does not mean that the buffer sizes have changed! You might need to // update internal delay data. // TODO: Handle this. - ret = 1; + 1 } ai::kAsioEngineVersion => { // Return the supported ASIO version of the host application If a host applications // does not implement this selector, ASIO 1.0 is assumed by the driver - ret = 2; + 2 } ai::kAsioSupportsTimeInfo => { // Informs the driver whether the asioCallbacks.bufferSwitchTimeInfo() callback is // supported. For compatibility with ASIO 1.0 drivers the host application should // always support the "old" bufferSwitch method, too, which we do. - ret = 1; + 1 } ai::kAsioSupportsTimeCode => { // Informs the driver whether the application is interested in time code info. If an // application does not need to know about time code, the driver has less work to do. // TODO: Provide an option for this? - ret = 0; + 0 } - _ => (), // Unknown/unhandled message type. + _ => 0, // Unknown/unhandled message type. } - - ret } -/// Similar to buffer switch but with time info -/// Not currently used +/// Similar to buffer switch but with time info. +/// +/// If only `buffer_switch` is called by the driver instead, the `buffer_switch` callback will +/// create the necessary timing info and call this function. /// /// TODO: Provide some access to `ai::ASIOTime` once CPAL gains support for time stamps. extern "C" fn buffer_switch_time_info( diff --git a/src/platform/mod.rs b/src/platform/mod.rs index d64f1d2..314705d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -523,7 +523,6 @@ mod platform_impl { } } -// TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] mod platform_impl { #[cfg(feature = "asio")]