Something went wrong. Try again.
Plays short audio files such as a terminal bell through PipeWire on Linux and WASAPI on Windows.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! FLAC, native and in Ogg. FLAC's own channel order for up to eight//! channels is the WAVE order (RFC 9639, Section 9.1.3), so nothing is moved.
const std = @import("std");const Allocator = std.mem.Allocator;const flac = @import("flac");
const decode_mod = @import("../decode.zig");const Builder = decode_mod.Builder;const Error = decode_mod.Error;
pub fn decode(gpa: Allocator, bytes: []const u8, in_ogg: bool, out: *Builder) Error!void { var input: std.Io.Reader = .fixed(bytes); // The MD5 is not checked: a Sound is for hearing, and a file that decodes // is worth playing whether or not its checksum agrees. const options: flac.DecodeOptions = .{ .verify_md5 = false }; if (in_ogg) { const buffer = try gpa.create([flac.ogg.page_buffer_size]u8); defer gpa.destroy(buffer); var d = flac.ogg.Decoder.init(gpa, &input, buffer, .{ .flac = options }) catch |err| return mapError(err); defer d.deinit(); try drain(&d, d.flac.streaminfo, out); } else { var d = flac.Decoder.init(gpa, &input, options) catch |err| return mapError(err); defer d.deinit(); try drain(&d, d.streaminfo, out); }}
fn drain(d: anytype, info: flac.StreamInfo, out: *Builder) Error!void { try out.begin(info.sample_rate, info.channels); if (info.bits_per_sample == 0 or info.bits_per_sample > 32) return error.InvalidData; const scale = 1.0 / @as(f32, @floatFromInt(@as(u64, 1) << @intCast(info.bits_per_sample - 1))); while (d.next() catch |err| return mapError(err)) |frame| { if (frame.channels.len != info.channels) return error.InvalidData; const n = frame.header.block_size; const samples = try out.addFrames(n); for (frame.channels, 0..) |channel, c| { for (channel[0..n], 0..) |s, i| samples[i * info.channels + c] = @as(f32, @floatFromInt(s)) * scale; } }}
fn mapError(err: anyerror) Error { return switch (err) { error.OutOfMemory => error.OutOfMemory, else => error.InvalidData, };}
test "native FLAC" { try decode_mod.expectTone(@embedFile("../tests/data/tone-stereo-44100.flac"), 44100, 2, 4410);}
test "Ogg FLAC" { try decode_mod.expectTone(@embedFile("../tests/data/tone-mono-44100.oga"), 44100, 1, 4410);}
test "a FLAC stream cut off inside its streaminfo block is invalid" { const bytes = @embedFile("../tests/data/tone-stereo-44100.flac"); try std.testing.expectError(error.InvalidData, decode_mod.decode(std.testing.allocator, bytes[0..20], 30_000));}