all: move standalone libraries to libs/ subdirectory
The root dir of our repository has grown quite a lot the past few months.
I'd like to make it more clear where the bulk of the engine lives (`src/`) and
also make it more clear which Mach libraries are consumable as standalone projects.
As for the name of this directory, `libs` was my first choice but there's a bit of
a convention of that being external libraries in Zig projects _today_, while these
are libraries maintained as part of Mach in this repository - not external ones.
We will name this directory `libs`, and if we have a need for external libraries
we will use `external` or `deps` for that directory name. I considered other names
such as `components`, `systems`, `modules` (which are bad as they overlap with
major ECS / engine concepts), and it seems likely the official Zig package manager
will break the convention of using a `libs` dir anyway.
Performed via:
```sh
mkdir libs/
git mv freetype libs/
git mv basisu libs/
git mv gamemode libs/
git mv glfw libs/
git mv gpu libs/
git mv gpu-dawn libs/
git mv sysaudio libs/
git mv sysjs libs/
git mv ecs libs/
```
git-subtree-dir: glfw
git-subtree-mainline: 0d5b853443
git-subtree-split: 572d1144f11b353abdb64fff828b25a4f0fbb7ca
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
git mv ecs libs/
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
79ec61396f
commit
0645429df9
240 changed files with 6 additions and 6 deletions
141
libs/sysaudio/src/main.zig
Normal file
141
libs/sysaudio/src/main.zig
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const testing = std.testing;
|
||||
const builtin = @import("builtin");
|
||||
const Backend = if (builtin.cpu.arch == .wasm32) @import("webaudio.zig") else 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" {
|
||||
return error.SkipZigTest;
|
||||
// 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));
|
||||
}
|
||||
163
libs/sysaudio/src/soundio.zig
Normal file
163
libs/sysaudio/src/soundio.zig
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
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 = if (@import("builtin").zig_backend == .stage1)
|
||||
fn (device: Device, frame_count: u32) void
|
||||
else
|
||||
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,
|
||||
};
|
||||
}
|
||||
144
libs/sysaudio/src/webaudio.zig
Normal file
144
libs/sysaudio/src/webaudio.zig
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
const std = @import("std");
|
||||
const Mode = @import("main.zig").Mode;
|
||||
const DeviceDescriptor = @import("main.zig").DeviceDescriptor;
|
||||
const js = @import("sysjs");
|
||||
|
||||
const Audio = @This();
|
||||
|
||||
pub const DataCallback = if (@import("builtin").zig_backend == .stage1)
|
||||
fn (device: *Device, user_data: ?*anyopaque, sample: *f32) void
|
||||
else
|
||||
*const fn (device: *Device, user_data: ?*anyopaque, sample: *f32) void;
|
||||
|
||||
pub const Device = struct {
|
||||
context: js.Object,
|
||||
|
||||
pub fn deinit(device: Device) void {
|
||||
device.context.deinit();
|
||||
}
|
||||
|
||||
pub fn setCallback(device: Device, callback: DataCallback, user_data: ?*anyopaque) void {
|
||||
device.context.set("device", js.createNumber(@intToFloat(f64, @ptrToInt(&device))));
|
||||
device.context.set("callback", js.createNumber(@intToFloat(f64, @ptrToInt(&callback))));
|
||||
if (user_data) |ud|
|
||||
device.context.set("user_data", js.createNumber(@intToFloat(f64, @ptrToInt(ud))));
|
||||
}
|
||||
|
||||
pub fn pause(device: Device) void {
|
||||
device.context.call("suspend", &.{});
|
||||
}
|
||||
|
||||
pub fn start(device: Device) void {
|
||||
_ = device.context.call("resume", &.{});
|
||||
}
|
||||
};
|
||||
|
||||
pub const DeviceIterator = struct {
|
||||
ctx: *Audio,
|
||||
mode: Mode,
|
||||
|
||||
pub fn next(_: DeviceIterator) IteratorError!?DeviceDescriptor {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const IteratorError = error{};
|
||||
|
||||
pub const Error = error{
|
||||
AudioUnsupported,
|
||||
};
|
||||
|
||||
context_constructor: js.Function,
|
||||
|
||||
pub fn init() Error!Audio {
|
||||
const context = js.global().get("AudioContext");
|
||||
if (context.is(.undef))
|
||||
return error.AudioUnsupported;
|
||||
|
||||
return Audio{ .context_constructor = context.view(.func) };
|
||||
}
|
||||
|
||||
pub fn deinit(audio: Audio) void {
|
||||
audio.context_constructor.deinit();
|
||||
}
|
||||
|
||||
pub fn waitEvents(_: Audio) void {}
|
||||
|
||||
const default_channel_count = 2;
|
||||
const default_sample_rate = 48000;
|
||||
const default_buffer_size = 512;
|
||||
|
||||
pub fn requestDevice(audio: Audio, config: DeviceDescriptor) Error!Device {
|
||||
// NOTE: WebAudio only supports F32 audio format, so config.format is unused
|
||||
const mode = config.mode orelse .output;
|
||||
const channels = config.channels orelse default_channel_count;
|
||||
const sample_rate = config.sample_rate orelse default_sample_rate;
|
||||
|
||||
const context_options = js.createMap();
|
||||
defer context_options.deinit();
|
||||
context_options.set("sampleRate", js.createNumber(@intToFloat(f64, sample_rate)));
|
||||
|
||||
const context = audio.context_constructor.construct(&.{context_options.toValue()});
|
||||
_ = context.call("suspend", &.{});
|
||||
|
||||
const input_channels = if (mode == .input) js.createNumber(@intToFloat(f64, channels)) else js.createUndefined();
|
||||
const output_channels = if (mode == .output) js.createNumber(@intToFloat(f64, channels)) else js.createUndefined();
|
||||
|
||||
const node = context.call("createScriptProcessor", &.{ js.createNumber(default_buffer_size), input_channels, output_channels }).view(.object);
|
||||
defer node.deinit();
|
||||
|
||||
context.set("node", node.toValue());
|
||||
|
||||
{
|
||||
const audio_process_event = js.createFunction(audioProcessEvent, &.{context.toValue()});
|
||||
defer audio_process_event.deinit();
|
||||
node.set("onaudioprocess", audio_process_event.toValue());
|
||||
}
|
||||
|
||||
{
|
||||
const destination = context.get("destination").view(.object);
|
||||
defer destination.deinit();
|
||||
_ = node.call("connect", &.{destination.toValue()});
|
||||
}
|
||||
|
||||
return Device{ .context = context };
|
||||
}
|
||||
|
||||
fn audioProcessEvent(args: js.Object, _: usize, captures: []js.Value) js.Value {
|
||||
const device_context = captures[0].view(.object);
|
||||
|
||||
const audio_event = args.getIndex(0).view(.object);
|
||||
const output_buffer = audio_event.get("outputBuffer").view(.object);
|
||||
|
||||
const callback = device_context.get("callback");
|
||||
if (!callback.is(.undef)) {
|
||||
// Do not deinit, we are not making a new device, just creating a view to the current one.
|
||||
var dev = Device{ .context = device_context };
|
||||
const cb = @intToPtr(*DataCallback, @floatToInt(usize, callback.view(.num)));
|
||||
const user_data = device_context.get("user_data");
|
||||
const ud = if (user_data.is(.undef)) null else @intToPtr(*anyopaque, @floatToInt(usize, user_data.view(.num)));
|
||||
|
||||
var channel: usize = 0;
|
||||
while (channel < @floatToInt(usize, output_buffer.get("numberOfChannels").view(.num))) : (channel += 1) {
|
||||
const output_data = output_buffer.call("getChannelData", &.{js.createNumber(@intToFloat(f64, channel))}).view(.object);
|
||||
defer output_data.deinit();
|
||||
|
||||
var sample: usize = 0;
|
||||
while (sample < @floatToInt(usize, output_buffer.get("length").view(.num))) : (sample += 1) {
|
||||
var ret_sample: f32 = undefined;
|
||||
cb.*(&dev, ud, &ret_sample);
|
||||
output_data.setIndex(sample, js.createNumber(ret_sample));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return js.createUndefined();
|
||||
}
|
||||
|
||||
pub fn outputDeviceIterator(audio: Audio) DeviceIterator {
|
||||
return .{ .audio = audio, .mode = .output };
|
||||
}
|
||||
|
||||
pub fn inputDeviceIterator(audio: Audio) DeviceIterator {
|
||||
return .{ .audio = audio, .mode = .input };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue