sysaudio: add webaudio backend
This commit is contained in:
parent
052d3a7da8
commit
f9b3ac2106
11 changed files with 841 additions and 487 deletions
|
|
@ -1,38 +1,40 @@
|
|||
const std = @import("std");
|
||||
const sysaudio = @import("sysaudio");
|
||||
|
||||
var player: sysaudio.Player = undefined;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var a = try sysaudio.Context.init(null, allocator, .{ .deviceChangeFn = deviceChange });
|
||||
defer a.deinit();
|
||||
try a.refresh();
|
||||
var ctx = try sysaudio.Context.init(null, allocator, .{ .deviceChangeFn = deviceChange });
|
||||
defer ctx.deinit();
|
||||
try ctx.refresh();
|
||||
|
||||
const device = a.defaultDevice(.playback) orelse return error.NoDevice;
|
||||
const device = ctx.defaultDevice(.playback) orelse return error.NoDevice;
|
||||
|
||||
var p = try a.createPlayer(device, writeCallback, .{});
|
||||
defer p.deinit();
|
||||
try p.start();
|
||||
player = try ctx.createPlayer(device, writeCallback, .{});
|
||||
defer player.deinit();
|
||||
try player.start();
|
||||
|
||||
try p.setVolume(0.85);
|
||||
try player.setVolume(0.85);
|
||||
|
||||
var buf: [16]u8 = undefined;
|
||||
while (true) {
|
||||
std.debug.print("( paused = {}, volume = {d} )\n> ", .{ p.paused(), try p.volume() });
|
||||
std.debug.print("( paused = {}, volume = {d} )\n> ", .{ player.paused(), try player.volume() });
|
||||
const line = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')) orelse break;
|
||||
var iter = std.mem.split(u8, line, ":");
|
||||
const cmd = std.mem.trimRight(u8, iter.first(), &std.ascii.whitespace);
|
||||
if (std.mem.eql(u8, cmd, "vol")) {
|
||||
var vol = try std.fmt.parseFloat(f32, std.mem.trim(u8, iter.next().?, &std.ascii.whitespace));
|
||||
try p.setVolume(vol);
|
||||
try player.setVolume(vol);
|
||||
} else if (std.mem.eql(u8, cmd, "pause")) {
|
||||
try p.pause();
|
||||
try std.testing.expect(p.paused());
|
||||
try player.pause();
|
||||
try std.testing.expect(player.paused());
|
||||
} else if (std.mem.eql(u8, cmd, "play")) {
|
||||
try p.play();
|
||||
try std.testing.expect(!p.paused());
|
||||
try player.play();
|
||||
try std.testing.expect(!player.paused());
|
||||
} else if (std.mem.eql(u8, cmd, "exit")) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -42,19 +44,16 @@ pub fn main() !void {
|
|||
const pitch = 440.0;
|
||||
const radians_per_second = pitch * 2.0 * std.math.pi;
|
||||
var seconds_offset: f32 = 0.0;
|
||||
fn writeCallback(self_opaque: *anyopaque, n_frame: usize) void {
|
||||
var self = @ptrCast(*sysaudio.Player, @alignCast(@alignOf(sysaudio.Player), self_opaque));
|
||||
|
||||
const seconds_per_frame = 1.0 / @intToFloat(f32, self.sampleRate());
|
||||
fn writeCallback(_: ?*anyopaque, n_frame: usize) void {
|
||||
const seconds_per_frame = 1.0 / @intToFloat(f32, player.sampleRate());
|
||||
var frame: usize = 0;
|
||||
while (frame < n_frame) : (frame += 1) {
|
||||
const sample = std.math.sin((seconds_offset + @intToFloat(f32, frame) * seconds_per_frame) * radians_per_second);
|
||||
self.writeAll(frame, sample);
|
||||
player.writeAll(frame, sample);
|
||||
}
|
||||
seconds_offset = @mod(seconds_offset + seconds_per_frame * @intToFloat(f32, n_frame), 1.0);
|
||||
}
|
||||
|
||||
fn deviceChange(self: ?*anyopaque) void {
|
||||
_ = self;
|
||||
fn deviceChange(_: ?*anyopaque) void {
|
||||
std.debug.print("Device change detected!\n", .{});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
//
|
||||
|
|
@ -13,7 +13,7 @@ pub const Context = struct {
|
|||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
user_data: ?*anyopaque,
|
||||
thread: std.Thread,
|
||||
aborted: std.atomic.Atomic(bool),
|
||||
notify_fd: std.os.fd_t,
|
||||
|
|
@ -71,7 +71,7 @@ pub const Context = struct {
|
|||
|
||||
break :blk .{
|
||||
.deviceChangeFn = deviceChangeFn,
|
||||
.userdata = options.userdata,
|
||||
.user_data = options.user_data,
|
||||
.aborted = .{ .value = false },
|
||||
.notify_fd = notify_fd,
|
||||
.notify_wd = notify_wd,
|
||||
|
|
@ -172,7 +172,7 @@ pub const Context = struct {
|
|||
}
|
||||
|
||||
if (scan) {
|
||||
watcher.deviceChangeFn(self.watcher.?.userdata);
|
||||
watcher.deviceChangeFn(self.watcher.?.user_data);
|
||||
scan = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -410,15 +410,11 @@ pub const Context = struct {
|
|||
return error.IncompatibleDevice;
|
||||
}
|
||||
|
||||
return .{
|
||||
.alsa = .{
|
||||
var player = try self.allocator.create(Player);
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.thread = undefined,
|
||||
.mutex = .{},
|
||||
._channels = device.channels,
|
||||
._format = format,
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.aborted = .{ .value = false },
|
||||
.sample_buffer = try self.allocator.alloc(u8, period_size * format.frameSize(device.channels.len)),
|
||||
.period_size = period_size,
|
||||
|
|
@ -426,8 +422,14 @@ pub const Context = struct {
|
|||
.mixer = mixer.?,
|
||||
.selem = selem.?,
|
||||
.mixer_elm = mixer_elm.?,
|
||||
},
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.user_data = options.user_data,
|
||||
.channels = device.channels,
|
||||
.format = format,
|
||||
.write_step = format.frameSize(device.channels.len),
|
||||
};
|
||||
return .{ .alsa = player };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -435,10 +437,6 @@ pub const Player = struct {
|
|||
allocator: std.mem.Allocator,
|
||||
thread: std.Thread,
|
||||
mutex: std.Thread.Mutex,
|
||||
_channels: []main.Channel,
|
||||
_format: main.Format,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
aborted: std.atomic.Atomic(bool),
|
||||
sample_buffer: []u8,
|
||||
period_size: c_ulong,
|
||||
|
|
@ -446,6 +444,13 @@ pub const Player = struct {
|
|||
mixer: *c.snd_mixer_t,
|
||||
selem: *c.snd_mixer_selem_id_t,
|
||||
mixer_elm: *c.snd_mixer_elem_t,
|
||||
writeFn: main.WriteFn,
|
||||
user_data: ?*anyopaque,
|
||||
sample_rate: u24,
|
||||
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
write_step: u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.aborted.store(true, .Unordered);
|
||||
|
|
@ -457,6 +462,7 @@ pub const Player = struct {
|
|||
_ = c.snd_pcm_hw_free(self.pcm);
|
||||
|
||||
self.allocator.free(self.sample_buffer);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn start(self: *Player) !void {
|
||||
|
|
@ -471,16 +477,14 @@ pub const Player = struct {
|
|||
}
|
||||
|
||||
fn writeLoop(self: *Player) void {
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
|
||||
for (self.channels()) |*ch, i| {
|
||||
ch.*.ptr = self.sample_buffer.ptr + self.format().frameSize(i);
|
||||
for (self.channels) |*ch, i| {
|
||||
ch.*.ptr = self.sample_buffer.ptr + self.format.frameSize(i);
|
||||
}
|
||||
|
||||
while (!self.aborted.load(.Unordered)) {
|
||||
var frames_left = self.period_size;
|
||||
while (frames_left > 0) {
|
||||
self.writeFn(parent, frames_left);
|
||||
self.writeFn(self.user_data, frames_left);
|
||||
const n = c.snd_pcm_writei(self.pcm, self.sample_buffer.ptr, frames_left);
|
||||
if (n < 0) {
|
||||
if (c.snd_pcm_recover(self.pcm, @intCast(c_int, n), 1) < 0) {
|
||||
|
|
@ -554,19 +558,6 @@ pub const Player = struct {
|
|||
return @intToFloat(f32, vol) / @intToFloat(f32, max_vol - min_vol);
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + frame * self.format().frameSize(self.channels().len);
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self._channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
return self._format;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
|
|
@ -595,7 +586,6 @@ pub fn toAlsaFormat(format: main.Format) !c.snd_pcm_format_t {
|
|||
.i24_4b => if (is_little) c.SND_PCM_FORMAT_S24_LE else c.SND_PCM_FORMAT_S24_BE,
|
||||
.i32 => if (is_little) c.SND_PCM_FORMAT_S32_LE else c.SND_PCM_FORMAT_S32_BE,
|
||||
.f32 => if (is_little) c.SND_PCM_FORMAT_FLOAT_LE else c.SND_PCM_FORMAT_FLOAT_BE,
|
||||
.f64 => if (is_little) c.SND_PCM_FORMAT_FLOAT64_LE else c.SND_PCM_FORMAT_FLOAT64_BE,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,29 +20,47 @@ pub const BackendContext = switch (builtin.os.tag) {
|
|||
wasapi: *@import("wasapi.zig").Context,
|
||||
dummy: *@import("dummy.zig").Context,
|
||||
},
|
||||
.freestanding => switch (builtin.cpu.arch) {
|
||||
.wasm32 => union(enum) {
|
||||
webaudio: *@import("webaudio.zig").Context,
|
||||
dummy: *@import("dummy.zig").Context,
|
||||
},
|
||||
else => union(enum) {
|
||||
dummy: *@import("dummy.zig").Context,
|
||||
},
|
||||
},
|
||||
else => union(enum) {
|
||||
dummy: *@import("dummy.zig").Context,
|
||||
},
|
||||
};
|
||||
pub const BackendPlayer = switch (builtin.os.tag) {
|
||||
.linux => union(enum) {
|
||||
pulseaudio: @import("pulseaudio.zig").Player,
|
||||
alsa: @import("alsa.zig").Player,
|
||||
jack: @import("jack.zig").Player,
|
||||
dummy: @import("dummy.zig").Player,
|
||||
pulseaudio: *@import("pulseaudio.zig").Player,
|
||||
alsa: *@import("alsa.zig").Player,
|
||||
jack: *@import("jack.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
.freebsd, .netbsd, .openbsd, .solaris => union(enum) {
|
||||
pulseaudio: @import("pulseaudio.zig").Player,
|
||||
dummy: @import("dummy.zig").Player,
|
||||
pulseaudio: *@import("pulseaudio.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
.macos, .ios, .watchos, .tvos => union(enum) {
|
||||
dummy: @import("dummy.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
.windows => union(enum) {
|
||||
wasapi: @import("wasapi.zig").Player,
|
||||
dummy: @import("dummy.zig").Player,
|
||||
wasapi: *@import("wasapi.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
.freestanding => switch (builtin.cpu.arch) {
|
||||
.wasm32 => union(enum) {
|
||||
webaudio: *@import("webaudio.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
else => union(enum) {
|
||||
dummy: @import("dummy.zig").Player,
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
},
|
||||
else => union(enum) {
|
||||
dummy: *@import("dummy.zig").Player,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,19 +44,6 @@ pub const Context = struct {
|
|||
.devices_info = util.DevicesInfo.init(),
|
||||
};
|
||||
|
||||
try self.devices_info.list.append(self.allocator, dummy_playback);
|
||||
try self.devices_info.list.append(self.allocator, dummy_capture);
|
||||
self.devices_info.list.items[0].channels = try allocator.alloc(main.Channel, 1);
|
||||
self.devices_info.list.items[0].channels[0] = .{
|
||||
.id = .front_center,
|
||||
};
|
||||
self.devices_info.list.items[1].channels = try allocator.alloc(main.Channel, 1);
|
||||
self.devices_info.list.items[1].channels[0] = .{
|
||||
.id = .front_center,
|
||||
};
|
||||
self.devices_info.setDefault(.playback, 0);
|
||||
self.devices_info.setDefault(.capture, 1);
|
||||
|
||||
return .{ .dummy = self };
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +55,22 @@ pub const Context = struct {
|
|||
}
|
||||
|
||||
pub fn refresh(self: *Context) !void {
|
||||
_ = self;
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.clear(self.allocator);
|
||||
|
||||
try self.devices_info.list.append(self.allocator, dummy_playback);
|
||||
try self.devices_info.list.append(self.allocator, dummy_capture);
|
||||
self.devices_info.list.items[0].channels = try self.allocator.alloc(main.Channel, 1);
|
||||
self.devices_info.list.items[0].channels[0] = .{
|
||||
.id = .front_center,
|
||||
};
|
||||
self.devices_info.list.items[1].channels = try self.allocator.alloc(main.Channel, 1);
|
||||
self.devices_info.list.items[1].channels[0] = .{
|
||||
.id = .front_center,
|
||||
};
|
||||
self.devices_info.setDefault(.playback, 0);
|
||||
self.devices_info.setDefault(.capture, 1);
|
||||
}
|
||||
|
||||
pub fn devices(self: Context) []const main.Device {
|
||||
|
|
@ -80,29 +82,33 @@ pub const Context = struct {
|
|||
}
|
||||
|
||||
pub fn createPlayer(self: *Context, device: main.Device, writeFn: main.WriteFn, options: main.Player.Options) !backends.BackendPlayer {
|
||||
_ = self;
|
||||
_ = writeFn;
|
||||
return .{
|
||||
.dummy = .{
|
||||
._channels = device.channels,
|
||||
._format = options.format,
|
||||
var player = try self.allocator.create(Player);
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.sample_rate = options.sample_rate,
|
||||
.is_paused = false,
|
||||
.vol = 1.0,
|
||||
},
|
||||
.channels = device.channels,
|
||||
.format = options.format,
|
||||
.write_step = 0,
|
||||
};
|
||||
return .{ .dummy = player };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Player = struct {
|
||||
_channels: []main.Channel,
|
||||
_format: main.Format,
|
||||
allocator: std.mem.Allocator,
|
||||
sample_rate: u24,
|
||||
is_paused: bool,
|
||||
vol: f32,
|
||||
|
||||
pub fn deinit(self: Player) void {
|
||||
_ = self;
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
write_step: u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn start(self: Player) !void {
|
||||
|
|
@ -129,21 +135,6 @@ pub const Player = struct {
|
|||
return self.vol;
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
_ = self;
|
||||
_ = channel;
|
||||
_ = frame;
|
||||
_ = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self._channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
return self._format;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub const Context = struct {
|
|||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
user_data: ?*anyopaque,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: main.Context.Options) !backends.BackendContext {
|
||||
|
|
@ -34,7 +34,7 @@ pub const Context = struct {
|
|||
},
|
||||
.watcher = if (options.deviceChangeFn) |deviceChangeFn| .{
|
||||
.deviceChangeFn = deviceChangeFn,
|
||||
.userdata = options.userdata,
|
||||
.user_data = options.user_data,
|
||||
} else null,
|
||||
};
|
||||
|
||||
|
|
@ -115,18 +115,18 @@ pub const Context = struct {
|
|||
|
||||
fn sampleRateCallback(_: c.jack_nframes_t, arg: ?*anyopaque) callconv(.C) c_int {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.user_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn portRegistrationCallback(_: c.jack_port_id_t, _: c_int, arg: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.user_data);
|
||||
}
|
||||
|
||||
fn portRenameCalllback(_: c.jack_port_id_t, _: [*c]const u8, _: [*c]const u8, arg: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.user_data);
|
||||
}
|
||||
|
||||
pub fn devices(self: *Context) []const main.Device {
|
||||
|
|
@ -138,8 +138,6 @@ pub const Context = struct {
|
|||
}
|
||||
|
||||
pub fn createPlayer(self: *Context, device: main.Device, writeFn: main.WriteFn, options: main.Player.Options) !backends.BackendPlayer {
|
||||
_ = options;
|
||||
|
||||
var ports = try self.allocator.alloc(*c.jack_port_t, device.channels.len);
|
||||
var dest_ports = try self.allocator.alloc([:0]const u8, ports.len);
|
||||
var buf: [64]u8 = undefined;
|
||||
|
|
@ -151,36 +149,46 @@ pub const Context = struct {
|
|||
dest_ports[i] = dest_name;
|
||||
}
|
||||
|
||||
return .{
|
||||
.jack = .{
|
||||
var player = try self.allocator.create(Player);
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.mutex = .{},
|
||||
.cond = .{},
|
||||
.device = device,
|
||||
.writeFn = writeFn,
|
||||
.client = self.client,
|
||||
.ports = ports,
|
||||
.dest_ports = dest_ports,
|
||||
},
|
||||
.device = device,
|
||||
.vol = 1.0,
|
||||
.writeFn = writeFn,
|
||||
.user_data = options.user_data,
|
||||
.channels = device.channels,
|
||||
.format = .f32,
|
||||
.write_step = main.Format.size(.f32),
|
||||
};
|
||||
return .{ .jack = player };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
mutex: std.Thread.Mutex,
|
||||
cond: std.Thread.Condition,
|
||||
device: main.Device,
|
||||
writeFn: main.WriteFn,
|
||||
client: *c.jack_client_t,
|
||||
ports: []const *c.jack_port_t,
|
||||
dest_ports: []const [:0]const u8,
|
||||
device: main.Device,
|
||||
vol: f32,
|
||||
writeFn: main.WriteFn,
|
||||
user_data: ?*anyopaque,
|
||||
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
write_step: u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.allocator.free(self.ports);
|
||||
for (self.dest_ports) |d|
|
||||
self.allocator.free(d);
|
||||
self.allocator.free(self.dest_ports);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn start(self: *Player) !void {
|
||||
|
|
@ -198,11 +206,10 @@ pub const Player = struct {
|
|||
|
||||
fn processCallback(n_frames: c.jack_nframes_t, self_opaque: ?*anyopaque) callconv(.C) c_int {
|
||||
const self = @ptrCast(*Player, @alignCast(@alignOf(*Player), self_opaque.?));
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
for (self.channels()) |*ch, i| {
|
||||
for (self.channels) |*ch, i| {
|
||||
ch.*.ptr = @ptrCast([*]u8, c.jack_port_get_buffer(self.ports[i], n_frames));
|
||||
}
|
||||
self.writeFn(parent, n_frames);
|
||||
self.writeFn(self.user_data, n_frames);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -235,28 +242,11 @@ pub const Player = struct {
|
|||
}
|
||||
|
||||
pub fn setVolume(self: *Player, vol: f32) !void {
|
||||
_ = self;
|
||||
_ = vol;
|
||||
@panic("incompatible backend");
|
||||
self.vol = vol;
|
||||
}
|
||||
|
||||
pub fn volume(self: *Player) !f32 {
|
||||
_ = self;
|
||||
@panic("incompatible backend");
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + frame * self.format().size();
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self.device.channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
_ = self;
|
||||
return .f32;
|
||||
return self.vol;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub const Context = struct {
|
|||
pub const Options = struct {
|
||||
app_name: [:0]const u8 = "Mach Game",
|
||||
deviceChangeFn: ?DeviceChangeFn = null,
|
||||
userdata: ?*anyopaque = null,
|
||||
user_data: ?*anyopaque = null,
|
||||
};
|
||||
|
||||
data: backends.BackendContext,
|
||||
|
|
@ -89,7 +89,6 @@ pub const Context = struct {
|
|||
std.debug.assert(device.mode == .playback);
|
||||
|
||||
return .{
|
||||
.userdata = options.userdata,
|
||||
.data = switch (self.data) {
|
||||
inline else => |b| try b.createPlayer(device, writeFn, options),
|
||||
},
|
||||
|
|
@ -99,21 +98,20 @@ pub const Context = struct {
|
|||
|
||||
// TODO: `*Player` instead `*anyopaque`
|
||||
// https://github.com/ziglang/zig/issues/12325
|
||||
pub const WriteFn = *const fn (self: *anyopaque, frame_count_max: usize) void;
|
||||
pub const WriteFn = *const fn (user_data: ?*anyopaque, frame_count_max: usize) void;
|
||||
|
||||
pub const Player = struct {
|
||||
pub const Options = struct {
|
||||
format: Format = .f32,
|
||||
sample_rate: u24 = default_sample_rate,
|
||||
userdata: ?*anyopaque = null,
|
||||
user_data: ?*anyopaque = null,
|
||||
};
|
||||
|
||||
userdata: ?*anyopaque,
|
||||
data: backends.BackendPlayer,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
pub fn deinit(self: Player) void {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.deinit(),
|
||||
inline else => |b| b.deinit(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -123,9 +121,9 @@ pub const Player = struct {
|
|||
SystemResources,
|
||||
};
|
||||
|
||||
pub fn start(self: *Player) StartError!void {
|
||||
pub fn start(self: Player) StartError!void {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.start(),
|
||||
inline else => |b| b.start(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -134,9 +132,9 @@ pub const Player = struct {
|
|||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub fn play(self: *Player) PlayError!void {
|
||||
pub fn play(self: Player) PlayError!void {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.play(),
|
||||
inline else => |b| b.play(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -145,15 +143,15 @@ pub const Player = struct {
|
|||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub fn pause(self: *Player) PauseError!void {
|
||||
pub fn pause(self: Player) PauseError!void {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.pause(),
|
||||
inline else => |b| b.pause(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn paused(self: *Player) bool {
|
||||
pub fn paused(self: Player) bool {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.paused(),
|
||||
inline else => |b| b.paused(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -162,10 +160,10 @@ pub const Player = struct {
|
|||
};
|
||||
|
||||
// confidence interval (±) depends on the device
|
||||
pub fn setVolume(self: *Player, vol: f32) SetVolumeError!void {
|
||||
pub fn setVolume(self: Player, vol: f32) SetVolumeError!void {
|
||||
std.debug.assert(vol <= 1.0);
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.setVolume(vol),
|
||||
inline else => |b| b.setVolume(vol),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -174,115 +172,96 @@ pub const Player = struct {
|
|||
};
|
||||
|
||||
// confidence interval (±) depends on the device
|
||||
pub fn volume(self: *Player) GetVolumeError!f32 {
|
||||
pub fn volume(self: Player) GetVolumeError!f32 {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.volume(),
|
||||
inline else => |b| b.volume(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: Channel, frame: usize, sample: anytype) void {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.writeRaw(channel, frame, sample),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeAll(self: *Player, frame: usize, value: anytype) void {
|
||||
pub fn writeAll(self: Player, frame: usize, value: anytype) void {
|
||||
for (self.channels()) |ch|
|
||||
self.write(ch, frame, value);
|
||||
}
|
||||
|
||||
pub fn write(self: *Player, channel: Channel, frame: usize, sample: anytype) void {
|
||||
pub fn write(self: Player, channel: Channel, frame: usize, sample: anytype) void {
|
||||
switch (@TypeOf(sample)) {
|
||||
u8 => self.writeU8(channel, frame, sample),
|
||||
i16 => self.writeI16(channel, frame, sample),
|
||||
i24 => self.writeI24(channel, frame, sample),
|
||||
i32 => self.writeI32(channel, frame, sample),
|
||||
f32 => self.writeF32(channel, frame, sample),
|
||||
f64 => self.writeF64(channel, frame, sample),
|
||||
f32, u8, i8, i16, i24, i32 => {},
|
||||
else => @compileError(
|
||||
\\invalid sample type. supported types are:
|
||||
\\u8, i8, i16, i24, i32, f32, f32, f64
|
||||
\\u8, i8, i16, i24, i32, f32
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeU8(self: *Player, channel: Channel, frame: usize, sample: u8) void {
|
||||
var ptr = channel.ptr + frame * self.writeStep();
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, sample),
|
||||
.i8 => self.writeRaw(channel, frame, unsignedToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, unsignedToSigned(i16, sample)),
|
||||
.i24 => self.writeRaw(channel, frame, unsignedToSigned(i24, sample)),
|
||||
.u8 => std.mem.bytesAsValue(u8, ptr[0..@sizeOf(u8)]).* = switch (@TypeOf(sample)) {
|
||||
u8 => sample,
|
||||
i8, i16, i24, i32 => signedToUnsigned(u8, sample),
|
||||
f32 => floatToUnsigned(u8, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
.i8 => std.mem.bytesAsValue(i8, ptr[0..@sizeOf(i8)]).* = switch (@TypeOf(sample)) {
|
||||
i8 => sample,
|
||||
u8 => unsignedToSigned(i8, sample),
|
||||
i16, i24, i32 => signedToSigned(i8, sample),
|
||||
f32 => floatToSigned(i8, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
.i16 => std.mem.bytesAsValue(i16, ptr[0..@sizeOf(i16)]).* = switch (@TypeOf(sample)) {
|
||||
i16 => sample,
|
||||
u8 => unsignedToSigned(i16, sample),
|
||||
i8, i24, i32 => signedToSigned(i16, sample),
|
||||
f32 => floatToSigned(i16, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
.i24 => std.mem.bytesAsValue(i24, ptr[0..@sizeOf(i24)]).* = switch (@TypeOf(sample)) {
|
||||
i24 => sample,
|
||||
u8 => unsignedToSigned(i24, sample),
|
||||
i8, i16, i32 => signedToSigned(i24, sample),
|
||||
f32 => floatToSigned(i24, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, unsignedToSigned(i32, sample)),
|
||||
.f32 => self.writeRaw(channel, frame, unsignedToFloat(f32, sample)),
|
||||
.f64 => self.writeRaw(channel, frame, unsignedToFloat(f64, sample)),
|
||||
.i32 => std.mem.bytesAsValue(i32, ptr[0..@sizeOf(i32)]).* = switch (@TypeOf(sample)) {
|
||||
i32 => sample,
|
||||
u8 => unsignedToSigned(i32, sample),
|
||||
i8, i16, i24 => signedToSigned(i32, sample),
|
||||
f32 => floatToSigned(i32, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
.f32 => std.mem.bytesAsValue(f32, ptr[0..@sizeOf(f32)]).* = switch (@TypeOf(sample)) {
|
||||
f32 => sample,
|
||||
u8 => unsignedToFloat(f32, sample),
|
||||
i8, i16, i24, i32 => signedToFloat(f32, sample),
|
||||
else => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeI16(self: *Player, channel: Channel, frame: usize, sample: i16) void {
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, signedToUnsigned(u8, sample)),
|
||||
.i8 => self.writeRaw(channel, frame, signedToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, sample),
|
||||
.i24 => self.writeRaw(channel, frame, sample),
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, sample),
|
||||
.f32 => self.writeRaw(channel, frame, signedToFloat(f32, sample)),
|
||||
.f64 => self.writeRaw(channel, frame, signedToFloat(f64, sample)),
|
||||
}
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return switch (self.data) {
|
||||
inline else => |b| b.sampleRate(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeI24(self: *Player, channel: Channel, frame: usize, sample: i24) void {
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, signedToUnsigned(u8, sample)),
|
||||
.i8 => self.writeRaw(channel, frame, signedToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, signedToSigned(i16, sample)),
|
||||
.i24 => self.writeRaw(channel, frame, sample),
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, sample),
|
||||
.f32 => self.writeRaw(channel, frame, signedToFloat(f32, sample)),
|
||||
.f64 => self.writeRaw(channel, frame, signedToFloat(f64, sample)),
|
||||
}
|
||||
pub fn channels(self: Player) []Channel {
|
||||
return switch (self.data) {
|
||||
inline else => |b| b.channels,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeI32(self: *Player, channel: Channel, frame: usize, sample: i32) void {
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, signedToUnsigned(u8, sample)),
|
||||
.i8 => self.writeRaw(channel, frame, signedToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, signedToSigned(i16, sample)),
|
||||
.i24 => self.writeRaw(channel, frame, signedToSigned(i24, sample)),
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, sample),
|
||||
.f32 => self.writeRaw(channel, frame, signedToFloat(f32, sample)),
|
||||
.f64 => self.writeRaw(channel, frame, signedToFloat(f64, sample)),
|
||||
}
|
||||
pub fn format(self: Player) Format {
|
||||
return switch (self.data) {
|
||||
inline else => |b| b.format,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeF32(self: *Player, channel: Channel, frame: usize, sample: f32) void {
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, floatToUnsigned(u8, sample)),
|
||||
.i8 => self.writeRaw(channel, frame, floatToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, floatToSigned(i16, sample)),
|
||||
.i24 => self.writeRaw(channel, frame, floatToSigned(i24, sample)),
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, floatToSigned(i32, sample)),
|
||||
.f32 => self.writeRaw(channel, frame, sample),
|
||||
.f64 => self.writeRaw(channel, frame, sample),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeF64(self: *Player, channel: Channel, frame: usize, sample: f64) void {
|
||||
switch (self.format()) {
|
||||
.u8 => self.writeRaw(channel, frame, floatToUnsigned(u8, sample)),
|
||||
.i8 => self.writeRaw(channel, frame, floatToSigned(i8, sample)),
|
||||
.i16 => self.writeRaw(channel, frame, floatToSigned(i16, sample)),
|
||||
.i24 => self.writeRaw(channel, frame, floatToSigned(i24, sample)),
|
||||
.i24_4b => @panic("TODO"),
|
||||
.i32 => self.writeRaw(channel, frame, floatToSigned(i32, sample)),
|
||||
.f32 => self.writeRaw(channel, frame, sample),
|
||||
.f64 => self.writeRaw(channel, frame, sample),
|
||||
}
|
||||
pub fn writeStep(self: Player) u8 {
|
||||
return switch (self.data) {
|
||||
inline else => |b| b.write_step,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn unsignedToSigned(comptime T: type, sample: anytype) T {
|
||||
const half = 1 << (@bitSizeOf(@TypeOf(sample)) - 1);
|
||||
|
|
@ -320,41 +299,6 @@ pub const Player = struct {
|
|||
return @floatToInt(T, sample * (half - 1) + half);
|
||||
}
|
||||
|
||||
// TODO: needs test
|
||||
// fn f32Toi24_4b(sample: f32) i32 {
|
||||
// const scaled = sample * std.math.maxInt(i32);
|
||||
// if (builtin.cpu.arch.endian() == .Little) {
|
||||
// return @floatToInt(i32, scaled);
|
||||
// } else {
|
||||
// var res: [4]u8 = undefined;
|
||||
// std.mem.writeIntSliceBig(i32, &res, @floatToInt(i32, res));
|
||||
// return @bitCast(i32, res);
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn channels(self: Player) []Channel {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.channels(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Player) Format {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.format(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return switch (self.data) {
|
||||
inline else => |*b| b.sampleRate(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn frameSize(self: Player) u8 {
|
||||
return self.format().frameSize(self.channels().len);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Device = struct {
|
||||
id: [:0]const u8,
|
||||
name: [:0]const u8,
|
||||
|
|
@ -421,7 +365,6 @@ pub const Format = enum {
|
|||
i24_4b,
|
||||
i32,
|
||||
f32,
|
||||
f64,
|
||||
|
||||
pub fn size(self: Format) u8 {
|
||||
return switch (self) {
|
||||
|
|
@ -429,7 +372,6 @@ pub const Format = enum {
|
|||
.i16 => 2,
|
||||
.i24 => 3,
|
||||
.i24_4b, .i32, .f32 => 4,
|
||||
.f64 => 8,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +381,6 @@ pub const Format = enum {
|
|||
.i16 => 2,
|
||||
.i24, .i24_4b => 3,
|
||||
.i32, .f32 => 4,
|
||||
.f64 => 8,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub const Context = struct {
|
|||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
user_data: ?*anyopaque,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: main.Context.Options) !backends.BackendContext {
|
||||
|
|
@ -44,7 +44,7 @@ pub const Context = struct {
|
|||
.default_source = null,
|
||||
.watcher = if (options.deviceChangeFn) |dcf| .{
|
||||
.deviceChangeFn = dcf,
|
||||
.userdata = options.userdata,
|
||||
.user_data = options.user_data,
|
||||
} else null,
|
||||
};
|
||||
|
||||
|
|
@ -97,13 +97,13 @@ pub const Context = struct {
|
|||
return .{ .pulseaudio = self };
|
||||
}
|
||||
|
||||
fn subscribeOp(_: ?*c.pa_context, _: c.pa_subscription_event_type_t, _: u32, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), userdata.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
fn subscribeOp(_: ?*c.pa_context, _: c.pa_subscription_event_type_t, _: u32, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), user_data.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.user_data);
|
||||
}
|
||||
|
||||
fn contextStateOp(ctx: ?*c.pa_context, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), userdata.?));
|
||||
fn contextStateOp(ctx: ?*c.pa_context, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), user_data.?));
|
||||
|
||||
self.ctx_state = c.pa_context_get_state(ctx);
|
||||
c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
|
|
@ -159,8 +159,8 @@ pub const Context = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn serverInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_server_info, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), userdata.?));
|
||||
fn serverInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_server_info, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), user_data.?));
|
||||
|
||||
defer c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
self.default_sink = self.allocator.dupeZ(u8, std.mem.span(info.*.default_sink_name)) catch return;
|
||||
|
|
@ -170,8 +170,8 @@ pub const Context = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn sinkInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_sink_info, eol: c_int, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), userdata.?));
|
||||
fn sinkInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_sink_info, eol: c_int, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), user_data.?));
|
||||
if (eol != 0) {
|
||||
c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
return;
|
||||
|
|
@ -180,8 +180,8 @@ pub const Context = struct {
|
|||
self.deviceInfoOp(info, .playback) catch return;
|
||||
}
|
||||
|
||||
fn sourceInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_source_info, eol: c_int, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), userdata.?));
|
||||
fn sourceInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_source_info, eol: c_int, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), user_data.?));
|
||||
if (eol != 0) {
|
||||
c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
return;
|
||||
|
|
@ -274,19 +274,22 @@ pub const Context = struct {
|
|||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.pulseaudio = .{
|
||||
var player = try self.allocator.create(Player);
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.main_loop = self.main_loop,
|
||||
.ctx = self.ctx,
|
||||
.stream = stream.?,
|
||||
._channels = device.channels,
|
||||
._format = format,
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.write_ptr = undefined,
|
||||
.vol = 1.0,
|
||||
},
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.user_data = options.user_data,
|
||||
.channels = device.channels,
|
||||
.format = format,
|
||||
.write_step = format.frameSize(device.channels.len),
|
||||
};
|
||||
return .{ .pulseaudio = player };
|
||||
}
|
||||
|
||||
const StreamStatus = struct {
|
||||
|
|
@ -298,8 +301,8 @@ pub const Context = struct {
|
|||
},
|
||||
};
|
||||
|
||||
fn streamStateOp(stream: ?*c.pa_stream, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*StreamStatus, @alignCast(@alignOf(*StreamStatus), userdata.?));
|
||||
fn streamStateOp(stream: ?*c.pa_stream, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*StreamStatus, @alignCast(@alignOf(*StreamStatus), user_data.?));
|
||||
|
||||
switch (c.pa_stream_get_state(stream)) {
|
||||
c.PA_STREAM_UNCONNECTED,
|
||||
|
|
@ -320,15 +323,19 @@ pub const Context = struct {
|
|||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
main_loop: *c.pa_threaded_mainloop,
|
||||
ctx: *c.pa_context,
|
||||
stream: *c.pa_stream,
|
||||
_channels: []main.Channel,
|
||||
_format: main.Format,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
write_ptr: [*]u8,
|
||||
vol: f32,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
user_data: ?*anyopaque,
|
||||
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
write_step: u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
c.pa_threaded_mainloop_lock(self.main_loop);
|
||||
|
|
@ -352,9 +359,8 @@ pub const Player = struct {
|
|||
c.pa_stream_set_write_callback(self.stream, playbackStreamWriteOp, self);
|
||||
}
|
||||
|
||||
fn playbackStreamWriteOp(_: ?*c.pa_stream, nbytes: usize, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), userdata.?));
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
fn playbackStreamWriteOp(_: ?*c.pa_stream, nbytes: usize, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), user_data.?));
|
||||
|
||||
var frames_left = nbytes;
|
||||
while (frames_left > 0) {
|
||||
|
|
@ -371,12 +377,12 @@ pub const Player = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
for (self.channels()) |*ch, i| {
|
||||
ch.*.ptr = self.write_ptr + self.format().frameSize(i);
|
||||
for (self.channels) |*ch, i| {
|
||||
ch.*.ptr = self.write_ptr + self.format.frameSize(i);
|
||||
}
|
||||
|
||||
const frames = chunk_size / self.format().frameSize(self.channels().len);
|
||||
self.writeFn(parent, frames);
|
||||
const frames = chunk_size / self.format.frameSize(self.channels.len);
|
||||
self.writeFn(self.user_data, frames);
|
||||
|
||||
if (c.pa_stream_write(self.stream, self.write_ptr, chunk_size, null, 0, c.PA_SEEK_RELATIVE) != 0) {
|
||||
if (std.debug.runtime_safety) unreachable;
|
||||
|
|
@ -422,7 +428,7 @@ pub const Player = struct {
|
|||
|
||||
var cvolume: c.pa_cvolume = undefined;
|
||||
_ = c.pa_cvolume_init(&cvolume);
|
||||
_ = c.pa_cvolume_set(&cvolume, @intCast(c_uint, self.channels().len), c.pa_sw_volume_from_linear(vol));
|
||||
_ = c.pa_cvolume_set(&cvolume, @intCast(c_uint, self.channels.len), c.pa_sw_volume_from_linear(vol));
|
||||
|
||||
performOperation(
|
||||
self.main_loop,
|
||||
|
|
@ -436,8 +442,8 @@ pub const Player = struct {
|
|||
);
|
||||
}
|
||||
|
||||
fn successOp(_: ?*c.pa_context, success: c_int, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), userdata.?));
|
||||
fn successOp(_: ?*c.pa_context, success: c_int, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), user_data.?));
|
||||
if (success == 1)
|
||||
c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
}
|
||||
|
|
@ -459,8 +465,8 @@ pub const Player = struct {
|
|||
return self.vol;
|
||||
}
|
||||
|
||||
fn sinkInputInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_sink_input_info, eol: c_int, userdata: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), userdata.?));
|
||||
fn sinkInputInfoOp(_: ?*c.pa_context, info: [*c]const c.pa_sink_input_info, eol: c_int, user_data: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), user_data.?));
|
||||
|
||||
if (eol != 0) {
|
||||
c.pa_threaded_mainloop_signal(self.main_loop, 0);
|
||||
|
|
@ -470,19 +476,6 @@ pub const Player = struct {
|
|||
self.vol = @intToFloat(f32, info.*.volume.values[0]) / @intToFloat(f32, c.PA_VOLUME_NORM);
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + self.format().frameSize(self.channels().len) * frame;
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self._channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
return self._format;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
|
|
@ -552,7 +545,7 @@ pub fn toPAFormat(format: main.Format) !c.pa_sample_format_t {
|
|||
.i32 => if (is_little) c.PA_SAMPLE_S32LE else c.PA_SAMPLE_S32BE,
|
||||
.f32 => if (is_little) c.PA_SAMPLE_FLOAT32LE else c.PA_SAMPLE_FLOAT32BE,
|
||||
|
||||
.f64, .i8 => error.Invalid,
|
||||
.i8 => error.Invalid,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub const Context = struct {
|
|||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
user_data: ?*anyopaque,
|
||||
notif_client: win32.IMMNotificationClient,
|
||||
};
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ pub const Context = struct {
|
|||
},
|
||||
.watcher = if (options.deviceChangeFn) |deviceChangeFn| .{
|
||||
.deviceChangeFn = deviceChangeFn,
|
||||
.userdata = options.userdata,
|
||||
.user_data = options.user_data,
|
||||
.notif_client = win32.IMMNotificationClient{
|
||||
.vtable = &.{
|
||||
.base = .{
|
||||
|
|
@ -108,31 +108,31 @@ pub const Context = struct {
|
|||
|
||||
fn onDeviceStateChangedCB(self: *const win32.IMMNotificationClient, _: ?[*:0]const u16, _: u32) callconv(std.os.windows.WINAPI) win32.HRESULT {
|
||||
var watcher = @fieldParentPtr(Watcher, "notif_client", self);
|
||||
watcher.deviceChangeFn(watcher.userdata);
|
||||
watcher.deviceChangeFn(watcher.user_data);
|
||||
return win32.S_OK;
|
||||
}
|
||||
|
||||
fn onDeviceAddedCB(self: *const win32.IMMNotificationClient, _: ?[*:0]const u16) callconv(std.os.windows.WINAPI) win32.HRESULT {
|
||||
var watcher = @fieldParentPtr(Watcher, "notif_client", self);
|
||||
watcher.deviceChangeFn(watcher.userdata);
|
||||
watcher.deviceChangeFn(watcher.user_data);
|
||||
return win32.S_OK;
|
||||
}
|
||||
|
||||
fn onDeviceRemovedCB(self: *const win32.IMMNotificationClient, _: ?[*:0]const u16) callconv(std.os.windows.WINAPI) win32.HRESULT {
|
||||
var watcher = @fieldParentPtr(Watcher, "notif_client", self);
|
||||
watcher.deviceChangeFn(watcher.userdata);
|
||||
watcher.deviceChangeFn(watcher.user_data);
|
||||
return win32.S_OK;
|
||||
}
|
||||
|
||||
fn onDefaultDeviceChangedCB(self: *const win32.IMMNotificationClient, _: win32.DataFlow, _: win32.Role, _: ?[*:0]const u16) callconv(std.os.windows.WINAPI) win32.HRESULT {
|
||||
var watcher = @fieldParentPtr(Watcher, "notif_client", self);
|
||||
watcher.deviceChangeFn(watcher.userdata);
|
||||
watcher.deviceChangeFn(watcher.user_data);
|
||||
return win32.S_OK;
|
||||
}
|
||||
|
||||
fn onPropertyValueChangedCB(self: *const win32.IMMNotificationClient, _: ?[*:0]const u16, _: win32.PROPERTYKEY) callconv(std.os.windows.WINAPI) win32.HRESULT {
|
||||
var watcher = @fieldParentPtr(Watcher, "notif_client", self);
|
||||
watcher.deviceChangeFn(watcher.userdata);
|
||||
watcher.deviceChangeFn(watcher.user_data);
|
||||
return win32.S_OK;
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +303,17 @@ pub const Context = struct {
|
|||
},
|
||||
.formats = blk: {
|
||||
var audio_client: ?*win32.IAudioClient = null;
|
||||
var audio_client3: ?*win32.IAudioClient3 = null;
|
||||
hr = imm_device.?.Activate(win32.IID_IAudioClient3, win32.CLSCTX_ALL, null, @ptrCast(?*?*anyopaque, &audio_client3));
|
||||
if (hr == win32.S_OK) {
|
||||
hr = audio_client3.?.QueryInterface(win32.IID_IAudioClient, @ptrCast(?*?*anyopaque, &audio_client));
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
win32.E_NOINTERFACE => unreachable,
|
||||
win32.E_POINTER => unreachable,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
} else {
|
||||
hr = imm_device.?.Activate(win32.IID_IAudioClient, win32.CLSCTX_ALL, null, @ptrCast(?*?*anyopaque, &audio_client));
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
|
|
@ -313,7 +324,7 @@ pub const Context = struct {
|
|||
win32.AUDCLNT_E_DEVICE_INVALIDATED => unreachable,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
defer _ = audio_client.?.Release();
|
||||
}
|
||||
|
||||
var fmt_arr = std.ArrayList(main.Format).init(self.allocator);
|
||||
var closest_match: ?*win32.WAVEFORMATEX = null;
|
||||
|
|
@ -413,7 +424,7 @@ pub const Context = struct {
|
|||
.u8, .i16, .i24, .i24_4b, .i32 => {
|
||||
wf.SubFormat = win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*;
|
||||
},
|
||||
.f32, .f64 => {
|
||||
.f32 => {
|
||||
wf.SubFormat = win32.CLSID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.*;
|
||||
},
|
||||
.i8 => return error.Invalid,
|
||||
|
|
@ -439,6 +450,17 @@ pub const Context = struct {
|
|||
}
|
||||
|
||||
var audio_client: ?*win32.IAudioClient = null;
|
||||
var audio_client3: ?*win32.IAudioClient3 = null;
|
||||
hr = imm_device.?.Activate(win32.IID_IAudioClient3, win32.CLSCTX_ALL, null, @ptrCast(?*?*anyopaque, &audio_client3));
|
||||
if (hr == win32.S_OK) {
|
||||
hr = audio_client3.?.QueryInterface(win32.IID_IAudioClient, @ptrCast(?*?*anyopaque, &audio_client));
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
win32.E_NOINTERFACE => unreachable,
|
||||
win32.E_POINTER => unreachable,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
} else {
|
||||
hr = imm_device.?.Activate(win32.IID_IAudioClient, win32.CLSCTX_ALL, null, @ptrCast(?*?*anyopaque, &audio_client));
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
|
|
@ -446,12 +468,13 @@ pub const Context = struct {
|
|||
win32.E_INVALIDARG => unreachable,
|
||||
win32.E_NOINTERFACE => unreachable,
|
||||
win32.E_OUTOFMEMORY => return error.OutOfMemory,
|
||||
win32.AUDCLNT_E_DEVICE_INVALIDATED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_DEVICE_INVALIDATED => unreachable,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
}
|
||||
|
||||
const format = device.preferredFormat(options.format);
|
||||
const sample_rate = device.sample_rate.clamp(options.sample_rate);
|
||||
const sample_rate = device.sample_rate.min;
|
||||
|
||||
const wave_format = win32.WAVEFORMATEXTENSIBLE{
|
||||
.Format = .{
|
||||
|
|
@ -470,10 +493,35 @@ pub const Context = struct {
|
|||
.SubFormat = toSubFormat(format) catch return error.OpeningDevice,
|
||||
};
|
||||
|
||||
if (audio_client3) |_| {
|
||||
hr = audio_client3.?.InitializeSharedAudioStream(
|
||||
win32.AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
0,
|
||||
@ptrCast(?*const win32.WAVEFORMATEX, @alignCast(@alignOf(*win32.WAVEFORMATEX), &wave_format)),
|
||||
null,
|
||||
);
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
win32.E_OUTOFMEMORY => return error.OutOfMemory,
|
||||
win32.E_POINTER => unreachable,
|
||||
win32.E_INVALIDARG => unreachable,
|
||||
win32.AUDCLNT_E_ALREADY_INITIALIZED => unreachable,
|
||||
win32.AUDCLNT_E_WRONG_ENDPOINT_TYPE => unreachable,
|
||||
win32.AUDCLNT_E_CPUUSAGE_EXCEEDED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_DEVICE_INVALIDATED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_DEVICE_IN_USE => unreachable,
|
||||
win32.AUDCLNT_E_ENGINE_FORMAT_LOCKED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_ENGINE_PERIODICITY_LOCKED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_ENDPOINT_CREATE_FAILED => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_INVALID_DEVICE_PERIOD => return error.OpeningDevice,
|
||||
win32.AUDCLNT_E_UNSUPPORTED_FORMAT => unreachable,
|
||||
win32.AUDCLNT_E_SERVICE_NOT_RUNNING => return error.OpeningDevice,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
} else {
|
||||
hr = audio_client.?.Initialize(
|
||||
.SHARED,
|
||||
win32.AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
win32.AUDCLNT_STREAMFLAGS_NOPERSIST,
|
||||
win32.AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
0,
|
||||
0,
|
||||
@ptrCast(?*const win32.WAVEFORMATEX, @alignCast(@alignOf(*win32.WAVEFORMATEX), &wave_format)),
|
||||
|
|
@ -499,7 +547,7 @@ pub const Context = struct {
|
|||
win32.AUDCLNT_E_SERVICE_NOT_RUNNING => return error.OpeningDevice,
|
||||
else => return error.OpeningDevice,
|
||||
}
|
||||
errdefer _ = audio_client.?.Release();
|
||||
}
|
||||
|
||||
var render_client: ?*win32.IAudioRenderClient = null;
|
||||
hr = audio_client.?.GetService(win32.IID_IAudioRenderClient, @ptrCast(?*?*anyopaque, &render_client));
|
||||
|
|
@ -539,35 +587,38 @@ pub const Context = struct {
|
|||
else => return error.OpeningDevice,
|
||||
}
|
||||
|
||||
return .{
|
||||
.wasapi = .{
|
||||
var player = try self.allocator.create(Player);
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.thread = undefined,
|
||||
.mutex = .{},
|
||||
._channels = device.channels,
|
||||
._format = format,
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.audio_client = audio_client,
|
||||
.audio_client3 = audio_client3,
|
||||
.simple_volume = simple_volume,
|
||||
.imm_device = imm_device,
|
||||
.render_client = render_client,
|
||||
.ready_event = ready_event,
|
||||
.is_paused = false,
|
||||
.vol = 1.0,
|
||||
.aborted = .{ .value = false },
|
||||
},
|
||||
.is_paused = false,
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.user_data = options.user_data,
|
||||
.channels = device.channels,
|
||||
.format = format,
|
||||
.write_step = format.frameSize(device.channels.len),
|
||||
};
|
||||
return .{ .wasapi = player };
|
||||
}
|
||||
|
||||
fn toSubFormat(format: main.Format) !win32.Guid {
|
||||
return switch (format) {
|
||||
.u8 => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.i16 => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.i24 => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.i24_4b => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.i32 => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.u8,
|
||||
.i16,
|
||||
.i24,
|
||||
.i24_4b,
|
||||
.i32,
|
||||
=> win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*,
|
||||
.f32 => win32.CLSID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.*,
|
||||
.f64 => win32.CLSID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.*,
|
||||
else => error.Invalid,
|
||||
};
|
||||
}
|
||||
|
|
@ -599,20 +650,24 @@ pub const Context = struct {
|
|||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
thread: std.Thread,
|
||||
mutex: std.Thread.Mutex,
|
||||
_channels: []main.Channel,
|
||||
_format: main.Format,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
simple_volume: ?*win32.ISimpleAudioVolume,
|
||||
imm_device: ?*win32.IMMDevice,
|
||||
audio_client: ?*win32.IAudioClient,
|
||||
audio_client3: ?*win32.IAudioClient3,
|
||||
render_client: ?*win32.IAudioRenderClient,
|
||||
ready_event: win32.HANDLE,
|
||||
aborted: std.atomic.Atomic(bool),
|
||||
is_paused: bool,
|
||||
vol: f32,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
user_data: ?*anyopaque,
|
||||
|
||||
write_step: u8,
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.aborted.store(true, .Unordered);
|
||||
|
|
@ -620,7 +675,9 @@ pub const Player = struct {
|
|||
_ = self.simple_volume.?.Release();
|
||||
_ = self.render_client.?.Release();
|
||||
_ = self.audio_client.?.Release();
|
||||
_ = self.audio_client3.?.Release();
|
||||
_ = self.imm_device.?.Release();
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn start(self: *Player) !void {
|
||||
|
|
@ -635,8 +692,6 @@ pub const Player = struct {
|
|||
}
|
||||
|
||||
fn writeLoop(self: *Player) void {
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
|
||||
var hr = self.audio_client.?.Start();
|
||||
switch (hr) {
|
||||
win32.S_OK => {},
|
||||
|
|
@ -689,11 +744,11 @@ pub const Player = struct {
|
|||
else => unreachable,
|
||||
}
|
||||
|
||||
for (self.channels()) |*ch, i| {
|
||||
ch.*.ptr = data + self.format().frameSize(i);
|
||||
for (self.channels) |*ch, i| {
|
||||
ch.*.ptr = data + self.format.frameSize(i);
|
||||
}
|
||||
|
||||
self.writeFn(parent, frames);
|
||||
self.writeFn(self.user_data, frames);
|
||||
|
||||
hr = self.render_client.?.ReleaseBuffer(frames, 0);
|
||||
switch (hr) {
|
||||
|
|
@ -766,20 +821,6 @@ pub const Player = struct {
|
|||
}
|
||||
return vol;
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + frame * self.format().frameSize(self.channels().len);
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self._channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
return self._format;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1272,6 +1272,162 @@ pub const IAudioClient = extern struct {
|
|||
}
|
||||
pub usingnamespace MethodMixin(@This());
|
||||
};
|
||||
pub const AUDCLNT_STREAMOPTIONS = enum(u32) {
|
||||
NONE = 0,
|
||||
RAW = 1,
|
||||
MATCH_FORMAT = 2,
|
||||
AMBISONICS = 4,
|
||||
};
|
||||
pub const AudioClientProperties = extern struct {
|
||||
cbSize: u32,
|
||||
bIsOffload: BOOL,
|
||||
eCategory: AUDIO_STREAM_CATEGORY,
|
||||
Options: AUDCLNT_STREAMOPTIONS,
|
||||
};
|
||||
pub const AUDIO_STREAM_CATEGORY = enum(i32) {
|
||||
Other = 0,
|
||||
ForegroundOnlyMedia = 1,
|
||||
Communications = 3,
|
||||
Alerts = 4,
|
||||
SoundEffects = 5,
|
||||
GameEffects = 6,
|
||||
GameMedia = 7,
|
||||
GameChat = 8,
|
||||
Speech = 9,
|
||||
Movie = 10,
|
||||
Media = 11,
|
||||
FarFieldSpeech = 12,
|
||||
UniformSpeech = 13,
|
||||
VoiceTyping = 14,
|
||||
};
|
||||
const IID_IAudioClient2 = &Guid.initString("726778cd-f60a-4eda-82de-e47610cd78aa");
|
||||
pub const IAudioClient2 = extern struct {
|
||||
pub const VTable = extern struct {
|
||||
base: IAudioClient.VTable,
|
||||
IsOffloadCapable: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient2,
|
||||
Category: AUDIO_STREAM_CATEGORY,
|
||||
pbOffloadCapable: ?*BOOL,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient2,
|
||||
Category: AUDIO_STREAM_CATEGORY,
|
||||
pbOffloadCapable: ?*BOOL,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
SetClientProperties: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient2,
|
||||
pProperties: ?*const AudioClientProperties,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient2,
|
||||
pProperties: ?*const AudioClientProperties,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
GetBufferSizeLimits: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient2,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
bEventDriven: BOOL,
|
||||
phnsMinBufferDuration: ?*i64,
|
||||
phnsMaxBufferDuration: ?*i64,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient2,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
bEventDriven: BOOL,
|
||||
phnsMinBufferDuration: ?*i64,
|
||||
phnsMaxBufferDuration: ?*i64,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
};
|
||||
vtable: *const VTable,
|
||||
pub fn MethodMixin(comptime T: type) type {
|
||||
return struct {
|
||||
pub usingnamespace IAudioClient.MethodMixin(T);
|
||||
pub inline fn IsOffloadCapable(self: *const T, Category: AUDIO_STREAM_CATEGORY, pbOffloadCapable: ?*BOOL) HRESULT {
|
||||
return @ptrCast(*const IAudioClient2.VTable, self.vtable).IsOffloadCapable(@ptrCast(*const IAudioClient2, self), Category, pbOffloadCapable);
|
||||
}
|
||||
pub inline fn SetClientProperties(self: *const T, pProperties: ?*const AudioClientProperties) HRESULT {
|
||||
return @ptrCast(*const IAudioClient2.VTable, self.vtable).SetClientProperties(@ptrCast(*const IAudioClient2, self), pProperties);
|
||||
}
|
||||
pub inline fn GetBufferSizeLimits(self: *const T, pFormat: ?*const WAVEFORMATEX, bEventDriven: BOOL, phnsMinBufferDuration: ?*i64, phnsMaxBufferDuration: ?*i64) HRESULT {
|
||||
return @ptrCast(*const IAudioClient2.VTable, self.vtable).GetBufferSizeLimits(@ptrCast(*const IAudioClient2, self), pFormat, bEventDriven, phnsMinBufferDuration, phnsMaxBufferDuration);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub usingnamespace MethodMixin(@This());
|
||||
};
|
||||
pub const IID_IAudioClient3 = &Guid.initString("7ed4ee07-8e67-4cd4-8c1a-2b7a5987ad42");
|
||||
pub const IAudioClient3 = extern struct {
|
||||
pub const VTable = extern struct {
|
||||
base: IAudioClient2.VTable,
|
||||
GetSharedModeEnginePeriod: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient3,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
pDefaultPeriodInFrames: ?*u32,
|
||||
pFundamentalPeriodInFrames: ?*u32,
|
||||
pMinPeriodInFrames: ?*u32,
|
||||
pMaxPeriodInFrames: ?*u32,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient3,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
pDefaultPeriodInFrames: ?*u32,
|
||||
pFundamentalPeriodInFrames: ?*u32,
|
||||
pMinPeriodInFrames: ?*u32,
|
||||
pMaxPeriodInFrames: ?*u32,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
GetCurrentSharedModeEnginePeriod: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient3,
|
||||
ppFormat: ?*?*WAVEFORMATEX,
|
||||
pCurrentPeriodInFrames: ?*u32,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient3,
|
||||
ppFormat: ?*?*WAVEFORMATEX,
|
||||
pCurrentPeriodInFrames: ?*u32,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
InitializeSharedAudioStream: switch (@import("builtin").zig_backend) {
|
||||
.stage1 => fn (
|
||||
self: *const IAudioClient3,
|
||||
StreamFlags: u32,
|
||||
PeriodInFrames: u32,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
AudioSessionGuid: ?*const Guid,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
else => *const fn (
|
||||
self: *const IAudioClient3,
|
||||
StreamFlags: u32,
|
||||
PeriodInFrames: u32,
|
||||
pFormat: ?*const WAVEFORMATEX,
|
||||
AudioSessionGuid: ?*const Guid,
|
||||
) callconv(WINAPI) HRESULT,
|
||||
},
|
||||
};
|
||||
vtable: *const VTable,
|
||||
pub fn MethodMixin(comptime T: type) type {
|
||||
return struct {
|
||||
pub usingnamespace IAudioClient2.MethodMixin(T);
|
||||
pub inline fn GetSharedModeEnginePeriod(self: *const T, pFormat: ?*const WAVEFORMATEX, pDefaultPeriodInFrames: ?*u32, pFundamentalPeriodInFrames: ?*u32, pMinPeriodInFrames: ?*u32, pMaxPeriodInFrames: ?*u32) HRESULT {
|
||||
return @ptrCast(*const IAudioClient3.VTable, self.vtable).GetSharedModeEnginePeriod(@ptrCast(*const IAudioClient3, self), pFormat, pDefaultPeriodInFrames, pFundamentalPeriodInFrames, pMinPeriodInFrames, pMaxPeriodInFrames);
|
||||
}
|
||||
pub inline fn GetCurrentSharedModeEnginePeriod(self: *const T, ppFormat: ?*?*WAVEFORMATEX, pCurrentPeriodInFrames: ?*u32) HRESULT {
|
||||
return @ptrCast(*const IAudioClient3.VTable, self.vtable).GetCurrentSharedModeEnginePeriod(@ptrCast(*const IAudioClient3, self), ppFormat, pCurrentPeriodInFrames);
|
||||
}
|
||||
pub inline fn InitializeSharedAudioStream(self: *const T, StreamFlags: u32, PeriodInFrames: u32, pFormat: ?*const WAVEFORMATEX, AudioSessionGuid: ?*const Guid) HRESULT {
|
||||
return @ptrCast(*const IAudioClient3.VTable, self.vtable).InitializeSharedAudioStream(@ptrCast(*const IAudioClient3, self), StreamFlags, PeriodInFrames, pFormat, AudioSessionGuid);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub usingnamespace MethodMixin(@This());
|
||||
};
|
||||
pub extern "ole32" fn CoTaskMemFree(pv: ?*anyopaque) callconv(WINAPI) void;
|
||||
pub const IID_IAudioRenderClient = &Guid.initString("f294acfc-3146-4483-a7bf-addca7c260e2");
|
||||
pub const IAudioRenderClient = extern struct {
|
||||
|
|
|
|||
236
libs/sysaudio/src/webaudio.zig
Normal file
236
libs/sysaudio/src/webaudio.zig
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
const std = @import("std");
|
||||
const js = @import("sysjs");
|
||||
const main = @import("main.zig");
|
||||
const backends = @import("backends.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
const channel_size = 1024;
|
||||
const channel_size_bytes = channel_size * @sizeOf(f32);
|
||||
|
||||
const dummy_playback = main.Device{
|
||||
.id = "default-playback",
|
||||
.name = "Default Device",
|
||||
.mode = .playback,
|
||||
.channels = undefined,
|
||||
.formats = &.{.f32},
|
||||
.sample_rate = .{
|
||||
.min = 8_000,
|
||||
.max = 96_000,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Context = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
devices_info: util.DevicesInfo,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: main.Context.Options) !backends.BackendContext {
|
||||
_ = options;
|
||||
|
||||
const audio_context = js.global().get("AudioContext");
|
||||
if (audio_context.is(.undefined))
|
||||
return error.ConnectionRefused;
|
||||
|
||||
var self = try allocator.create(Context);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.devices_info = util.DevicesInfo.init(),
|
||||
};
|
||||
|
||||
return .{ .webaudio = self };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.list.deinit(self.allocator);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn refresh(self: *Context) !void {
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.clear(self.allocator);
|
||||
|
||||
try self.devices_info.list.append(self.allocator, dummy_playback);
|
||||
self.devices_info.list.items[0].channels = try self.allocator.alloc(main.Channel, 2);
|
||||
self.devices_info.list.items[0].channels[0] = .{ .id = .front_left };
|
||||
self.devices_info.list.items[0].channels[1] = .{ .id = .front_right };
|
||||
self.devices_info.setDefault(.playback, 0);
|
||||
}
|
||||
|
||||
pub fn devices(self: Context) []const main.Device {
|
||||
return self.devices_info.list.items;
|
||||
}
|
||||
|
||||
pub fn defaultDevice(self: Context, mode: main.Device.Mode) ?main.Device {
|
||||
return self.devices_info.default(mode);
|
||||
}
|
||||
|
||||
pub fn createPlayer(self: *Context, device: main.Device, writeFn: main.WriteFn, options: main.Player.Options) !backends.BackendPlayer {
|
||||
const context_options = js.createMap();
|
||||
defer context_options.deinit();
|
||||
context_options.set("sampleRate", js.createNumber(@intToFloat(f64, options.sample_rate)));
|
||||
|
||||
const audio_context = js.constructType("AudioContext", &.{context_options.toValue()});
|
||||
const gain_node = audio_context.call("createGain", &.{
|
||||
js.createNumber(1),
|
||||
js.createNumber(0),
|
||||
js.createNumber(@intToFloat(f64, device.channels.len)),
|
||||
}).view(.object);
|
||||
const process_node = audio_context.call("createScriptProcessor", &.{
|
||||
js.createNumber(channel_size),
|
||||
js.createNumber(@intToFloat(f64, device.channels.len)),
|
||||
}).view(.object);
|
||||
|
||||
var player = try self.allocator.create(Player);
|
||||
errdefer self.allocator.destroy(player);
|
||||
|
||||
var captures = try self.allocator.alloc(js.Value, 1);
|
||||
captures[0] = js.createNumber(@intToFloat(f64, @ptrToInt(player)));
|
||||
|
||||
const document = js.global().get("document").view(.object);
|
||||
defer document.deinit();
|
||||
const click_event_str = js.createString("click");
|
||||
defer click_event_str.deinit();
|
||||
const resume_on_click = js.createFunction(Player.resumeOnClick, captures);
|
||||
_ = document.call("addEventListener", &.{ click_event_str.toValue(), resume_on_click.toValue() });
|
||||
|
||||
const audio_process_event = js.createFunction(Player.audioProcessEvent, captures);
|
||||
defer audio_process_event.deinit();
|
||||
process_node.set("onaudioprocess", audio_process_event.toValue());
|
||||
|
||||
player.* = .{
|
||||
.allocator = self.allocator,
|
||||
.audio_context = audio_context,
|
||||
.process_node = process_node,
|
||||
.gain_node = gain_node,
|
||||
.process_captures = captures,
|
||||
.resume_on_click = resume_on_click,
|
||||
.buf = try self.allocator.alloc(u8, channel_size_bytes * device.channels.len),
|
||||
.buf_js = js.constructType("Uint8Array", &.{js.createNumber(channel_size_bytes)}),
|
||||
.is_paused = false,
|
||||
.writeFn = writeFn,
|
||||
.user_data = options.user_data,
|
||||
.sample_rate = options.sample_rate,
|
||||
.channels = device.channels,
|
||||
.format = .f32,
|
||||
.write_step = @sizeOf(f32),
|
||||
};
|
||||
|
||||
for (player.channels) |*ch, i| {
|
||||
ch.*.ptr = player.buf.ptr + i * channel_size_bytes;
|
||||
}
|
||||
|
||||
return .{ .webaudio = player };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
audio_context: js.Object,
|
||||
process_node: js.Object,
|
||||
gain_node: js.Object,
|
||||
process_captures: []js.Value,
|
||||
resume_on_click: js.Function,
|
||||
buf: []u8,
|
||||
buf_js: js.Object,
|
||||
is_paused: bool,
|
||||
writeFn: main.WriteFn,
|
||||
user_data: ?*anyopaque,
|
||||
sample_rate: u24,
|
||||
|
||||
channels: []main.Channel,
|
||||
format: main.Format,
|
||||
write_step: u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.resume_on_click.deinit();
|
||||
self.buf_js.deinit();
|
||||
self.gain_node.deinit();
|
||||
self.process_node.deinit();
|
||||
self.audio_context.deinit();
|
||||
self.allocator.free(self.process_captures);
|
||||
self.allocator.free(self.buf);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn start(self: Player) !void {
|
||||
const destination = self.audio_context.get("destination").view(.object);
|
||||
defer destination.deinit();
|
||||
_ = self.gain_node.call("connect", &.{destination.toValue()});
|
||||
_ = self.process_node.call("connect", &.{self.gain_node.toValue()});
|
||||
}
|
||||
|
||||
fn resumeOnClick(args: js.Object, _: usize, captures: []js.Value) js.Value {
|
||||
const self = @intToPtr(*Player, @floatToInt(usize, captures[0].view(.num)));
|
||||
self.play() catch {};
|
||||
|
||||
const document = js.global().get("document").view(.object);
|
||||
defer document.deinit();
|
||||
|
||||
const event = args.getIndex(0).view(.object);
|
||||
defer event.deinit();
|
||||
_ = document.call("removeEventListener", &.{ event.toValue(), self.resume_on_click.toValue() });
|
||||
|
||||
return js.createUndefined();
|
||||
}
|
||||
|
||||
fn audioProcessEvent(args: js.Object, _: usize, captures: []js.Value) js.Value {
|
||||
const self = @intToPtr(*Player, @floatToInt(usize, captures[0].view(.num)));
|
||||
|
||||
const event = args.getIndex(0).view(.object);
|
||||
defer event.deinit();
|
||||
const output_buffer = event.get("outputBuffer").view(.object);
|
||||
defer output_buffer.deinit();
|
||||
|
||||
self.writeFn(self.user_data, channel_size);
|
||||
|
||||
for (self.channels) |_, i| {
|
||||
self.buf_js.copyBytes(self.buf[i * channel_size_bytes .. (i + 1) * channel_size_bytes]);
|
||||
const buf_f32_js = js.constructType("Float32Array", &.{ self.buf_js.get("buffer"), self.buf_js.get("byteOffset"), js.createNumber(channel_size) });
|
||||
defer buf_f32_js.deinit();
|
||||
_ = output_buffer.call("copyToChannel", &.{ buf_f32_js.toValue(), js.createNumber(@intToFloat(f64, i)) });
|
||||
}
|
||||
|
||||
return js.createUndefined();
|
||||
}
|
||||
|
||||
pub fn play(self: *Player) !void {
|
||||
_ = self.audio_context.call("resume", &.{js.createUndefined()});
|
||||
self.is_paused = false;
|
||||
}
|
||||
|
||||
pub fn pause(self: *Player) !void {
|
||||
_ = self.audio_context.call("suspend", &.{js.createUndefined()});
|
||||
self.is_paused = true;
|
||||
}
|
||||
|
||||
pub fn paused(self: Player) bool {
|
||||
return self.is_paused;
|
||||
}
|
||||
|
||||
pub fn setVolume(self: *Player, vol: f32) !void {
|
||||
const gain = self.gain_node.get("gain").view(.object);
|
||||
defer gain.deinit();
|
||||
gain.set("value", js.createNumber(vol));
|
||||
}
|
||||
|
||||
pub fn volume(self: Player) !f32 {
|
||||
const gain = self.gain_node.get("gain").view(.object);
|
||||
defer gain.deinit();
|
||||
return @floatCast(f32, gain.get("value").view(.num));
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
};
|
||||
|
||||
fn freeDevice(allocator: std.mem.Allocator, device: main.Device) void {
|
||||
allocator.free(device.channels);
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue