summary refs log tree commit diff
path: root/src/zeal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/zeal.zig')
-rw-r--r--src/zeal.zig188
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 });
+}