const std = @import("std"); const Format = @import("Format.zig"); const format: Format = .{ .magic_length = magic.len, // 4B - sample count // 1B - channels // 3B - sample rate .info_length = magic.len + 8, .extension = "qoa", .media_type = "audio/qoa", .isFormat = isQoa, }; const magic = "qoaf"; const Header = union(enum) { streaming: HeaderStreaming, static: HeaderStatic, pub fn initStatic(static: HeaderStatic) Header { return .{ .static = static }; } }; const HeaderStreaming = void; const HeaderStatic = struct { samples: u32, channels: u8, sample_rate: u24, pub fn lengthInSeconds(self: HeaderStatic) f32 { const samples: f64 = @floatFromInt(self.samples); const sample_rate: f64 = @floatFromInt(self.sample_rate); return @floatCast(samples / sample_rate); } }; /// The caller asserts that the buffer is at least `format.magic_length` bytes /// long. pub fn isQoa(buffer: []const u8) bool { return std.mem.eql(u8, buffer[0..format.magic_length], magic); } /// The caller asserts that the buffer is at least `format.info_length` bytes /// long. pub fn info(buffer: []const u8) ?Header { std.debug.assert(buffer.len >= format.info_length); if (!isQoa(buffer)) { return null; } const samples = std.mem.readInt(u32, buffer[4..8], .big); const channels = buffer[8]; const sample_rate = std.mem.readInt(u24, buffer[9..12], .big); if (channels == 0 or channels > 8 or sample_rate == 0) { return null; } if (samples == 0) { return .streaming; } else { return .initStatic(.{ .samples = samples, .channels = channels, .sample_rate = sample_rate, }); } }