From 8d142977acf209299d6842b0d4a119e9a089214b Mon Sep 17 00:00:00 2001 From: Dragan Date: Mon, 15 Dec 2025 20:38:02 +0100 Subject: [PATCH 1/5] add native dsd support --- src/host/alsa/mod.rs | 38 +++++++++-- src/lib.rs | 2 +- src/samples_formats.rs | 142 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 7 deletions(-) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 53a847f01..ab79b4ee6 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -380,7 +380,7 @@ impl Device { Ok(handle) => handle, }; let can_pause = set_hw_params_from_format(&handle, conf, sample_format)?; - let period_samples = set_sw_params_from_format(&handle, conf, stream_type)?; + let period_samples = set_sw_params_from_format(&handle, conf, sample_format, stream_type)?; handle.prepare()?; @@ -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, @@ -1284,6 +1289,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 => fill_typed!(u8), + SampleFormat::DsdU16 => fill_typed!(u16), + SampleFormat::DsdU32 => fill_typed!(u32), } } @@ -1350,6 +1358,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"), @@ -1416,6 +1433,7 @@ fn set_hw_params_from_format( fn set_sw_params_from_format( pcm_handle: &alsa::pcm::PCM, config: &StreamConfig, + sample_format: SampleFormat, stream_type: alsa::Direction, ) -> Result { let sw_params = pcm_handle.sw_params_current()?; @@ -1429,10 +1447,18 @@ fn set_sw_params_from_format( } let start_threshold = match stream_type { alsa::Direction::Playback => { - // Always use 2-period double-buffering: one period playing from hardware, one - // period queued in the software buffer. This ensures consistent low latency - // regardless of the total buffer size. - 2 * period + // For playback, we want to start only when the buffer is full for DSD content + // to avoid underruns. For PCM, we keep the default low-latency behavior. + let is_dsd = matches!( + sample_format, + SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 + ); + + if is_dsd { + buffer + } else { + 2 * period + } } alsa::Direction::Capture => 1, }; diff --git a/src/lib.rs b/src/lib.rs index e2cae1c5a..5988c5aac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,7 @@ pub use platform::{ available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream, SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS, }; -pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24}; +pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24, DsdU8, DsdU16, DsdU32}; use std::convert::TryInto; use std::time::Duration; diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 58dcc0d20..fa2f9e057 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -105,6 +105,16 @@ pub enum SampleFormat { /// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F64, + + // DSD Formats + // ----------- + + /// DSD stream (U8) + DsdU8, + /// DSD stream (U16) + DsdU16, + /// DSD stream (U32) + DsdU32, } impl SampleFormat { @@ -129,6 +139,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 +166,9 @@ impl SampleFormat { SampleFormat::U64 => u64::BITS, SampleFormat::F32 => 32, SampleFormat::F64 => 64, + SampleFormat::DsdU8 => 8, + SampleFormat::DsdU16 => 16, + SampleFormat::DsdU32 => 32, } } @@ -181,6 +197,9 @@ impl SampleFormat { | SampleFormat::U32 // | SampleFormat::U48 | SampleFormat::U64 + | SampleFormat::DsdU8 + | SampleFormat::DsdU16 + | SampleFormat::DsdU32 ) } @@ -208,6 +227,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) } @@ -286,3 +308,123 @@ impl SizedSample for f32 { impl SizedSample for f64 { const FORMAT: SampleFormat = SampleFormat::F64; } + +/// DSD stream sample (U8). +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct DsdU8(pub u8); + +impl Sample for DsdU8 { + type Float = f32; + type Signed = i8; + + const EQUILIBRIUM: Self = DsdU8(0x69); +} + +// Conflict with blanket impl in dasp_sample +// impl FromSample for DsdU8 { ... } + +impl FromSample for DsdU8 { + fn from_sample_(_sample: f32) -> Self { + DsdU8(0x69) + } +} + +impl FromSample for DsdU8 { + fn from_sample_(_sample: i8) -> Self { + DsdU8(0x69) + } +} + +impl FromSample for i8 { + fn from_sample_(_sample: DsdU8) -> Self { + 0 + } +} + +impl FromSample for f32 { + fn from_sample_(_sample: DsdU8) -> Self { + 0.0 + } +} + +impl SizedSample for DsdU8 { + const FORMAT: SampleFormat = SampleFormat::DsdU8; +} + +/// DSD stream sample (U16). +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct DsdU16(pub u16); + +impl Sample for DsdU16 { + type Float = f32; + type Signed = i16; + + const EQUILIBRIUM: Self = DsdU16(0x6969); +} + +impl FromSample for DsdU16 { + fn from_sample_(_sample: f32) -> Self { + DsdU16(0x6969) + } +} + +impl FromSample for f32 { + fn from_sample_(_sample: DsdU16) -> Self { + 0.0 + } +} + +impl FromSample for DsdU16 { + fn from_sample_(_sample: i16) -> Self { + DsdU16(0x6969) + } +} + +impl FromSample for i16 { + fn from_sample_(_sample: DsdU16) -> Self { + 0 + } +} + +impl SizedSample for DsdU16 { + const FORMAT: SampleFormat = SampleFormat::DsdU16; +} + +/// DSD stream sample (U32). +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct DsdU32(pub u32); + +impl Sample for DsdU32 { + type Float = f32; + type Signed = i32; + + const EQUILIBRIUM: Self = DsdU32(0x69696969); +} + +impl FromSample for DsdU32 { + fn from_sample_(_sample: f32) -> Self { + DsdU32(0x69696969) + } +} + +impl FromSample for f32 { + fn from_sample_(_sample: DsdU32) -> Self { + 0.0 + } +} + +impl FromSample for DsdU32 { + fn from_sample_(_sample: i32) -> Self { + DsdU32(0x69696969) + } +} + +impl FromSample for i32 { + fn from_sample_(_sample: DsdU32) -> Self { + 0 + } +} + +impl SizedSample for DsdU32 { + const FORMAT: SampleFormat = SampleFormat::DsdU32; +} From 2b027ec38518efc9414e79c25d50a543795431cd Mon Sep 17 00:00:00 2001 From: Dragan Date: Sat, 20 Dec 2025 23:31:50 +0100 Subject: [PATCH 2/5] remove dsd structs and from impl --- src/lib.rs | 2 +- src/samples_formats.rs | 126 +---------------------------------------- 2 files changed, 4 insertions(+), 124 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5988c5aac..e2cae1c5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,7 @@ pub use platform::{ available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream, SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS, }; -pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24, DsdU8, DsdU16, DsdU32}; +pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24}; use std::convert::TryInto; use std::time::Duration; diff --git a/src/samples_formats.rs b/src/samples_formats.rs index fa2f9e057..9133ac4ed 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -166,9 +166,9 @@ impl SampleFormat { SampleFormat::U64 => u64::BITS, SampleFormat::F32 => 32, SampleFormat::F64 => 64, - SampleFormat::DsdU8 => 8, - SampleFormat::DsdU16 => 16, - SampleFormat::DsdU32 => 32, + SampleFormat::DsdU8 => 1, + SampleFormat::DsdU16 => 1, + SampleFormat::DsdU32 => 1, } } @@ -308,123 +308,3 @@ impl SizedSample for f32 { impl SizedSample for f64 { const FORMAT: SampleFormat = SampleFormat::F64; } - -/// DSD stream sample (U8). -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct DsdU8(pub u8); - -impl Sample for DsdU8 { - type Float = f32; - type Signed = i8; - - const EQUILIBRIUM: Self = DsdU8(0x69); -} - -// Conflict with blanket impl in dasp_sample -// impl FromSample for DsdU8 { ... } - -impl FromSample for DsdU8 { - fn from_sample_(_sample: f32) -> Self { - DsdU8(0x69) - } -} - -impl FromSample for DsdU8 { - fn from_sample_(_sample: i8) -> Self { - DsdU8(0x69) - } -} - -impl FromSample for i8 { - fn from_sample_(_sample: DsdU8) -> Self { - 0 - } -} - -impl FromSample for f32 { - fn from_sample_(_sample: DsdU8) -> Self { - 0.0 - } -} - -impl SizedSample for DsdU8 { - const FORMAT: SampleFormat = SampleFormat::DsdU8; -} - -/// DSD stream sample (U16). -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct DsdU16(pub u16); - -impl Sample for DsdU16 { - type Float = f32; - type Signed = i16; - - const EQUILIBRIUM: Self = DsdU16(0x6969); -} - -impl FromSample for DsdU16 { - fn from_sample_(_sample: f32) -> Self { - DsdU16(0x6969) - } -} - -impl FromSample for f32 { - fn from_sample_(_sample: DsdU16) -> Self { - 0.0 - } -} - -impl FromSample for DsdU16 { - fn from_sample_(_sample: i16) -> Self { - DsdU16(0x6969) - } -} - -impl FromSample for i16 { - fn from_sample_(_sample: DsdU16) -> Self { - 0 - } -} - -impl SizedSample for DsdU16 { - const FORMAT: SampleFormat = SampleFormat::DsdU16; -} - -/// DSD stream sample (U32). -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct DsdU32(pub u32); - -impl Sample for DsdU32 { - type Float = f32; - type Signed = i32; - - const EQUILIBRIUM: Self = DsdU32(0x69696969); -} - -impl FromSample for DsdU32 { - fn from_sample_(_sample: f32) -> Self { - DsdU32(0x69696969) - } -} - -impl FromSample for f32 { - fn from_sample_(_sample: DsdU32) -> Self { - 0.0 - } -} - -impl FromSample for DsdU32 { - fn from_sample_(_sample: i32) -> Self { - DsdU32(0x69696969) - } -} - -impl FromSample for i32 { - fn from_sample_(_sample: DsdU32) -> Self { - 0 - } -} - -impl SizedSample for DsdU32 { - const FORMAT: SampleFormat = SampleFormat::DsdU32; -} From 2638bbb54d96c23d5f643b49e74f26f518043b66 Mon Sep 17 00:00:00 2001 From: Dragan Date: Sat, 20 Dec 2025 23:32:39 +0100 Subject: [PATCH 3/5] revert start_threshold change for dsd as not needed --- src/host/alsa/mod.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index ab79b4ee6..ad952da14 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -380,7 +380,7 @@ impl Device { Ok(handle) => handle, }; let can_pause = set_hw_params_from_format(&handle, conf, sample_format)?; - let period_samples = set_sw_params_from_format(&handle, conf, sample_format, stream_type)?; + let period_samples = set_sw_params_from_format(&handle, conf, stream_type)?; handle.prepare()?; @@ -1433,7 +1433,6 @@ fn set_hw_params_from_format( fn set_sw_params_from_format( pcm_handle: &alsa::pcm::PCM, config: &StreamConfig, - sample_format: SampleFormat, stream_type: alsa::Direction, ) -> Result { let sw_params = pcm_handle.sw_params_current()?; @@ -1447,18 +1446,10 @@ fn set_sw_params_from_format( } let start_threshold = match stream_type { alsa::Direction::Playback => { - // For playback, we want to start only when the buffer is full for DSD content - // to avoid underruns. For PCM, we keep the default low-latency behavior. - let is_dsd = matches!( - sample_format, - SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 - ); - - if is_dsd { - buffer - } else { - 2 * period - } + // Always use 2-period double-buffering: one period playing from hardware, one + // period queued in the software buffer. This ensures consistent low latency + // regardless of the total buffer size. + 2 * period } alsa::Direction::Capture => 1, }; From 7f3276bdeed26280cc206f8d9693f39ce21f7ba8 Mon Sep 17 00:00:00 2001 From: Dragan Date: Sat, 20 Dec 2025 23:52:30 +0100 Subject: [PATCH 4/5] fix fmt --- src/samples_formats.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 9133ac4ed..fec82ec87 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -105,14 +105,13 @@ pub enum SampleFormat { /// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F64, - - // DSD Formats - // ----------- /// DSD stream (U8) DsdU8, + /// DSD stream (U16) DsdU16, + /// DSD stream (U32) DsdU32, } @@ -197,8 +196,8 @@ impl SampleFormat { | SampleFormat::U32 // | SampleFormat::U48 | SampleFormat::U64 - | SampleFormat::DsdU8 - | SampleFormat::DsdU16 + | SampleFormat::DsdU8 + | SampleFormat::DsdU16 | SampleFormat::DsdU32 ) } From d971024daee0397349b67c103032798e279cc9ce Mon Sep 17 00:00:00 2001 From: Dragan Date: Mon, 22 Dec 2025 22:28:40 +0100 Subject: [PATCH 5/5] add dsd512 and dsd1024 sample rates --- src/lib.rs | 2 +- src/samples_formats.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 fec82ec87..1156e5d75 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -105,13 +105,13 @@ pub enum SampleFormat { /// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F64, - + /// DSD stream (U8) DsdU8, - + /// DSD stream (U16) DsdU16, - + /// DSD stream (U32) DsdU32, }