Compare commits

..

No commits in common. "1ab961243c9405427799621861bafd6233f337c0" and "002e33b48b381fae150afd2dfc2f177eeddc2908" have entirely different histories.

14 changed files with 154 additions and 270 deletions

View File

@ -15,10 +15,12 @@ const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
const getHalf = util.getHalf; const getHalf = util.getHalf;
const setHalf = util.setHalf; const setHalf = util.setHalf;
const intToBytes = util.intToBytes; const intToBytes = util.intToBytes;
const RingBuffer = util.RingBuffer;
const log = std.log.scoped(.APU); const log = std.log.scoped(.APU);
pub const host_rate = @import("../platform.zig").sample_rate;
pub const host_format = @import("../platform.zig").sample_format;
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T { pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
const byte_addr = @truncate(u8, addr); const byte_addr = @truncate(u8, addr);
@ -106,121 +108,106 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void { pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
const byte_addr = @truncate(u8, addr); const byte_addr = @truncate(u8, addr);
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (T) { switch (T) {
u32 => { u32 => switch (byte_addr) {
// 0x80 and 0x81 handled in setSoundCnt 0x60 => apu.ch1.setSound1Cnt(value),
if (byte_addr < 0x80 and !apu.cnt.apu_enable.read()) return; 0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
switch (byte_addr) { 0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
0x60 => apu.ch1.setSound1Cnt(value), 0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)), 0x70 => apu.ch3.setSound3Cnt(value),
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)), 0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
0x70 => apu.ch3.setSound3Cnt(value), 0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)), 0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)), 0x80 => apu.setSoundCnt(value),
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)), 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
0x88 => apu.bias.raw = @truncate(u16, value),
0x8C => {},
0x80 => apu.setSoundCnt(value), 0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0xA0 => apu.chA.push(value), // FIFO_A
0x88 => apu.bias.raw = @truncate(u16, value), 0xA4 => apu.chB.push(value), // FIFO_B
0x8C => {}, else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0xA0 => apu.chA.push(value), // FIFO_A
0xA4 => apu.chB.push(value), // FIFO_B
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
u16 => { u16 => switch (byte_addr) {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return; 0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
0x62 => apu.ch1.setSound1CntH(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
0x66 => {},
switch (byte_addr) { 0x68 => apu.ch2.setSound2CntL(value),
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L 0x6A => {},
0x62 => apu.ch1.setSound1CntH(value), 0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value), 0x6E => {},
0x66 => {},
0x68 => apu.ch2.setSound2CntL(value), 0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
0x6A => {}, 0x72 => apu.ch3.setSound3CntH(value),
0x6C => apu.ch2.setSound2CntH(&apu.fs, value), 0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
0x6E => {}, 0x76 => {},
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)), 0x78 => apu.ch4.setSound4CntL(value),
0x72 => apu.ch3.setSound3CntH(value), 0x7A => {},
0x74 => apu.ch3.setSound3CntX(&apu.fs, value), 0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
0x76 => {}, 0x7E => {},
0x78 => apu.ch4.setSound4CntL(value), 0x80 => apu.setSoundCntL(value),
0x7A => {}, 0x82 => apu.setSoundCntH(value),
0x7C => apu.ch4.setSound4CntH(&apu.fs, value), 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
0x7E => {}, 0x86 => {},
0x88 => apu.bias.raw = value, // SOUNDBIAS
0x8A, 0x8C, 0x8E => {},
0x80 => apu.setSoundCntL(value), 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0x82 => apu.setSoundCntH(value), 0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }),
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }),
0x86 => {}, else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
0x88 => apu.bias.raw = value, // SOUNDBIAS
0x8A, 0x8C, 0x8E => {},
0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }),
0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
u8 => { u8 => switch (byte_addr) {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return; 0x60 => apu.ch1.setSound1CntL(value),
0x61 => {},
0x62 => apu.ch1.setNr11(value),
0x63 => apu.ch1.setNr12(value),
0x64 => apu.ch1.setNr13(value),
0x65 => apu.ch1.setNr14(&apu.fs, value),
0x66, 0x67 => {},
switch (byte_addr) { 0x68 => apu.ch2.setNr21(value),
0x60 => apu.ch1.setSound1CntL(value), 0x69 => apu.ch2.setNr22(value),
0x61 => {}, 0x6A, 0x6B => {},
0x62 => apu.ch1.setNr11(value), 0x6C => apu.ch2.setNr23(value),
0x63 => apu.ch1.setNr12(value), 0x6D => apu.ch2.setNr24(&apu.fs, value),
0x64 => apu.ch1.setNr13(value), 0x6E, 0x6F => {},
0x65 => apu.ch1.setNr14(&apu.fs, value),
0x66, 0x67 => {},
0x68 => apu.ch2.setNr21(value), 0x70 => apu.ch3.setSound3CntL(value), // NR30
0x69 => apu.ch2.setNr22(value), 0x71 => {},
0x6A, 0x6B => {}, 0x72 => apu.ch3.setNr31(value),
0x6C => apu.ch2.setNr23(value), 0x73 => apu.ch3.vol.raw = value, // NR32
0x6D => apu.ch2.setNr24(&apu.fs, value), 0x74 => apu.ch3.setNr33(value),
0x6E, 0x6F => {}, 0x75 => apu.ch3.setNr34(&apu.fs, value),
0x76, 0x77 => {},
0x70 => apu.ch3.setSound3CntL(value), // NR30 0x78 => apu.ch4.setNr41(value),
0x71 => {}, 0x79 => apu.ch4.setNr42(value),
0x72 => apu.ch3.setNr31(value), 0x7A, 0x7B => {},
0x73 => apu.ch3.vol.raw = value, // NR32 0x7C => apu.ch4.poly.raw = value, // NR 43
0x74 => apu.ch3.setNr33(value), 0x7D => apu.ch4.setNr44(&apu.fs, value),
0x75 => apu.ch3.setNr34(&apu.fs, value), 0x7E, 0x7F => {},
0x76, 0x77 => {},
0x78 => apu.ch4.setNr41(value), 0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)),
0x79 => apu.ch4.setNr42(value), 0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)),
0x7A, 0x7B => {}, 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
0x7C => apu.ch4.poly.raw = value, // NR 43 0x85 => {},
0x7D => apu.ch4.setNr44(&apu.fs, value), 0x86, 0x87 => {},
0x7E, 0x7F => {}, 0x88, 0x89 => apu.bias.raw = setHalf(u16, apu.bias.raw, byte_addr, value), // SOUNDBIAS
0x8A...0x8F => {},
0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)), 0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)), 0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }),
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }),
0x85 => {}, else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
0x86, 0x87 => {},
0x88, 0x89 => apu.bias.raw = setHalf(u16, apu.bias.raw, byte_addr, value), // SOUNDBIAS
0x8A...0x8F => {},
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }),
0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
else => @compileError("APU: Unsupported write width"), else => @compileError("APU: Unsupported write width"),
} }
@ -244,20 +231,17 @@ pub const Apu = struct {
sampling_cycle: u2, sampling_cycle: u2,
sample_queue: RingBuffer(u16), stream: *SDL.SDL_AudioStream,
sched: *Scheduler, sched: *Scheduler,
fs: FrameSequencer, fs: FrameSequencer,
capacitor: f32, capacitor: f32,
is_buffer_full: bool,
pub const Tick = enum { Length, Envelope, Sweep }; pub const Tick = enum { Length, Envelope, Sweep };
pub fn init(sched: *Scheduler) Self { pub fn init(sched: *Scheduler) Self {
const NUM_CHANNELS: usize = 2;
const allocator = std.heap.c_allocator;
const sample_buf = allocator.alloc(u16, 0x800 * NUM_CHANNELS) catch @panic("failed to allocate sample buffer");
const apu: Self = .{ const apu: Self = .{
.ch1 = ToneSweep.init(sched), .ch1 = ToneSweep.init(sched),
.ch2 = Tone.init(sched), .ch2 = Tone.init(sched),
@ -272,11 +256,12 @@ pub const Apu = struct {
.bias = .{ .raw = 0x0200 }, .bias = .{ .raw = 0x0200 },
.sampling_cycle = 0b00, .sampling_cycle = 0b00,
.sample_queue = RingBuffer(u16).init(sample_buf), .stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, host_format, 2, host_rate).?,
.sched = sched, .sched = sched,
.capacitor = 0, .capacitor = 0,
.fs = FrameSequencer.init(), .fs = FrameSequencer.init(),
.is_buffer_full = false,
}; };
sched.push(.SampleAudio, apu.interval()); sched.push(.SampleAudio, apu.interval());
@ -290,20 +275,15 @@ pub const Apu = struct {
} }
fn reset(self: *Self) void { fn reset(self: *Self) void {
// All PSG Registers between 0x0400_0060..0x0400_0081 are zeroed
// 0x0400_0082 and 0x0400_0088 retain their values
self.ch1.reset(); self.ch1.reset();
self.ch2.reset(); self.ch2.reset();
self.ch3.reset(); self.ch3.reset();
self.ch4.reset(); self.ch4.reset();
// GBATEK says 4000060h..4000081h I take this to mean inclusive
self.psg_cnt.raw = 0x0000;
} }
/// SOUNDCNT /// SOUNDCNT
fn setSoundCnt(self: *Self, value: u32) void { fn setSoundCnt(self: *Self, value: u32) void {
if (self.cnt.apu_enable.read()) self.setSoundCntL(@truncate(u16, value)); self.setSoundCntL(@truncate(u16, value));
self.setSoundCntH(@truncate(u16, value >> 16)); self.setSoundCntH(@truncate(u16, value >> 16));
} }
@ -342,14 +322,11 @@ pub const Apu = struct {
self.fs.step = 0; // Reset Frame Sequencer self.fs.step = 0; // Reset Frame Sequencer
// Reset Square Wave Offsets // Reset Square Wave Offsets
self.ch1.square.reset(); self.ch1.square.pos = 0;
self.ch2.square.reset(); self.ch2.square.pos = 0;
// Reset Wave // Reset Wave Device Offsets
self.ch3.wave_dev.reset(); self.ch3.wave_dev.offset = 0;
// Rest Noise
self.ch4.lfsr.reset();
} else { } else {
self.reset(); self.reset();
} }
@ -370,6 +347,11 @@ pub const Apu = struct {
pub fn sampleAudio(self: *Self, late: u64) void { pub fn sampleAudio(self: *Self, late: u64) void {
self.sched.push(.SampleAudio, self.interval() -| late); self.sched.push(.SampleAudio, self.interval() -| late);
// Whether the APU is busy or not is determined by the main loop in emu.zig
// This should only ever be true (because this side of the emu is single threaded)
// When audio sync is disaabled
if (self.is_buffer_full) return;
var left: i16 = 0; var left: i16 = 0;
var right: i16 = 0; var right: i16 = 0;
@ -413,8 +395,8 @@ pub const Apu = struct {
right += if (self.dma_cnt.chB_right.read()) chB_sample else 0; right += if (self.dma_cnt.chB_right.read()) chB_sample else 0;
// Add SOUNDBIAS // Add SOUNDBIAS
// FIXME: SOUNDBIAS is 10-bit but The waveform is centered around 0 if I treat it as 11-bit // FIXME: Is SOUNDBIAS 9-bit or 10-bit?
const bias = @as(i16, self.bias.level.read()) << 2; const bias = @as(i16, self.bias.level.read()) << 1;
left += bias; left += bias;
right += bias; right += bias;
@ -425,7 +407,23 @@ pub const Apu = struct {
const ext_left = (clamped_left << 5) | (clamped_left >> 6); const ext_left = (clamped_left << 5) | (clamped_left >> 6);
const ext_right = (clamped_right << 5) | (clamped_right >> 6); const ext_right = (clamped_right << 5) | (clamped_right >> 6);
self.sample_queue.push(ext_left, ext_right) catch {}; if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
}
fn replaceSDLResampler(self: *Self) void {
@setCold(true);
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
// Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
// FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
const old_stream = self.stream;
defer SDL.SDL_FreeAudioStream(old_stream);
self.sampling_cycle = self.bias.sampling_cycle.read();
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, sample_rate), host_format, 2, host_rate).?;
} }
fn interval(self: *const Self) u64 { fn interval(self: *const Self) u64 {

View File

@ -49,13 +49,10 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.len = 0; // NR41 self.len = 0;
self.envelope.raw = 0; // NR42 self.envelope.raw = 0;
self.poly.raw = 0; // NR43 self.poly.raw = 0;
self.cnt.raw = 0; // NR44 self.cnt.raw = 0;
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -43,12 +43,9 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.duty.raw = 0; // NR21 self.duty.raw = 0;
self.envelope.raw = 0; // NR22 self.envelope.raw = 0;
self.freq.raw = 0; // NR32, NR24 self.freq.raw = 0;
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -50,14 +50,12 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.sweep.raw = 0; // NR10 self.sweep.raw = 0;
self.duty.raw = 0; // NR11 self.sweep_dev.calc_performed = false;
self.envelope.raw = 0; // NR12
self.freq.raw = 0; // NR13, NR14
self.len_dev.reset(); self.duty.raw = 0;
self.sweep_dev.reset(); self.envelope.raw = 0;
self.env_dev.reset(); self.freq.raw = 0;
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -42,13 +42,10 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.select.raw = 0; // NR30 self.select.raw = 0;
self.length = 0; // NR31 self.length = 0;
self.vol.raw = 0; // NR32 self.vol.raw = 0;
self.freq.raw = 0; // NR33, NR34 self.freq.raw = 0;
self.len_dev.reset();
self.wave_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -11,11 +11,6 @@ pub fn create() Self {
return .{ .timer = 0, .vol = 0 }; return .{ .timer = 0, .vol = 0 };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.vol = 0;
}
pub fn tick(self: *Self, nrx2: io.Envelope) void { pub fn tick(self: *Self, nrx2: io.Envelope) void {
if (nrx2.period.read() != 0) { if (nrx2.period.read() != 0) {
if (self.timer != 0) self.timer -= 1; if (self.timer != 0) self.timer -= 1;

View File

@ -6,10 +6,6 @@ pub fn create() Self {
return .{ .timer = 0 }; return .{ .timer = 0 };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
}
pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void { pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void {
if (enabled) { if (enabled) {
if (self.timer == 0) return; if (self.timer == 0) return;

View File

@ -18,13 +18,6 @@ pub fn create() Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.enabled = false;
self.shadow = 0;
self.calc_performed = false;
}
pub fn tick(self: *Self, ch1: *ToneSweep) void { pub fn tick(self: *Self, ch1: *ToneSweep) void {
if (self.timer != 0) self.timer -= 1; if (self.timer != 0) self.timer -= 1;

View File

@ -19,11 +19,6 @@ pub fn create(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.shift = 0;
self.timer = 0;
}
pub fn sample(self: *const Self) i8 { pub fn sample(self: *const Self) i8 {
return if ((~self.shift & 1) == 1) 1 else -1; return if ((~self.shift & 1) == 1) 1 else -1;
} }

View File

@ -20,11 +20,6 @@ pub fn init(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.pos = 0;
}
/// Scheduler Event Handler for Square Synth Timer Expire /// Scheduler Event Handler for Square Synth Timer Expire
pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void { pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void {
comptime std.debug.assert(T == ToneSweep or T == Tone); comptime std.debug.assert(T == ToneSweep or T == Tone);

View File

@ -38,13 +38,6 @@ pub fn init(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.offset = 0;
// sample buffer isn't reset because it's outside of the range of what NR52{7}'s effects
}
/// Reload internal Wave Timer /// Reload internal Wave Timer
pub fn reload(self: *Self, value: u11) void { pub fn reload(self: *Self, value: u11) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 2 }); self.sched.removeScheduledEvent(.{ .ApuChannel = 2 });

View File

@ -5,7 +5,6 @@ const config = @import("../config.zig");
const Scheduler = @import("scheduler.zig").Scheduler; const Scheduler = @import("scheduler.zig").Scheduler;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const FpsTracker = @import("../util.zig").FpsTracker; const FpsTracker = @import("../util.zig").FpsTracker;
const RingBuffer = @import("../util.zig").RingBuffer;
const Timer = std.time.Timer; const Timer = std.time.Timer;
const Atomic = std.atomic.Atomic; const Atomic = std.atomic.Atomic;
@ -59,7 +58,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
while (!quit.load(.SeqCst)) { while (!quit.load(.SeqCst)) {
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
audioSync(audio_sync, &cpu.bus.apu.sample_queue); audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
if (kind == .UnlimitedFPS) tracker.?.tick(); if (kind == .UnlimitedFPS) tracker.?.tick();
} }
@ -78,7 +77,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
// the amount of time needed for audio to catch up rather than // the amount of time needed for audio to catch up rather than
// our expected wake-up time // our expected wake-up time
audioSync(audio_sync, &cpu.bus.apu.sample_queue); audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
if (!audio_sync) spinLoop(&timer, wake_time); if (!audio_sync) spinLoop(&timer, wake_time);
wake_time = new_wake_time; wake_time = new_wake_time;
@ -105,13 +104,22 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
} }
} }
fn audioSync(audio_sync: bool, sample_queue: *RingBuffer(u16)) void { fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16); comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
// 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; // Determine whether the APU is busy right at this moment
_ = sample_queue; var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
// If Busy is false, there's no need to sync here
if (!still_full) return;
while (true) {
still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
if (!audio_sync or !still_full) break;
}
} }
fn videoSync(timer: *Timer, wake_time: u64) u64 { fn videoSync(timer: *Timer, wake_time: u64) u64 {

View File

@ -12,7 +12,7 @@ const FpsTracker = @import("util.zig").FpsTracker;
const gba_width = @import("core/ppu.zig").width; const gba_width = @import("core/ppu.zig").width;
const gba_height = @import("core/ppu.zig").height; const gba_height = @import("core/ppu.zig").height;
pub const sample_rate = 1 << 16; pub const sample_rate = 1 << 15;
pub const sample_format = SDL.AUDIO_U16; pub const sample_format = SDL.AUDIO_U16;
const default_title: []const u8 = "ZBA"; const default_title: []const u8 = "ZBA";
@ -202,7 +202,7 @@ pub const Gui = struct {
SDL.SDLK_RSHIFT => io.keyinput.select.set(), SDL.SDLK_RSHIFT => io.keyinput.select.set(),
SDL.SDLK_i => { SDL.SDLK_i => {
comptime std.debug.assert(sample_format == SDL.AUDIO_U16); comptime std.debug.assert(sample_format == SDL.AUDIO_U16);
log.err("Sample Count: {}", .{cpu.bus.apu.sample_queue.len() / 2}); log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))});
}, },
SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }), SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => { SDL.SDLK_k => {
@ -292,15 +292,7 @@ const Audio = struct {
const T = *Apu; const T = *Apu;
const apu = @ptrCast(T, @alignCast(@alignOf(T), userdata)); const apu = @ptrCast(T, @alignCast(@alignOf(T), userdata));
comptime std.debug.assert(sample_format == SDL.AUDIO_U16); _ = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
const sample_buf = @ptrCast([*]u16, @alignCast(@alignOf(u16), stream))[0 .. @intCast(u32, len) / @sizeOf(u16)];
var previous: u16 = 0x8000;
for (sample_buf) |*sample| {
if (apu.sample_queue.pop()) |value| previous = value;
sample.* = previous;
}
} }
}; };

View File

@ -275,73 +275,3 @@ fn HalfInt(comptime T: type) type {
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1); return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
} }
const Mutex = std.Thread.Mutex;
pub fn RingBuffer(comptime T: type) type {
return struct {
const Self = @This();
const Index = usize;
const max_capacity = (std.math.powi(Index, 2, @typeInfo(Index).Int.bits - 1) catch @compileError("uhhhhh")) - 1;
read: Index,
write: Index,
buf: []T,
mutex: Mutex,
const Error = error{buffer_full};
pub fn init(buf: []T) Self {
std.debug.assert(std.math.isPowerOfTwo(buf.len)); // capacity must be a power of two
std.debug.assert(buf.len <= max_capacity); // Capacity must be half the range of the index data types
return .{ .read = 0, .write = 0, .buf = buf, .mutex = .{} };
}
pub fn push(self: *Self, left: T, right: T) Error!void {
self.mutex.lock();
defer self.mutex.unlock();
try self._push(left);
self._push(right) catch |e| {
self.write -= 1; // undo the previous write;
return e;
};
}
pub fn pop(self: *Self) ?T {
self.mutex.lock();
defer self.mutex.unlock();
if (self.isEmpty()) return null;
defer self.read += 1;
return self.buf[self.mask(self.read)];
}
pub fn len(self: *const Self) Index {
return self.write - self.read;
}
fn _push(self: *Self, value: T) Error!void {
if (self.isFull()) return error.buffer_full;
defer self.write += 1;
self.buf[self.mask(self.write)] = value;
}
fn isFull(self: *const Self) bool {
return self.len() == self.buf.len;
}
fn isEmpty(self: *const Self) bool {
return self.read == self.write;
}
fn mask(self: *const Self, idx: Index) Index {
return idx & (self.buf.len - 1);
}
};
}