diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1e69e7b..377486ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **WASAPI**: `Send` and `Sync` implementations to `Stream`. - **WebAudio**: `Send` and `Sync` implementations to `Stream`. - **WebAudio**: `BufferSize::Fixed` validation against supported range. +- **ALSA**: Add support for native DSD playback. ### Changed diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 1fea0ee89..bbe8f86cf 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -150,7 +150,9 @@ fn main() -> Result<(), anyhow::Error> { } fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat { - if format.is_float() { + if format.is_dsd() { + panic!("DSD formats cannot be written to WAV files"); + } else if format.is_float() { hound::SampleFormat::Float } else { hound::SampleFormat::Int diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 53a847f01..95abe8b07 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -482,7 +482,7 @@ impl Device { // Test both LE and BE formats to detect what the hardware actually supports. // LE is listed first as it's the common case for most audio hardware. // Hardware reports its supported formats regardless of CPU endianness. - const FORMATS: [(SampleFormat, alsa::pcm::Format); 18] = [ + const FORMATS: [(SampleFormat, alsa::pcm::Format); 23] = [ (SampleFormat::I8, alsa::pcm::Format::S8), (SampleFormat::U8, alsa::pcm::Format::U8), (SampleFormat::I16, alsa::pcm::Format::S16LE), @@ -501,6 +501,11 @@ impl Device { (SampleFormat::F32, alsa::pcm::Format::FloatBE), (SampleFormat::F64, alsa::pcm::Format::Float64LE), (SampleFormat::F64, alsa::pcm::Format::Float64BE), + (SampleFormat::DsdU8, alsa::pcm::Format::DSDU8), + (SampleFormat::DsdU16, alsa::pcm::Format::DSDU16LE), + (SampleFormat::DsdU16, alsa::pcm::Format::DSDU16BE), + (SampleFormat::DsdU32, alsa::pcm::Format::DSDU32LE), + (SampleFormat::DsdU32, alsa::pcm::Format::DSDU32BE), //SND_PCM_FORMAT_IEC958_SUBFRAME_LE, //SND_PCM_FORMAT_IEC958_SUBFRAME_BE, //SND_PCM_FORMAT_MU_LAW, @@ -1268,6 +1273,7 @@ fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) { } }}; } + const DSD_SILENCE_BYTE: u8 = 0x69; match sample_format { SampleFormat::I8 => fill_typed!(i8), @@ -1284,6 +1290,9 @@ fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) { SampleFormat::U64 => fill_typed!(u64), SampleFormat::F32 => fill_typed!(f32), SampleFormat::F64 => fill_typed!(f64), + SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => { + buffer.fill(DSD_SILENCE_BYTE) + } } } @@ -1350,6 +1359,15 @@ fn sample_format_to_alsa_format( SampleFormat::F64 => (Format::Float64LE, Format::Float64BE), #[cfg(target_endian = "big")] SampleFormat::F64 => (Format::Float64BE, Format::Float64LE), + SampleFormat::DsdU8 => return Ok(Format::DSDU8), + #[cfg(target_endian = "little")] + SampleFormat::DsdU16 => (Format::DSDU16LE, Format::DSDU16BE), + #[cfg(target_endian = "big")] + SampleFormat::DsdU16 => (Format::DSDU16BE, Format::DSDU16LE), + #[cfg(target_endian = "little")] + SampleFormat::DsdU32 => (Format::DSDU32LE, Format::DSDU32BE), + #[cfg(target_endian = "big")] + SampleFormat::DsdU32 => (Format::DSDU32BE, Format::DSDU32LE), _ => { return Err(BackendSpecificError { description: format!("Sample format '{sample_format}' is not supported"), diff --git a/src/lib.rs b/src/lib.rs index e2cae1c5a..cfabb7b1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1000,7 +1000,7 @@ impl From for StreamConfig { #[allow(dead_code)] pub(crate) const COMMON_SAMPLE_RATES: &[SampleRate] = &[ 5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, - 176400, 192000, 352800, 384000, + 176400, 192000, 352800, 384000, 705600, 768000, 1411200, 1536000, ]; #[test] diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 58dcc0d20..24d6ffd8d 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -105,6 +105,15 @@ pub enum SampleFormat { /// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F64, + + /// DSD 1-bit stream in u8 container (8 bits = 8 DSD samples) with 0x69 being the silence byte pattern. + DsdU8, + + /// DSD 1-bit stream in u16 container (16 bits = 16 DSD samples) with 0x69 being the silence byte pattern. + DsdU16, + + /// DSD 1-bit stream in u32 container (32 bits = 32 DSD samples) with 0x69 being the silence byte pattern. + DsdU32, } impl SampleFormat { @@ -129,6 +138,9 @@ impl SampleFormat { SampleFormat::U64 => mem::size_of::(), SampleFormat::F32 => mem::size_of::(), SampleFormat::F64 => mem::size_of::(), + SampleFormat::DsdU8 => mem::size_of::(), + SampleFormat::DsdU16 => mem::size_of::(), + SampleFormat::DsdU32 => mem::size_of::(), } } @@ -153,6 +165,7 @@ impl SampleFormat { SampleFormat::U64 => u64::BITS, SampleFormat::F32 => 32, SampleFormat::F64 => 64, + SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => 1, } } @@ -189,6 +202,15 @@ impl SampleFormat { pub fn is_float(&self) -> bool { matches!(*self, SampleFormat::F32 | SampleFormat::F64) } + + #[inline] + #[must_use] + pub fn is_dsd(&self) -> bool { + matches!( + *self, + SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 + ) + } } impl Display for SampleFormat { @@ -208,6 +230,9 @@ impl Display for SampleFormat { SampleFormat::U64 => "u64", SampleFormat::F32 => "f32", SampleFormat::F64 => "f64", + SampleFormat::DsdU8 => "dsdu8", + SampleFormat::DsdU16 => "dsdu16", + SampleFormat::DsdU32 => "dsdu32", } .fmt(f) }