game-2026-01-30/src/engine/audio/store.zig
2026-01-30 23:28:18 +02:00

107 lines
3.3 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const Math = @import("../math.zig");
const STBVorbis = @import("stb_vorbis");
const AudioData = @import("./data.zig").Data;
const Store = @This();
arena: std.heap.ArenaAllocator,
list: std.ArrayList(AudioData),
temp_vorbis_alloc_buffer: []u8,
const Options = struct {
allocator: std.mem.Allocator,
max_vorbis_alloc_buffer_size: u32,
};
pub fn init(opts: Options) !Store {
const gpa = opts.allocator;
const temp_vorbis_alloc_buffer = try gpa.alloc(u8, opts.max_vorbis_alloc_buffer_size);
errdefer gpa.free(temp_vorbis_alloc_buffer);
return Store{
.arena = std.heap.ArenaAllocator.init(gpa),
.list = .empty,
.temp_vorbis_alloc_buffer = temp_vorbis_alloc_buffer
};
}
pub fn deinit(self: *Store) void {
const gpa = self.arena.child_allocator;
gpa.free(self.temp_vorbis_alloc_buffer);
self.list.deinit(gpa);
self.arena.deinit();
}
pub const LoadOptions = struct {
const PlaybackStyle = enum {
stream,
decode_once,
// If the decoded size is less than `stream_threshold`, then .decode_once will by default be used.
const stream_threshold = 10 * Math.bytes_per_mib;
};
const Format = enum {
vorbis
};
format: Format,
data: []const u8,
playback_style: ?PlaybackStyle = null,
};
pub fn load(self: *Store, opts: LoadOptions) !AudioData.Id {
const gpa = self.arena.child_allocator;
const id = self.list.items.len;
try self.list.ensureUnusedCapacity(gpa, 1);
const PlaybackStyle = LoadOptions.PlaybackStyle;
const temp_stb_vorbis = try STBVorbis.init(opts.data, self.temp_vorbis_alloc_buffer);
const info = temp_stb_vorbis.getInfo();
const duration_in_samples = temp_stb_vorbis.getStreamLengthInSamples();
const decoded_size = info.channels * duration_in_samples * @sizeOf(f32);
const stream_threshold = PlaybackStyle.stream_threshold;
const default_playback_style: PlaybackStyle = if (decoded_size < stream_threshold) .decode_once else .stream;
const arena_allocator = self.arena.allocator();
const playback_style = opts.playback_style orelse default_playback_style;
if (playback_style == .decode_once) {
const channels = try arena_allocator.alloc([*]f32, info.channels);
for (channels) |*channel| {
channel.* = (try arena_allocator.alloc(f32, duration_in_samples)).ptr;
}
const samples_decoded = temp_stb_vorbis.getSamples(channels, duration_in_samples);
assert(samples_decoded == duration_in_samples);
self.list.appendAssumeCapacity(AudioData{
.raw = .{
.channels = channels,
.sample_count = duration_in_samples,
.sample_rate = info.sample_rate
}
});
} else {
const alloc_buffer = try arena_allocator.alloc(u8, temp_stb_vorbis.getMinimumAllocBufferSize());
const stb_vorbis = STBVorbis.init(opts.data, alloc_buffer) catch unreachable;
self.list.appendAssumeCapacity(AudioData{
.vorbis = .{
.alloc_buffer = alloc_buffer,
.stb_vorbis = stb_vorbis
}
});
}
return @enumFromInt(id);
}
pub fn get(self: Store, id: AudioData.Id) AudioData {
return self.list.items[@intFromEnum(id)];
}