Skip to main content

flexaudio_core/
quant.rs

1//! Signed 16-bit PCM quantization, shared by every output path.
2//!
3//! A single canonical quantizer keeps the CLI, the encoders and the language
4//! bindings byte-identical. Sample values are host-native `i16`; serializing to
5//! a little-endian wire format (s16le) is the consumer's responsibility (this
6//! crate returns values, not byte-order-specific buffers).
7
8/// Quantize one f32 sample to signed 16-bit PCM (no dither).
9///
10/// The scale factor 32768 uses the full negative range (`-1.0` maps to
11/// `-32768`); `+1.0` cannot be represented and clamps to `32767`. Inputs
12/// outside `[-1.0, 1.0]` saturate to the range bounds. Non-finite inputs
13/// saturate through the `as` cast (`NaN -> 0`, `+inf -> 32767`,
14/// `-inf -> -32768`). Rounding is round-half-away-from-zero.
15#[inline]
16pub fn quantize_i16(x: f32) -> i16 {
17    (x * 32768.0).round().clamp(-32768.0, 32767.0) as i16
18}
19
20#[cfg(test)]
21mod tests {
22    use super::*;
23
24    #[test]
25    fn quantizes_reference_values() {
26        assert_eq!(quantize_i16(0.0), 0);
27        assert_eq!(quantize_i16(0.25), 8192);
28        // Full negative scale; positive full-scale clamps one LSB below.
29        assert_eq!(quantize_i16(-1.0), -32768);
30        assert_eq!(quantize_i16(1.0), 32767);
31    }
32
33    #[test]
34    fn saturates_out_of_range_and_non_finite() {
35        // Out-of-range inputs clamp to the range bounds.
36        assert_eq!(quantize_i16(2.0), 32767);
37        assert_eq!(quantize_i16(-2.0), -32768);
38        // Non-finite inputs saturate.
39        assert_eq!(quantize_i16(f32::NAN), 0);
40        assert_eq!(quantize_i16(f32::INFINITY), 32767);
41        assert_eq!(quantize_i16(f32::NEG_INFINITY), -32768);
42    }
43
44    #[test]
45    fn rounds_half_away_from_zero() {
46        // 1.5 / 32768 is exactly representable in binary; rounds to 2.
47        assert_eq!(quantize_i16(1.5 / 32768.0), 2);
48        assert_eq!(quantize_i16(-1.5 / 32768.0), -2);
49    }
50}