summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/al.zig39
-rw-r--r--src/alc.zig42
-rw-r--r--src/main.zig96
3 files changed, 152 insertions, 25 deletions
diff --git a/src/al.zig b/src/al.zig
new file mode 100644
index 0000000..b15f2a1
--- /dev/null
+++ b/src/al.zig
@@ -0,0 +1,39 @@
+// Audio Library 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/>.
+
+usingnamespace @cImport({ @cInclude("AL/al.h"); });
+
+pub const POSITION = AL_POSITION;
+pub const ORIENTATION = AL_ORIENTATION;
+
+pub const listener = struct {
+    pub const setFloat = alListenerf;
+    pub const setFloatVector = alListenerfv;
+};
+
+// alBufferData
+// alDeleteSources
+// alGenBuffers
+// alGenSources
+// alGetSourcei
+// alSourcef
+// alSourcefv
+// alSourcei
+// alSourcePause
+// alSourcePlay
+// alSourceStop
diff --git a/src/alc.zig b/src/alc.zig
index 68e12b3..405d8bc 100644
--- a/src/alc.zig
+++ b/src/alc.zig
@@ -16,21 +16,40 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with zeal.  If not, see <https://www.gnu.org/licenses/>.
 
-usingnamespace @cImport({ @cInclude("AL/alc.h"); });
+usingnamespace @cImport({
+    @cInclude("AL/alc.h");
+    @cInclude("AL/alext.h");
+});
 
+/// Opaque device handle.
 pub const Device = ALCdevice;
+/// Opaque context handle.
 pub const Context = ALCcontext;
+
 pub const Error = error {
+    /// Invalid device handle.
     InvalidDevice,
+    /// Invalid context handle.
     InvalidContext,
+    /// Invalid enum parameter passed to an ALC call.
     InvalidEnum,
+    /// Invalid value parameter passed to an ALC call.
     InvalidValue,
+    /// Out of memory.
     OutOfMemory,
 };
 
+pub const FALSE = ALC_FALSE;
+pub const TRUE = ALC_TRUE;
+pub const DONT_CARE  = ALC_DONT_CATE_SOFT;
+
+/// Context creation key to specify whether to enable HRTF
+/// (either `FALSE`, `TRUE` or `DONT_CARE`).
+pub const HRTF = ALC_HRTF_SOFT;
+
 /// Create and attach a context to the given device.
-pub fn createContext(device: *Device, attrlist: [*c]const ALCint) !*Context {
-    if (alcCreateContext(device, attrlist)) |context|
+pub fn createContext(device: *Device, attributes: [:0]const i32) !*Context {
+    if (alcCreateContext(device, attributes.ptr)) |context|
         return context;
     return switch (alcGetError(device)) {
         ALC_INVALID_DEVICE => Error.InvalidDevice,
@@ -42,7 +61,7 @@ pub fn createContext(device: *Device, attrlist: [*c]const ALCint) !*Context {
 /// Make the given context the active process-wide context.
 /// Passing NULL clears the active context.
 pub fn makeContextCurrent(context: ?*Context) !void {
-    if (alcMakeContextCurrent(context) == ALC_TRUE)
+    if (alcMakeContextCurrent(context) == TRUE)
         return;
     if (alcGetError(null) == ALC_INVALID_CONTEXT)
         return Error.InvalidContext;
@@ -56,11 +75,9 @@ pub fn destroyContext(context: *Context) !void {
         return Error.InvalidContext;
 }
 
-/// Returns the currently active context.
-pub fn getCurrentContext() *Context {
-    if (alcGetCurrentContext()) |context|
-        return context;
-    unreachable;
+/// Return the currently active context.
+pub fn getCurrentContext() ?*Context {
+    return alcGetCurrentContext();
 }
 
 /// Return the device that a particular context is attached to.
@@ -73,8 +90,9 @@ pub fn getContextsDevice(context: *Context) !*Device {
 }
 
 /// Open the named playback device.
-pub fn openDevice(device_name: [*c]const u8) !*Device {
-    if (alcOpenDevice(device_name)) |device|
+pub fn openDevice(name: ?[]const u8) !*Device {
+    const name_ptr = if (name == null) null else name.?.ptr;
+    if (alcOpenDevice(name_ptr)) |device|
         return device;
     return switch (alcGetError(null)) {
         ALC_INVALID_VALUE => Error.InvalidValue,
@@ -85,7 +103,7 @@ pub fn openDevice(device_name: [*c]const u8) !*Device {
 
 /// Close the given playback device.
 pub fn closeDevice(device: *Device) !void {
-    if (alcCloseDevice(device) == ALC_TRUE)
+    if (alcCloseDevice(device) == TRUE)
         return;
     if (alcGetError(device) == ALC_INVALID_DEVICE)
         return Error.InvalidDevice;
diff --git a/src/main.zig b/src/main.zig
index add48ae..249d7c7 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -16,23 +16,93 @@
 // 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 expect = std.testing.expect;
+const eql = std.meta.eql;
+const std = @import("std");
 
-pub fn init(device_name: [*c]const u8) !void {
-    const device = try alc.openDevice(device_name);
-    const context = try alc.createContext(device, null);
-    try alc.makeContextCurrent(context);
+pub const Device = struct {
+    pimpl: *alc.Device,
+
+    pub fn init(name: ?[]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;
 }
 
-pub fn deinit() !void {
-    const context = alc.getCurrentContext();
-    const device = try alc.getContextsDevice(context);
-    try alc.makeContextCurrent(null);
-    try alc.destroyContext(context);
-    try alc.closeDevice(device);
+fn checkContext(context: Context) !void {
+    if (context.pimpl != alc.getCurrentContext())
+        return error.UncurrentContext;
 }
 
-test {
-    try init(null);
-    try deinit();
+pub const Listener = struct {
+    context: Context,
+
+    pub fn setPosition(self: Listener, position: [3]f32) !void {
+        try checkContext(self.context);
+        al.listener.setFloatVector(al.POSITION, &position);
+    }
+
+    pub fn setOrientation(self: Listener, at: [3]f32, up: [3]f32) !void {
+        try checkContext(self.context);
+        al.listener.setFloatVector(al.ORIENTATION, &[_]f32 {
+            at[0], at[1], at[2],
+            up[0], up[1], up[2],
+        });
+    }
+};
+
+test "temporary" {
+    const device = try Device.init("OpenAL Soft");
+    defer device.deinit() catch unreachable;
+    const context = try Context.init(device, &.{ alc.HRTF, alc.TRUE });
+    defer context.deinit() catch unreachable;
+
+    try expect(null == try currentContext());
+    if (checkContext(context)) |_| unreachable else |err| switch (err) {
+        error.UncurrentContext => {},
+    }
+
+    try useContext(context);
+    const current_context = (try currentContext()).?;
+    try expect(eql(current_context, context));
+    try expect(eql(current_context.device, device));
+    try checkContext(context);
+
+    const listener = Listener{ .context = context };
+    try listener.setPosition(.{ 0, 0, 0 });
+    try listener.setOrientation(.{ 0, 0, 0 }, .{ 0, 0, 0 });
 }