Skip to main content

dspk_core/recording/
format.rs

1use serde::{Deserialize, Serialize};
2
3/// Numeric type of the samples as stored on disk (always little-endian).
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum SampleFormat {
7    I8,
8    I16,
9    U16,
10    I32,
11    F32,
12    F64,
13}
14
15impl SampleFormat {
16    pub const ALL: [SampleFormat; 6] =
17        [SampleFormat::I8, SampleFormat::I16, SampleFormat::U16, SampleFormat::I32, SampleFormat::F32, SampleFormat::F64];
18
19    pub const fn bytes(self) -> usize {
20        match self {
21            SampleFormat::I8 => 1,
22            SampleFormat::I16 | SampleFormat::U16 => 2,
23            SampleFormat::I32 | SampleFormat::F32 => 4,
24            SampleFormat::F64 => 8,
25        }
26    }
27
28    /// Canonical name (NumPy / Zarr spelling: `int16`, `float32`, …).
29    pub const fn name(self) -> &'static str {
30        match self {
31            SampleFormat::I8 => "int8",
32            SampleFormat::I16 => "int16",
33            SampleFormat::U16 => "uint16",
34            SampleFormat::I32 => "int32",
35            SampleFormat::F32 => "float32",
36            SampleFormat::F64 => "float64",
37        }
38    }
39
40    /// Decodes `out.len()` contiguous little-endian samples of this format from `bytes`, scaled
41    /// to `stored · gain + offset`.
42    ///
43    /// # Panics
44    /// If `bytes` holds fewer than `out.len()` samples.
45    pub fn decode(self, bytes: &[u8], out: &mut [f32], gain: f32, offset: f32) {
46        fn run<const B: usize>(bytes: &[u8], out: &mut [f32], gain: f32, offset: f32, value: impl Fn([u8; B]) -> f32) {
47            assert!(bytes.len() >= out.len() * B, "{} bytes hold fewer than {} samples", bytes.len(), out.len());
48            for (o, b) in out.iter_mut().zip(bytes.as_chunks::<B>().0) {
49                *o = value(*b) * gain + offset;
50            }
51        }
52        match self {
53            SampleFormat::I8 => run::<1>(bytes, out, gain, offset, |b| i8::from_le_bytes(b) as f32),
54            SampleFormat::I16 => run::<2>(bytes, out, gain, offset, |b| i16::from_le_bytes(b) as f32),
55            SampleFormat::U16 => run::<2>(bytes, out, gain, offset, |b| u16::from_le_bytes(b) as f32),
56            SampleFormat::I32 => run::<4>(bytes, out, gain, offset, |b| i32::from_le_bytes(b) as f32),
57            SampleFormat::F32 => run::<4>(bytes, out, gain, offset, f32::from_le_bytes),
58            SampleFormat::F64 => run::<8>(bytes, out, gain, offset, |b| f64::from_le_bytes(b) as f32),
59        }
60    }
61
62    /// Parses common spellings: `i16`/`int16`/`<i2`, `f32`/`float32`/`float32-le`, ….
63    pub fn parse(s: &str) -> Option<Self> {
64        match s.trim().to_ascii_lowercase().trim_end_matches("-le") {
65            "i8" | "int8" | "<i1" | "|i1" => Some(SampleFormat::I8),
66            "i16" | "int16" | "<i2" => Some(SampleFormat::I16),
67            "u16" | "uint16" | "<u2" => Some(SampleFormat::U16),
68            "i32" | "int32" | "<i4" => Some(SampleFormat::I32),
69            "f32" | "float32" | "float" | "<f4" => Some(SampleFormat::F32),
70            "f64" | "float64" | "double" | "<f8" => Some(SampleFormat::F64),
71            _ => None,
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn decode_scales_every_format() {
82        let mut out = [0.0f32; 2];
83        SampleFormat::I16.decode(&[0x10, 0x00, 0xF0, 0xFF], &mut out, 0.5, 1.0);
84        assert_eq!(out, [9.0, -7.0]);
85        SampleFormat::U16.decode(&[0xFF, 0xFF, 0x00, 0x00], &mut out, 1.0, 0.0);
86        assert_eq!(out, [65535.0, 0.0]);
87        SampleFormat::F64.decode(&[1.5f64.to_le_bytes(), (-2.0f64).to_le_bytes()].concat(), &mut out, 2.0, 0.0);
88        assert_eq!(out, [3.0, -4.0]);
89    }
90
91    #[test]
92    fn test_parse_and_size() {
93        assert_eq!(SampleFormat::parse("int16"), Some(SampleFormat::I16));
94        assert_eq!(SampleFormat::parse("float32-le"), Some(SampleFormat::F32));
95        assert_eq!(SampleFormat::parse("<u2"), Some(SampleFormat::U16));
96        assert_eq!(SampleFormat::parse("<f8"), Some(SampleFormat::F64));
97        assert_eq!(SampleFormat::parse("complex64"), None);
98        assert_eq!(SampleFormat::I16.bytes(), 2);
99        assert_eq!(SampleFormat::F64.bytes(), 8);
100        for f in SampleFormat::ALL {
101            assert_eq!(SampleFormat::parse(f.name()), Some(f));
102        }
103    }
104}