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