audio: initial interface
This commit is contained in:
parent
f1845c0f41
commit
caef084a1c
2 changed files with 299 additions and 0 deletions
140
audio/src/main.zig
Normal file
140
audio/src/main.zig
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const testing = std.testing;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Backend = switch (builtin.os.tag) {
|
||||||
|
.linux,
|
||||||
|
.windows,
|
||||||
|
.macos,
|
||||||
|
.ios,
|
||||||
|
=> @import("soundio.zig"),
|
||||||
|
else => @compileError("unsupported os"),
|
||||||
|
};
|
||||||
|
pub const Error = Backend.Error;
|
||||||
|
pub const Device = Backend.Device;
|
||||||
|
pub const DeviceIterator = Backend.DeviceIterator;
|
||||||
|
|
||||||
|
pub const Mode = enum {
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Format = enum {
|
||||||
|
U8,
|
||||||
|
S16,
|
||||||
|
S24,
|
||||||
|
S32,
|
||||||
|
F32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DeviceDescriptor = struct {
|
||||||
|
mode: ?Mode = null,
|
||||||
|
format: ?Format = null,
|
||||||
|
is_raw: ?bool = null,
|
||||||
|
channels: ?u8 = null,
|
||||||
|
sample_rate: ?u32 = null,
|
||||||
|
id: ?[:0]const u8 = null,
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Audio = @This();
|
||||||
|
|
||||||
|
backend: Backend,
|
||||||
|
|
||||||
|
pub fn init() Error!Audio {
|
||||||
|
return Audio{
|
||||||
|
.backend = try Backend.init(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Audio) void {
|
||||||
|
self.backend.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn waitEvents(self: Audio) void {
|
||||||
|
self.backend.waitEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requestDevice(self: Audio, config: DeviceDescriptor) Error!Device {
|
||||||
|
return self.backend.requestDevice(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputDeviceIterator(self: Audio) DeviceIterator {
|
||||||
|
return self.backend.inputDeviceIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outputDeviceIterator(self: Audio) DeviceIterator {
|
||||||
|
return self.backend.outputDeviceIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "list devices" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
var iter = a.inputDeviceIterator();
|
||||||
|
while (try iter.next()) |_| {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "connect to device" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
const d = try a.requestDevice(.{ .mode = .output });
|
||||||
|
defer d.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "connect to device from descriptor" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
var iter = a.outputDeviceIterator();
|
||||||
|
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||||
|
|
||||||
|
const d = try a.requestDevice(device_desc);
|
||||||
|
defer d.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "requestDevice behavior: null is_raw" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
var iter = a.outputDeviceIterator();
|
||||||
|
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||||
|
|
||||||
|
const bad_desc = DeviceDescriptor{
|
||||||
|
.is_raw = null,
|
||||||
|
.mode = device_desc.mode,
|
||||||
|
.id = device_desc.id,
|
||||||
|
};
|
||||||
|
try testing.expectError(error.InvalidParameter, a.requestDevice(bad_desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "requestDevice behavior: null mode" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
var iter = a.outputDeviceIterator();
|
||||||
|
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||||
|
|
||||||
|
const bad_desc = DeviceDescriptor{
|
||||||
|
.is_raw = device_desc.is_raw,
|
||||||
|
.mode = null,
|
||||||
|
.id = device_desc.id,
|
||||||
|
};
|
||||||
|
try testing.expectError(error.InvalidParameter, a.requestDevice(bad_desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "requestDevice behavior: invalid id" {
|
||||||
|
const a = try init();
|
||||||
|
defer a.deinit();
|
||||||
|
|
||||||
|
var iter = a.outputDeviceIterator();
|
||||||
|
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||||
|
|
||||||
|
const bad_desc = DeviceDescriptor{
|
||||||
|
.is_raw = device_desc.is_raw,
|
||||||
|
.mode = device_desc.mode,
|
||||||
|
.id = "wrong-id",
|
||||||
|
};
|
||||||
|
try testing.expectError(error.DeviceUnavailable, a.requestDevice(bad_desc));
|
||||||
|
}
|
||||||
159
audio/src/soundio.zig
Normal file
159
audio/src/soundio.zig
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Mode = @import("main.zig").Mode;
|
||||||
|
const DeviceDescriptor = @import("main.zig").DeviceDescriptor;
|
||||||
|
const c = @import("soundio").c;
|
||||||
|
const Aim = @import("soundio").Aim;
|
||||||
|
const SoundIo = @import("soundio").SoundIo;
|
||||||
|
const SoundIoDevice = @import("soundio").Device;
|
||||||
|
const SoundIoInStream = @import("soundio").InStream;
|
||||||
|
const SoundIoOutStream = @import("soundio").OutStream;
|
||||||
|
const SoundIoStream = union(Mode) {
|
||||||
|
input: SoundIoInStream,
|
||||||
|
output: SoundIoOutStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Audio = @This();
|
||||||
|
|
||||||
|
pub const DataCallback = fn (device: Device, frame_count: u32) void;
|
||||||
|
pub const Device = struct {
|
||||||
|
handle: SoundIoStream,
|
||||||
|
data_callback: ?DataCallback = null,
|
||||||
|
user_data: ?*anyopaque = null,
|
||||||
|
|
||||||
|
pub fn setCallback(self: Device, callback: DataCallback, data: *anyopaque) void {
|
||||||
|
self.data_callback = callback;
|
||||||
|
self.user_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Device) void {
|
||||||
|
return switch (self.handle) {
|
||||||
|
.input => |d| d.deinit(),
|
||||||
|
.output => |d| d.deinit(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub const DeviceIterator = struct {
|
||||||
|
ctx: Audio,
|
||||||
|
mode: Mode,
|
||||||
|
device_len: u16,
|
||||||
|
index: u16,
|
||||||
|
|
||||||
|
pub fn next(self: *DeviceIterator) IteratorError!?DeviceDescriptor {
|
||||||
|
if (self.index < self.device_len) {
|
||||||
|
const device_desc = switch (self.mode) {
|
||||||
|
.input => self.ctx.handle.getInputDevice(self.index) orelse return null,
|
||||||
|
.output => self.ctx.handle.getOutputDevice(self.index) orelse return null,
|
||||||
|
};
|
||||||
|
self.index += 1;
|
||||||
|
return DeviceDescriptor{
|
||||||
|
.mode = switch (@intToEnum(Aim, device_desc.handle.aim)) {
|
||||||
|
.input => .input,
|
||||||
|
.output => .output,
|
||||||
|
},
|
||||||
|
.is_raw = device_desc.handle.is_raw,
|
||||||
|
.id = device_desc.id(),
|
||||||
|
.name = device_desc.name(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const IteratorError = error{OutOfMemory};
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
InvalidDeviceID,
|
||||||
|
InvalidParameter,
|
||||||
|
NoDeviceFound,
|
||||||
|
AlreadyConnected,
|
||||||
|
CannotConnect,
|
||||||
|
UnsupportedOS,
|
||||||
|
UnsupportedBackend,
|
||||||
|
DeviceUnavailable,
|
||||||
|
};
|
||||||
|
|
||||||
|
handle: SoundIo,
|
||||||
|
|
||||||
|
pub fn init() Error!Audio {
|
||||||
|
var self = Audio{
|
||||||
|
.handle = try SoundIo.init(),
|
||||||
|
};
|
||||||
|
self.handle.connect() catch |err| {
|
||||||
|
return switch (err) {
|
||||||
|
error.SystemResources, error.NoSuchClient => error.CannotConnect,
|
||||||
|
error.Invalid => error.AlreadyConnected,
|
||||||
|
error.OutOfMemory => error.OutOfMemory,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
self.handle.flushEvents();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Audio) void {
|
||||||
|
self.handle.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn waitEvents(self: Audio) void {
|
||||||
|
self.handle.waitEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requestDevice(self: Audio, config: DeviceDescriptor) Error!Device {
|
||||||
|
return Device{
|
||||||
|
.handle = blk: {
|
||||||
|
var sio_device: SoundIoDevice = undefined;
|
||||||
|
|
||||||
|
if (config.id) |id| {
|
||||||
|
if (config.mode == null or config.is_raw == null)
|
||||||
|
return error.InvalidParameter;
|
||||||
|
|
||||||
|
sio_device = switch (config.mode.?) {
|
||||||
|
.input => self.handle.getInputDeviceFromID(id, config.is_raw.?),
|
||||||
|
.output => self.handle.getOutputDeviceFromID(id, config.is_raw.?),
|
||||||
|
} orelse {
|
||||||
|
return if (switch (config.mode.?) {
|
||||||
|
.input => self.handle.inputDeviceCount().?,
|
||||||
|
.output => self.handle.outputDeviceCount().?,
|
||||||
|
} == 0)
|
||||||
|
error.NoDeviceFound
|
||||||
|
else
|
||||||
|
error.DeviceUnavailable;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (config.mode == null) return error.InvalidParameter;
|
||||||
|
|
||||||
|
const id = switch (config.mode.?) {
|
||||||
|
.input => self.handle.defaultInputDeviceIndex(),
|
||||||
|
.output => self.handle.defaultOutputDeviceIndex(),
|
||||||
|
} orelse return error.NoDeviceFound;
|
||||||
|
sio_device = switch (config.mode.?) {
|
||||||
|
.input => self.handle.getInputDevice(id),
|
||||||
|
.output => self.handle.getOutputDevice(id),
|
||||||
|
} orelse return error.DeviceUnavailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk switch (config.mode.?) {
|
||||||
|
.input => SoundIoStream{ .input = try sio_device.createInStream() },
|
||||||
|
.output => SoundIoStream{ .output = try sio_device.createOutStream() },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outputDeviceIterator(self: Audio) DeviceIterator {
|
||||||
|
return .{
|
||||||
|
.ctx = self,
|
||||||
|
.mode = .output,
|
||||||
|
.device_len = self.handle.outputDeviceCount() orelse 0,
|
||||||
|
.index = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputDeviceIterator(self: Audio) DeviceIterator {
|
||||||
|
return .{
|
||||||
|
.ctx = self,
|
||||||
|
.mode = .input,
|
||||||
|
.device_len = self.handle.inputDeviceCount() orelse 0,
|
||||||
|
.index = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue