const std = @import("std"); const assert = std.debug.assert; const log = std.log.scoped(.stb_vorbis); const c = @cImport({ @cDefine("STB_VORBIS_NO_INTEGER_CONVERSION", {}); @cDefine("STB_VORBIS_NO_STDIO", {}); @cDefine("STB_VORBIS_HEADER_ONLY", {}); @cInclude("stb_vorbis.c"); }); const STBVorbis = @This(); pub const Error = error { NeedMoreData, InvalidApiMixing, OutOfMemory, FeatureNotSupported, TooManyChannels, FileOpenFailure, SeekWithoutLength, UnexpectedEof, SeekInvalid, InvalidSetup, InvalidStream, MissingCapturePattern, InvalidStreamStructureVersion, ContinuedPacketFlagInvalid, IncorrectStreamSerialNumber, InvalidFirstPage, BadPacketType, CantFindLastPage, SeekFailed, OggSkeletonNotSupported, Unknown }; fn errorFromInt(err: c_int) ?Error { return switch (err) { c.VORBIS__no_error => null, c.VORBIS_need_more_data => Error.NeedMoreData, c.VORBIS_invalid_api_mixing => Error.InvalidApiMixing, c.VORBIS_outofmem => Error.OutOfMemory, c.VORBIS_feature_not_supported => Error.FeatureNotSupported, c.VORBIS_too_many_channels => Error.TooManyChannels, c.VORBIS_file_open_failure => Error.FileOpenFailure, c.VORBIS_seek_without_length => Error.SeekWithoutLength, c.VORBIS_unexpected_eof => Error.UnexpectedEof, c.VORBIS_seek_invalid => Error.SeekInvalid, c.VORBIS_invalid_setup => Error.InvalidSetup, c.VORBIS_invalid_stream => Error.InvalidStream, c.VORBIS_missing_capture_pattern => Error.MissingCapturePattern, c.VORBIS_invalid_stream_structure_version => Error.InvalidStreamStructureVersion, c.VORBIS_continued_packet_flag_invalid => Error.ContinuedPacketFlagInvalid, c.VORBIS_incorrect_stream_serial_number => Error.IncorrectStreamSerialNumber, c.VORBIS_invalid_first_page => Error.InvalidFirstPage, c.VORBIS_bad_packet_type => Error.BadPacketType, c.VORBIS_cant_find_last_page => Error.CantFindLastPage, c.VORBIS_seek_failed => Error.SeekFailed, c.VORBIS_ogg_skeleton_not_supported => Error.OggSkeletonNotSupported, else => Error.Unknown }; } handle: *c.stb_vorbis, pub fn init(data: []const u8, alloc_buffer: []u8) Error!STBVorbis { const stb_vorbis_alloc: c.stb_vorbis_alloc = .{ .alloc_buffer = alloc_buffer.ptr, .alloc_buffer_length_in_bytes = @intCast(alloc_buffer.len) }; var error_code: c_int = -1; const handle = c.stb_vorbis_open_memory( data.ptr, @intCast(data.len), &error_code, &stb_vorbis_alloc ); if (handle == null) { return errorFromInt(error_code) orelse Error.Unknown; } return STBVorbis{ .handle = handle.? }; } pub fn getMinimumAllocBufferSize(self: STBVorbis) u32 { const info = self.getInfo(); return info.setup_memory_required + @max(info.setup_temp_memory_required, info.temp_memory_required); } fn getLastError(self: STBVorbis) ?Error { const error_code = c.stb_vorbis_get_error(self.handle); return errorFromInt(error_code); } pub fn seek(self: STBVorbis, sample_number: u32) !void { const success = c.stb_vorbis_seek(self.handle, sample_number); if (success != 1) { return self.getLastError() orelse Error.Unknown; } } pub fn getStreamLengthInSamples(self: STBVorbis) u32 { return c.stb_vorbis_stream_length_in_samples(self.handle); } pub fn getStreamLengthInSeconds(self: STBVorbis) f32 { return c.stb_vorbis_stream_length_in_seconds(self.handle); } pub fn getSamples( self: STBVorbis, channels: []const [*]f32, max_samples_per_channel: u32 ) u32 { const samples_per_channel = c.stb_vorbis_get_samples_float( self.handle, @intCast(channels.len), @constCast(@ptrCast(channels.ptr)), @intCast(max_samples_per_channel) ); return @intCast(samples_per_channel); } const Frame = struct { channels: []const [*c]const f32, samples_per_channel: u32 }; pub fn getFrame(self: STBVorbis) Frame { var output: [*c][*c]f32 = null; var channels: c_int = undefined; const samples_per_channel = c.stb_vorbis_get_frame_float( self.handle, &channels, &output ); return Frame{ .channels = output[0..@intCast(channels)], .samples_per_channel = @intCast(samples_per_channel) }; } pub const Info = struct { sample_rate: u32, channels: u32, setup_memory_required: u32, setup_temp_memory_required: u32, temp_memory_required: u32, max_frame_size: u32 }; pub fn getInfo(self: STBVorbis) Info { const info = c.stb_vorbis_get_info(self.handle); return Info{ .sample_rate = info.sample_rate, .channels = @intCast(info.channels), .setup_memory_required = info.setup_memory_required, .setup_temp_memory_required = info.setup_temp_memory_required, .temp_memory_required = info.temp_memory_required, .max_frame_size = @intCast(info.max_frame_size), }; }