lac 0.1.0

Lo Audio Codec — lossless audio codec with LPC + partitioned Rice coding.
Documentation
//! LAC — Lo Audio Codec.
//!
//! Lossless audio codec targeting FLAC-class compression. Integer-only, bit-exact,
//! streaming-oriented. Scope:
//!
//! - **Input**: signed 24-bit PCM (values in `[-2^23, 2^23 − 1]`), sample rate is
//!   caller-specified and not encoded in the stream — the container or transport
//!   carries it.
//! - **Channels**: mono per encoded stream. Stereo is delivered as two independent
//!   mono streams (e.g., two QUIC streams in a streaming context); cross-channel
//!   decorrelation is explicitly out of scope.
//! - **Frames**: self-contained. Each frame decodes independently — no cross-frame
//!   state. A lost or corrupt frame never affects subsequent frames.
//!
//! # Pipeline
//!
//! Encode: `samples → LPC analysis → residuals → partitioned Rice coding → frame
//! bytes`. Decode is the exact inverse.
//!
//! - LPC: Levinson-Durbin at any order in `[0, 32]`. Order 0 is verbatim mode
//!   (residuals equal the raw samples).
//! - Rice: residuals split into `2^partition_order` equal partitions with
//!   `partition_order ∈ [0, 7]` (1 to 128 partitions). Each partition chooses its
//!   own Rice parameter `k ∈ [0, 23]`.
//!
//! Both selections are made by the encoder via exhaustive search over the
//! combinations whose partition count evenly divides the frame size.
//!
//! # Error handling
//!
//! Decode returns `Err(DecodeError)` on any structural failure (bad sync, invalid
//! header fields, truncated buffer). Callers are expected to substitute
//! `frame_sample_count` zeros (or equivalent silence) for the frame period —
//! never propagate partial state.
//!
//! # `no_std`
//!
//! The library is `#![no_std]` with a dependency on the `alloc` crate for
//! `Vec`-backed buffers. Targets with an allocator (std OSes, WASI/WASM,
//! embedded with heap) all link without feature flags. True bare-metal
//! (no allocator) is unsupported because variable-length residual and
//! coefficient buffers are fundamental to the codec. `DecodeError`
//! implements `core::error::Error` (stable since Rust 1.81), so
//! `?`-propagation through `Result`-returning traits works in both std
//! and no_std contexts.
//!
//! # Wire-format touchpoints
//!
//! The normative wire format lives in `Specification.md`; its implementation is
//! split across three crate-private modules by concern. Any future
//! wire-format revision (CRC tag, version flag, new partition mode, …)
//! touches at least [`frame`] plus whichever module owns the new bits.
//!
//! - [`frame`] — fixed header layout (sync word, `prediction_order`,
//!   `partition_order`, `coefficient_shift`, `frame_sample_count`, LPC
//!   coefficients) plus synthesis. [`parse_header`], [`encode_frame`],
//!   [`decode_frame`], and [`AudioFrameHeader`] all live here.
//! - `rice` (crate-private) — partitioned Rice bitstream. Owns the
//!   5-bit per-partition `k` field, the zigzag mapping and its inverse,
//!   and the `q > u32::MAX >> k` rejection bound from spec §4.2.
//! - `bit_io` (crate-private) — MSB-first bit reader / writer
//!   primitives. Byte-level packing semantics (first bit → bit 7 of
//!   first byte) live here; both `frame` and `rice` are clients.
//!
//! When reading the spec alongside the source, §3 maps to [`frame`],
//! §4 to `rice`, and the bit-order conventions of §2 / §4 to `bit_io`.

#![no_std]

extern crate alloc;

// Module visibility note: `bit_io`, `lpc`, and `rice` are crate-private.
// Nothing outside the crate legitimately needs to reach them by a
// module-qualified path; the public surface is exported at the crate
// root below (everything in `frame` plus a small set of re-exports).
// Keeping these modules `pub(crate)` avoids locking internal types
// (`BitWriter`, `LpcLevels`, `select_k`, …) into semver-stable public
// API before 1.0.
pub(crate) mod bit_io;
pub mod frame;
pub(crate) mod lpc;
pub(crate) mod rice;

#[cfg(test)]
pub(crate) mod test_signals;

pub use frame::{
    AudioFrameHeader, DecodeError, SYNC_WORD, decode_frame, decode_frame_into, encode_frame,
    encode_frame_into, parse_header,
};

pub use lpc::MAX_COEFFICIENT_SHIFT;

/// Feature-gated re-export for the internal benchmark suite in
/// `benches/codec.rs`, which drives `compute_residuals` directly to
/// isolate the LPC hot kernel from the rest of the encode path. The
/// `__internal-for-bench` feature is listed as `required-features`
/// on the bench target, so `cargo bench` enables it automatically and
/// normal builds never see the symbol. Explicitly **not** part of the
/// public API — any external caller that enables this feature takes
/// responsibility for tracking LPC-internal refactors themselves.
#[cfg(feature = "__internal-for-bench")]
#[doc(hidden)]
pub use lpc::compute_residuals;

/// Maximum supported LPC prediction order.
///
/// The Levinson-Durbin recursion is stable for any order up to the frame length,
/// but beyond 32 the marginal compression gain is negligible on audio signals and
/// the encoder-side per-order coefficient search dominates runtime.
pub const MAX_LPC_ORDER: u8 = 32;

/// Maximum supported partition order. `partition_count = 1 << partition_order`.
///
/// Seven gives a ceiling of 128 partitions per frame. Beyond that, the per-partition
/// `k` parameter header overhead (5 bits × 128 = 80 bytes at the maximum) begins
/// to outrun the per-partition rate adaptation gain on typical audio frames.
pub const MAX_PARTITION_ORDER: u8 = 7;

/// Maximum Rice parameter `k`.
///
/// 24-bit samples produce zigzag-encoded residuals of at most ~25 bits. `k = 23`
/// holds the entire value in the binary remainder with a unary quotient of 0 or 1
/// in the worst case, which is always optimal for that quotient range. Higher `k`
/// values would only add remainder bits without reducing the quotient further.
pub const MAX_RICE_K: u8 = 23;