From c3611a0f00eee8a34fbd1cb5dfe7220439555a1e Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Wed, 20 Apr 2022 04:43:25 -0300 Subject: [PATCH] feat: add audio resampler Also implement extremely naive audio sync --- src/apu.zig | 41 ++++++++++++++++++++++++++++++----------- src/main.zig | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/apu.zig b/src/apu.zig index f539b12..5800061 100644 --- a/src/apu.zig +++ b/src/apu.zig @@ -11,8 +11,7 @@ const AudioDeviceId = SDL.SDL_AudioDeviceID; const intToBytes = @import("util.zig").intToBytes; const log = std.log.scoped(.APU); -const sample_rate = 32768; -const sample_ticks = (1 << 24) / sample_rate; +pub const host_sample_rate = 1 << 15; pub const Apu = struct { const Self = @This(); @@ -29,7 +28,8 @@ pub const Apu = struct { dma_cnt: io.DmaSoundControl, cnt: io.SoundControl, - dev: ?AudioDeviceId, + sampling_cycle: u2, + stream: *SDL.SDL_AudioStream, sched: *Scheduler, pub fn init(sched: *Scheduler) Self { @@ -46,18 +46,16 @@ pub const Apu = struct { .cnt = .{ .raw = 0 }, .bias = .{ .raw = 0x0200 }, - .dev = null, + .sampling_cycle = 0b00, + .stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, 1 << 15, SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable, .sched = sched, }; - sched.push(.SampleAudio, sched.now() + sample_ticks); + + sched.push(.SampleAudio, sched.now() + apu.sampleTicks()); return apu; } - pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void { - self.dev = dev; - } - pub fn setDmaCnt(self: *Self, value: u16) void { const new: io.DmaSoundControl = .{ .raw = value }; @@ -97,9 +95,30 @@ pub const Apu = struct { const left = (chA_left + chB_left) / 2; const right = (chA_right + chB_right) / 2; - if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &[2]f32{ left, right }, 2 * @sizeOf(f32)); + if (self.sampling_cycle != self.bias.sampling_cycle.read()) { + log.warn("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); - self.sched.push(.SampleAudio, self.sched.now() + sample_ticks - late); + // Sample Rate Changed, Create a new Resampler since i can't figure out how to change + // the parameters of the old one + const old = self.stream; + defer SDL.SDL_FreeAudioStream(old); + + self.sampling_cycle = self.bias.sampling_cycle.read(); + self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_F32, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_F32, 2, host_sample_rate) orelse unreachable; + } + + while (SDL.SDL_AudioStreamAvailable(self.stream) > (@sizeOf(f32) * 2 * 0x800)) {} + + _ = SDL.SDL_AudioStreamPut(self.stream, &[2]f32{ left, right }, 2 * @sizeOf(f32)); + self.sched.push(.SampleAudio, self.sched.now() + self.sampleTicks() - late); + } + + inline fn sampleTicks(self: *const Self) u64 { + return (1 << 24) / self.sampleRate(); + } + + inline fn sampleRate(self: *const Self) u64 { + return @as(u64, 1) << (15 + @as(u6, self.bias.sampling_cycle.read())); } pub fn handleTimerOverflow(self: *Self, cpu: *Arm7tdmi, tim_id: u3) void { diff --git a/src/main.zig b/src/main.zig index 34481a3..90a8266 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,6 +5,7 @@ const known_folders = @import("known_folders"); const emu = @import("emu.zig"); const Bus = @import("Bus.zig"); +const Apu = @import("apu.zig").Apu; const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Scheduler = @import("scheduler.zig").Scheduler; const FpsAverage = @import("util.zig").FpsAverage; @@ -20,6 +21,8 @@ const gba_height = @import("ppu.zig").height; const framebuf_pitch = @import("ppu.zig").framebuf_pitch; const expected_rate = @import("emu.zig").frame_rate; +const sample_rate = @import("apu.zig").host_sample_rate; + pub const enable_logging: bool = false; const is_binary: bool = false; const log = std.log.scoped(.GUI); @@ -70,10 +73,6 @@ pub fn main() anyerror!void { _ = initSdl2(); defer SDL.SDL_Quit(); - // Initialize SDL Audio - const audio_dev = initAudio(); - defer SDL.SDL_CloseAudioDevice(audio_dev); - // Initialize Emulator var scheduler = Scheduler.init(alloc); defer scheduler.deinit(); @@ -81,10 +80,12 @@ pub fn main() anyerror!void { const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_path }; var cpu = try Arm7tdmi.init(alloc, &scheduler, paths); defer cpu.deinit(); - - cpu.bus.apu.attachAudioDevice(audio_dev); cpu.fastBoot(); + // Initialize SDL Audio + const audio_dev = initAudio(&cpu.bus.apu); + defer SDL.SDL_CloseAudioDevice(audio_dev); + const log_file: ?File = if (enable_logging) blk: { const file = try std.fs.cwd().createFile(if (is_binary) "zba.bin" else "zba.log", .{}); cpu.useLogger(&file, is_binary); @@ -155,6 +156,7 @@ pub fn main() anyerror!void { SDL.SDLK_s => io.keyinput.shoulder_r.set(), SDL.SDLK_RETURN => io.keyinput.start.set(), SDL.SDLK_RSHIFT => io.keyinput.select.set(), + SDL.SDLK_i => std.debug.print("{} samples\n", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(f32))}), else => {}, } }, @@ -238,19 +240,31 @@ fn createTexture(renderer: *SDL.SDL_Renderer, width: c_int, height: c_int) *SDL. ) orelse sdlPanic(); } -fn initAudio() SDL.SDL_AudioDeviceID { +fn initAudio(apu: *Apu) SDL.SDL_AudioDeviceID { var have: SDL.SDL_AudioSpec = undefined; - var want = std.mem.zeroes(SDL.SDL_AudioSpec); - want.freq = 32768; - want.format = SDL.AUDIO_F32; - want.channels = 2; - want.samples = 0x100; - want.callback = null; + var want: SDL.SDL_AudioSpec = .{ + .freq = sample_rate, + .format = SDL.AUDIO_F32, + .channels = 2, + .samples = 0x100, + .callback = audioCallback, + .userdata = apu, + .silence = undefined, + .size = undefined, + .padding = undefined, + }; const dev = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0); if (dev == 0) sdlPanic(); - // Start Playback on the Audio evice + // Start Playback on the Audio device SDL.SDL_PauseAudioDevice(dev, 0); return dev; } + +export fn audioCallback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void { + const apu = @ptrCast(*Apu, @alignCast(8, userdata)); + const result = SDL.SDL_AudioStreamGet(apu.stream, stream, len); + + if (result < 0) log.err("Audio Callback Underflow", .{}); +}