diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/al.zig | 182 | ||||
-rw-r--r-- | src/alc.zig | 2 | ||||
-rw-r--r-- | src/main.zig | 76 | ||||
-rw-r--r-- | src/sf.zig | 94 |
4 files changed, 302 insertions, 52 deletions
diff --git a/src/al.zig b/src/al.zig index bfb93b6..7b2e6db 100644 --- a/src/al.zig +++ b/src/al.zig @@ -33,12 +33,16 @@ pub const Error = error { OutOfMemory, }; +pub const BUFFER = AL_BUFFER; pub const POSITION = AL_POSITION; pub const ORIENTATION = AL_ORIENTATION; +pub const PLAYING = AL_PLAYING; +pub const SEC_OFFSET = AL_SEC_OFFSET; +pub const SOURCE_STATE = AL_SOURCE_STATE; pub const listener = struct { /// Set a property for the listener. - pub fn set(param: ALenum, value: anytype) !void { + pub fn set(param: ALenum, value: anytype) Error!void { const T = @TypeOf(value); switch (T) { f32 => alListenerf(param, value), @@ -60,42 +64,154 @@ pub const listener = struct { } }; -/// Generate one buffer for audio data and return its reference. -pub fn genBuffer() !u32 { - var reference: u32 = undefined; - alGenBuffers(1, &reference); - return switch (alGetError()) { - AL_NO_ERROR => reference, - AL_INVALID_VALUE => Error.InvalidValue, - AL_OUT_OF_MEMORY => Error.OutOfMemory, - else => unreachable, - }; -} - -// TODO: genBuffers - -/// Free resources used by one buffer. -/// Buffers attached to a source cannot be deleted. -pub fn deleteBuffer(reference: *const u32) !void { - alDeleteBuffers(1, reference); - switch (alGetError()) { - AL_NO_ERROR => {}, - AL_INVALID_OPERATION => return Error.InvalidOperation, - AL_INVALID_NAME => return Error.InvalidName, - AL_INVALID_VALUE => return Error.InvalidValue, - else => unreachable, +pub const Data = union(enum) { + /// Unsigned 8-bit mono. + mono8: []const u8, + /// Signed 16-bit mono. + mono16: []const i16, + /// Unsigned 8-bit stereo. + stereo8: []const u8, + /// Signed 16-bit stereo. + stereo16: []const i16, +}; + +pub const buffer = struct { + /// Generate one buffer for audio data and return its reference. + pub fn create() Error!u32 { + var reference: u32 = undefined; + alGenBuffers(1, &reference); + return switch (alGetError()) { + AL_NO_ERROR => reference, + AL_INVALID_VALUE => Error.InvalidValue, + AL_OUT_OF_MEMORY => Error.OutOfMemory, + else => unreachable, + }; + } + + /// Free resources used by one buffer. + /// Buffers attached to a source cannot be deleted. + pub fn destroy(reference: *const u32) Error!void { + alDeleteBuffers(1, reference); + switch (alGetError()) { + AL_NO_ERROR => {}, + AL_INVALID_OPERATION => return Error.InvalidOperation, + AL_INVALID_NAME => return Error.InvalidName, + AL_INVALID_VALUE => return Error.InvalidValue, + else => unreachable, + } + } + + /// Fill a buffer with audio data. + pub fn fill(reference: u32, data: Data, freq: i32) Error!void { + const format = switch (data) { + .mono8 => AL_FORMAT_MONO8, + .mono16 => AL_FORMAT_MONO16, + .stereo8 => AL_FORMAT_STEREO8, + .stereo16 => AL_FORMAT_STEREO16, + }; + + switch (data) { + .mono8, .stereo8 => |slice| { + const size = @intCast(c_int, slice.len); + alBufferData(reference, format, slice.ptr, size, freq); + }, + .mono16, .stereo16 => |slice| { + const size = @intCast(c_int, slice.len) * 2; + alBufferData(reference, format, slice.ptr, size, freq); + }, + } + + switch (alGetError()) { + AL_NO_ERROR => {}, + AL_OUT_OF_MEMORY => return Error.OutOfMemory, + AL_INVALID_VALUE => return Error.InvalidValue, + AL_INVALID_ENUM => return Error.InvalidEnum, + else => unreachable, + } + } +}; + +pub const source = struct { + /// Generate one source for audio data and return its reference. + pub fn create() Error!u32 { + var reference: u32 = undefined; + alGenSources(1, &reference); + return switch (alGetError()) { + AL_NO_ERROR => reference, + AL_INVALID_VALUE => Error.InvalidValue, + AL_OUT_OF_MEMORY => Error.OutOfMemory, + else => unreachable, + }; + } + + /// Free resources used by one source. + /// Sources attached to a source cannot be deleted. + pub fn destroy(reference: *const u32) Error!void { + alDeleteSources(1, reference); + switch (alGetError()) { + AL_NO_ERROR => {}, + AL_INVALID_OPERATION => return Error.InvalidOperation, + AL_INVALID_NAME => return Error.InvalidName, + AL_INVALID_VALUE => return Error.InvalidValue, + else => unreachable, + } } -} -// TODO: deleteBuffers + /// Set a property for the source. + pub fn set(reference: u32, param: ALenum, value: anytype) Error!void { + const T = @TypeOf(value); + switch (T) { + f32 => alSourcef(reference, param, value), + i32 => alSourcei(reference, param, value), + else => switch (Child(T)) { + f32 => alSourcefv(reference, param, value[0..]), + i32 => alSourceiv(reference, param, value[0..]), + else => unreachable, + } + } + + switch (alGetError()) { + AL_NO_ERROR => {}, + AL_INVALID_VALUE => return Error.InvalidValue, + AL_INVALID_ENUM => return Error.InvalidEnum, + AL_INVALID_NAME => return Error.InvalidName, + AL_INVALID_OPERATION => return Error.InvalidOperation, + else => unreachable, + } + } + + /// Get a scalar property from the source. + pub fn get(comptime T: type, reference: u32, param: ALenum) Error!T { + var value: T = undefined; + switch (T) { + f32 => alGetSourcef(reference, param, &value), + i32 => alGetSourcei(reference, param, &value), + else => unreachable, + } + + return switch (alGetError()) { + AL_NO_ERROR => value, + AL_INVALID_VALUE => return Error.InvalidValue, + AL_INVALID_ENUM => return Error.InvalidEnum, + AL_INVALID_NAME => return Error.InvalidName, + AL_INVALID_OPERATION => return Error.InvalidOperation, + else => unreachable, + }; + } + + /// Play the source. + pub fn play(reference: u32) Error!void { + alSourcePlay(reference); + switch (alGetError()) { + AL_NO_ERROR => {}, + AL_INVALID_NAME => return Error.InvalidName, + AL_INVALID_OPERATION => return Error.InvalidOperation, + else => unreachable, + } + } +}; -// alBufferData -// alDeleteSources -// alGenSources // alGetSourcei -// alSourcef -// alSourcefv -// alSourcei // alSourcePause // alSourcePlay // alSourceStop diff --git a/src/alc.zig b/src/alc.zig index 405d8bc..8e478e8 100644 --- a/src/alc.zig +++ b/src/alc.zig @@ -90,7 +90,7 @@ pub fn getContextsDevice(context: *Context) !*Device { } /// Open the named playback device. -pub fn openDevice(name: ?[]const u8) !*Device { +pub fn openDevice(name: ?[:0]const u8) !*Device { const name_ptr = if (name == null) null else name.?.ptr; if (alcOpenDevice(name_ptr)) |device| return device; diff --git a/src/main.zig b/src/main.zig index 21e4f3a..7e8d8c5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,16 +16,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with zeal. If not, see <https://www.gnu.org/licenses/>. -const al = @import("al.zig"); -const alc = @import("alc.zig"); -const expectEqual = std.testing.expectEqual; -const eql = std.meta.eql; +const Allocator = std.mem.Allocator; +const SndFile = sf.SndFile; +pub const al = @import("al.zig"); +pub const alc = @import("alc.zig"); +const sf = @import("sf.zig"); const std = @import("std"); pub const Device = struct { pimpl: *alc.Device, - pub fn init(name: ?[]const u8) alc.Error!Device { + pub fn init(name: ?[:0]const u8) alc.Error!Device { return Device{ .pimpl = try alc.openDevice(name) }; } @@ -87,15 +88,61 @@ pub const Buffer = struct { context: Context, reference: u32, - pub fn init(context: Context, name: []const u8) !Buffer { + pub fn init(allocator: *Allocator, context: Context, + name: [:0]const u8) !Buffer { try checkContext(context); - const reference = try al.genBuffer(); + const snd_file = try SndFile.open(name, sf.Mode.read); + defer snd_file.close(); + const data = try snd_file.readAll(allocator); + defer allocator.free(data); + const al_data = switch (snd_file.channels) { + 1 => al.Data{ .mono16 = data }, + 2 => al.Data{ .stereo16 = data }, + else => unreachable, + }; + + const reference = try al.buffer.create(); + try al.buffer.fill(reference, al_data, snd_file.sample_rate); return Buffer{ .context = context, .reference = reference }; } pub fn deinit(self: Buffer) !void { try checkContext(self.context); - try al.deleteBuffer(&self.reference); + try al.buffer.destroy(&self.reference); + } +}; + +pub const Source = struct { + context: Context, + reference: u32, + + pub fn init(context: Context) !Source { + try checkContext(context); + const reference = try al.source.create(); + return Source{ .context = context, .reference = reference }; + } + + pub fn deinit(self: Source) !void { + try checkContext(self.context); + try al.source.destroy(&self.reference); + } + + pub fn isPlaying(self: Source) !bool { + try checkContext(self.context); + const state = try al.source.get(i32, self.reference, al.SOURCE_STATE); + return state == al.PLAYING; + } + + pub fn getSecOffset(self: Source) !f32 { + try checkContext(self.context); + return try al.source.get(f32, self.reference, al.SEC_OFFSET); + } + + pub fn play(self: Source, buffer: Buffer) !void { + try checkContext(self.context); + try al.source.set(self.reference, al.BUFFER, + @intCast(i32, buffer.reference)); + try al.source.play(self.reference); } }; @@ -104,6 +151,9 @@ test "Device" { try device.deinit(); } +const testing = std.testing; +const expectEqual = testing.expectEqual; + test "Context" { const device = try Device.init(null); defer device.deinit() catch unreachable; @@ -132,13 +182,3 @@ test "Listener" { try listener.setPosition(.{ 1, 2, 3 }); try listener.setOrientation(.{ 4, 5, 6 }, .{ 7, 8, 9 }); } - -test "Buffer" { - const context = try Context.init(try Device.init(null), &.{}); - defer context.device.deinit() catch unreachable; - defer context.deinit() catch unreachable; - - try useContext(context); - const buffer = try Buffer.init(context, ""); - defer buffer.deinit() catch unreachable; -} diff --git a/src/sf.zig b/src/sf.zig new file mode 100644 index 0000000..d38446c --- /dev/null +++ b/src/sf.zig @@ -0,0 +1,94 @@ +// libsndfile wrapper +// Copyright (C) 2021 Nguyễn Gia Phong +// +// This file is part of zeal. +// +// Zeal is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Zeal is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with zeal. If not, see <https://www.gnu.org/licenses/>. + +const Allocator = @import("std").mem.Allocator; + +usingnamespace @cImport(@cInclude("sndfile.h")); + +pub const Mode = enum { + read, + write, + read_write, +}; + +pub const Error = error { + UnrecognizedFormat, + SystemError, + MalformedFile, + UnsupportedEncoding, +}; + +pub const SndFile = struct { + pimpl: *SNDFILE, + frames: usize, + sample_rate: c_int, + channels: c_int, + format: c_int, + sections: c_int, + seekable: bool, + + /// Open the sound file at the specified path. + pub fn open(path: [:0]const u8, mode: Mode) Error!SndFile { + const c_mode = switch (mode) { + .read => SFM_READ, + .write => SFM_WRITE, + .read_write => SFM_RDWR, + }; + + var info: SF_INFO = undefined; + const pimpl = sf_open(path.ptr, c_mode, &info); + return SndFile { + .pimpl = pimpl orelse return switch (sf_error(null)) { + SF_ERR_UNRECOGNISED_FORMAT => Error.UnrecognizedFormat, + SF_ERR_SYSTEM => Error.SystemError, + SF_ERR_MALFORMED_FILE => Error.MalformedFile, + SF_ERR_UNSUPPORTED_ENCODING => Error.UnsupportedEncoding, + else => unreachable, + }, + .frames = @intCast(usize, info.frames), + .sample_rate = info.samplerate, + .channels = info.channels, + .format = info.format, + .sections = info.sections, + .seekable = if (info.seekable == 0) false else true, + }; + } + + /// Read the requested number of frames. + /// The returned memory is managed by the caller. + pub fn read(self: SndFile, allocator: *Allocator, + frames: usize) Allocator.Error![]const i16 { + const items = frames * @intCast(usize, self.channels); + const memory = try allocator.alloc(i16, items); + const n = sf_read_short(self.pimpl, memory.ptr, @intCast(i64, items)); + return try allocator.realloc(memory, @intCast(usize, n)); + } + + /// Read the entire file. The returned memory is managed by the caller. + pub fn readAll(self: SndFile, + allocator: *Allocator) Allocator.Error![]const i16 { + return self.read(allocator, self.frames); + } + + /// Close the file and deallocate its internal buffers. + /// Like std.os.close, this function does not return + /// any indication of failure. + pub fn close(self: SndFile) void { + _ = sf_close(self.pimpl); + } +}; |