aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/al.zig182
-rw-r--r--src/alc.zig2
-rw-r--r--src/main.zig76
-rw-r--r--src/sf.zig94
4 files changed, 302 insertions, 52 deletions
diff --git a/src/al.zig b/src/al.zig
index bfb93b6..7b2e6db 100644
--- a/src/al.zig
+++ b/src/al.zig
@@ -33,12 +33,16 @@ pub const Error = error {
OutOfMemory,
};
+pub const BUFFER = AL_BUFFER;
pub const POSITION = AL_POSITION;
pub const ORIENTATION = AL_ORIENTATION;
+pub const PLAYING = AL_PLAYING;
+pub const SEC_OFFSET = AL_SEC_OFFSET;
+pub const SOURCE_STATE = AL_SOURCE_STATE;
pub const listener = struct {
/// Set a property for the listener.
- pub fn set(param: ALenum, value: anytype) !void {
+ pub fn set(param: ALenum, value: anytype) Error!void {
const T = @TypeOf(value);
switch (T) {
f32 => alListenerf(param, value),
@@ -60,42 +64,154 @@ pub const listener = struct {
}
};
-/// Generate one buffer for audio data and return its reference.
-pub fn genBuffer() !u32 {
- var reference: u32 = undefined;
- alGenBuffers(1, &reference);
- return switch (alGetError()) {
- AL_NO_ERROR => reference,
- AL_INVALID_VALUE => Error.InvalidValue,
- AL_OUT_OF_MEMORY => Error.OutOfMemory,
- else => unreachable,
- };
-}
-
-// TODO: genBuffers
-
-/// Free resources used by one buffer.
-/// Buffers attached to a source cannot be deleted.
-pub fn deleteBuffer(reference: *const u32) !void {
- alDeleteBuffers(1, reference);
- switch (alGetError()) {
- AL_NO_ERROR => {},
- AL_INVALID_OPERATION => return Error.InvalidOperation,
- AL_INVALID_NAME => return Error.InvalidName,
- AL_INVALID_VALUE => return Error.InvalidValue,
- 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;
+ alGenBuffers(1, &reference);
+ return switch (alGetError()) {
+ AL_NO_ERROR => reference,
+ AL_INVALID_VALUE => Error.InvalidValue,
+ 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 {
+ alDeleteBuffers(1, reference);
+ switch (alGetError()) {
+ AL_NO_ERROR => {},
+ AL_INVALID_OPERATION => return Error.InvalidOperation,
+ AL_INVALID_NAME => return Error.InvalidName,
+ 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 => AL_FORMAT_MONO8,
+ .mono16 => AL_FORMAT_MONO16,
+ .stereo8 => AL_FORMAT_STEREO8,
+ .stereo16 => AL_FORMAT_STEREO16,
+ };
+
+ switch (data) {
+ .mono8, .stereo8 => |slice| {
+ const size = @intCast(c_int, slice.len);
+ alBufferData(reference, format, slice.ptr, size, freq);
+ },
+ .mono16, .stereo16 => |slice| {
+ const size = @intCast(c_int, slice.len) * 2;
+ alBufferData(reference, format, slice.ptr, size, freq);
+ },
+ }
+
+ switch (alGetError()) {
+ AL_NO_ERROR => {},
+ AL_OUT_OF_MEMORY => return Error.OutOfMemory,
+ AL_INVALID_VALUE => return Error.InvalidValue,
+ 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;
+ alGenSources(1, &reference);
+ return switch (alGetError()) {
+ AL_NO_ERROR => reference,
+ AL_INVALID_VALUE => Error.InvalidValue,
+ 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 {
+ alDeleteSources(1, reference);
+ switch (alGetError()) {
+ AL_NO_ERROR => {},
+ AL_INVALID_OPERATION => return Error.InvalidOperation,
+ AL_INVALID_NAME => return Error.InvalidName,
+ AL_INVALID_VALUE => return Error.InvalidValue,
+ else => unreachable,
+ }
}
-}
-// TODO: deleteBuffers
+ /// Set a property for the source.
+ pub fn set(reference: u32, param: ALenum, value: anytype) Error!void {
+ const T = @TypeOf(value);
+ switch (T) {
+ f32 => alSourcef(reference, param, value),
+ i32 => alSourcei(reference, param, value),
+ else => switch (Child(T)) {
+ f32 => alSourcefv(reference, param, value[0..]),
+ i32 => alSourceiv(reference, param, value[0..]),
+ else => unreachable,
+ }
+ }
+
+ switch (alGetError()) {
+ AL_NO_ERROR => {},
+ AL_INVALID_VALUE => return Error.InvalidValue,
+ AL_INVALID_ENUM => return Error.InvalidEnum,
+ AL_INVALID_NAME => return Error.InvalidName,
+ AL_INVALID_OPERATION => return Error.InvalidOperation,
+ else => unreachable,
+ }
+ }
+
+ /// Get a scalar property from the source.
+ pub fn get(comptime T: type, reference: u32, param: ALenum) Error!T {
+ var value: T = undefined;
+ switch (T) {
+ f32 => alGetSourcef(reference, param, &value),
+ i32 => alGetSourcei(reference, param, &value),
+ else => unreachable,
+ }
+
+ return switch (alGetError()) {
+ AL_NO_ERROR => value,
+ AL_INVALID_VALUE => return Error.InvalidValue,
+ AL_INVALID_ENUM => return Error.InvalidEnum,
+ AL_INVALID_NAME => return Error.InvalidName,
+ AL_INVALID_OPERATION => return Error.InvalidOperation,
+ else => unreachable,
+ };
+ }
+
+ /// Play the source.
+ pub fn play(reference: u32) Error!void {
+ alSourcePlay(reference);
+ switch (alGetError()) {
+ AL_NO_ERROR => {},
+ AL_INVALID_NAME => return Error.InvalidName,
+ AL_INVALID_OPERATION => return Error.InvalidOperation,
+ else => unreachable,
+ }
+ }
+};
-// alBufferData
-// alDeleteSources
-// alGenSources
// alGetSourcei
-// alSourcef
-// alSourcefv
-// alSourcei
// alSourcePause
// alSourcePlay
// alSourceStop
diff --git a/src/alc.zig b/src/alc.zig
index 405d8bc..8e478e8 100644
--- a/src/alc.zig
+++ b/src/alc.zig
@@ -90,7 +90,7 @@ pub fn getContextsDevice(context: *Context) !*Device {
}
/// Open the named playback device.
-pub fn openDevice(name: ?[]const u8) !*Device {
+pub fn openDevice(name: ?[:0]const u8) !*Device {
const name_ptr = if (name == null) null else name.?.ptr;
if (alcOpenDevice(name_ptr)) |device|
return device;
diff --git a/src/main.zig b/src/main.zig
index 21e4f3a..7e8d8c5 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -16,16 +16,17 @@
// 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 expectEqual = std.testing.expectEqual;
-const eql = std.meta.eql;
+const Allocator = std.mem.Allocator;
+const SndFile = sf.SndFile;
+pub const al = @import("al.zig");
+pub const alc = @import("alc.zig");
+const sf = @import("sf.zig");
const std = @import("std");
pub const Device = struct {
pimpl: *alc.Device,
- pub fn init(name: ?[]const u8) alc.Error!Device {
+ pub fn init(name: ?[:0]const u8) alc.Error!Device {
return Device{ .pimpl = try alc.openDevice(name) };
}
@@ -87,15 +88,61 @@ pub const Buffer = struct {
context: Context,
reference: u32,
- pub fn init(context: Context, name: []const u8) !Buffer {
+ pub fn init(allocator: *Allocator, context: Context,
+ name: [:0]const u8) !Buffer {
try checkContext(context);
- const reference = try al.genBuffer();
+ const snd_file = try SndFile.open(name, sf.Mode.read);
+ defer snd_file.close();
+ const data = try snd_file.readAll(allocator);
+ defer allocator.free(data);
+ const al_data = switch (snd_file.channels) {
+ 1 => al.Data{ .mono16 = data },
+ 2 => al.Data{ .stereo16 = data },
+ else => unreachable,
+ };
+
+ const reference = try al.buffer.create();
+ try al.buffer.fill(reference, al_data, snd_file.sample_rate);
return Buffer{ .context = context, .reference = reference };
}
pub fn deinit(self: Buffer) !void {
try checkContext(self.context);
- try al.deleteBuffer(&self.reference);
+ try al.buffer.destroy(&self.reference);
+ }
+};
+
+pub const Source = struct {
+ context: Context,
+ reference: u32,
+
+ pub fn init(context: Context) !Source {
+ try checkContext(context);
+ const reference = try al.source.create();
+ return Source{ .context = context, .reference = reference };
+ }
+
+ pub fn deinit(self: Source) !void {
+ try checkContext(self.context);
+ try al.source.destroy(&self.reference);
+ }
+
+ pub fn isPlaying(self: Source) !bool {
+ try checkContext(self.context);
+ const state = try al.source.get(i32, self.reference, al.SOURCE_STATE);
+ return state == al.PLAYING;
+ }
+
+ pub fn getSecOffset(self: Source) !f32 {
+ try checkContext(self.context);
+ return try al.source.get(f32, self.reference, al.SEC_OFFSET);
+ }
+
+ pub fn play(self: Source, buffer: Buffer) !void {
+ try checkContext(self.context);
+ try al.source.set(self.reference, al.BUFFER,
+ @intCast(i32, buffer.reference));
+ try al.source.play(self.reference);
}
};
@@ -104,6 +151,9 @@ test "Device" {
try device.deinit();
}
+const testing = std.testing;
+const expectEqual = testing.expectEqual;
+
test "Context" {
const device = try Device.init(null);
defer device.deinit() catch unreachable;
@@ -132,13 +182,3 @@ test "Listener" {
try listener.setPosition(.{ 1, 2, 3 });
try listener.setOrientation(.{ 4, 5, 6 }, .{ 7, 8, 9 });
}
-
-test "Buffer" {
- const context = try Context.init(try Device.init(null), &.{});
- defer context.device.deinit() catch unreachable;
- defer context.deinit() catch unreachable;
-
- try useContext(context);
- const buffer = try Buffer.init(context, "");
- defer buffer.deinit() catch unreachable;
-}
diff --git a/src/sf.zig b/src/sf.zig
new file mode 100644
index 0000000..d38446c
--- /dev/null
+++ b/src/sf.zig
@@ -0,0 +1,94 @@
+// libsndfile 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/>.
+
+const Allocator = @import("std").mem.Allocator;
+
+usingnamespace @cImport(@cInclude("sndfile.h"));
+
+pub const Mode = enum {
+ read,
+ write,
+ read_write,
+};
+
+pub const Error = error {
+ UnrecognizedFormat,
+ SystemError,
+ MalformedFile,
+ UnsupportedEncoding,
+};
+
+pub const SndFile = struct {
+ pimpl: *SNDFILE,
+ frames: usize,
+ sample_rate: c_int,
+ channels: c_int,
+ format: c_int,
+ sections: c_int,
+ seekable: bool,
+
+ /// Open the sound file at the specified path.
+ pub fn open(path: [:0]const u8, mode: Mode) Error!SndFile {
+ const c_mode = switch (mode) {
+ .read => SFM_READ,
+ .write => SFM_WRITE,
+ .read_write => SFM_RDWR,
+ };
+
+ var info: SF_INFO = undefined;
+ const pimpl = sf_open(path.ptr, c_mode, &info);
+ return SndFile {
+ .pimpl = pimpl orelse return switch (sf_error(null)) {
+ SF_ERR_UNRECOGNISED_FORMAT => Error.UnrecognizedFormat,
+ SF_ERR_SYSTEM => Error.SystemError,
+ SF_ERR_MALFORMED_FILE => Error.MalformedFile,
+ SF_ERR_UNSUPPORTED_ENCODING => Error.UnsupportedEncoding,
+ else => unreachable,
+ },
+ .frames = @intCast(usize, info.frames),
+ .sample_rate = info.samplerate,
+ .channels = info.channels,
+ .format = info.format,
+ .sections = info.sections,
+ .seekable = if (info.seekable == 0) false else true,
+ };
+ }
+
+ /// Read the requested number of frames.
+ /// The returned memory is managed by the caller.
+ pub fn read(self: SndFile, allocator: *Allocator,
+ frames: usize) Allocator.Error![]const i16 {
+ const items = frames * @intCast(usize, self.channels);
+ const memory = try allocator.alloc(i16, items);
+ const n = sf_read_short(self.pimpl, memory.ptr, @intCast(i64, items));
+ return try allocator.realloc(memory, @intCast(usize, n));
+ }
+
+ /// Read the entire file. The returned memory is managed by the caller.
+ pub fn readAll(self: SndFile,
+ allocator: *Allocator) Allocator.Error![]const i16 {
+ return self.read(allocator, self.frames);
+ }
+
+ /// Close the file and deallocate its internal buffers.
+ /// Like std.os.close, this function does not return
+ /// any indication of failure.
+ pub fn close(self: SndFile) void {
+ _ = sf_close(self.pimpl);
+ }
+};