change of branch
This commit is contained in:
parent
e0a0dc9ec7
commit
49968f4b82
|
@ -3,3 +3,10 @@
|
||||||
.cargo/
|
.cargo/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
recorded.wav
|
recorded.wav
|
||||||
|
asio-sys/target
|
||||||
|
asio-sys/Cargo.lock
|
||||||
|
asio-sys/.cargo/
|
||||||
|
asio-sys/.DS_Store
|
||||||
|
*~
|
||||||
|
*.swap
|
||||||
|
*.swo
|
||||||
|
|
|
@ -11,10 +11,14 @@ keywords = ["audio", "sound"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1.5"
|
failure = "0.1.5"
|
||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
|
itertools = "0.7.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hound = "3.4"
|
hound = "3.4"
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "windows" ))'.dependencies]
|
||||||
|
asio-sys = { version = "0.1", path = "asio-sys" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "std", "synchapi", "winuser"] }
|
winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "std", "synchapi", "winuser"] }
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "asio-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Gowan <s3549186@student.rmit.edu.au>"]
|
||||||
|
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"
|
|
@ -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<ASIOSampleRate *>(rate));
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
@CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" %*
|
||||||
|
@start powershell
|
|
@ -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<PathBuf> = 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<PathBuf>| {
|
||||||
|
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!");
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,72 @@
|
||||||
|
/* This example aims to produce the same behaviour
|
||||||
|
* as the enumerate example in cpal
|
||||||
|
* by Tom Gowan
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate asio_sys as sys;
|
||||||
|
|
||||||
|
// This is the same data that enumerate
|
||||||
|
// is trying to find
|
||||||
|
// Basically these are stubbed versions
|
||||||
|
//
|
||||||
|
// Format that each sample has.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SampleFormat {
|
||||||
|
// The value 0 corresponds to 0.
|
||||||
|
I16,
|
||||||
|
// The value 0 corresponds to 32768.
|
||||||
|
U16,
|
||||||
|
// The boundaries are (-1.0, 1.0).
|
||||||
|
F32,
|
||||||
|
}
|
||||||
|
// Number of channels.
|
||||||
|
pub type ChannelCount = u16;
|
||||||
|
|
||||||
|
// The number of samples processed per second for a single channel of audio.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct SampleRate(pub u32);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Format {
|
||||||
|
pub channels: ChannelCount,
|
||||||
|
pub sample_rate: SampleRate,
|
||||||
|
pub data_type: SampleFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let driver_list = sys::get_driver_list();
|
||||||
|
|
||||||
|
let format = Format {
|
||||||
|
channels: 0,
|
||||||
|
sample_rate: SampleRate(0),
|
||||||
|
// TODO Not sure about how to set the data type
|
||||||
|
data_type: SampleFormat::F32,
|
||||||
|
};
|
||||||
|
if driver_list.len() > 0 {
|
||||||
|
let format = match sys::get_channels(&driver_list[0]) {
|
||||||
|
Ok(channels) => Format {
|
||||||
|
channels: channels.ins as u16,
|
||||||
|
sample_rate: format.sample_rate,
|
||||||
|
data_type: format.data_type,
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error retrieving channels: {}", e);
|
||||||
|
format
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = match sys::get_sample_rate(&driver_list[0]) {
|
||||||
|
Ok(sample_rate) => Format {
|
||||||
|
channels: format.channels,
|
||||||
|
sample_rate: SampleRate(sample_rate.rate),
|
||||||
|
data_type: format.data_type,
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error retrieving sample rate: {}", e);
|
||||||
|
format
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Format {:?}", format);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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"));
|
|
@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<S, D> {
|
||||||
|
pub stream_id: S,
|
||||||
|
pub data: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BufferCallback(Box<FnMut(i32) + Send>);
|
||||||
|
|
||||||
|
lazy_static!{
|
||||||
|
static ref buffer_callback: Mutex<Option<BufferCallback>> = 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<F: 'static>(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<Channel, ASIOError> {
|
||||||
|
let channel: Result<Channel, ASIOError>;
|
||||||
|
// 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<String> {
|
||||||
|
let mut driver_list: Vec<String> = 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<SampleRate, ASIOError> {
|
||||||
|
let sample_rate: Result<SampleRate, ASIOError>;
|
||||||
|
// 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<AsioSampleType, ASIOError> {
|
||||||
|
let data_type: Result<AsioSampleType, ASIOError>;
|
||||||
|
// 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<AsioStream, ASIOError> {
|
||||||
|
//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::<AsioBufferInfo, ai::ASIOBufferInfo>(buffer_infos[0]),
|
||||||
|
mem::transmute::<AsioBufferInfo, ai::ASIOBufferInfo>(buffer_infos[1]),
|
||||||
|
];
|
||||||
|
let mut callbacks_convert =
|
||||||
|
mem::transmute::<AsioCallbacks, ai::ASIOCallbacks>(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::<ai::ASIOBufferInfo, AsioBufferInfo>(buffer_info_convert[0]),
|
||||||
|
mem::transmute::<ai::ASIOBufferInfo, AsioBufferInfo>(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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod windows;
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
extern crate asio_sys as sys;
|
||||||
|
use std;
|
||||||
|
pub type SupportedInputFormats = std::vec::IntoIter<SupportedFormat>;
|
||||||
|
pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>;
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
self.driver_name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just supporting default for now
|
||||||
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats,
|
||||||
|
FormatsEnumerationError> {
|
||||||
|
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<SupportedOutputFormats,
|
||||||
|
FormatsEnumerationError> {
|
||||||
|
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<Format, DefaultFormatError> {
|
||||||
|
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<Format, DefaultFormatError> {
|
||||||
|
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<Device> {
|
||||||
|
match self.drivers.next() {
|
||||||
|
Some(dn) => Some(Device{driver_name: dn}),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asio doesn't have a concept of default
|
||||||
|
// so returning first in list as default
|
||||||
|
pub fn default_input_device() -> Option<Device> {
|
||||||
|
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<Device> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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<Mutex<Option<sys::AsioStream>>>,
|
||||||
|
stream_count: Cell<usize>,
|
||||||
|
callbacks: Arc<Mutex<Vec<&'static mut (FnMut(StreamId, StreamData) + Send)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<StreamId, CreationError> {
|
||||||
|
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>>) -> 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<StreamId, CreationError> {
|
||||||
|
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<Vec<$SampleType>>{
|
||||||
|
let mut channels: Vec<Vec<$SampleType>> = 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<F>(&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) {}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod asio;
|
Loading…
Reference in New Issue