fix(apu): disable APU writes when APU is disabled

This commit is contained in:
Rekai Nyangadzayi Musuka 2022-11-26 12:20:42 -04:00
parent 002e33b48b
commit 46e29245b7
11 changed files with 172 additions and 105 deletions

View File

@ -108,106 +108,121 @@ 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 => switch (byte_addr) { u32 => {
0x60 => apu.ch1.setSound1Cnt(value), // 0x80 and 0x81 handled in setSoundCnt
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)), if (byte_addr < 0x80 and !apu.cnt.apu_enable.read()) return;
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)), switch (byte_addr) {
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)), 0x60 => apu.ch1.setSound1Cnt(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
0x70 => apu.ch3.setSound3Cnt(value), 0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)), 0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)), 0x70 => apu.ch3.setSound3Cnt(value),
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)), 0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
0x80 => apu.setSoundCnt(value), 0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
0x88 => apu.bias.raw = @truncate(u16, value),
0x8C => {},
0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value), 0x80 => apu.setSoundCnt(value),
0xA0 => apu.chA.push(value), // FIFO_A 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
0xA4 => apu.chB.push(value), // FIFO_B 0x88 => apu.bias.raw = @truncate(u16, value),
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), 0x8C => {},
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 => switch (byte_addr) { u16 => {
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
0x62 => apu.ch1.setSound1CntH(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
0x66 => {},
0x68 => apu.ch2.setSound2CntL(value), switch (byte_addr) {
0x6A => {}, 0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
0x6C => apu.ch2.setSound2CntH(&apu.fs, value), 0x62 => apu.ch1.setSound1CntH(value),
0x6E => {}, 0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
0x66 => {},
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)), 0x68 => apu.ch2.setSound2CntL(value),
0x72 => apu.ch3.setSound3CntH(value), 0x6A => {},
0x74 => apu.ch3.setSound3CntX(&apu.fs, value), 0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
0x76 => {}, 0x6E => {},
0x78 => apu.ch4.setSound4CntL(value), 0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
0x7A => {}, 0x72 => apu.ch3.setSound3CntH(value),
0x7C => apu.ch4.setSound4CntH(&apu.fs, value), 0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
0x7E => {}, 0x76 => {},
0x80 => apu.setSoundCntL(value), 0x78 => apu.ch4.setSound4CntL(value),
0x82 => apu.setSoundCntH(value), 0x7A => {},
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
0x86 => {}, 0x7E => {},
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), 0x80 => apu.setSoundCntL(value),
0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }), 0x82 => apu.setSoundCntH(value),
0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }), 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), 0x86 => {},
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 => switch (byte_addr) { u8 => {
0x60 => apu.ch1.setSound1CntL(value), if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
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 => {},
0x68 => apu.ch2.setNr21(value), switch (byte_addr) {
0x69 => apu.ch2.setNr22(value), 0x60 => apu.ch1.setSound1CntL(value),
0x6A, 0x6B => {}, 0x61 => {},
0x6C => apu.ch2.setNr23(value), 0x62 => apu.ch1.setNr11(value),
0x6D => apu.ch2.setNr24(&apu.fs, value), 0x63 => apu.ch1.setNr12(value),
0x6E, 0x6F => {}, 0x64 => apu.ch1.setNr13(value),
0x65 => apu.ch1.setNr14(&apu.fs, value),
0x66, 0x67 => {},
0x70 => apu.ch3.setSound3CntL(value), // NR30 0x68 => apu.ch2.setNr21(value),
0x71 => {}, 0x69 => apu.ch2.setNr22(value),
0x72 => apu.ch3.setNr31(value), 0x6A, 0x6B => {},
0x73 => apu.ch3.vol.raw = value, // NR32 0x6C => apu.ch2.setNr23(value),
0x74 => apu.ch3.setNr33(value), 0x6D => apu.ch2.setNr24(&apu.fs, value),
0x75 => apu.ch3.setNr34(&apu.fs, value), 0x6E, 0x6F => {},
0x76, 0x77 => {},
0x78 => apu.ch4.setNr41(value), 0x70 => apu.ch3.setSound3CntL(value), // NR30
0x79 => apu.ch4.setNr42(value), 0x71 => {},
0x7A, 0x7B => {}, 0x72 => apu.ch3.setNr31(value),
0x7C => apu.ch4.poly.raw = value, // NR 43 0x73 => apu.ch3.vol.raw = value, // NR32
0x7D => apu.ch4.setNr44(&apu.fs, value), 0x74 => apu.ch3.setNr33(value),
0x7E, 0x7F => {}, 0x75 => apu.ch3.setNr34(&apu.fs, value),
0x76, 0x77 => {},
0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)), 0x78 => apu.ch4.setNr41(value),
0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)), 0x79 => apu.ch4.setNr42(value),
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), 0x7A, 0x7B => {},
0x85 => {}, 0x7C => apu.ch4.poly.raw = value, // NR 43
0x86, 0x87 => {}, 0x7D => apu.ch4.setNr44(&apu.fs, value),
0x88, 0x89 => apu.bias.raw = setHalf(u16, apu.bias.raw, byte_addr, value), // SOUNDBIAS 0x7E, 0x7F => {},
0x8A...0x8F => {},
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value), 0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)),
0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }), 0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)),
0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }), 0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), 0x85 => {},
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"),
} }
@ -275,15 +290,20 @@ 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 {
self.setSoundCntL(@truncate(u16, value)); if (self.cnt.apu_enable.read()) self.setSoundCntL(@truncate(u16, value));
self.setSoundCntH(@truncate(u16, value >> 16)); self.setSoundCntH(@truncate(u16, value >> 16));
} }
@ -322,11 +342,14 @@ 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.pos = 0; self.ch1.square.reset();
self.ch2.square.pos = 0; self.ch2.square.reset();
// Reset Wave Device Offsets // Reset Wave
self.ch3.wave_dev.offset = 0; self.ch3.wave_dev.reset();
// Rest Noise
self.ch4.lfsr.reset();
} else { } else {
self.reset(); self.reset();
} }
@ -395,8 +418,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: Is SOUNDBIAS 9-bit or 10-bit? // FIXME: SOUNDBIAS is 10-bit but The waveform is centered around 0 if I treat it as 11-bit
const bias = @as(i16, self.bias.level.read()) << 1; const bias = @as(i16, self.bias.level.read()) << 2;
left += bias; left += bias;
right += bias; right += bias;

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,11 @@ 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,6 +6,10 @@ 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,6 +18,13 @@ 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,6 +19,11 @@ 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,6 +20,11 @@ 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,6 +38,13 @@ 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 });