@@ -35,81 +35,100 @@ impl Converter {
3535 }
3636 }
3737
38- /// To convert PCM samples from floating point normalized as `-1.0..=1.0`
39- /// to 32-bit signed integer, multiply by 2147483648 (0x80000000) and
40- /// saturate at the bounds of `i32`.
41- const SCALE_S32 : f64 = 2147483648. ;
42-
43- /// To convert PCM samples from floating point normalized as `-1.0..=1.0`
44- /// to 24-bit signed integer, multiply by 8388608 (0x800000) and saturate
45- /// at the bounds of `i24`.
46- const SCALE_S24 : f64 = 8388608. ;
47-
48- /// To convert PCM samples from floating point normalized as `-1.0..=1.0`
49- /// to 16-bit signed integer, multiply by 32768 (0x8000) and saturate at
50- /// the bounds of `i16`. When the samples were encoded using the same
51- /// scaling factor, like the reference Vorbis encoder does, this makes
52- /// conversions transparent.
53- const SCALE_S16 : f64 = 32768. ;
54-
55- pub fn scale ( & mut self , sample : f64 , factor : f64 ) -> f64 {
56- // From the many float to int conversion methods available, match what
57- // the reference Vorbis implementation uses: sample * 32768 (for 16 bit)
58-
59- // Casting float to integer rounds towards zero by default, i.e. it
60- // truncates, and that generates larger error than rounding to nearest.
38+ /// Base bit positions for PCM format scaling. These represent the position
39+ /// of the most significant bit in each format's full-scale representation.
40+ /// For signed integers in two's complement, full scale is 2^(bits-1).
41+ const SHIFT_S16 : u8 = 15 ; // 16-bit: 2^15 = 32768
42+ const SHIFT_S24 : u8 = 23 ; // 24-bit: 2^23 = 8388608
43+ const SHIFT_S32 : u8 = 31 ; // 32-bit: 2^31 = 2147483648
44+
45+
46+ /// Additional bit shifts needed to scale from 16-bit to higher bit depths.
47+ /// These are the differences between the base shift amounts above.
48+ const SHIFT_16_TO_24 : u8 = Self :: SHIFT_S24 - Self :: SHIFT_S16 ; // 23 - 15 = 8
49+ const SHIFT_16_TO_32 : u8 = Self :: SHIFT_S32 - Self :: SHIFT_S16 ; // 31 - 15 = 16
50+
51+ /// Pre-calculated scale factor for 24-bit clamping bounds
52+ const SCALE_S24 : f64 = ( 1_u64 << Self :: SHIFT_S24 ) as f64 ;
53+
54+ /// Scale audio samples with optimal dithering strategy for Spotify's 16-bit source material.
55+ ///
56+ /// Since Spotify audio is always 16-bit depth, this function:
57+ /// 1. When dithering: applies noise at 16-bit level, preserves fractional precision,
58+ /// then scales to target format and rounds once at the end
59+ /// 2. When not dithering: scales directly from normalized float to target format
60+ ///
61+ /// The `shift` parameter specifies how many extra bits to shift beyond
62+ /// the base 16-bit scaling (0 for 16-bit, 8 for 24-bit, 16 for 32-bit).
63+ #[ inline]
64+ pub fn scale ( & mut self , sample : f64 , shift : u8 ) -> f64 {
6165 match self . ditherer . as_mut ( ) {
62- Some ( d) => ( sample * factor + d. noise ( ) ) . round ( ) ,
63- None => ( sample * factor) . round ( ) ,
66+ Some ( d) => {
67+ // With dithering: Apply noise at 16-bit level to address original quantization,
68+ // then scale up to target format while preserving sub-LSB information
69+ let dithered_16bit = sample * ( 1_u64 << Self :: SHIFT_S16 ) as f64 + d. noise ( ) ;
70+ let scaled = dithered_16bit * ( 1_u64 << shift) as f64 ;
71+ scaled. round ( )
72+ }
73+ None => {
74+ // No dithering: Scale directly from normalized float to target format
75+ // using a single bit shift operation (base 16-bit shift + additional shift)
76+ let total_shift = Self :: SHIFT_S16 + shift;
77+ ( sample * ( 1_u64 << total_shift) as f64 ) . round ( )
78+ }
6479 }
6580 }
6681
67- // Special case for samples packed in a word of greater bit depth (e.g.
68- // S24): clamp between min and max to ensure that the most significant
69- // byte is zero. Otherwise, dithering may cause an overflow. This is not
70- // necessary for other formats, because casting to integer will saturate
71- // to the bounds of the primitive.
72- pub fn clamping_scale ( & mut self , sample : f64 , factor : f64 ) -> f64 {
73- let int_value = self . scale ( sample, factor) ;
74-
82+ /// Clamping scale specifically for 24-bit output to prevent MSB overflow.
83+ /// Only used for S24 formats where samples are packed in 32-bit words.
84+ /// Ensures the most significant byte is zero to prevent overflow during dithering.
85+ #[ inline]
86+ pub fn clamping_scale_s24 ( & mut self , sample : f64 ) -> f64 {
87+ let int_value = self . scale ( sample, Self :: SHIFT_16_TO_24 ) ;
88+
7589 // In two's complement, there are more negative than positive values.
76- let min = -factor ;
77- let max = factor - 1.0 ;
78-
90+ let min = -Self :: SCALE_S24 ;
91+ let max = Self :: SCALE_S24 - 1.0 ;
92+
7993 int_value. clamp ( min, max)
8094 }
8195
96+ #[ inline]
8297 pub fn f64_to_f32 ( & mut self , samples : & [ f64 ] ) -> Vec < f32 > {
8398 samples. iter ( ) . map ( |sample| * sample as f32 ) . collect ( )
8499 }
85100
101+ #[ inline]
86102 pub fn f64_to_s32 ( & mut self , samples : & [ f64 ] ) -> Vec < i32 > {
87103 samples
88104 . iter ( )
89- . map ( |sample| self . scale ( * sample, Self :: SCALE_S32 ) as i32 )
105+ . map ( |sample| self . scale ( * sample, Self :: SHIFT_16_TO_32 ) as i32 )
90106 . collect ( )
91107 }
92108
93- // S24 is 24-bit PCM packed in an upper 32-bit word
109+ /// S24 is 24-bit PCM packed in an upper 32-bit word
110+ #[ inline]
94111 pub fn f64_to_s24 ( & mut self , samples : & [ f64 ] ) -> Vec < i32 > {
95112 samples
96113 . iter ( )
97- . map ( |sample| self . clamping_scale ( * sample, Self :: SCALE_S24 ) as i32 )
114+ . map ( |sample| self . clamping_scale_s24 ( * sample) as i32 )
98115 . collect ( )
99116 }
100117
101- // S24_3 is 24-bit PCM in a 3-byte array
118+ /// S24_3 is 24-bit PCM in a 3-byte array
119+ #[ inline]
102120 pub fn f64_to_s24_3 ( & mut self , samples : & [ f64 ] ) -> Vec < i24 > {
103121 samples
104122 . iter ( )
105- . map ( |sample| i24:: from_s24 ( self . clamping_scale ( * sample, Self :: SCALE_S24 ) as i32 ) )
123+ . map ( |sample| i24:: from_s24 ( self . clamping_scale_s24 ( * sample) as i32 ) )
106124 . collect ( )
107125 }
108126
127+ #[ inline]
109128 pub fn f64_to_s16 ( & mut self , samples : & [ f64 ] ) -> Vec < i16 > {
110129 samples
111130 . iter ( )
112- . map ( |sample| self . scale ( * sample, Self :: SCALE_S16 ) as i16 )
131+ . map ( |sample| self . scale ( * sample, 0 ) as i16 )
113132 . collect ( )
114133 }
115134}
0 commit comments