sysaudio: add webaudio backend

This commit is contained in:
Ali Chraghi 2022-12-25 13:13:00 +03:30 committed by Stephen Gutekanst
parent 052d3a7da8
commit f9b3ac2106
11 changed files with 841 additions and 487 deletions

View file

@ -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,187 +172,133 @@ 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 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 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),
}
}
fn unsignedToSigned(comptime T: type, sample: anytype) T {
const half = 1 << (@bitSizeOf(@TypeOf(sample)) - 1);
const trunc = @bitSizeOf(T) - @bitSizeOf(@TypeOf(sample));
return @intCast(T, sample -% half) << trunc;
}
fn unsignedToFloat(comptime T: type, sample: anytype) T {
const max_int = std.math.maxInt(@TypeOf(sample)) + 1.0;
return (@intToFloat(T, sample) - max_int) * 1.0 / max_int;
}
fn signedToSigned(comptime T: type, sample: anytype) T {
const trunc = @bitSizeOf(@TypeOf(sample)) - @bitSizeOf(T);
return @intCast(T, sample >> trunc);
}
fn signedToUnsigned(comptime T: type, sample: anytype) T {
const half = 1 << (@bitSizeOf(T) - 1);
const trunc = @bitSizeOf(@TypeOf(sample)) - @bitSizeOf(T);
return @intCast(T, (sample >> trunc) + half);
}
fn signedToFloat(comptime T: type, sample: anytype) T {
const max_int = std.math.maxInt(@TypeOf(sample)) + 1.0;
return @intToFloat(T, sample) * 1.0 / max_int;
}
fn floatToSigned(comptime T: type, sample: f64) T {
return @floatToInt(T, sample * std.math.maxInt(T));
}
fn floatToUnsigned(comptime T: type, sample: f64) T {
const half = 1 << @bitSizeOf(T) - 1;
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(),
inline else => |b| b.channels,
};
}
pub fn format(self: Player) Format {
return switch (self.data) {
inline else => |*b| b.format(),
inline else => |b| b.format,
};
}
pub fn sampleRate(self: Player) u24 {
pub fn writeStep(self: Player) u8 {
return switch (self.data) {
inline else => |*b| b.sampleRate(),
inline else => |b| b.write_step,
};
}
pub fn frameSize(self: Player) u8 {
return self.format().frameSize(self.channels().len);
}
};
fn unsignedToSigned(comptime T: type, sample: anytype) T {
const half = 1 << (@bitSizeOf(@TypeOf(sample)) - 1);
const trunc = @bitSizeOf(T) - @bitSizeOf(@TypeOf(sample));
return @intCast(T, sample -% half) << trunc;
}
fn unsignedToFloat(comptime T: type, sample: anytype) T {
const max_int = std.math.maxInt(@TypeOf(sample)) + 1.0;
return (@intToFloat(T, sample) - max_int) * 1.0 / max_int;
}
fn signedToSigned(comptime T: type, sample: anytype) T {
const trunc = @bitSizeOf(@TypeOf(sample)) - @bitSizeOf(T);
return @intCast(T, sample >> trunc);
}
fn signedToUnsigned(comptime T: type, sample: anytype) T {
const half = 1 << (@bitSizeOf(T) - 1);
const trunc = @bitSizeOf(@TypeOf(sample)) - @bitSizeOf(T);
return @intCast(T, (sample >> trunc) + half);
}
fn signedToFloat(comptime T: type, sample: anytype) T {
const max_int = std.math.maxInt(@TypeOf(sample)) + 1.0;
return @intToFloat(T, sample) * 1.0 / max_int;
}
fn floatToSigned(comptime T: type, sample: f64) T {
return @floatToInt(T, sample * std.math.maxInt(T));
}
fn floatToUnsigned(comptime T: type, sample: f64) T {
const half = 1 << @bitSizeOf(T) - 1;
return @floatToInt(T, sample * (half - 1) + half);
}
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,
};
}