flac

    Pure-MoonBit FLAC lossless audio decoder: parses stream metadata and decodes frames to PCM with no native dependencies.

    flac
    audio
    codec
    lossless
    decoder
    pcm
    rice
    lpc
    wav
    metadata
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    14 days ago
    Downloads
    27

    No dependencies

    #flac

    A pure-MoonBit decoder for FLAC (Free Lossless Audio Codec, RFC 9639). No native bindings, no FFI, no runtime dependencies beyond the MoonBit standard library — the whole pipeline compiles to WASM and runs wherever MoonBit does.

    #What it does

    flac turns a FLAC byte stream into interleaved signed PCM samples:

    • Metadata — the fLaC marker and the metadata-block chain, starting with STREAMINFO (sample rate, channels, bit depth, total samples, MD5).
    • Frames — the 14-bit sync code, block-size / sample-rate / channel / sample-size codes, the UTF-8 coded frame number, and the CRC-8 / CRC-16 guards.
    • Subframes — CONSTANT, VERBATIM, FIXED (orders 0–4) and LPC (orders 1–32), with partitioned Rice residuals including escaped partitions and wasted-bits handling.
    • Stereo — left+side, right+side and mid+side decorrelation back into left/right.
    • Tags & artwork — VORBIS_COMMENT, PICTURE, SEEKTABLE, APPLICATION and PADDING blocks decoded into typed values.
    • Output & integrity — WAV (RIFF/WAVE) container output, and an MD5 implementation that verifies a decoded stream against its STREAMINFO signature (@flac.verify).

    #Modules

    FilePurpose
    bitreader.mbtA forward-only, big-endian bit cursor over Bytes.
    crc.mbtCRC-8 and CRC-16 (MSB-first), including the two FLAC polynomials.
    error.mbtThe DecodeError type raised by every parsing entry point.
    streaminfo.mbtThe 34-byte STREAMINFO block.
    metadata.mbtThe fLaC marker and typed metadata-block chain.
    frame.mbtFrame-header parsing with CRC-8 verification.
    residual.mbtPartitioned Rice residual decoding.
    subframe.mbtCONSTANT / VERBATIM / FIXED / LPC subframe decoding.
    decorrelate.mbtStereo channel decorrelation.
    decode.mbtWhole-frame and whole-stream decoding to interleaved PCM.
    wav.mbtRIFF/WAVE container output from decoded PCM.
    md5.mbtMD5 digest (RFC 1321) with hex rendering.

    #Usage

    Decode a whole stream:

    let pcm : @flac.Pcm = @flac.decode(flac_bytes)
    // pcm.sample_rate, pcm.channels, pcm.bits_per_sample, pcm.samples

    pcm.samples is a flat Array[Int] of interleaved samples ([L0, R0, L1, R1, …] for stereo). For finer control, @flac.decode_frame decodes one frame at a time, and the per-module entry points (@flac.parse_metadata, @flac.BitReader::read_frame_header, …) expose each stage individually.

    Wrap the samples in a WAV container:

    let wav_bytes : Bytes = pcm.to_wav()

    Verify a stream end-to-end against the MD5 signature stored in its STREAMINFO block:

    let ok : Bool = @flac.verify(flac_bytes)

    #Demo

    Run the included demo to decode two embedded streams (a mono verbatim one and a mid+side stereo one) and print their properties and first samples:

    moon run cmd/main

    #Tests

    The test suite covers unit vectors for every stage (bit cursor, CRC, STREAMINFO, each metadata block type, subframes, residuals, WAV, MD5) plus end-to-end decoding of real FLAC files produced by the reference libsndfile/libFLAC encoder — decoded sample-by-sample against the encoder's own output and verified against the STREAMINFO MD5 signature.

    #License

    Apache-2.0

    DecodeError

    pub(all) suberror DecodeError {
    Truncated
    InvalidBitCount
    NotFlac
    InvalidMetadata
    InvalidFrameSync
    InvalidFrameHeader
    HeaderCrcMismatch
    FrameCrcMismatch
    InvalidResidual
    InvalidSubframe
    Unsupported(String)
    }

    Errors raised while reading or decoding a FLAC stream.

    Every parsing function in this package raises DecodeError; callers can either let it propagate or convert it to a Result with try ... catch { e => Err(e) }.

    BitReader

    pub struct BitReader {
    data : Bytes
    pos : Int
    }

    A forward-only bit reader over a byte buffer, reading bits most-significant first — the big-endian convention used throughout FLAC.

    The reader is immutable: every read returns the value together with the advanced reader. A BitReader value is therefore always a pure cursor into the source buffer, and the buffer itself is never copied.

    BitReader::align

    fn BitReader::align(self : BitReader) -> BitReader
    Advance to the next byte boundary (a no-op when already aligned).

    BitReader::data

    fn BitReader::data(self : BitReader) -> Bytes
    The underlying source buffer (for CRC checks over byte ranges).

    BitReader::has

    fn BitReader::has(self : BitReader, n : Int) -> Bool
    True when at least n more bits are available.

    BitReader::new

    fn BitReader::new(data : Bytes) -> BitReader
    Wrap a byte buffer for bit reading.

    BitReader::pos

    fn BitReader::pos(self : BitReader) -> Int
    The current bit position, counted from the first bit of the buffer.

    BitReader::read_bit

    fn BitReader::read_bit(self : BitReader) -> (Int, BitReader) raise DecodeError
    Read a single bit as 0 or 1.

    BitReader::read_bits

    fn BitReader::read_bits(self : BitReader, n : Int) -> (Int, BitReader) raise DecodeError
    Read n bits (0 <= n <= 32) as an unsigned integer, MSB first.

    For n == 32 the raw pattern is returned as an Int, which may be negative when the top bit is set; use read_u32 for an unsigned view.

    BitReader::read_byte

    fn BitReader::read_byte(self : BitReader) -> (Int, BitReader) raise DecodeError
    Read one whole byte (8 bits) as an unsigned integer.

    BitReader::read_frame_header

    fn BitReader::read_frame_header(self : BitReader, info : StreamInfo) -> (FrameHeader, BitReader) raise DecodeError
    Parse a frame header from a byte-aligned reader, verify its CRC-8, and return the decoded header plus the reader positioned at the first subframe.

    BitReader::read_residual

    fn BitReader::read_residual(self : BitReader, block_size : Int, predictor_order : Int) -> (Array[Int], BitReader) raise DecodeError
    Decode a complete residual block of block_size - predictor_order samples.

    The reader must be positioned at the 2-bit coding method. On success the returned reader is positioned just past the last residual sample.

    BitReader::read_signed

    fn BitReader::read_signed(self : BitReader, n : Int) -> (Int, BitReader) raise DecodeError
    Read n bits as a signed two's-complement integer (sign-extended to 32 bits).

    BitReader::read_streaminfo

    fn BitReader::read_streaminfo(self : BitReader) -> (StreamInfo, BitReader) raise DecodeError
    Parse a 34-byte STREAMINFO block from a byte-aligned reader, returning the decoded info and the reader advanced past the block.

    BitReader::read_subframe

    fn BitReader::read_subframe(self : BitReader, block_size : Int, bits_per_sample : Int) -> (Array[Int], BitReader) raise DecodeError
    Decode one subframe of block_size samples at bits_per_sample bits per sample, returning the signed samples and the reader past the subframe.

    BitReader::read_u16

    fn BitReader::read_u16(self : BitReader) -> (Int, BitReader) raise DecodeError
    Read a big-endian unsigned 16-bit integer.

    BitReader::read_u24

    fn BitReader::read_u24(self : BitReader) -> (Int, BitReader) raise DecodeError
    Read a big-endian unsigned 24-bit integer.

    BitReader::read_u32

    fn BitReader::read_u32(self : BitReader) -> (UInt, BitReader) raise DecodeError
    Read a big-endian unsigned 32-bit integer as a UInt.

    BitReader::read_unary

    fn BitReader::read_unary(self : BitReader) -> (Int, BitReader) raise DecodeError
    Read a unary-coded non-negative integer: the number of 0 bits terminated by a single 1 bit.

    BitReader::read_utf8_number

    fn BitReader::read_utf8_number(self : BitReader) -> (Int64, BitReader) raise DecodeError
    Read a FLAC-style UTF-8 coded non-negative integer (7 to 36 bits). The number of leading 1 bits in the first byte gives the total byte count.

    BitReader::remaining

    fn BitReader::remaining(self : BitReader) -> Int
    The number of bits still available after the current position.

    BitReader::skip

    fn BitReader::skip(self : BitReader, n : Int) -> BitReader raise DecodeError
    Skip n bits without reading them.

    DecodedFrame

    pub struct DecodedFrame {
    header : FrameHeader
    channels : Array[Array[Int]]
    }
    One decoded audio frame: its header plus the per-channel samples in standard channel order (left, right, ...).

    FlacHeader

    pub struct FlacHeader {
    info : StreamInfo
    frame_offset : Int
    }
    The result of parsing a stream's metadata: the STREAMINFO block plus the byte offset where the first audio frame begins.

    FrameHeader

    pub struct FrameHeader {
    blocking_strategy : Int
    block_size : Int
    sample_rate : Int
    channels : Int
    channel_assignment : Int
    bits_per_sample : Int
    frame_number : Int64
    }
    Decoded frame header. frame_number is the frame number for fixed-size blocks and the starting sample number for variable-size blocks.

    FrameHeader::new

    fn FrameHeader::new(blocking_strategy : Int, block_size : Int, sample_rate : Int, channels : Int, channel_assignment : Int, bits_per_sample : Int, frame_number : Int64) -> FrameHeader
    Build a FrameHeader from already-decoded fields.

    FrameHeader::starting_sample

    fn FrameHeader::starting_sample(self : FrameHeader) -> Int64
    The absolute sample index at which this frame's audio starts.

    MetadataBlock

    pub enum MetadataBlock {
    StreamInfo(StreamInfo)
    Padding(Int)
    Application(Int, Bytes)
    SeekTable(Array[SeekPoint])
    VorbisComment(VorbisComment)
    Picture(Picture)
    Other(Int, Bytes)
    }
    One decoded metadata block, tagged by type.

    Pcm

    pub struct Pcm {
    sample_rate : Int
    channels : Int
    bits_per_sample : Int
    samples : Array[Int]
    }
    A decoded PCM stream: the stream properties plus interleaved samples ([L0, R0, L1, R1, ...]).

    Pcm::new

    fn Pcm::new(sample_rate : Int, channels : Int, bits_per_sample : Int, samples : Array[Int]) -> Pcm
    Build a Pcm value from its decoded fields.

    Pcm::to_data

    fn Pcm::to_data(self : Pcm) -> Bytes
    The raw interleaved PCM payload, in the byte layout FLAC defines for its STREAMINFO MD5 signature (identical to a WAV data chunk).

    Pcm::to_wav

    fn Pcm::to_wav(self : Pcm) -> Bytes
    Encode decoded PCM as a standard PCM WAV. 16/24/32-bit samples are stored as little-endian two's complement; 8-bit samples use the WAV unsigned convention (a +128 offset).

    Picture

    pub struct Picture {
    picture_type : Int
    mime_type : String
    description : String
    width : Int
    height : Int
    depth : Int
    colors : Int
    data : Bytes
    }
    A PICTURE block: the embedded cover art and its dimensions.

    SeekPoint

    pub struct SeekPoint {
    sample_number : Int64
    offset : Int64
    frame_samples : Int
    }
    One entry of a SEEKTABLE block: a sample number, the byte offset of the frame that starts there, and that frame's sample count.

    StreamInfo

    pub struct StreamInfo {
    min_block_size : Int
    max_block_size : Int
    min_frame_size : Int
    max_frame_size : Int
    sample_rate : Int
    channels : Int
    bits_per_sample : Int
    total_samples : Int64
    md5 : Array[Byte]
    }
    Decoded STREAMINFO fields. All sizes are in bytes, the sample rate in Hz, channels is 1..=8 and bits_per_sample the per-channel PCM bit depth.

    StreamInfo::md5_hex

    fn StreamInfo::md5_hex(self : StreamInfo) -> String
    The MD5 signature stored in a STREAMINFO block, as hex. Pair it with md5_hex over the decoded samples to verify a stream's integrity.

    StreamInfo::new

    fn StreamInfo::new(min_block_size : Int, max_block_size : Int, min_frame_size : Int, max_frame_size : Int, sample_rate : Int, channels : Int, bits_per_sample : Int, total_samples : Int64, md5 : Array[Byte]) -> StreamInfo
    Build a StreamInfo value from already-decoded fields.

    VorbisComment

    pub struct VorbisComment {
    vendor : String
    comments : Array[String]
    }
    A VORBIS_COMMENT block: a vendor string plus the list of KEY=value comment strings.

    crc16

    fn crc16(data : Bytes, poly : Int, init : Int) -> Int
    CRC-16 over a whole buffer with the given polynomial and initial value.

    crc16_flac

    fn crc16_flac(data : Bytes) -> Int
    FLAC frame CRC-16 (polynomial x^16 + x^15 + x^2 + 1 = 0x8005, init 0).

    crc16_range

    fn crc16_range(data : Bytes, from : Int, to : Int, poly : Int, init : Int) -> Int
    CRC-16 over the byte range [from, to) with the given polynomial and initial value, processing bits most-significant first.

    crc8

    fn crc8(data : Bytes, poly : Int, init : Int) -> Int
    CRC-8 over a whole buffer with the given polynomial and initial value.

    crc8_flac

    fn crc8_flac(data : Bytes) -> Int
    FLAC frame-header CRC-8 (polynomial x^8 + x^2 + x + 1 = 0x07, init 0).

    crc8_range

    fn crc8_range(data : Bytes, from : Int, to : Int, poly : Int, init : Int) -> Int
    CRC-8 over the byte range [from, to) with the given polynomial and initial value, processing bits most-significant first.

    decode

    fn decode(data : Bytes) -> Pcm raise DecodeError
    Decode a whole FLAC stream into interleaved PCM, stopping at the end of the audio (or at the first non-frame byte, e.g. trailing tags).

    decode_frame

    fn decode_frame(data : Bytes, offset : Int, info : StreamInfo) -> (DecodedFrame, Int) raise DecodeError
    Decode the frame starting at byte offset of data, returning the frame and the byte offset of the next frame (just past the CRC-16).

    decorrelate

    fn decorrelate(channels : Array[Array[Int]], assignment : Int) -> Array[Array[Int]]
    Convert decoded planar channels into standard channel order.

    For the independent channel assignments (0..=7) the input is returned unchanged. For the stereo assignments 8 (left+side), 9 (right+side) and 10 (mid+side) the two decoded channels are combined into [left, right].

    hex

    fn hex(data : Bytes) -> String
    Render bytes as a lowercase hexadecimal string.

    md5

    fn md5(data : Bytes) -> Bytes
    Compute the 16-byte MD5 digest of data.

    md5_hex

    fn md5_hex(data : Bytes) -> String
    The MD5 digest of data as a 32-character lowercase hex string.

    parse_metadata

    fn parse_metadata(data : Bytes) -> FlacHeader raise DecodeError
    Parse the fLaC marker and metadata blocks, returning the decoded STREAMINFO and the offset of the first audio frame.

    The first block must be a STREAMINFO; other blocks are decoded and, if they are not a STREAMINFO, ignored here (use parse_metadata_blocks to keep them).

    parse_metadata_blocks

    fn parse_metadata_blocks(data : Bytes) -> Array[MetadataBlock] raise DecodeError
    Decode every metadata block in the stream.

    parse_streaminfo

    fn parse_streaminfo(data : Bytes) -> StreamInfo raise DecodeError
    Parse a standalone 34-byte STREAMINFO block.

    verify

    fn verify(data : Bytes) -> Bool raise DecodeError
    Decode a whole FLAC stream and check the result against the MD5 signature stored in its STREAMINFO block. The signature covers the raw interleaved samples (see Pcm::to_data), so a match means the decoded PCM is byte-for-byte what the encoder produced.