makepad/libs/audio_encode/src/bits.rs
Admin 016a171a35 libs/audio_*: MP3, Vorbis and Ogg of our own, plus lyric alignment and audio imaging
Another dependency the app should not be asking the platform for:

  - audio_decode — MP3 (layer 3, LSF tables, synthesis) and Ogg Vorbis
    (codebooks, floor, residue, MDCT) decoders, with tag reading. Both are
    checked against oracle fixtures rather than against our own expectations.
  - audio_encode — an Ogg Vorbis encoder: MDCT, psychoacoustics, floor and
    Huffman coding, setup tables, plus `oggenc` and `audiobench` binaries.
  - audio_picture — waveform and spectrogram rendering, and compositing.
  - audio_lyrics — word-level lyric alignment (DTW plus a DP snap) and the
    baked schema behind karaoke timing.
  - audio_sidechannels — the side-channel plumbing between them.

libs/voice grows a CUDA backend and an alignment path beside its CPU decoder,
with a `whisper_parity` binary to keep the two honest.
2026-08-23 01:34:35 +02:00

131 lines
3.7 KiB
Rust

//! LSB-first bit writer: the mirror of the decoder's `BitReader`.
//!
//! Vorbis packs header fields and residue vectors LSB-first, but Huffman
//! codewords are read bit-by-bit starting at the codeword's MSB, so codewords
//! are stored pre-reversed in the encode tables and written like any other
//! field ([`reverse_bits`]).
pub struct BitWriter {
bytes: Vec<u8>,
/// Bits accumulated below 8, LSB-first.
acc: u64,
/// Number of valid bits in `acc` (< 8 after every push).
fill: u32,
}
impl Default for BitWriter {
fn default() -> Self {
Self::new()
}
}
impl BitWriter {
pub fn new() -> Self {
Self { bytes: Vec::new(), acc: 0, fill: 0 }
}
pub fn with_capacity(bytes: usize) -> Self {
Self { bytes: Vec::with_capacity(bytes), acc: 0, fill: 0 }
}
/// Append the low `n` bits of `v`, LSB-first. `n <= 32`.
#[inline]
pub fn push(&mut self, v: u32, n: u32) {
debug_assert!(n <= 32);
debug_assert!(n == 32 || (v as u64) < (1u64 << n), "value {v} does not fit {n} bits");
self.acc |= (v as u64) << self.fill;
self.fill += n;
while self.fill >= 8 {
self.bytes.push((self.acc & 0xff) as u8);
self.acc >>= 8;
self.fill -= 8;
}
}
#[inline]
pub fn push_bit(&mut self, b: bool) {
self.push(b as u32, 1);
}
/// Bits written so far, including the unflushed tail.
pub fn bit_len(&self) -> usize {
self.bytes.len() * 8 + self.fill as usize
}
/// Pad the tail with zero bits to a byte boundary and return the bytes.
pub fn finish(mut self) -> Vec<u8> {
if self.fill > 0 {
self.bytes.push((self.acc & 0xff) as u8);
self.acc = 0;
self.fill = 0;
}
self.bytes
}
}
/// The low `len` bits of `code`, reversed. Canonical Huffman codes are defined
/// MSB-first; the stream is written LSB-first; a decoder reading one bit at a
/// time therefore sees the MSB first when the codeword is stored reversed.
pub fn reverse_bits(code: u32, len: u32) -> u32 {
let mut out = 0u32;
for i in 0..len {
out |= ((code >> (len - 1 - i)) & 1) << i;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use makepad_audio_decode::vorbis::bits::BitReader;
#[test]
fn writer_round_trips_through_the_decoder_reader() {
let fields: &[(u32, u32)] = &[
(0, 1),
(0x564342, 24),
(511, 9),
(1, 1),
(0xffff_ffff, 32),
(5, 3),
(0, 0),
(129, 8),
];
let mut w = BitWriter::new();
for &(v, n) in fields {
w.push(v, n);
}
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
for &(v, n) in fields {
assert_eq!(r.read(n), Some(v), "field {v}:{n}");
}
}
#[test]
fn bit_len_counts_the_unflushed_tail() {
let mut w = BitWriter::new();
assert_eq!(w.bit_len(), 0);
w.push(1, 3);
assert_eq!(w.bit_len(), 3);
w.push(0, 13);
assert_eq!(w.bit_len(), 16);
assert_eq!(w.finish().len(), 2);
}
#[test]
fn reversed_codewords_decode_msb_first() {
// Writing reverse_bits(code, len) must make a bit-at-a-time reader see
// the code MSB-first, which is how the decoder walks its Huffman tree.
let (code, len) = (0b110u32, 3u32);
let mut w = BitWriter::new();
w.push(reverse_bits(code, len), len);
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
let mut seen = 0u32;
for _ in 0..len {
seen = (seen << 1) | r.read(1).unwrap();
}
assert_eq!(seen, code);
}
}