diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | examples/hrtf.zig | 8 | ||||
-rw-r--r-- | examples/play.zig | 3 | ||||
-rw-r--r-- | src/al.zig | 6 | ||||
-rw-r--r-- | src/alc.zig | 203 | ||||
-rw-r--r-- | src/sf.zig | 2 | ||||
-rw-r--r-- | src/zeal.zig | 72 |
7 files changed, 260 insertions, 40 deletions
diff --git a/README.md b/README.md index c3cfba7..74b3997 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # zeal -Zig easy audio library, or zeal, is an OpenAL wrapper inspired -by [alure] and [palace]. +Zig easy audio library, or zeal, is a wrapper for [OpenAL] and [libsndfile], +inspired by [alure] and [palace]. ## Copying @@ -12,6 +12,8 @@ under the terms of the GNU [Lesser General Public License][lgplv3] as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +[OpenAL]: https://openal-soft.org +[libsndfile]: https://libsndfile.github.io/libsndfile [alure]: https://github.com/kcat/alure [palace]: https://mcsinyx.gitlab.io/palace [lgplv3]: https://www.gnu.org/licenses/lgpl-3.0.html diff --git a/examples/hrtf.zig b/examples/hrtf.zig index 76e1257..99c1780 100644 --- a/examples/hrtf.zig +++ b/examples/hrtf.zig @@ -23,8 +23,6 @@ const print = std.debug.print; const sleep = std.time.sleep; const zeal = @import("zeal"); -const al = zeal.al; -const alc = zeal.alc; const Device = zeal.Device; const Context = zeal.Context; const Audio = zeal.Audio; @@ -35,8 +33,8 @@ pub fn main() !void { const device = try Device.init(null); defer device.deinit() catch unreachable; - const context = try Context.init(device, &.{ alc.HRTF, alc.TRUE, 0 }); - if (try device.enabledHrtf()) + const context = try Context.init(device, .{ .hrtf = .true }); + if (try device.get(.hrtf)) print("HRTF enabled!\n", .{}) else print("HRTF not enabled!\n", .{}); @@ -54,7 +52,7 @@ pub fn main() !void { const source = try Source.init(); defer source.deinit() catch unreachable; - try source.setSpatialize(al.TRUE); + try source.setSpatialize(true); try source.bind(buffer); try source.play(); defer print("\n", .{}); diff --git a/examples/play.zig b/examples/play.zig index fab8f3f..60256c6 100644 --- a/examples/play.zig +++ b/examples/play.zig @@ -23,7 +23,6 @@ const print = std.debug.print; const sleep = std.time.sleep; const zeal = @import("zeal"); -const alc = zeal.alc; const Device = zeal.Device; const Context = zeal.Context; const Audio = zeal.Audio; @@ -33,7 +32,7 @@ const Source = zeal.Source; pub fn main() !void { const device = try Device.init(null); defer device.deinit() catch unreachable; - const context = try Context.init(device, &.{ 0 }); + const context = try Context.init(device, .{}); defer context.deinit() catch unreachable; var argv = try args(allocator); 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; |