76 lines
1.8 KiB
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,
|
|
});
|
|
}
|
|
}
|