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,