diff --git a/examples/android.rs b/examples/android.rs index 7ca17da45..65619a8e3 100644 --- a/examples/android.rs +++ b/examples/android.rs @@ -18,6 +18,7 @@ fn main() { match config.sample_format() { cpal::SampleFormat::F32 => run::(&device, &config.into()).unwrap(), cpal::SampleFormat::I16 => run::(&device, &config.into()).unwrap(), + cpal::SampleFormat::I32 => run::(&device, &config.into()).unwrap(), cpal::SampleFormat::U16 => run::(&device, &config.into()).unwrap(), } } diff --git a/examples/beep.rs b/examples/beep.rs index 92716c8f6..c8f54cdb6 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -86,6 +86,7 @@ fn main() -> anyhow::Result<()> { match config.sample_format() { cpal::SampleFormat::F32 => run::(&device, &config.into()), cpal::SampleFormat::I16 => run::(&device, &config.into()), + cpal::SampleFormat::I32 => run::(&device, &config.into()), cpal::SampleFormat::U16 => run::(&device, &config.into()), } } diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 767bfa16c..00888a75d 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -122,6 +122,11 @@ fn main() -> Result<(), anyhow::Error> { move |data, _: &_| write_input_data::(data, &writer_2), err_fn, )?, + cpal::SampleFormat::I32 => device.build_input_stream( + &config.into(), + move |data, _: &_| write_input_data::(data, &writer_2), + err_fn, + )?, cpal::SampleFormat::U16 => device.build_input_stream( &config.into(), move |data, _: &_| write_input_data::(data, &writer_2), @@ -141,8 +146,9 @@ fn main() -> Result<(), anyhow::Error> { fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat { match format { - cpal::SampleFormat::U16 => hound::SampleFormat::Int, - cpal::SampleFormat::I16 => hound::SampleFormat::Int, + cpal::SampleFormat::U16 | cpal::SampleFormat::I16 | cpal::SampleFormat::I32 => { + hound::SampleFormat::Int + } cpal::SampleFormat::F32 => hound::SampleFormat::Float, } } diff --git a/examples/wasm-beep/src/lib.rs b/examples/wasm-beep/src/lib.rs index 8914ed5a6..cbef1f3c6 100644 --- a/examples/wasm-beep/src/lib.rs +++ b/examples/wasm-beep/src/lib.rs @@ -36,6 +36,7 @@ pub fn beep() -> Handle { Handle(match config.sample_format() { cpal::SampleFormat::F32 => run::(&device, &config.into()), + cpal::SampleFormat::I32 => run::(&device, &config.into()), cpal::SampleFormat::I16 => run::(&device, &config.into()), cpal::SampleFormat::U16 => run::(&device, &config.into()), }) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 5bffdfa7d..5c3fa94bf 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -312,18 +312,18 @@ impl Device { let hw_params = alsa::pcm::HwParams::any(handle)?; // TODO: check endianess - const FORMATS: [(SampleFormat, alsa::pcm::Format); 3] = [ + const FORMATS: [(SampleFormat, alsa::pcm::Format); 4] = [ //SND_PCM_FORMAT_S8, //SND_PCM_FORMAT_U8, (SampleFormat::I16, alsa::pcm::Format::S16LE), //SND_PCM_FORMAT_S16_BE, (SampleFormat::U16, alsa::pcm::Format::U16LE), //SND_PCM_FORMAT_U16_BE, - //SND_PCM_FORMAT_S24_LE, + // (SampleFormat::I24, alsa::pcm::Format::S24LE), //SND_PCM_FORMAT_S24_BE, //SND_PCM_FORMAT_U24_LE, //SND_PCM_FORMAT_U24_BE, - //SND_PCM_FORMAT_S32_LE, + (SampleFormat::I32, alsa::pcm::Format::S32LE), //SND_PCM_FORMAT_S32_BE, //SND_PCM_FORMAT_U32_LE, //SND_PCM_FORMAT_U32_BE, @@ -339,7 +339,7 @@ impl Device { //SND_PCM_FORMAT_MPEG, //SND_PCM_FORMAT_GSM, //SND_PCM_FORMAT_SPECIAL, - //SND_PCM_FORMAT_S24_3LE, + //(SampleFormat::I24, alsa::pcm::Format::S243LE), //SND_PCM_FORMAT_S24_3BE, //SND_PCM_FORMAT_U24_3LE, //SND_PCM_FORMAT_U24_3BE, @@ -958,12 +958,14 @@ fn set_hw_params_from_format( match sample_format { SampleFormat::I16 => alsa::pcm::Format::S16BE, SampleFormat::U16 => alsa::pcm::Format::U16BE, + SampleFormat::I32 => alsa::pcm::Format::S32BE, SampleFormat::F32 => alsa::pcm::Format::FloatBE, } } else { match sample_format { SampleFormat::I16 => alsa::pcm::Format::S16LE, SampleFormat::U16 => alsa::pcm::Format::U16LE, + SampleFormat::I32 => alsa::pcm::Format::S32LE, SampleFormat::F32 => alsa::pcm::Format::FloatLE, } }; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index cb8feb3e3..fb3859970 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -209,13 +209,8 @@ pub(crate) fn convert_data_type(ty: &sys::AsioSampleType) -> Option 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, + sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I32, + sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I32, _ => return None, }; Some(fmt) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 3a9d2bc25..24e4ef8f4 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -181,8 +181,8 @@ impl Device { // 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::( + (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I32) => { + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -191,8 +191,8 @@ impl Device { from_le, ); } - (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { - process_input_callback::( + (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I32) => { + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -404,8 +404,8 @@ impl Device { // 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::( + (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32LSB) => { + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -415,8 +415,8 @@ impl Device { to_le, ); } - (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { - process_output_callback::( + (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32MSB) => { + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -675,7 +675,7 @@ fn check_config( } // unsigned formats are not supported by asio match sample_format { - SampleFormat::I16 | SampleFormat::F32 => (), + SampleFormat::I16 | SampleFormat::I32 | SampleFormat::F32 => (), SampleFormat::U16 => return Err(BuildStreamError::StreamConfigNotSupported), } if *channels > num_asio_channels { diff --git a/src/host/oboe/mod.rs b/src/host/oboe/mod.rs index 4e245cdb0..d81269eee 100644 --- a/src/host/oboe/mod.rs +++ b/src/host/oboe/mod.rs @@ -397,6 +397,10 @@ impl DeviceTrait for Device { description: "U16 format is not supported on Android.".to_owned(), } .into()), + SampleFormat::I32 => Err(BackendSpecificError { + description: "I32 format is not supported on Android.".to_owned(), + } + .into()), } } @@ -466,6 +470,10 @@ impl DeviceTrait for Device { .into()) } } + SampleFormat::I32 => Err(BackendSpecificError { + description: "I32 format is not supported on Android.".to_owned(), + } + .into()), SampleFormat::U16 => Err(BackendSpecificError { description: "U16 format is not supported on Android.".to_owned(), } diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 70445cfc5..4dc9c113b 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -300,7 +300,7 @@ unsafe fn format_from_waveformatex_ptr( a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 && a.Data4 == b.Data4 } let sample_format = match ( - (*waveformatex_ptr).wBitsPerSample, + (*waveformatex_ptr).wBitsPerSample, // 8 or 16 for integers, 32 for floats (*waveformatex_ptr).wFormatTag, ) { (16, mmreg::WAVE_FORMAT_PCM) => SampleFormat::I16, @@ -1228,7 +1228,7 @@ fn config_to_waveformatextensible( let format_tag = match sample_format { SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM, SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE, - SampleFormat::U16 => return None, + SampleFormat::U16 | SampleFormat::I32 => return None, }; let channels = config.channels as WORD; let sample_rate = config.sample_rate.0 as DWORD; @@ -1243,7 +1243,7 @@ fn config_to_waveformatextensible( let ex_size = mem::size_of::(); (extensible_size - ex_size) as WORD } - SampleFormat::U16 => return None, + SampleFormat::U16 | SampleFormat::I32 => return None, }; let waveformatex = mmreg::WAVEFORMATEX { wFormatTag: format_tag, @@ -1263,7 +1263,7 @@ fn config_to_waveformatextensible( let sub_format = match sample_format { SampleFormat::I16 => ksmedia::KSDATAFORMAT_SUBTYPE_PCM, SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, - SampleFormat::U16 => return None, + SampleFormat::U16 | SampleFormat::I32 => return None, }; let waveformatextensible = mmreg::WAVEFORMATEXTENSIBLE { Format: waveformatex, diff --git a/src/lib.rs b/src/lib.rs index 8454d7078..635453bdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ //! let stream = match sample_format { //! SampleFormat::F32 => device.build_output_stream(&config, write_silence::, err_fn), //! SampleFormat::I16 => device.build_output_stream(&config, write_silence::, err_fn), +//! SampleFormat::I32 => device.build_output_stream(&config, write_silence::, err_fn), //! SampleFormat::U16 => device.build_output_stream(&config, write_silence::, err_fn), //! }.unwrap(); //! @@ -603,6 +604,8 @@ impl SupportedStreamConfigRange { /// - f32 /// - i16 /// - u16 + /// - i32 + /// - i24 /// /// **Sample rate**: /// @@ -610,7 +613,7 @@ impl SupportedStreamConfigRange { /// - Max sample rate pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering { use std::cmp::Ordering::Equal; - use SampleFormat::{F32, I16, U16}; + use SampleFormat::{F32, I16, I32, U16}; let cmp_stereo = (self.channels == 2).cmp(&(other.channels == 2)); if cmp_stereo != Equal { @@ -642,6 +645,11 @@ impl SupportedStreamConfigRange { return cmp_u16; } + let cmp_i32 = (self.sample_format == I32).cmp(&(other.sample_format == I32)); + if cmp_i32 != Equal { + return cmp_i32; + } + const HZ_44100: SampleRate = SampleRate(44_100); let r44100_in_self = self.min_sample_rate <= HZ_44100 && HZ_44100 <= self.max_sample_rate; let r44100_in_other = @@ -693,6 +701,13 @@ fn test_cmp_default_heuristics() { max_sample_rate: SampleRate(22050), sample_format: SampleFormat::F32, }, + SupportedStreamConfigRange { + buffer_size: SupportedBufferSize::Range { min: 256, max: 512 }, + channels: 2, + min_sample_rate: SampleRate(1), + max_sample_rate: SampleRate(96000), + sample_format: SampleFormat::I32, + }, ]; formats.sort_by(|a, b| a.cmp_default_heuristics(b)); @@ -703,25 +718,30 @@ fn test_cmp_default_heuristics() { assert_eq!(formats[0].max_sample_rate(), SampleRate(96000)); assert_eq!(formats[0].channels(), 1); - assert_eq!(formats[1].sample_format(), SampleFormat::U16); + assert_eq!(formats[1].sample_format(), SampleFormat::I32); assert_eq!(formats[1].min_sample_rate(), SampleRate(1)); assert_eq!(formats[1].max_sample_rate(), SampleRate(96000)); assert_eq!(formats[1].channels(), 2); - assert_eq!(formats[2].sample_format(), SampleFormat::I16); + assert_eq!(formats[2].sample_format(), SampleFormat::U16); assert_eq!(formats[2].min_sample_rate(), SampleRate(1)); assert_eq!(formats[2].max_sample_rate(), SampleRate(96000)); assert_eq!(formats[2].channels(), 2); - assert_eq!(formats[3].sample_format(), SampleFormat::F32); + assert_eq!(formats[3].sample_format(), SampleFormat::I16); assert_eq!(formats[3].min_sample_rate(), SampleRate(1)); - assert_eq!(formats[3].max_sample_rate(), SampleRate(22050)); + assert_eq!(formats[3].max_sample_rate(), SampleRate(96000)); assert_eq!(formats[3].channels(), 2); assert_eq!(formats[4].sample_format(), SampleFormat::F32); assert_eq!(formats[4].min_sample_rate(), SampleRate(1)); - assert_eq!(formats[4].max_sample_rate(), SampleRate(96000)); + assert_eq!(formats[4].max_sample_rate(), SampleRate(22050)); assert_eq!(formats[4].channels(), 2); + + assert_eq!(formats[5].sample_format(), SampleFormat::F32); + assert_eq!(formats[5].min_sample_rate(), SampleRate(1)); + assert_eq!(formats[5].max_sample_rate(), SampleRate(96000)); + assert_eq!(formats[5].channels(), 2); } impl From for StreamConfig { diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 27d48a472..92ba7e1e8 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -5,6 +5,7 @@ use std::mem; pub enum SampleFormat { /// The value 0 corresponds to 0. I16, + I32, /// The value 0 corresponds to 32768. U16, /// The boundaries are (-1.0, 1.0). @@ -19,6 +20,7 @@ impl SampleFormat { SampleFormat::I16 => mem::size_of::(), SampleFormat::U16 => mem::size_of::(), SampleFormat::F32 => mem::size_of::(), + SampleFormat::I32 => mem::size_of::(), } } } @@ -30,12 +32,14 @@ pub unsafe trait Sample: Copy + Clone { /// Turns the sample into its equivalent as a floating-point. fn to_f32(&self) -> f32; - /// Converts this sample into a standard i16 sample. - fn to_i16(&self) -> i16; /// Converts this sample into a standard u16 sample. fn to_u16(&self) -> u16; + /// Converts this sample into a standard i16 sample. + fn to_i16(&self) -> i16; + /// Converts this sample into a standard i32 sample. + fn to_i32(&self) -> i32; - /// Converts any sample type to this one by calling `to_i16`, `to_u16` or `to_f32`. + /// Converts any sample type to this one by calling `to_i16`, `to_i32`, `to_u16`, or `to_f32`. fn from(s: &S) -> Self where S: Sample; @@ -49,14 +53,19 @@ unsafe impl Sample for u16 { self.to_i16().to_f32() } + #[inline] + fn to_u16(&self) -> u16 { + *self + } + #[inline] fn to_i16(&self) -> i16 { (*self as i16).wrapping_add(i16::MIN) } #[inline] - fn to_u16(&self) -> u16 { - *self + fn to_i32(&self) -> i32 { + self.to_f32().to_i32() } #[inline] @@ -80,14 +89,19 @@ unsafe impl Sample for i16 { } } + #[inline] + fn to_u16(&self) -> u16 { + self.wrapping_add(i16::MIN) as u16 + } + #[inline] fn to_i16(&self) -> i16 { *self } #[inline] - fn to_u16(&self) -> u16 { - self.wrapping_add(i16::MIN) as u16 + fn to_i32(&self) -> i32 { + self.to_f32().to_i32() } #[inline] @@ -106,7 +120,8 @@ unsafe impl Sample for f32 { fn to_f32(&self) -> f32 { *self } - + + /// This function inherently returns a lossy value due to scaling. #[inline] fn to_i16(&self) -> i16 { if *self >= 0.0 { @@ -117,6 +132,15 @@ unsafe impl Sample for f32 { } #[inline] + fn to_i32(&self) -> i32 { + if self.is_sign_positive() { + (*self as f64 * std::i32::MAX as f64).round() as i32 + } else { + (*self as f64 * -(std::i32::MIN as f64)).round() as i32 + } + } + + /// This function inherently returns a lossy value due to scaling. fn to_u16(&self) -> u16 { self.mul_add(F32_TO_16BIT_INT_MULTIPLIER, F32_TO_16BIT_INT_MULTIPLIER) .round() as u16 @@ -131,10 +155,82 @@ unsafe impl Sample for f32 { } } +unsafe impl Sample for i32 { + const FORMAT: SampleFormat = SampleFormat::I32; + + /// This function inherently returns a lossy value due to scaling. + #[inline] + fn to_f32(&self) -> f32 { + if *self < 0 { + (*self as f64 * (1.0 / -(::std::i32::MIN as f64))) as f32 + } else { + (*self as f64 * (1.0 / ::std::i32::MAX as f64)) as f32 + } + } + + /// This function inherently returns a lossy value due to scaling. + #[inline] + fn to_i16(&self) -> i16 { + self.to_f32().to_i16() + } + + /// This function inherently returns a lossy value due to scaling. + #[inline] + fn to_u16(&self) -> u16 { + self.to_f32().to_u16() + } + + #[inline] + fn to_i32(&self) -> i32 { + *self + } + + #[inline] + fn from(sample: &S) -> Self + where + S: Sample, + { + sample.to_i32() + } +} + #[cfg(test)] mod test { use super::Sample; + #[test] + fn i32_to_i16() { + assert_eq!(std::i32::MAX.to_i16(), std::i16::MAX); + assert_eq!((std::i32::MIN / 2).to_i16(), std::i16::MIN / 2); + assert_eq!(std::i32::MIN.to_i16(), std::i16::MIN); + assert_eq!(0i32.to_i16(), 0); + } + + #[test] + fn i32_to_i32() { + assert_eq!(std::i32::MAX.to_i32(), std::i32::MAX); + assert_eq!((std::i32::MIN / 2).to_i32(), std::i32::MIN / 2); + assert_eq!(std::i32::MIN.to_i32(), std::i32::MIN); + assert_eq!(0i32.to_i32(), 0); + } + + #[test] + fn i32_to_u16() { + assert_eq!(std::i32::MAX.to_u16(), std::u16::MAX); + assert_eq!(0i32.to_u16(), (std::u16::MAX as f32 / 2.0).round() as u16); + assert_eq!(std::i32::MIN.to_u16(), std::u16::MIN); + } + + #[test] + fn i32_to_f32() { + assert_eq!(std::i32::MAX.to_f32(), 1.0f32); + assert_eq!((std::i32::MAX / 8).to_f32(), 0.125f32); + assert_eq!((std::i32::MAX / -16).to_f32(), -0.0625f32); + assert_eq!((std::i32::MAX / -4).to_f32(), -0.25f32); + assert_eq!(std::i32::MIN.to_f32(), -1.0f32); + assert_eq!(0.to_f32(), 0f32); + } + #[test] fn i16_to_i16() { assert_eq!(0i16.to_i16(), 0); @@ -143,6 +239,13 @@ mod test { assert_eq!((-32768i16).to_i16(), -32768); } + #[test] + fn i16_to_i32() { + assert_eq!(0i16.to_i32(), 0); + assert_eq!(std::i16::MAX.to_i32(), std::i32::MAX); + assert_eq!(std::i16::MIN.to_i32(), std::i32::MIN); + } + #[test] fn i16_to_u16() { assert_eq!(0i16.to_u16(), 32768); @@ -155,6 +258,7 @@ mod test { fn i16_to_f32() { assert_eq!(0i16.to_f32(), 0.0); assert_eq!((-16384i16).to_f32(), -0.5); + assert_eq!((-16384i16 / 2).to_f32(), -0.25); assert_eq!(32767i16.to_f32(), 1.0); assert_eq!((-32768i16).to_f32(), -1.0); } @@ -167,6 +271,13 @@ mod test { assert_eq!(0u16.to_i16(), -32768); } + #[test] + fn u16_to_i32() { + assert_eq!(((std::u16::MAX as f32 / 2.0).round() as u16).to_i32(), 0); + assert_eq!(std::u16::MAX.to_i32(), std::i32::MAX); + assert_eq!(std::u16::MIN.to_i32(), std::i32::MIN); + } + #[test] fn u16_to_u16() { assert_eq!(0u16.to_u16(), 0); @@ -190,6 +301,16 @@ mod test { assert_eq!((-1.0f32).to_i16(), i16::MIN); } + #[test] + fn f32_to_i32() { + assert_eq!(1.0f32.to_i32(), std::i32::MAX); + assert_eq!(0.5f32.to_i32(), 1073741824); + assert_eq!(0.25f32.to_i32(), 536870912); + assert_eq!(0.to_i32(), 0); + assert_eq!((-0.5f32).to_i32(), std::i32::MIN / 2); + assert_eq!((-1.0f32).to_i32(), std::i32::MIN); + } + #[test] fn f32_to_u16() { assert_eq!((-1.0f32).to_u16(), 0);