Skip to main content

open_stomp/
pack.rs

1//! 8-in-7 packing: 8-bit structs carried as a little-endian 7-bit bitstream (docs/03).
2
3/// 8-bit struct bytes -> 7-bit MIDI bytes (LSB first).
4pub fn pack(data: &[u8]) -> Vec<u8> {
5    let mut out = Vec::with_capacity(data.len() * 8 / 7 + 1);
6    let (mut acc, mut nbits) = (0u32, 0u32);
7    for &b in data {
8        acc |= (b as u32) << nbits;
9        nbits += 8;
10        while nbits >= 7 {
11            out.push((acc & 0x7F) as u8);
12            acc >>= 7;
13            nbits -= 7;
14        }
15    }
16    if nbits > 0 {
17        out.push((acc & 0x7F) as u8);
18    }
19    out
20}
21
22/// 7-bit MIDI bytes -> 8-bit struct bytes. A trailing partial byte is dropped.
23pub fn unpack(midi: &[u8]) -> Vec<u8> {
24    let mut out = Vec::with_capacity(midi.len() * 7 / 8);
25    let (mut acc, mut nbits) = (0u32, 0u32);
26    for &b in midi {
27        acc |= ((b & 0x7F) as u32) << nbits;
28        nbits += 7;
29        if nbits >= 8 {
30            out.push((acc & 0xFF) as u8);
31            acc >>= 8;
32            nbits -= 8;
33        }
34    }
35    out
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn roundtrip() {
44        let data: Vec<u8> = (0..110u32).map(|i| (i * 37 + 11) as u8).collect();
45        let packed = pack(&data);
46        assert_eq!(packed.len(), 126);
47        assert!(packed.iter().all(|b| *b < 0x80));
48        assert_eq!(unpack(&packed), data);
49    }
50
51    #[test]
52    fn nam_slot_header() {
53        // docs/03: slot 1, sub-command 4 packs to MIDI `00 02 10`
54        assert_eq!(&pack(&[0, 1, 4])[..3], &[0x00, 0x02, 0x10]);
55    }
56}