Something went wrong. Try again.
A FLAC encoder and decoder for Zig, written against RFC 9639, for native FLAC streams and FLAC in Ogg
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! What the format allows, and the pieces of it that both the frame header and//! the subframes need to name.
const std = @import("std");
const bits = @import("bits.zig");
/// The four bytes every FLAC stream starts with (RFC 9639, Section 6).pub const signature = "fLaC";
/// The bounds the format itself imposes. A decoder holds an input to these;/// an encoder holds itself to them and to the streamable subset besides.pub const limits = struct { /// Section 8.2: the streaminfo block stores block sizes in 16 bits and /// forbids anything under 16 samples. pub const min_block_size = 16; pub const max_block_size = 65535;
/// Section 8.2, and the three channel bits of a frame header. pub const min_channels = 1; pub const max_channels = 8;
/// Section 8.2. The frame header can only name a subset of these. pub const min_bits_per_sample = 4; pub const max_bits_per_sample = 32;
/// Section 8.2: a 20-bit field, so no more. pub const max_sample_rate = (1 << 20) - 1;
/// Section 8.2: a 36-bit field. pub const max_total_samples = (1 << 36) - 1;
/// Section 9.2.5 and Section 9.2.6. pub const max_fixed_order = 4; pub const max_lpc_order = 32;
/// Section 9.2.6: the precision is stored as four bits holding one less /// than it, and all ones is forbidden. pub const min_qlp_precision = 1; pub const max_qlp_precision = 15;
/// Section 9.2.6: the prediction right shift is five bits, signed two's /// complement, and MUST NOT be negative -- so 15 is the largest there is, /// not 31 (Appendix B.4). pub const max_lpc_shift = 15;
/// Section 9.2.7: four bits of partition order. pub const max_partition_order = 15;
/// Section 8.1: a metadata block's length is a 24-bit field. pub const max_metadata_length = (1 << 24) - 1;
/// Section 7. Everything above holds for any stream; these two hold only /// for the streamable subset. pub const subset_max_block_size = 16384; pub const subset_max_block_size_48k = 4608; pub const subset_max_lpc_order_48k = 12; pub const subset_max_partition_order = 8;};
/// How the subframes of a frame map onto its channels (RFC 9639,/// Section 9.1.3). Two-channel audio has three further options, each of which/// codes one channel as the difference of the two -- the side channel, which/// needs one bit more depth than the frame declares (Section 4.2).pub const ChannelAssignment = union(enum) { /// One subframe per channel, each holding that channel's samples. independent: u4, /// Left in the first subframe, left minus right in the second. left_side, /// Left minus right in the first subframe, right in the second. right_side, /// (Left + right) >> 1 in the first subframe, left minus right in the /// second. mid_side,
pub fn fromBits(v: u4) error{ReservedChannelAssignment}!ChannelAssignment { return switch (v) { 0b0000...0b0111 => .{ .independent = v + 1 }, 0b1000 => .left_side, 0b1001 => .right_side, 0b1010 => .mid_side, else => error.ReservedChannelAssignment, }; }
pub fn toBits(a: ChannelAssignment) u4 { return switch (a) { .independent => |n| @intCast(n - 1), .left_side => 0b1000, .right_side => 0b1001, .mid_side => 0b1010, }; }
pub fn channelCount(a: ChannelAssignment) u4 { return switch (a) { .independent => |n| n, else => 2, }; }
/// Which of a frame's subframes carry a side channel, and so are coded one /// bit deeper than the frame header says. pub fn isSideChannel(a: ChannelAssignment, channel: usize) bool { return switch (a) { .independent => false, .left_side, .mid_side => channel == 1, .right_side => channel == 0, }; }};
/// What a subframe's six type bits say it is (RFC 9639, Section 9.2.1).pub const SubframeType = union(enum) { /// One sample, repeated for the whole block. constant, /// Every sample, unencoded. verbatim, /// One of the five predictors the format defines, order 0 to 4. fixed: u3, /// A predictor the subframe carries with it, order 1 to 32. lpc: u6,
pub fn fromBits(v: u6) error{ReservedSubframeType}!SubframeType { return switch (v) { 0b000000 => .constant, 0b000001 => .verbatim, 0b001000...0b001100 => .{ .fixed = @intCast(v - 8) }, 0b100000...0b111111 => .{ .lpc = @intCast(v - 31) }, else => error.ReservedSubframeType, }; }
pub fn toBits(t: SubframeType) u6 { return switch (t) { .constant => 0b000000, .verbatim => 0b000001, .fixed => |order| @as(u6, order) + 8, .lpc => |order| @as(u6, order) + 31, }; }
/// How many warm-up samples the subframe stores unencoded ahead of its /// residual, which is one per order of the predictor. pub fn predictorOrder(t: SubframeType) u6 { return switch (t) { .constant, .verbatim => 0, .fixed => |order| order, .lpc => |order| order, }; }};
/// The frame or sample number in a frame header, which is stored in the/// variable-length code of UTF-8 extended to 36 bits and seven octets/// (RFC 9639, Section 9.1.5). It counts frames or samples, not characters, so/// a general-purpose UTF-8 codec is no use for it.pub const coded_number = struct { pub const max = (1 << 36) - 1;
pub const Error = error{MalformedCodedNumber};
/// The octet count a value needs, which is the shortest form that holds /// it. Length 1 covers 7 bits and each further octet adds 5 then 6. pub fn length(value: u64) error{CodedNumberTooLarge}!u3 { return if (value < 0x80) 1 else if (value < 0x800) 2 else if (value < 0x10000) 3 else if (value < 0x200000) 4 else if (value < 0x4000000) 5 else if (value < 0x80000000) 6 else if (value <= max) 7 else error.CodedNumberTooLarge; }
pub fn write(w: *bits.BitWriter, value: u64) (bits.BitWriter.Error || error{CodedNumberTooLarge})!void { const n = try length(value); if (n == 1) { try w.put(8, value); return; } // A lead octet of n ones, a zero, and what room is left; then n-1 // continuation octets of 0b10 and six bits each, most significant // first. const payload_bits: u7 = @as(u7, n - 1) * 6; const lead_bits: u7 = 7 - n; try w.put(n, (@as(u64, 1) << @intCast(n)) - 1); try w.put(1, 0); try w.put(lead_bits, value >> @intCast(payload_bits)); var shift: u7 = payload_bits; while (shift > 0) { shift -= 6; try w.put(2, 0b10); try w.put(6, value >> @intCast(shift)); } }
pub fn read(r: *bits.BitReader) (bits.BitReader.Error || Error)!u64 { const lead = try r.take(8); if (lead < 0x80) return lead; // Between one and seven leading ones; a lone continuation octet and an // octet of all ones are both outside the table. const ones: u4 = @intCast(@clz(~@as(u8, @intCast(lead)))); if (ones < 2 or ones > 7) return error.MalformedCodedNumber; const lead_bits: u4 = @intCast(8 - ones - 1); var value: u64 = lead & ((@as(u64, 1) << lead_bits) - 1); for (1..ones) |_| { const cont = try r.take(8); if (cont & 0xc0 != 0x80) return error.MalformedCodedNumber; value = (value << 6) | (cont & 0x3f); } return value; }};
const testing = std.testing;
test "the coded number for 51 billion samples" { // RFC 9639, Section 9.1.5 spells this one out octet by octet. const value: u64 = 51_000_000_000; const expected = [_]u8{ 0xfe, 0xaf, 0x9f, 0xb5, 0xa3, 0xb8, 0x80 };
var buffer: [8]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try coded_number.write(&w, value); try testing.expect(w.isByteAligned()); try testing.expectEqualSlices(u8, &expected, buffer[0..7]);
var input: std.Io.Reader = .fixed(&expected); var r: bits.BitReader = .init(&input); try testing.expectEqual(value, try coded_number.read(&r));}
test "coded numbers round-trip at every length boundary" { const boundaries = [_]u64{ 0, 0x7f, 0x80, 0x7ff, 0x800, 0xffff, 0x10000, 0x1fffff, 0x200000, 0x3ffffff, 0x4000000, 0x7fffffff, 0x80000000, 0xfffffffff, 1, 2, 127, 51_000_000_000, }; for (boundaries) |value| { var buffer: [8]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try coded_number.write(&w, value); try testing.expect(w.isByteAligned()); try testing.expectEqual(@as(u64, try coded_number.length(value)), w.pos);
var input: std.Io.Reader = .fixed(buffer[0..@intCast(w.pos)]); var r: bits.BitReader = .init(&input); try testing.expectEqual(value, try coded_number.read(&r)); }}
test "coded numbers reject octets outside the table" { for ([_]u8{ 0x80, 0xbf, 0xff }) |lead| { var input: std.Io.Reader = .fixed(&.{ lead, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }); var r: bits.BitReader = .init(&input); try testing.expectError(error.MalformedCodedNumber, coded_number.read(&r)); }
// A lead octet promising a continuation that is not one. var input: std.Io.Reader = .fixed(&.{ 0xc2, 0x41 }); var r: bits.BitReader = .init(&input); try testing.expectError(error.MalformedCodedNumber, coded_number.read(&r));}
test "channel assignments and subframe types round-trip" { for (0..0b1011) |v| { const a = try ChannelAssignment.fromBits(@intCast(v)); try testing.expectEqual(@as(u4, @intCast(v)), a.toBits()); } for (0b1011..0b10000) |v| { try testing.expectError(error.ReservedChannelAssignment, ChannelAssignment.fromBits(@intCast(v))); }
for (0..0b1000000) |v| { const t = SubframeType.fromBits(@intCast(v)) catch continue; try testing.expectEqual(@as(u6, @intCast(v)), t.toBits()); } try testing.expectEqual(SubframeType{ .fixed = 4 }, try SubframeType.fromBits(0b001100)); try testing.expectEqual(SubframeType{ .lpc = 1 }, try SubframeType.fromBits(0b100000)); try testing.expectEqual(SubframeType{ .lpc = 32 }, try SubframeType.fromBits(0b111111));}