// Audio Library Context wrapper // Copyright (C) 2021-2023, 2025 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 . 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; /// Opaque context handle. pub const Context = c.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 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, }; /// 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) !*Context { if (c.alcCreateContext(device, attrlist.ptr)) |context| return context; return switch (c.alcGetError(device)) { c.ALC_INVALID_DEVICE => Error.InvalidDevice, c.ALC_INVALID_VALUE => Error.InvalidValue, else => unreachable, }; } /// Make the given context the active process-wide context. /// Passing NULL clears the active context. 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; unreachable; } /// Remove a context from its device and destroys it. pub fn destroyContext(context: *Context) !void { c.alcDestroyContext(context); if (c.alcGetError(null) == c.ALC_INVALID_CONTEXT) return Error.InvalidContext; } /// Return the currently active context. pub fn getCurrentContext() ?*Context { return c.alcGetCurrentContext(); } /// Return the device that a particular context is attached to. pub fn getContextsDevice(context: *Context) !*Device { if (c.alcGetContextsDevice(context)) |device| return device; if (c.alcGetError(null) == c.ALC_INVALID_CONTEXT) return Error.InvalidContext; unreachable; } /// Open the named playback 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; return switch (c.alcGetError(null)) { c.ALC_INVALID_VALUE => Error.InvalidValue, c.ALC_OUT_OF_MEMORY => Error.OutOfMemory, else => unreachable, }; } /// Close the given playback device. 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; } /// 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, @intFromEnum(attr))), else => array: { assert(@typeInfo(T).array.child == i32); var data: T = undefined; c.alcGetIntegerv(device, @intFromEnum(attr), data.len, &data); break :array data; }, }; return switch (c.alcGetError(device)) { c.ALC_NO_ERROR => data, c.ALC_INVALID_VALUE => Error.InvalidValue, c.ALC_INVALID_ENUM => Error.InvalidEnum, c.ALC_INVALID_DEVICE => Error.InvalidDevice, c.ALC_INVALID_CONTEXT => Error.InvalidContext, else => unreachable, }; } /// Return information about the device. pub fn get(device: ?*Device, comptime attr: Enum) !attr.getType() { return getInfo(attr.getType(), device, attr); }