Compare commits

8 Commits

9 changed files with 119 additions and 203 deletions

0
.gitmodules vendored
View File

View File

@@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
// Later on we'll use this module as the root module of a test executable // Later on we'll use this module as the root module of a test executable
// which requires us to specify a target. // which requires us to specify a target.
.target = target, .target = target,
.link_libc = true, .optimize = optimize,
}); });
// Here we define an executable. An executable needs to have a root module // Here we define an executable. An executable needs to have a root module
@@ -58,7 +58,11 @@ pub fn build(b: *std.Build) void {
// //
// If neither case applies to you, feel free to delete the declaration you // If neither case applies to you, feel free to delete the declaration you
// don't need and to put everything under a single module. // don't need and to put everything under a single module.
const exe = b.addExecutable(.{ .name = "zba", .root_module = exe_mod }); const exe = b.addExecutable(.{
.name = "zba",
.root_module = exe_mod,
.use_llvm = true,
});
const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl3_opengl3 }); const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl3_opengl3 });
const sdl = b.dependency("sdl", .{ .target = target, .optimize = optimize, .preferred_linkage = .static }); const sdl = b.dependency("sdl", .{ .target = target, .optimize = optimize, .preferred_linkage = .static });

View File

@@ -1,36 +0,0 @@
$SDL2Version = "2.30.0"
$ArchiveFile = ".\SDL2-devel-mingw.zip"
$Json = @"
{
"x86_64-windows-gnu": {
"include": ".build_config\\SDL2\\include",
"libs": ".build_config\\SDL2\\lib",
"bin": ".build_config\\SDL2\\bin"
}
}
"@
New-Item -Force -ItemType Directory -Path .\.build_config
Set-Location -Path .build_config -PassThru
if (!(Test-Path -PathType Leaf $ArchiveFile)) {
Invoke-WebRequest "https://github.com/libsdl-org/SDL/releases/download/release-$SDL2Version/SDL2-devel-$SDL2Version-mingw.zip" -OutFile $ArchiveFile
}
Expand-Archive $ArchiveFile
if (Test-Path -PathType Container .\SDL2) {
Remove-Item -Recurse .\SDL2
}
New-Item -Force -ItemType Directory -Path .\SDL2
Get-ChildItem -Path ".\SDL2-devel-mingw\SDL2-$SDL2Version\x86_64-w64-mingw32" | Move-Item -Destination .\SDL2
# #include <SDL.h>
Move-Item -Force -Path .\SDL2\include\SDL2\* -Destination .\SDL2\include
Remove-Item -Force .\SDL2\include\SDL2
New-Item -Force .\sdl.json -Value $Json
Remove-Item -Recurse .\SDL2-devel-mingw
Set-Location -Path .. -PassThru

View File

@@ -50,13 +50,23 @@ pub fn config() *const Config {
} }
/// Reads a config file and then loads it into the global state /// Reads a config file and then loads it into the global state
pub fn load(allocator: Allocator, file_path: []const u8) !void { pub fn load(allocator: Allocator, config_path: []const u8) !void {
var config_file = try std.fs.cwd().openFile(file_path, .{}); var dir = try std.fs.openDirAbsolute(config_path, .{});
defer config_file.close(); defer dir.close();
log.info("loaded from {s}", .{file_path}); const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos()); var file = try dir.openFile(sub_path, .{});
defer file.close();
{
const path = try std.fs.path.join(allocator, &.{ config_path, sub_path });
defer allocator.free(path);
log.info("loaded from {s}", .{path});
}
const contents = try file.readToEndAlloc(allocator, try file.getEndPos());
defer allocator.free(contents); defer allocator.free(contents);
// FIXME(2025-09-22): re-enable // FIXME(2025-09-22): re-enable

View File

@@ -247,7 +247,9 @@ pub const Apu = struct {
sampling_cycle: u2, sampling_cycle: u2,
stream: *c.SDL_AudioStream, // NB: This AudioStream is owned by platform.zig
stream: ?*c.SDL_AudioStream = null,
sched: *Scheduler, sched: *Scheduler,
fs: FrameSequencer, fs: FrameSequencer,
@@ -273,7 +275,6 @@ pub const Apu = struct {
.sampling_cycle = 0b00, .sampling_cycle = 0b00,
.sched = sched, .sched = sched,
.stream = undefined, // FIXME: bad practice
.capacitor = 0, .capacitor = 0,
.fs = FrameSequencer.init(), .fs = FrameSequencer.init(),
@@ -461,32 +462,23 @@ pub const Apu = struct {
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler(); if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16)); const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16));
if (!ret) @panic("TODO: Failed to put i16s into SDL Audio Queue"); if (!ret) std.debug.panic("failed to append to sample queue. SDL Error: {s}", .{c.SDL_GetError()});
} }
fn replaceSDLResampler(self: *Self) void { fn replaceSDLResampler(self: *Self) void {
@branchHint(.cold); const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
_ = self; log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
@panic("TODO: Implement Multiple Sample Rates..."); self.sampling_cycle = self.bias.sampling_cycle.read();
// const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read()); const desired: c.SDL_AudioSpec = .{
// log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate }); .channels = 2,
.format = c.SDL_AUDIO_S16LE,
.freq = @intCast(Self.sampleRate(self.sampling_cycle)),
};
// // Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler const ret = c.SDL_SetAudioStreamFormat(self.stream, &desired, null);
// // FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one if (!ret) std.debug.panic("failed to change sample rate. SDL Error: {s}", .{c.SDL_GetError()});
// const old_stream = self.stream;
// defer c.SDL_DestroyAudioStream(old_stream);
// self.sampling_cycle = self.bias.sampling_cycle.read();
// var desired: c.SDL_AudioSpec = std.mem.zeroes(c.SDL_AudioSpec);
// desired.format = c.SDL_AUDIO_S16;
// desired.channels = 2;
// desired.freq = @intCast(sample_rate);
// const new_stream = c.SDL_OpenAudioDeviceStream(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, null, null) orelse @panic("TODO: Failed to replace SDL Audio Stream");
// self.stream = new_stream;
} }
fn interval(self: *const Self) u64 { fn interval(self: *const Self) u64 {

View File

@@ -160,13 +160,13 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
} }
} }
fn audioSync(audio_sync: bool, stream: *c.SDL_AudioStream, is_buffer_full: *bool) void { fn audioSync(audio_sync: bool, stream: ?*c.SDL_AudioStream, is_buffer_full: *bool) void {
// comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16); if (stream == null) return;
const sample_size = 2 * @sizeOf(u16); const sample_size = 2 * @sizeOf(u16);
const max_buf_size: c_int = 0x400; const max_buf_size: c_int = 0x400;
_ = audio_sync; _ = audio_sync;
_ = stream;
_ = is_buffer_full; _ = is_buffer_full;
_ = sample_size; _ = sample_size;

View File

@@ -273,63 +273,44 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf }); _ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
defer zgui.end(); defer zgui.end();
// const tmp = blk: { var values: [histogram_len]u32 = undefined;
// var buf: [histogram_len]u32 = undefined; const len = state.fps_hist.copy(&values);
// const len = state.fps_hist.copy(&buf);
// break :blk .{ buf, len }; // FIXME(paoda): okay but why
// }; if (len == values.len) _ = state.fps_hist.pop();
// const values = tmp[0];
// const len = tmp[1];
// if (len == values.len) _ = state.fps_hist.pop(); const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(std.mem.max(u32, &values))) else emu.frame_rate;
const x_max: f64 = @floatFromInt(values.len);
// const sorted = blk: { if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
// var buf: @TypeOf(values) = undefined; defer zgui.plot.endPlot();
// @memcpy(buf[0..len], values[0..len]); zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
// std.mem.sort(u32, buf[0..len], {}, std.sort.asc(u32)); zgui.plot.setupAxis(.x1, .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } });
zgui.plot.setupAxis(.y1, .{ .label = "FPS", .flags = .{ .no_grid_lines = true } });
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
zgui.plot.setupFinish();
// break :blk buf; zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
// }; }
// const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(sorted[len - 1])) else emu.frame_rate; // the following metrics require a sorted array
// const x_max: f64 = @floatFromInt(values.len); std.mem.sort(u32, values[0..len], {}, std.sort.asc(u32));
// const y_args = .{ .flags = .{ .no_grid_lines = true } }; const avg: u32 = avg: {
// const x_args = .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } }; var sum: u32 = 0;
for (values[0..len]) |value| sum += value;
// if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) { break :avg @intCast(sum / len);
// defer zgui.plot.endPlot(); };
// zgui.plot.setupLegend(.{ .north = true, .east = true }, .{}); const median = if (len == 0) 0 else values[len / 2];
// zgui.plot.setupAxis(.x1, x_args); const low = if (len == 0) 0 else values[len / 100]; // 1% Low
// zgui.plot.setupAxis(.y1, y_args);
// zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
// zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
// zgui.plot.setupFinish();
// zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] }); zgui.text("Average: {:0>3} fps", .{avg});
// } zgui.text(" Median: {:0>3} fps", .{median});
zgui.text(" 1% Low: {:0>3} fps", .{low});
// const stats: struct { u32, u32, u32 } = blk: {
// if (len == 0) break :blk .{ 0, 0, 0 };
// const average: u32 = average: {
// var sum: u32 = 0;
// for (sorted[0..len]) |value| sum += value;
// break :average @intCast(sum / len);
// };
// const median = sorted[len / 2];
// const low = sorted[len / 100]; // 1% Low
// break :blk .{ average, median, low };
// };
// zgui.text("Average: {:0>3} fps", .{stats[0]});
// zgui.text(" Median: {:0>3} fps", .{stats[1]});
// zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
} }
if (state.win_stat.show_schedule) { if (state.win_stat.show_schedule) {

View File

@@ -31,7 +31,7 @@ const params = clap.parseParamsComptime(
\\ \\
); );
pub fn main() void { pub fn main() !void {
// Main Allocator for ZBA // Main Allocator for ZBA
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok); defer std.debug.assert(gpa.deinit() == .ok);
@@ -40,10 +40,8 @@ pub fn main() void {
// Determine the Data Directory (stores saves) // Determine the Data Directory (stores saves)
const data_path = blk: { const data_path = blk: {
const result = known_folders.getPath(allocator, .data); const path = (try known_folders.getPath(allocator, .data)) orelse return error.unknown_data_folder;
const option = result catch |e| exitln("interrupted while determining the data folder: {}", .{e}); try makePath(path, "zba" ++ std.fs.path.sep_str ++ "save");
const path = option orelse exitln("no valid data folder found", .{});
ensureDataDirsExist(path) catch |e| exitln("failed to create folders under \"{s}\": {}", .{ path, e });
break :blk path; break :blk path;
}; };
@@ -51,47 +49,41 @@ pub fn main() void {
// Determine the Config Directory // Determine the Config Directory
const config_path = blk: { const config_path = blk: {
const result = known_folders.getPath(allocator, .roaming_configuration); const path = (try known_folders.getPath(allocator, .roaming_configuration)) orelse return error.unknown_config_folder;
const option = result catch |e| exitln("interreupted while determining the config folder: {}", .{e}); try makePath(path, "zba");
const path = option orelse exitln("no valid config folder found", .{});
ensureConfigDirExists(path) catch |e| exitln("failed to create required folder \"{s}\": {}", .{ path, e });
break :blk path; break :blk path;
}; };
defer allocator.free(config_path); defer allocator.free(config_path);
// Parse CLI // Parse CLI
const result = try clap.parse(clap.Help, &params, clap.parsers.default, .{ .allocator = allocator });
const result = clap.parse(clap.Help, &params, clap.parsers.default, .{ .allocator = allocator }) catch |e| exitln("failed to parse cli: {}", .{e});
defer result.deinit(); defer result.deinit();
// TODO: Move config file to XDG Config directory? // TODO: Move config file to XDG Config directory?
const cfg_file_path = configFilePath(allocator, config_path) catch |e| exitln("failed to ready config file for access: {}", .{e}); try makeConfigFilePath(config_path);
defer allocator.free(cfg_file_path); try config.load(allocator, config_path);
config.load(allocator, cfg_file_path) catch |e| exitln("failed to load config file: {}", .{e}); var paths = try handleArguments(allocator, data_path, &result);
var paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
defer paths.deinit(allocator); defer paths.deinit(allocator);
// if paths.bios is null, then we want to see if it's in the data directory // if paths.bios is null, then we want to see if it's in the data directory
if (paths.bios == null) blk: { if (paths.bios == null) blk: {
const bios_path = std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }) catch |e| exitln("failed to allocate backup bios dir path: {}", .{e}); const bios_path = try std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }); // FIXME: std.fs.path_sep or something
defer allocator.free(bios_path); defer allocator.free(bios_path);
_ = std.fs.cwd().statFile(bios_path) catch |e| switch (e) { _ = std.fs.cwd().statFile(bios_path) catch |e| {
error.FileNotFound => { // ZBA will crash on attempt to read BIOS but that's fine if (e != std.fs.Dir.StatFileError.FileNotFound) return e;
log.err("file located at {s} was not found", .{bios_path});
break :blk; log.err("file located at {s} was not found", .{bios_path});
}, break :blk;
else => exitln("error when checking \"{s}\": {}", .{ bios_path, e }),
}; };
paths.bios = allocator.dupe(u8, bios_path) catch |e| exitln("failed to duplicate path to bios: {}", .{e}); paths.bios = try allocator.dupe(u8, bios_path);
} }
const log_file = switch (config.config().debug.cpu_trace) { const log_file = switch (config.config().debug.cpu_trace) {
true => std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e}), true => try std.fs.cwd().createFile("zba.log", .{}),
false => null, false => null,
}; };
defer if (log_file) |file| file.close(); defer if (log_file) |file| file.close();
@@ -107,7 +99,7 @@ pub fn main() void {
var cpu = Arm7tdmi.init(ischeduler, ibus); var cpu = Arm7tdmi.init(ischeduler, ibus);
bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e}); try bus.init(allocator, &scheduler, &cpu, paths);
defer bus.deinit(); defer bus.deinit();
if (config.config().guest.skip_bios or result.args.skip != 0 or paths.bios == null) { if (config.config().guest.skip_bios or result.args.skip != 0 or paths.bios == null) {
@@ -117,10 +109,10 @@ pub fn main() void {
const title_ptr = if (paths.rom != null) &bus.pak.title else null; const title_ptr = if (paths.rom != null) &bus.pak.title else null;
// TODO: Just copy the title instead of grabbing a pointer to it // TODO: Just copy the title instead of grabbing a pointer to it
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e}); var gui = try Gui.init(allocator, &bus.apu, title_ptr);
defer gui.deinit(); defer gui.deinit();
var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e}); var sync = try Synchro.init(allocator);
defer sync.deinit(allocator); defer sync.deinit(allocator);
if (result.args.gdb != 0) { if (result.args.gdb != 0) {
@@ -133,34 +125,22 @@ pub fn main() void {
log.info("Ready to connect", .{}); log.info("Ready to connect", .{});
var server = Server.init( var server = try Server.init(emulator, .{ .memory_map = EmuThing.map, .target = EmuThing.target });
emulator,
.{ .memory_map = EmuThing.map, .target = EmuThing.target },
) catch |e| exitln("failed to init gdb server: {}", .{e});
defer server.deinit(allocator); defer server.deinit(allocator);
log.info("Starting GDB Server Thread", .{}); log.info("Starting GDB Server Thread", .{});
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit }) catch |e| exitln("gdb server thread crashed: {}", .{e}); const thread = try std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit });
defer thread.join(); defer thread.join();
gui.run(.{ try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .sync = &sync });
.cpu = &cpu,
.scheduler = &scheduler,
.sync = &sync,
}) catch |e| exitln("main thread panicked: {}", .{e});
} else { } else {
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync }) catch |e| exitln("emu thread panicked: {}", .{e}); const thread = try std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync });
defer thread.join(); defer thread.join();
gui.run(.{ try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .tracker = &tracker, .sync = &sync });
.cpu = &cpu,
.scheduler = &scheduler,
.tracker = &tracker,
.sync = &sync,
}) catch |e| exitln("main thread panicked: {}", .{e});
} }
} }
@@ -184,53 +164,36 @@ fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const c
}; };
} }
fn configFilePath(allocator: Allocator, config_path: []const u8) ![]const u8 { fn makeConfigFilePath(config_path: []const u8) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ config_path, "zba", "config.toml" }); var dir = try std.fs.openDirAbsolute(config_path, .{});
errdefer allocator.free(path); defer dir.close();
const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
// We try to create the file exclusively, meaning that we err out if the file already exists. // We try to create the file exclusively, meaning that we err out if the file already exists.
// All we care about is a file being there so we can just ignore that error in particular and // All we care about is a file being there so we can just ignore that error in particular and
// continue down the happy pathj // continue down the happy pathj
std.fs.accessAbsolute(path, .{}) catch |e| { dir.access(sub_path, .{}) catch |e| {
if (e != error.FileNotFound) return e; if (e != std.fs.Dir.AccessError.FileNotFound) return e;
const config_file = std.fs.createFileAbsolute(path, .{}) catch |err| exitln("failed to create \"{s}\": {}", .{ path, err }); const config_file = try dir.createFile(sub_path, .{});
defer config_file.close(); defer config_file.close();
try config_file.writeAll(@embedFile("example.toml")); try config_file.writeAll(@embedFile("example.toml"));
}; };
return path;
}
fn ensureDataDirsExist(data_path: []const u8) !void {
var dir = try std.fs.openDirAbsolute(data_path, .{});
defer dir.close();
// Will recursively create directories
try dir.makePath("zba" ++ std.fs.path.sep_str ++ "save");
}
fn ensureConfigDirExists(config_path: []const u8) !void {
var dir = try std.fs.openDirAbsolute(config_path, .{});
defer dir.close();
try dir.makePath("zba");
} }
fn romPath(allocator: Allocator, result: *const clap.Result(clap.Help, &params, clap.parsers.default)) !?[]const u8 { fn romPath(allocator: Allocator, result: *const clap.Result(clap.Help, &params, clap.parsers.default)) !?[]const u8 {
return switch (result.positionals.len) { return switch (result.positionals.len) {
0 => null, 0 => null,
1 => if (result.positionals[0]) |path| try allocator.dupe(u8, path) else null, 1 => if (result.positionals[0]) |path| try allocator.dupe(u8, path) else null,
else => exitln("ZBA received too many positional arguments.", .{}), else => error.too_many_positional_arguments,
}; };
} }
fn exitln(comptime format: []const u8, args: anytype) noreturn { fn makePath(path: []const u8, sub_path: []const u8) !void {
var buf: [1024]u8 = undefined; var dir = try std.fs.openDirAbsolute(path, .{});
var stderr = std.fs.File.stderr().writer(&buf).interface; defer dir.close();
stderr.print(format, args) catch {}; // Just exit already... try dir.makePath(sub_path);
stderr.writeByte('\n') catch {};
std.process.exit(1);
} }

View File

@@ -46,6 +46,8 @@ pub const Gui = struct {
allocator: Allocator, allocator: Allocator,
pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self { pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self {
errdefer |err| if (err == error.sdl_error) log.err("SDL Error: {s}", .{c.SDL_GetError()});
c.SDL_SetMainReady(); c.SDL_SetMainReady();
try errify(c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO | c.SDL_INIT_EVENTS)); try errify(c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO | c.SDL_INIT_EVENTS));
@@ -200,16 +202,16 @@ pub const Gui = struct {
var zgui_redraw: bool = false; var zgui_redraw: bool = false;
switch (self.state.emulation) { switch (self.state.emulation) {
.Transition => |inner| switch (inner) { .Transition => |target| switch (target) {
.Active => { .Active => {
sync.paused.store(false, .monotonic); sync.paused.store(false, .monotonic);
if (!config.config().host.mute) try errify(c.SDL_PauseAudioDevice(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)); if (!config.config().host.mute) try errify(c.SDL_ResumeAudioStreamDevice(self.audio.stream));
self.state.emulation = .Active; self.state.emulation = .Active;
}, },
.Inactive => { .Inactive => {
// Assert that double pausing is impossible // Assert that double pausing is impossible
try errify(c.SDL_ResumeAudioDevice(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)); if (!config.config().host.mute) try errify(c.SDL_PauseAudioStreamDevice(self.audio.stream));
sync.paused.store(true, .monotonic); sync.paused.store(true, .monotonic);
self.state.emulation = .Inactive; self.state.emulation = .Inactive;
@@ -233,7 +235,7 @@ pub const Gui = struct {
// spurious calls to SDL_LockAudioDevice? // spurious calls to SDL_LockAudioDevice?
try errify(c.SDL_LockAudioStream(self.audio.stream)); try errify(c.SDL_LockAudioStream(self.audio.stream));
defer errify(c.SDL_UnlockAudioStream(self.audio.stream)) catch @panic("TODO: FIXME"); defer errify(c.SDL_UnlockAudioStream(self.audio.stream)) catch std.debug.panic("SDL Error: {s}", .{c.SDL_GetError()});
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]); zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]);
}, },
@@ -257,17 +259,17 @@ pub const Gui = struct {
} }
}; };
const Audio = struct { pub const Audio = struct {
const Self = @This(); const log = std.log.scoped(.platform_audio);
const log = std.log.scoped(.PlatformAudio);
stream: *c.SDL_AudioStream, stream: *c.SDL_AudioStream,
fn init(apu: *Apu) !Self { fn init(apu: *Apu) !@This() {
var desired: c.SDL_AudioSpec = std.mem.zeroes(c.SDL_AudioSpec); const desired: c.SDL_AudioSpec = .{
desired.freq = sample_rate; .freq = sample_rate,
desired.format = c.SDL_AUDIO_S16LE; .format = c.SDL_AUDIO_S16LE,
desired.channels = 2; .channels = 2,
};
log.info("Host Sample Rate: {}Hz, Host Format: SDL_AUDIO_S16LE", .{sample_rate}); log.info("Host Sample Rate: {}Hz, Host Format: SDL_AUDIO_S16LE", .{sample_rate});
@@ -278,7 +280,7 @@ const Audio = struct {
return .{ .stream = stream }; return .{ .stream = stream };
} }
fn deinit(self: *Self) void { fn deinit(self: *@This()) void {
c.SDL_DestroyAudioStream(self.stream); c.SDL_DestroyAudioStream(self.stream);
self.* = undefined; self.* = undefined;
} }