summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/al.zig6
-rw-r--r--src/alc.zig203
-rw-r--r--src/sf.zig2
-rw-r--r--src/zeal.zig72
4 files changed, 252 insertions, 31 deletions
diff --git a/src/al.zig b/src/al.zig
index 2f03fdb..9e52786 100644
--- a/src/al.zig
+++ b/src/al.zig
@@ -35,11 +35,15 @@ pub const Error = error {
     OutOfMemory,
 };
 
-// FIXME: turn into enum
 pub const FALSE = c.AL_FALSE;
 pub const TRUE = c.AL_TRUE;
 pub const AUTO = c.AL_AUTO_SOFT;
 
+/// Convert bool to AL enumeration.
+pub fn boolToEnum(value: bool) c_int {
+    return if (value) TRUE else FALSE;
+}
+
 pub const listener = struct {
     const Property = enum(c.ALenum) {
         gain = c.AL_GAIN,
diff --git a/src/alc.zig b/src/alc.zig
index c69a54d..0b6439a 100644
--- a/src/alc.zig
+++ b/src/alc.zig
@@ -16,7 +16,16 @@
 // 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 assert = std.debug.assert;
 const c = @import("cimport.zig");
+const find = std.mem.indexOfScalar;
+const span = std.mem.span;
+const std = @import("std");
+comptime {
+    // OpenAL Soft defines these as int, but comments they're 32-bit.
+    assert(@sizeOf(c.ALCenum) == @sizeOf(i32));
+    assert(@sizeOf(c.ALCint) == @sizeOf(i32));
+}
 
 /// Opaque device handle.
 pub const Device = c.ALCdevice;
@@ -36,16 +45,156 @@ pub const Error = error {
     OutOfMemory,
 };
 
-pub const FALSE = c.ALC_FALSE;
-pub const TRUE = c.ALC_TRUE;
-pub const DONT_CARE  = c.ALC_DONT_CATE_SOFT;
+pub const Enum = enum(i32) {
+    /// No error.
+    no_error = c.ALC_NO_ERROR,
+    /// Invalid enum parameter passed to an ALC call.
+    invalid_enum = c.ALC_INVALID_ENUM,
+    /// Invalid value parameter passed to an ALC call.
+    invalid_value = c.ALC_INVALID_VALUE,
+    /// Invalid device handle.
+    invalid_device = c.ALC_INVALID_DEVICE,
+    /// Invalid context handle.
+    invalid_context = c.ALC_INVALID_CONTEXT,
+    /// Out of memory error.
+    out_of_memory = c.ALC_OUT_OF_MEMORY,
+
+    device_specifier = c.ALC_DEVICE_SPECIFIER,
+    all_devices_specifier = c.ALC_ALL_DEVICES_SPECIFIER,
+    capture_device_specifier = c.ALC_CAPTURE_DEVICE_SPECIFIER,
+    default_device_specifier = c.ALC_DEFAULT_DEVICE_SPECIFIER,
+    default_all_devices_specifier = c.ALC_DEFAULT_ALL_DEVICES_SPECIFIER,
+    capture_default_device_specifier = c.ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER,
+
+    /// Extension list.
+    extensions = c.ALC_EXTENSIONS,
+    hrtf_specifier = c.ALC_HRTF_SPECIFIER_SOFT,
+
+    /// OpenAL major version.
+    major_version = c.ALC_MAJOR_VERSION,
+    /// OpenAL minor version.
+    minor_version = c.ALC_MINOR_VERSION,
+    /// Effects Extension major version.
+    efx_major_version = c.ALC_EFX_MAJOR_VERSION,
+    /// Effects Extension minor version.
+    efx_minor_version = c.ALC_EFX_MINOR_VERSION,
+
+    /// Audio channels' format.
+    channels_format = c.ALC_FORMAT_CHANNELS_SOFT,
+    /// Sample type.
+    sample_type = c.ALC_FORMAT_TYPE_SOFT,
+    /// Frequency in hertz.
+    frequency = c.ALC_FREQUENCY,
+
+    /// Ambisonic component ordering.
+    ambisonic_layout = c.ALC_AMBISONIC_LAYOUT_SOFT,
+    /// Ambisonic normalization.
+    ambisonic_scaling = c.ALC_AMBISONIC_SCALING_SOFT,
+    /// Ambisonic order.
+    ambisonic_order = c.ALC_AMBISONIC_ORDER_SOFT,
+
+    /// Number of mono (3D) sources.
+    mono_sources = c.ALC_MONO_SOURCES,
+    /// Number of stereo sources.
+    stereo_sources = c.ALC_STEREO_SOURCES,
+    /// Maximum number of auxiliary source sends.
+    max_auxiliary_sends = c.ALC_MAX_AUXILIARY_SENDS,
+
+    /// Enabling HRTF.
+    hrtf = c.ALC_HRTF_SOFT,
+    /// The HRTF being used.
+    hrtf_id = c.ALC_HRTF_ID_SOFT,
+    /// Enabling gain limiter.
+    output_limiter = c.ALC_OUTPUT_LIMITER_SOFT,
+    /// The output mode.
+    output_mode = c.ALC_OUTPUT_MODE_SOFT,
+
+    /// Keys with boolean values.
+    const booleans = [_]Enum{ .hrtf, .output_limiter };
+    /// Keys with integral values.
+    const integers = [_]Enum{
+        .major_version, .minor_version, .efx_major_version, .efx_minor_version,
+        .channels_format, .sample_type, .frequency,
+        .ambisonic_layout, .ambisonic_scaling, .ambisonic_order,
+        .mono_sources, .stereo_sources, .max_auxiliary_sends,
+        .hrtf_id, .output_mode,
+    };
+    /// Keys with string values.
+    const strings = [_]Enum{
+        .no_error, .invalid_enum, .invalid_value,
+        .invalid_device, .invalid_context, .out_of_memory,
+        .device_specifier, .default_device_specifier,
+        .all_devices_specifier, .default_all_devices_specifier,
+        .capture_device_specifier, .capture_default_device_specifier,
+        .extensions, .hrtf_specifier,
+    };
+
+    /// Return type of the key's corresponding value.
+    pub fn getType(comptime self: Enum) type {
+        if (find(Enum, &Enum.booleans, self)) |_|
+            return bool;
+        if (find(Enum, &Enum.integers, self)) |_|
+            return i32;
+        if (find(Enum, &Enum.strings, self)) |_|
+            return [:0]const u8;
+        unreachable;
+    }
+};
+
+/// Format of audio channels.
+pub const ChannelsFormat = enum(i32) {
+    /// Monophonic sound.
+    mono = c.ALC_MONO_SOFT,
+    /// Stereophonic sound.
+    stereo = c.ALC_STEREO_SOFT,
+    /// 4.0 surround sound.
+    quad = c.ALC_QUAD_SOFT,
+    /// 5.1 surround sound.
+    x51 = c.ALC_5POINT1_SOFT,
+    /// 6.1 surround sound.
+    x61 = c.ALC_6POINT1_SOFT,
+    /// 7.1 surround sound.
+    x71 = c.ALC_7POINT1_SOFT,
+    /// Ambisonics.
+    b_format = c.ALC_BFORMAT3D_SOFT,
+};
+/// Sample format.
+pub const SampleType = enum(i32) {
+    i8 = c.ALC_BYTE_SOFT,
+    u8 = c.ALC_UNSIGNED_BYTE_SOFT,
+    i16 = c.ALC_SHORT_SOFT,
+    u16 = c.ALC_UNSIGNED_SHORT_SOFT,
+    i32 = c.ALC_INT_SOFT,
+    u32 = c.ALC_UNSIGNED_INT_SOFT,
+    f32 = c.ALC_FLOAT_SOFT,
+};
+
+/// Ambisonic format layout.
+pub const AmbisonicLayout = enum(i32) {
+    /// Furse-Malham ordering.
+    fu_ma = c.AL_FUMA_SOFT,
+    /// Ambisonic Channel Number ordering.
+    acn = c.AL_ACN_SOFT,
+};
+/// Ambisonic normalization method.
+pub const AmbisonicScaling = enum(i32) {
+    /// Furse-Malham format (maxN normalisation?).
+    fu_ma = c.AL_FUMA_SOFT,
+    /// Schmidt semi-normalisation.
+    sn3d = c.AL_SN3D_SOFT,
+    /// Full 3D normalisation.
+    n3d = c.AL_N3D_SOFT,
+};
 
-/// Context creation key to specify whether to enable HRTF
-/// (either `FALSE`, `TRUE` or `DONT_CARE`).
-pub const HRTF = c.ALC_HRTF_SOFT;
+/// Logical value.
+pub const Logical = enum(i32) {
+    false = c.ALC_FALSE,
+    true = c.ALC_TRUE,
+    null = c.ALC_DONT_CARE_SOFT,
+};
 
 /// Create and attach a context to the given device.
-pub fn createContext(device: *Device, attrlist: [:0]const i32) Error!*Context {
+pub fn createContext(device: *Device, attrlist: [:0]const i32) !*Context {
     if (c.alcCreateContext(device, attrlist.ptr)) |context|
         return context;
     return switch (c.alcGetError(device)) {
@@ -57,8 +206,8 @@ pub fn createContext(device: *Device, attrlist: [:0]const i32) Error!*Context {
 
 /// Make the given context the active process-wide context.
 /// Passing NULL clears the active context.
-pub fn makeContextCurrent(context: ?*Context) Error!void {
-    if (c.alcMakeContextCurrent(context) == TRUE)
+pub fn makeContextCurrent(context: ?*Context) !void {
+    if (c.alcMakeContextCurrent(context) == c.ALC_TRUE)
         return;
     if (c.alcGetError(null) == c.ALC_INVALID_CONTEXT)
         return Error.InvalidContext;
@@ -66,7 +215,7 @@ pub fn makeContextCurrent(context: ?*Context) Error!void {
 }
 
 /// Remove a context from its device and destroys it.
-pub fn destroyContext(context: *Context) Error!void {
+pub fn destroyContext(context: *Context) !void {
     c.alcDestroyContext(context);
     if (c.alcGetError(null) == c.ALC_INVALID_CONTEXT)
         return Error.InvalidContext;
@@ -78,7 +227,7 @@ pub fn getCurrentContext() ?*Context {
 }
 
 /// Return the device that a particular context is attached to.
-pub fn getContextsDevice(context: *Context) Error!*Device {
+pub fn getContextsDevice(context: *Context) !*Device {
     if (c.alcGetContextsDevice(context)) |device|
         return device;
     if (c.alcGetError(null) == c.ALC_INVALID_CONTEXT)
@@ -87,7 +236,7 @@ pub fn getContextsDevice(context: *Context) Error!*Device {
 }
 
 /// Open the named playback device.
-pub fn openDevice(name: ?[:0]const u8) Error!*Device {
+pub fn openDevice(name: ?[:0]const u8) !*Device {
     const name_ptr = if (name == null) null else name.?.ptr;
     if (c.alcOpenDevice(name_ptr)) |device|
         return device;
@@ -99,17 +248,32 @@ pub fn openDevice(name: ?[:0]const u8) Error!*Device {
 }
 
 /// Close the given playback device.
-pub fn closeDevice(device: *Device) Error!void {
-    if (c.alcCloseDevice(device) == TRUE)
+pub fn closeDevice(device: *Device) !void {
+    if (c.alcCloseDevice(device) == c.ALC_TRUE)
         return;
     if (c.alcGetError(device) == c.ALC_INVALID_DEVICE)
         return Error.InvalidDevice;
     unreachable;
 }
 
-pub fn getInt(device: *Device, param: c.ALCenum) Error!i32 {
-    var data: i32 = undefined;
-    c.alcGetIntegerv(device, param, 1, &data);
+/// Return information about the device.
+fn getInfo(comptime T: type, device: ?*Device, attr: Enum) !T {
+    const data = switch (T) {
+        bool => return switch (try getInfo(i32, device, attr)) {
+            c.ALC_FALSE => false,
+            c.ALC_TRUE => true,
+            else => unreachable,
+        },
+        i32 => return (try getInfo([1]i32, device, attr))[0],
+        [:0]const u8 => span(c.alcGetString(device, @enumToInt(attr))),
+        else => array: {
+            assert(@typeInfo(T).Array.child == i32);
+            var data: T = undefined;
+            c.alcGetIntegerv(device, @enumToInt(attr), data.len, &data);
+            break :array data;
+        },
+    };
+
     return switch (c.alcGetError(device)) {
         c.ALC_NO_ERROR => data,
         c.ALC_INVALID_VALUE => Error.InvalidValue,
@@ -119,3 +283,8 @@ pub fn getInt(device: *Device, param: c.ALCenum) Error!i32 {
         else => unreachable,
     };
 }
+
+/// Return information about the device.
+pub fn get(device: ?*Device, comptime attr: Enum) !attr.getType() {
+    return getInfo(attr.getType(), device, attr);
+}
diff --git a/src/sf.zig b/src/sf.zig
index cee3118..bce6dcc 100644
--- a/src/sf.zig
+++ b/src/sf.zig
@@ -18,6 +18,7 @@
 
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
+const span = std.mem.span;
 const std = @import("std");
 
 const c = @import("cimport.zig");
@@ -53,6 +54,7 @@ pub const SndFile = struct {
         };
 
         var info: c.SF_INFO = undefined;
+        info.format = 0;
         const pimpl = c.sf_open(path.ptr, c_mode, &info);
         _ = c.sf_command(pimpl, c.SFC_SET_SCALE_FLOAT_INT_READ,
                          null, c.SF_TRUE);
diff --git a/src/zeal.zig b/src/zeal.zig
index 7103f59..e5984bf 100644
--- a/src/zeal.zig
+++ b/src/zeal.zig
@@ -18,16 +18,20 @@
 
 const Allocator = std.mem.Allocator;
 const SndFile = sf.SndFile;
+const al = @import("al.zig");
+const alc = @import("alc.zig");
 const sf = @import("sf.zig");
 const std = @import("std");
 
-pub const al = @import("al.zig");
-pub const alc = @import("alc.zig");
-
 pub const Error = al.Error || error {
     UncurrentContext,
 };
 
+/// Return version information and error strings.
+pub fn get(comptime attr: alc.Enum) !attr.getType() {
+    return alc.get(null, attr);
+}
+
 pub const Device = struct {
     pimpl: *alc.Device,
 
@@ -35,8 +39,9 @@ pub const Device = struct {
         return Device{ .pimpl = try alc.openDevice(name) };
     }
 
-    pub fn enabledHrtf(self: Device) alc.Error!bool {
-        return 0 != try alc.getInt(self.pimpl, alc.HRTF);
+    /// Return infomation about the device.
+    pub fn get(self: Device, comptime attr: alc.Enum) !attr.getType() {
+        return alc.get(self.pimpl, attr);
     }
 
     pub fn deinit(self: Device) alc.Error!void {
@@ -48,9 +53,49 @@ pub const Context = struct {
     pimpl: *alc.Context,
     device: Device,
 
-    pub fn init(device: Device, attributes: [:0]const i32) alc.Error!Context {
+    pub const Attributes = struct {
+        /// Channel configuration.
+        channels_format: ?alc.ChannelsFormat = null,
+        /// Sample type.
+        sample_type: ?alc.SampleType = null,
+        /// Frequency in hertz.
+        frequency: ?i32 = null,
+
+        /// Ambisonic format layout.
+        ambisonic_layout: ?alc.AmbisonicLayout = null,
+        /// Ambisonic normalization method.
+        ambisonic_scaling: ?alc.AmbisonicScaling = null,
+        /// Ambisonic order.
+        ambisonic_order: ?i32 = null,
+
+        /// Number of mono (3D) sources.
+        mono_sources: ?i32 = null,
+        /// Number of stereo sources.
+        stereo_sources: ?i32 = null,
+        /// Maximum number of auxiliary source sends.
+        max_auxiliary_sends: ?i32 = null,
+        /// Enabling HRTF.
+
+        hrtf: ?alc.Logical = null,
+        /// The HRTF to be used.
+        hrtf_id: ?i32 = null,
+        /// Enabling gain limiter.
+        output_limiter: ?alc.Logical = null,
+        /// Output mode.
+        output_mode: ?i32 = null,
+    };
+
+    pub fn init(device: Device, attributes: Attributes) alc.Error!Context {
+        const fields = @typeInfo(Attributes).Struct.fields;
+        var attr_list = [_]i32{ 0 } ** (fields.len * 2 + 1);
+        var i: u8 = 0;
+        inline for (fields) |f| if (@field(attributes, f.name)) |v| {
+            attr_list[i] = @enumToInt(@field(alc.Enum, f.name));
+            attr_list[i + 1] = if (@TypeOf(v) == i32) v else @enumToInt(v);
+            i += 2;
+        };
         return Context {
-            .pimpl = try alc.createContext(device.pimpl, attributes),
+            .pimpl = try alc.createContext(device.pimpl, attr_list[0..i:0]),
             .device = device,
         };
     }
@@ -160,10 +205,11 @@ pub const Source = struct {
                           @intCast(i32, buffer.reference));
     }
 
-    /// Specify if the source always has 3D spatialization features (al.ON),
-    /// never has 3D spatialization features (al.OFF), or if spatialization
-    /// is enabled based on playing a mono sound or not (al.AUTO, default).
-    pub fn setSpatialize(self: Source, value: i32) Error!void {
+    /// Specify if the source always has 3D spatialization features (true),
+    /// never has 3D spatialization features (false), or if spatialization
+    /// is enabled based on playing a mono sound or not (null, default).
+    pub fn setSpatialize(self: Source, enable: ?bool) Error!void {
+        const value: i32 = if (enable) |b| al.boolToEnum(b) else al.AUTO;
         try al.source.set(self.reference, .spatialize, value);
     }
 
@@ -198,7 +244,7 @@ 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, 0 });
+    const context = try Context.init(device, .{ .hrtf = .true });
     defer context.deinit() catch unreachable;
 
     try expectEqual(Context.getCurrent(), null);
@@ -214,7 +260,7 @@ test "Context" {
 }
 
 test "listener" {
-    const context = try Context.init(try Device.init(null), &.{ 0 });
+    const context = try Context.init(try Device.init(null), .{});
     defer context.device.deinit() catch unreachable;
     defer context.deinit() catch unreachable;