melpe-rs 0.1.2

MELPe vocoder (STANAG 4591) in pure Rust — 600 bps voice codec, no_std compatible
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
/// Bitstream packing and unpacking for 600 bps MELPe superframes
///
/// Packs a SuperFrameQuantized into a 6-byte (41-bit) wire format
/// and unpacks it back. Bit-level operations with a clear allocation table.

use crate::core_types::{
    NUM_LSF, SUPERFRAME_BITS_600, SUPERFRAME_BYTES_600,
    LSF_BITS_600, PITCH_BITS_600, GAIN_BITS_600,
    VOICING_BITS_600, JITTER_BITS_600, SPARE_BITS_600,
    SuperFrameQuantized,
};

/// Bit allocation table for the 600 bps superframe.
/// Each entry is (field_name, bit_count) in transmission order.
/// Total: 41 bits → 6 bytes (7 spare bits in last byte).
pub const BIT_ALLOC_600: &[(&str, usize)] = &[
    ("lsf0", 3), ("lsf1", 3), ("lsf2", 3), ("lsf3", 3),
    ("lsf4", 2), ("lsf5", 2), ("lsf6", 2), ("lsf7", 2),
    ("lsf8", 2), ("lsf9", 2),
    ("pitch", 6),
    ("gain", 5),
    ("voicing", 4),
    ("jitter", 1),
    ("spare", 1),
];

// ── Bitstream writer ──

/// Writes bits MSB-first into a byte buffer.
struct BitWriter<'a> {
    buf: &'a mut [u8],
    byte_pos: usize,
    bit_pos: u8, // 0..7, counts from MSB
}

impl<'a> BitWriter<'a> {
    fn new(buf: &'a mut [u8]) -> Self {
        for b in buf.iter_mut() {
            *b = 0;
        }
        Self {
            buf,
            byte_pos: 0,
            bit_pos: 0,
        }
    }

    /// Write `n_bits` (1..=8) from the low bits of `value`.
    fn write(&mut self, value: u8, n_bits: usize) {
        debug_assert!(n_bits >= 1 && n_bits <= 8);
        for i in (0..n_bits).rev() {
            let bit = (value >> i) & 1;
            if bit == 1 {
                self.buf[self.byte_pos] |= 1 << (7 - self.bit_pos);
            }
            self.bit_pos += 1;
            if self.bit_pos >= 8 {
                self.bit_pos = 0;
                self.byte_pos += 1;
            }
        }
    }

    /// Total bits written so far.
    fn bits_written(&self) -> usize {
        self.byte_pos * 8 + self.bit_pos as usize
    }
}

// ── Bitstream reader ──

/// Reads bits MSB-first from a byte buffer.
struct BitReader<'a> {
    buf: &'a [u8],
    byte_pos: usize,
    bit_pos: u8,
}

impl<'a> BitReader<'a> {
    fn new(buf: &'a [u8]) -> Self {
        Self {
            buf,
            byte_pos: 0,
            bit_pos: 0,
        }
    }

    /// Read `n_bits` (1..=8) and return them in the low bits.
    fn read(&mut self, n_bits: usize) -> u8 {
        debug_assert!(n_bits >= 1 && n_bits <= 8);
        let mut value: u8 = 0;
        for _ in 0..n_bits {
            value <<= 1;
            if self.byte_pos < self.buf.len() {
                let bit = (self.buf[self.byte_pos] >> (7 - self.bit_pos)) & 1;
                value |= bit;
            }
            self.bit_pos += 1;
            if self.bit_pos >= 8 {
                self.bit_pos = 0;
                self.byte_pos += 1;
            }
        }
        value
    }

    /// Total bits read so far.
    fn bits_read(&self) -> usize {
        self.byte_pos * 8 + self.bit_pos as usize
    }
}

// ── Pack / Unpack ──

/// Pack a SuperFrameQuantized into a byte buffer (6 bytes for 600 bps).
/// Returns the number of bits written (should be SUPERFRAME_BITS_600).
pub fn pack_superframe(sq: &SuperFrameQuantized, out: &mut [u8; SUPERFRAME_BYTES_600]) -> usize {
    let mut w = BitWriter::new(out);

    // LSFs: 10 coefficients with variable bit widths
    for i in 0..NUM_LSF {
        w.write(sq.lsf_indices[i], LSF_BITS_600[i]);
    }

    // Pitch: 6 bits
    w.write(sq.pitch_index, PITCH_BITS_600);

    // Gain: 5 bits
    w.write(sq.gain_index, GAIN_BITS_600);

    // Voicing: 4 bits
    w.write(sq.voicing_bits, VOICING_BITS_600);

    // Jitter: 1 bit
    w.write(sq.jitter_bit, JITTER_BITS_600);

    // Spare: 1 bit (write 0)
    w.write(0, SPARE_BITS_600);

    w.bits_written()
}

/// Unpack a byte buffer into a SuperFrameQuantized.
/// Returns the struct and the number of bits consumed.
pub fn unpack_superframe(data: &[u8; SUPERFRAME_BYTES_600]) -> (SuperFrameQuantized, usize) {
    let mut r = BitReader::new(data);
    let mut sq = SuperFrameQuantized::default();

    // LSFs
    for i in 0..NUM_LSF {
        sq.lsf_indices[i] = r.read(LSF_BITS_600[i]);
    }

    // Pitch
    sq.pitch_index = r.read(PITCH_BITS_600);

    // Gain
    sq.gain_index = r.read(GAIN_BITS_600);

    // Voicing
    sq.voicing_bits = r.read(VOICING_BITS_600);

    // Jitter
    sq.jitter_bit = r.read(JITTER_BITS_600);

    // Skip spare
    let _spare = r.read(SPARE_BITS_600);

    (sq, r.bits_read())
}

/// Verify that the bit allocation table sums correctly.
pub fn verify_bit_budget() -> bool {
    let total: usize = BIT_ALLOC_600.iter().map(|(_, bits)| *bits).sum();
    total == SUPERFRAME_BITS_600
}

#[cfg(test)]
mod tests {
    use super::*;

    // ── BitWriter / BitReader unit tests ──

    #[test]
    fn test_bitwriter_single_bits() {
        let mut buf = [0u8; 2];
        let bits_written;
        {
            let mut w = BitWriter::new(&mut buf);
            w.write(1, 1); // bit 7
            w.write(0, 1); // bit 6
            w.write(1, 1); // bit 5
            w.write(1, 1); // bit 4
            w.write(0, 1); // bit 3
            w.write(0, 1); // bit 2
            w.write(1, 1); // bit 1
            w.write(0, 1); // bit 0
            bits_written = w.bits_written();
        }
        // Should be 0b10110010 = 0xB2
        assert_eq!(buf[0], 0xB2);
        assert_eq!(bits_written, 8);
    }

    #[test]
    fn test_bitwriter_multi_bit() {
        let mut buf = [0u8; 2];
        let bits_written;
        {
            let mut w = BitWriter::new(&mut buf);
            w.write(0b101, 3); // bits 7-5
            w.write(0b11010, 5); // bits 4-0
            bits_written = w.bits_written();
        }
        // byte 0: 10111010 = 0xBA
        assert_eq!(buf[0], 0xBA);
        assert_eq!(bits_written, 8);
    }

    #[test]
    fn test_bitwriter_crosses_byte() {
        let mut buf = [0u8; 2];
        let bits_written;
        {
            let mut w = BitWriter::new(&mut buf);
            w.write(0b111, 3);    // byte 0 bits 7-5
            w.write(0b101010, 6); // byte 0 bits 4-0 + byte 1 bit 7
            bits_written = w.bits_written();
        }
        // byte 0: 111_10101 = 0xF5
        // byte 1: 0_0000000 = 0x00
        assert_eq!(buf[0], 0xF5);
        assert_eq!(buf[1], 0x00);
        assert_eq!(bits_written, 9);
    }

    #[test]
    fn test_bitreader_single_bits() {
        let buf = [0xB2u8, 0x00]; // 10110010
        let mut r = BitReader::new(&buf);
        assert_eq!(r.read(1), 1);
        assert_eq!(r.read(1), 0);
        assert_eq!(r.read(1), 1);
        assert_eq!(r.read(1), 1);
        assert_eq!(r.read(1), 0);
        assert_eq!(r.read(1), 0);
        assert_eq!(r.read(1), 1);
        assert_eq!(r.read(1), 0);
        assert_eq!(r.bits_read(), 8);
    }

    #[test]
    fn test_bitreader_multi_bit() {
        let buf = [0xBA]; // 10111010
        let mut r = BitReader::new(&buf);
        assert_eq!(r.read(3), 0b101); // 101
        assert_eq!(r.read(5), 0b11010); // 11010
    }

    #[test]
    fn test_writer_reader_roundtrip() {
        let mut buf = [0u8; 4];
        let mut w = BitWriter::new(&mut buf);
        w.write(0b110, 3);
        w.write(0b01011, 5);
        w.write(0b1111, 4);
        w.write(0b00, 2);
        w.write(0b101101, 6);

        let mut r = BitReader::new(&buf);
        assert_eq!(r.read(3), 0b110);
        assert_eq!(r.read(5), 0b01011);
        assert_eq!(r.read(4), 0b1111);
        assert_eq!(r.read(2), 0b00);
        assert_eq!(r.read(6), 0b101101);
    }

    // ── Bit budget verification ──

    #[test]
    fn test_bit_budget_table() {
        assert!(verify_bit_budget(), "Bit allocation table doesn't sum to {}", SUPERFRAME_BITS_600);
    }

    #[test]
    fn test_bit_alloc_field_count() {
        // 10 LSFs + pitch + gain + voicing + jitter + spare = 15 entries
        assert_eq!(BIT_ALLOC_600.len(), 15);
    }

    // ── Pack/Unpack roundtrips ──

    #[test]
    fn test_pack_unpack_zeros() {
        let sq = SuperFrameQuantized::default();
        let mut buf = [0u8; SUPERFRAME_BYTES_600];
        let bits = pack_superframe(&sq, &mut buf);
        assert_eq!(bits, SUPERFRAME_BITS_600);

        let (recovered, bits_read) = unpack_superframe(&buf);
        assert_eq!(bits_read, SUPERFRAME_BITS_600);
        assert_eq!(recovered, sq);
    }

    #[test]
    fn test_pack_unpack_max_values() {
        let sq = SuperFrameQuantized {
            lsf_indices: [
                (1 << LSF_BITS_600[0]) - 1,
                (1 << LSF_BITS_600[1]) - 1,
                (1 << LSF_BITS_600[2]) - 1,
                (1 << LSF_BITS_600[3]) - 1,
                (1 << LSF_BITS_600[4]) - 1,
                (1 << LSF_BITS_600[5]) - 1,
                (1 << LSF_BITS_600[6]) - 1,
                (1 << LSF_BITS_600[7]) - 1,
                (1 << LSF_BITS_600[8]) - 1,
                (1 << LSF_BITS_600[9]) - 1,
            ],
            pitch_index: (1 << PITCH_BITS_600) - 1,
            gain_index: (1 << GAIN_BITS_600) - 1,
            voicing_bits: (1 << VOICING_BITS_600) - 1,
            jitter_bit: 1,
        };
        let mut buf = [0u8; SUPERFRAME_BYTES_600];
        pack_superframe(&sq, &mut buf);

        let (recovered, _) = unpack_superframe(&buf);
        assert_eq!(recovered, sq);
    }

    #[test]
    fn test_pack_unpack_mixed_values() {
        let sq = SuperFrameQuantized {
            lsf_indices: [5, 3, 7, 1, 2, 0, 3, 1, 2, 3],
            pitch_index: 42,
            gain_index: 17,
            voicing_bits: 0b1011,
            jitter_bit: 1,
        };
        let mut buf = [0u8; SUPERFRAME_BYTES_600];
        pack_superframe(&sq, &mut buf);

        let (recovered, _) = unpack_superframe(&buf);
        assert_eq!(recovered.lsf_indices, sq.lsf_indices);
        assert_eq!(recovered.pitch_index, sq.pitch_index);
        assert_eq!(recovered.gain_index, sq.gain_index);
        assert_eq!(recovered.voicing_bits, sq.voicing_bits);
        assert_eq!(recovered.jitter_bit, sq.jitter_bit);
    }

    #[test]
    fn test_pack_unpack_exhaustive_lsf() {
        // Test every valid LSF index combination for the first 4 LSFs (3-bit each)
        for i0 in 0..8u8 {
            for i1 in 0..8u8 {
                let sq = SuperFrameQuantized {
                    lsf_indices: [i0, i1, 4, 2, 1, 3, 0, 2, 1, 3],
                    pitch_index: 30,
                    gain_index: 15,
                    voicing_bits: 5,
                    jitter_bit: 0,
                };
                let mut buf = [0u8; SUPERFRAME_BYTES_600];
                pack_superframe(&sq, &mut buf);
                let (recovered, _) = unpack_superframe(&buf);
                assert_eq!(recovered, sq, "Failed for lsf[0]={}, lsf[1]={}", i0, i1);
            }
        }
    }

    #[test]
    fn test_pack_bit_count() {
        let sq = SuperFrameQuantized::default();
        let mut buf = [0u8; SUPERFRAME_BYTES_600];
        let bits = pack_superframe(&sq, &mut buf);
        assert_eq!(bits, 41);
    }

    #[test]
    fn test_buffer_size() {
        // 41 bits needs exactly 6 bytes
        assert_eq!(SUPERFRAME_BYTES_600, 6);
        assert_eq!((SUPERFRAME_BITS_600 + 7) / 8, 6);
    }

    #[test]
    fn test_spare_bits_zero() {
        // Spare bit should be 0 after packing
        let sq = SuperFrameQuantized {
            lsf_indices: [7, 7, 7, 7, 3, 3, 3, 3, 3, 3],
            pitch_index: 63,
            gain_index: 31,
            voicing_bits: 15,
            jitter_bit: 1,
        };
        let mut buf = [0u8; SUPERFRAME_BYTES_600];
        pack_superframe(&sq, &mut buf);

        // Read back and verify the spare bit is 0
        let mut r = BitReader::new(&buf);
        // Skip all fields: 24 + 6 + 5 + 4 + 1 = 40 bits
        for _ in 0..40 {
            r.read(1);
        }
        let spare = r.read(1);
        assert_eq!(spare, 0, "Spare bit should be 0");
    }

    #[test]
    fn test_pack_deterministic() {
        let sq = SuperFrameQuantized {
            lsf_indices: [2, 5, 1, 6, 3, 0, 2, 1, 3, 1],
            pitch_index: 33,
            gain_index: 20,
            voicing_bits: 0b0110,
            jitter_bit: 0,
        };

        let mut buf1 = [0u8; SUPERFRAME_BYTES_600];
        let mut buf2 = [0u8; SUPERFRAME_BYTES_600];
        pack_superframe(&sq, &mut buf1);
        pack_superframe(&sq, &mut buf2);
        assert_eq!(buf1, buf2, "Packing must be deterministic");
    }
}