// OpenAL 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 Child = meta.Child; const Tag = meta.Tag; const meta = @import("std").meta; const c = @import("cimport.zig"); pub const Error = error{ /// Bad name (ID) passed to an OpenAL function. InvalidName, /// Invalid enum parameter passed to an OpenAL function. InvalidEnum, /// Invalid value parameter passed to an OpenAL function. InvalidValue, /// Requested operation invalid. InvalidOperation, /// Requested operation resulted in OpenAL running out of memory. OutOfMemory, }; 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, position = c.AL_POSITION, velocity = c.AL_VELOCITY, orientation = c.AL_ORIENTATION, meters_per_unit = c.AL_METERS_PER_UNIT, }; /// Set a property for the listener. pub fn set(property: Property, value: anytype) Error!void { const param = @intFromEnum(property); const T = @TypeOf(value); switch (T) { f32 => c.alListenerf(param, value), i32 => c.alListeneri(param, value), else => switch (Child(T)) { f32 => c.alListenerfv(param, value[0..]), i32 => c.alListeneriv(param, value[0..]), else => unreachable, }, } switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_INVALID_VALUE => return Error.InvalidValue, c.AL_INVALID_ENUM => return Error.InvalidEnum, c.AL_INVALID_OPERATION => return Error.InvalidOperation, else => unreachable, } } }; pub const Data = union(enum) { /// Unsigned 8-bit mono. mono8: []const u8, /// Signed 16-bit mono. mono16: []const i16, /// Unsigned 8-bit stereo. stereo8: []const u8, /// Signed 16-bit stereo. stereo16: []const i16, }; pub const buffer = struct { /// Generate one buffer for audio data and return its reference. pub fn create() Error!u32 { var reference: u32 = undefined; c.alGenBuffers(1, &reference); return switch (c.alGetError()) { c.AL_NO_ERROR => reference, c.AL_INVALID_VALUE => Error.InvalidValue, c.AL_OUT_OF_MEMORY => Error.OutOfMemory, else => unreachable, }; } /// Free resources used by one buffer. /// Buffers attached to a source cannot be deleted. pub fn destroy(reference: *const u32) Error!void { c.alDeleteBuffers(1, reference); switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_INVALID_OPERATION => return Error.InvalidOperation, c.AL_INVALID_NAME => return Error.InvalidName, c.AL_INVALID_VALUE => return Error.InvalidValue, else => unreachable, } } /// Fill a buffer with audio data. pub fn fill(reference: u32, data: Data, freq: i32) Error!void { const format = switch (data) { .mono8 => c.AL_FORMAT_MONO8, .mono16 => c.AL_FORMAT_MONO16, .stereo8 => c.AL_FORMAT_STEREO8, .stereo16 => c.AL_FORMAT_STEREO16, }; switch (data) { .mono8, .stereo8 => |slice| c.alBufferData(reference, format, slice.ptr, @intCast(slice.len), freq), .mono16, .stereo16 => |slice| c.alBufferData(reference, format, slice.ptr, @intCast(slice.len * 2), freq), } switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_OUT_OF_MEMORY => return Error.OutOfMemory, c.AL_INVALID_VALUE => return Error.InvalidValue, c.AL_INVALID_ENUM => return Error.InvalidEnum, else => unreachable, } } }; pub const source = struct { /// Generate one source for audio data and return its reference. pub fn create() Error!u32 { var reference: u32 = undefined; c.alGenSources(1, &reference); return switch (c.alGetError()) { c.AL_NO_ERROR => reference, c.AL_INVALID_VALUE => Error.InvalidValue, c.AL_OUT_OF_MEMORY => Error.OutOfMemory, else => unreachable, }; } /// Free resources used by one source. /// Sources attached to a source cannot be deleted. pub fn destroy(reference: *const u32) Error!void { c.alDeleteSources(1, reference); switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_INVALID_OPERATION => return Error.InvalidOperation, c.AL_INVALID_NAME => return Error.InvalidName, c.AL_INVALID_VALUE => return Error.InvalidValue, else => unreachable, } } const Property = enum(c.ALenum) { pitch = c.AL_PITCH, gain = c.AL_GAIN, position = c.AL_POSITION, velocity = c.AL_VELOCITY, direction = c.AL_DIRECTION, relative = c.AL_SOURCE_RELATIVE, looping = c.AL_LOOPING, buffer = c.AL_BUFFER, state = c.AL_SOURCE_STATE, sec_offset = c.AL_SEC_OFFSET, spatialize = c.AL_SOURCE_SPATIALIZE_SOFT, }; pub const State = enum(i32) { initial = c.AL_INITIAL, playing = c.AL_PLAYING, paused = c.AL_PAUSED, stopped = c.AL_STOPPED, }; /// Set a property for the source. pub fn set(reference: u32, property: Property, value: anytype) Error!void { const param = @intFromEnum(property); const T = @TypeOf(value); switch (T) { f32 => c.alSourcef(reference, param, value), i32 => c.alSourcei(reference, param, value), else => switch (@typeInfo(T)) { .@"enum" => c.alSourcei(reference, param, @intFromEnum(value)), else => switch (Child(T)) { f32 => c.alSourcefv(reference, param, value[0..]), i32 => c.alSourceiv(reference, param, value[0..]), else => unreachable, }, }, } switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_INVALID_VALUE => return Error.InvalidValue, c.AL_INVALID_ENUM => return Error.InvalidEnum, c.AL_INVALID_NAME => return Error.InvalidName, c.AL_INVALID_OPERATION => return Error.InvalidOperation, else => unreachable, } } /// Get a scalar property from the source. pub fn get(comptime T: type, reference: u32, property: Property) Error!T { const param = @intFromEnum(property); var value: T = undefined; switch (T) { f32 => c.alGetSourcef(reference, param, &value), i32 => c.alGetSourcei(reference, param, &value), else => switch (@typeInfo(T)) { .@"enum" => { var raw: i32 = undefined; c.alGetSourcei(reference, param, &raw); value = @enumFromInt(raw); }, else => unreachable, }, } return switch (c.alGetError()) { c.AL_NO_ERROR => value, c.AL_INVALID_VALUE => return Error.InvalidValue, c.AL_INVALID_ENUM => return Error.InvalidEnum, c.AL_INVALID_NAME => return Error.InvalidName, c.AL_INVALID_OPERATION => return Error.InvalidOperation, else => unreachable, }; } /// Play the source. pub fn play(reference: u32) Error!void { c.alSourcePlay(reference); switch (c.alGetError()) { c.AL_NO_ERROR => {}, c.AL_INVALID_NAME => return Error.InvalidName, c.AL_INVALID_OPERATION => return Error.InvalidOperation, else => unreachable, } } }; // alSourcePause // alSourceStop