diff options
Diffstat (limited to 'src/zeal.zig')
-rw-r--r-- | src/zeal.zig | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/src/zeal.zig b/src/zeal.zig new file mode 100644 index 0000000..d73c674 --- /dev/null +++ b/src/zeal.zig @@ -0,0 +1,188 @@ +// Zig easy audio library +// 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 = 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 Error = al.Error || error { + UncurrentContext, +}; + +pub const Device = struct { + pimpl: *alc.Device, + + pub fn init(name: ?[:0]const u8) alc.Error!Device { + return Device{ .pimpl = try alc.openDevice(name) }; + } + + pub fn deinit(self: Device) alc.Error!void { + try alc.closeDevice(self.pimpl); + } +}; + +pub const Context = struct { + pimpl: *alc.Context, + device: Device, + + pub fn init(device: Device, attributes: [:0]const i32) alc.Error!Context { + return Context { + .pimpl = try alc.createContext(device.pimpl, attributes), + .device = device, + }; + } + + pub fn deinit(self: Context) alc.Error!void { + try alc.makeContextCurrent(null); + try alc.destroyContext(self.pimpl); + } +}; + +pub fn useContext(context: ?Context) alc.Error!void { + try alc.makeContextCurrent(if (context == null) null else context.?.pimpl); +} + +pub fn currentContext() ?alc.Error!Context { + if (alc.getCurrentContext()) |pimpl| + return Context { + .pimpl = pimpl, + .device = Device { .pimpl = try alc.getContextsDevice(pimpl) }, + }; + return null; +} + +fn checkContext(context: Context) !void { + if (context.pimpl != alc.getCurrentContext()) + return error.UncurrentContext; +} + +pub const Listener = struct { + context: Context, + + pub fn setPosition(self: Listener, position: [3]f32) Error!void { + try checkContext(self.context); + try al.listener.set(al.POSITION, position); + } + + pub fn setOrientation(self: Listener, at: [3]f32, up: [3]f32) Error!void { + try checkContext(self.context); + const orientation = [_]f32{ at[0], at[1], at[2], up[0], up[1], up[2] }; + try al.listener.set(al.ORIENTATION, orientation); + } +}; + +pub const Buffer = struct { + context: Context, + reference: u32, + + pub fn init(allocator: *Allocator, context: Context, + name: [:0]const u8) (Error || sf.Error)!Buffer { + try checkContext(context); + 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) Error!void { + try checkContext(self.context); + try al.buffer.destroy(&self.reference); + } +}; + +pub const Source = struct { + context: Context, + reference: u32, + + pub fn init(context: Context) Error!Source { + try checkContext(context); + const reference = try al.source.create(); + return Source{ .context = context, .reference = reference }; + } + + pub fn deinit(self: Source) Error!void { + try checkContext(self.context); + try al.source.destroy(&self.reference); + } + + pub fn isPlaying(self: Source) Error!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) Error!f32 { + try checkContext(self.context); + return try al.source.get(f32, self.reference, al.SEC_OFFSET); + } + + pub fn play(self: Source, buffer: Buffer) Error!void { + try checkContext(self.context); + try al.source.set(self.reference, al.BUFFER, + @intCast(i32, buffer.reference)); + try al.source.play(self.reference); + } +}; + +test "Device" { + const device = try Device.init("OpenAL Soft"); + try device.deinit(); +} + +const expectEqual = std.testing.expectEqual; + +test "Context" { + const device = try Device.init(null); + defer device.deinit() catch unreachable; + const context = try Context.init(device, &.{ alc.HRTF, alc.TRUE }); + defer context.deinit() catch unreachable; + + try expectEqual(currentContext(), null); + if (checkContext(context)) unreachable else |err| switch (err) { + error.UncurrentContext => {}, + } + + try useContext(context); + const current_context = try currentContext().?; + try expectEqual(current_context, context); + try expectEqual(current_context.device, device); + try checkContext(context); +} + +test "Listener" { + const context = try Context.init(try Device.init(null), &.{}); + defer context.device.deinit() catch unreachable; + defer context.deinit() catch unreachable; + + try useContext(context); + const listener = Listener{ .context = context }; + try listener.setPosition(.{ 1, 2, 3 }); + try listener.setOrientation(.{ 4, 5, 6 }, .{ 7, 8, 9 }); +} |