Files
castle/packages/media/src/qoa.zig

76 lines
1.8 KiB
Zig

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,
});
}
}