107 lines
3.3 KiB
Zig
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)];
|
|
}
|