chore: improvements to APU accuracy

This commit is contained in:
Rekai Nyangadzayi Musuka 2022-10-21 05:12:39 -03:00
parent 1a23073424
commit 896ae0935a
2 changed files with 111 additions and 82 deletions

View File

@ -131,24 +131,30 @@ pub const Apu = struct {
} }
pub fn sampleAudio(self: *Self, late: u64) void { pub fn sampleAudio(self: *Self, late: u64) void {
// zig fmt: off
const any_ch_enabled = self.ch1.enabled
or self.ch2.enabled
or self.ch3.enabled
or self.ch4.enabled;
// zig fmt: on
// Sample Channel 1 // Sample Channel 1
const ch1_sample = self.highPassFilter(self.ch1.amplitude(), self.ch1.enabled); const ch1_sample = self.highPass(self.ch1.amplitude(), any_ch_enabled);
const ch1_left = if (self.psg_cnt.ch1_left.read()) ch1_sample else 0; const ch1_left = if (self.psg_cnt.ch1_left.read()) ch1_sample else 0;
const ch1_right = if (self.psg_cnt.ch1_right.read()) ch1_sample else 0; const ch1_right = if (self.psg_cnt.ch1_right.read()) ch1_sample else 0;
// Sample Channel 2 // Sample Channel 2
const ch2_sample = self.highPassFilter(self.ch2.amplitude(), self.ch2.enabled); const ch2_sample = self.highPass(self.ch2.amplitude(), any_ch_enabled);
const ch2_left = if (self.psg_cnt.ch2_left.read()) ch2_sample else 0; const ch2_left = if (self.psg_cnt.ch2_left.read()) ch2_sample else 0;
const ch2_right = if (self.psg_cnt.ch2_right.read()) ch2_sample else 0; const ch2_right = if (self.psg_cnt.ch2_right.read()) ch2_sample else 0;
// Sample Channel 3 // Sample Channel 3
const ch3_sample = self.highPassFilter(self.ch3.amplitude(), self.ch3.enabled); const ch3_sample = self.highPass(self.ch3.amplitude(), any_ch_enabled);
const ch3_left = if (self.psg_cnt.ch3_left.read()) ch3_sample else 0; const ch3_left = if (self.psg_cnt.ch3_left.read()) ch3_sample else 0;
const ch3_right = if (self.psg_cnt.ch3_right.read()) ch3_sample else 0; const ch3_right = if (self.psg_cnt.ch3_right.read()) ch3_sample else 0;
// Sample Channel 4 // Sample Channel 4
const ch4_sample = self.highPassFilter(self.ch4.amplitude(), self.ch4.enabled); const ch4_sample = self.highPass(self.ch4.amplitude(), any_ch_enabled);
const ch4_left = if (self.psg_cnt.ch4_left.read()) ch4_sample else 0; const ch4_left = if (self.psg_cnt.ch4_left.read()) ch4_sample else 0;
const ch4_right = if (self.psg_cnt.ch4_right.read()) ch4_sample else 0; const ch4_right = if (self.psg_cnt.ch4_right.read()) ch4_sample else 0;
@ -191,6 +197,9 @@ pub const Apu = struct {
const left = (chA_left + chB_left + (psg_left * 0.05)) / 3; const left = (chA_left + chB_left + (psg_left * 0.05)) / 3;
const right = (chA_right + chB_right + (psg_right * 0.05)) / 3; const right = (chA_right + chB_right + (psg_right * 0.05)) / 3;
// const left = psg_left * 0.1;
// const right = psg_right * 0.1;
if (self.sampling_cycle != self.bias.sampling_cycle.read()) { if (self.sampling_cycle != self.bias.sampling_cycle.read()) {
log.info("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); log.info("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() });
@ -261,16 +270,15 @@ pub const Apu = struct {
} }
} }
fn highPassFilter(self: *Self, sample: f32, ch_enabled: bool) f32 { fn highPass(self: *Self, sample: f32, enabled: bool) f32 {
const charge_factor = 0.999958; return if (enabled) blk: {
const out = sample - self.capacitor;
const charge_factor =
std.math.pow(f32, 0.999958, @intToFloat(f32, (1 << 22) / self.sampleRate()));
var output: f32 = 0; self.capacitor = sample - out * charge_factor;
if (ch_enabled) { break :blk out;
output = sample - self.capacitor; } else 0.0;
self.capacitor = sample - output * std.math.pow(f32, charge_factor, @intToFloat(f32, (1 << 22) / self.sampleRate()));
}
return output;
} }
}; };
@ -377,7 +385,7 @@ const ToneSweep = struct {
} }
pub fn tickLength(self: *Self) void { pub fn tickLength(self: *Self) void {
self.len_dev.tick(self.freq, &self.enabled); self.len_dev.tick(self.freq.length_enable.read(), &self.enabled);
} }
pub fn tickEnvelope(self: *Self) void { pub fn tickEnvelope(self: *Self) void {
@ -430,20 +438,19 @@ const ToneSweep = struct {
var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
if (new.trigger.read()) { if (new.trigger.read()) {
self.enabled = true; // FIXME: is this necessary? self.enabled = true;
if (self.len_dev.timer == 0) { if (self.len_dev.timer == 0) {
self.len_dev.timer = 64; self.len_dev.timer =
if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
// We unset this on the old frequency because of the obscure
// behaviour outside of this if statement's scope
self.freq.length_enable.write(false);
} }
self.square.reloadTimer(.Ch1, self.freq.frequency.read()); self.square.reloadTimer(.Ch1, self.freq.frequency.read());
// Reload Envelope period and timer // Reload Envelope period and timer
self.env_dev.timer = self.envelope.period.read(); self.env_dev.timer = self.envelope.period.read();
if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
self.env_dev.vol = self.envelope.init_vol.read(); self.env_dev.vol = self.envelope.init_vol.read();
// Sweep Trigger Behaviour // Sweep Trigger Behaviour
@ -512,7 +519,7 @@ const Tone = struct {
} }
pub fn tickLength(self: *Self) void { pub fn tickLength(self: *Self) void {
self.len_dev.tick(self.freq, &self.enabled); self.len_dev.tick(self.freq.length_enable.read(), &self.enabled);
} }
pub fn tickEnvelope(self: *Self) void { pub fn tickEnvelope(self: *Self) void {
@ -565,21 +572,19 @@ const Tone = struct {
var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
if (new.trigger.read()) { if (new.trigger.read()) {
self.enabled = true; // Same as ch1, is this necessary? self.enabled = true;
if (self.len_dev.timer == 0) { if (self.len_dev.timer == 0) {
self.len_dev.timer = 64; self.len_dev.timer =
if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
// We unset this on the old frequency because of the obscure
// behaviour outside of this if statement's scope
// FIXME: This wasn't in my ch2 GB implementation
self.freq.length_enable.write(false);
} }
self.square.reloadTimer(.Ch2, self.freq.frequency.read()); self.square.reloadTimer(.Ch2, self.freq.frequency.read());
// Reload Envelope period and timer // Reload Envelope period and timer
self.env_dev.timer = self.envelope.period.read(); self.env_dev.timer = self.envelope.period.read();
if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
self.env_dev.vol = self.envelope.init_vol.read(); self.env_dev.vol = self.envelope.init_vol.read();
self.enabled = self.isDacEnabled(); self.enabled = self.isDacEnabled();
@ -639,7 +644,7 @@ const Wave = struct {
} }
pub fn tickLength(self: *Self) void { pub fn tickLength(self: *Self) void {
self.len_dev.tick(self.freq, &self.enabled); self.len_dev.tick(self.freq.length_enable.read(), &self.enabled);
} }
/// NR30 /// NR30
@ -676,15 +681,11 @@ const Wave = struct {
var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) };
if (new.trigger.read()) { if (new.trigger.read()) {
self.enabled = true; // FIXME: Same as ch1, ch2, is this necessary? self.enabled = true;
if (self.len_dev.timer == 0) { if (self.len_dev.timer == 0) {
self.len_dev.timer = 256; self.len_dev.timer =
if (!fs.isLengthNext() and new.length_enable.read()) 255 else 256;
// We unset this on the old frequency because of the obscure
// behaviour outside of this if statement's scope
// FIXME: This wasn't in my ch3 GB implementation
self.freq.length_enable.write(false);
} }
// Update The Frequency Timer // Update The Frequency Timer
@ -764,7 +765,7 @@ const Noise = struct {
} }
pub fn tickLength(self: *Self) void { pub fn tickLength(self: *Self) void {
self.len_dev.ch4Tick(self.cnt, &self.enabled); self.len_dev.tick(self.cnt.length_enable.read(), &self.enabled);
} }
pub fn tickEnvelope(self: *Self) void { pub fn tickEnvelope(self: *Self) void {
@ -800,23 +801,21 @@ const Noise = struct {
var new: io.NoiseControl = .{ .raw = byte }; var new: io.NoiseControl = .{ .raw = byte };
if (new.trigger.read()) { if (new.trigger.read()) {
self.enabled = true; // FIXME: Same as ch1, ch2, is this necessary? self.enabled = true;
if (self.len_dev.timer == 0) { if (self.len_dev.timer == 0) {
self.len_dev.timer = 64; self.len_dev.timer =
if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64;
// We unset this on the old frequency because of the obscure
// behaviour outside of this if statement's scope
// FIXME: This wasn't in my ch4 GB implementation
self.cnt.length_enable.write(false);
} }
// Update The Frequency Timer // Update The Frequency Timer
// self.lfsr.reloadTimer(self.freq.frequency.read()); // TODO: Do we do something here? self.lfsr.reloadTimer(self.poly);
self.lfsr.shift = 0x7FFF; self.lfsr.shift = 0x7FFF;
// Update Envelope and Volume // Update Envelope and Volume
self.env_dev.timer = self.envelope.period.read(); self.env_dev.timer = self.envelope.period.read();
if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1;
self.env_dev.vol = self.envelope.init_vol.read(); self.env_dev.vol = self.envelope.init_vol.read();
self.enabled = self.isDacEnabled(); self.enabled = self.isDacEnabled();
@ -895,9 +894,13 @@ const FrameSequencer = struct {
self.step +%= 1; self.step +%= 1;
} }
fn lengthIsNext(self: *const Self) bool { fn isLengthNext(self: *const Self) bool {
return (self.step +% 1) & 1 == 0; // Steps, 0, 2, 4, and 6 clock length return (self.step +% 1) & 1 == 0; // Steps, 0, 2, 4, and 6 clock length
} }
fn isEnvelopeNext(self: *const Self) bool {
return (self.step +% 1) == 7;
}
}; };
const LengthDevice = struct { const LengthDevice = struct {
@ -909,24 +912,13 @@ const LengthDevice = struct {
return .{ .timer = 0 }; return .{ .timer = 0 };
} }
fn tick(self: *Self, freq: io.Frequency, ch_enabled: *bool) void { fn tick(self: *Self, length_enable: bool, ch_enabled: *bool) void {
const len_enable = freq.length_enable.read(); if (length_enable) {
if (self.timer == 0) return;
if (len_enable and self.timer > 0) {
self.timer -= 1; self.timer -= 1;
// if length timer is now 0 // By returning early if timer == 0, this is only
if (self.timer == 0) ch_enabled.* = false; // true if timer == 0 because of the decrement we just did
}
}
fn ch4Tick(self: *Self, cnt: io.NoiseControl, ch_enabled: *bool) void {
const len_enable = cnt.length_enable.read();
if (len_enable and self.timer > 0) {
self.timer -= 1;
// if length timer is now 0
if (self.timer == 0) ch_enabled.* = false; if (self.timer == 0) ch_enabled.* = false;
} }
} }
@ -1019,12 +1011,18 @@ const WaveDevice = struct {
}; };
} }
/// Obscure NRx4 Behaviour fn updateLength(_: *Self, fs: *const FrameSequencer, ch3: *Wave, new: io.Frequency) void {
fn updateLength(_: *Self, fs: *const FrameSequencer, ch3: *Wave, new_cnt: io.Frequency) void { // Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.lengthIsNext() and !ch3.freq.length_enable.read() and new_cnt.length_enable.read() and ch3.len_dev.timer != 0) { if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch3.freq.length_enable.read() and new.length_enable.read() and ch3.len_dev.timer != 0) {
ch3.len_dev.timer -= 1; ch3.len_dev.timer -= 1;
if (ch3.len_dev.timer == 0 and !new_cnt.trigger.read()) ch3.enabled = false; // If Length Timer is now 0 and trigger is clear, disable the channel
if (ch3.len_dev.timer == 0 and !new.trigger.read()) ch3.enabled = false;
}
} }
} }
@ -1069,21 +1067,33 @@ const SquareWave = struct {
const ChannelKind = enum { Ch1, Ch2 }; const ChannelKind = enum { Ch1, Ch2 };
/// Obscure NRx4 Behaviour fn updateToneSweepLength(_: *Self, fs: *const FrameSequencer, ch1: *ToneSweep, new: io.Frequency) void {
fn updateToneSweepLength(_: *Self, fs: *const FrameSequencer, ch1: *ToneSweep, new_cnt: io.Frequency) void { // Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.lengthIsNext() and !ch1.freq.length_enable.read() and new_cnt.length_enable.read() and ch1.len_dev.timer != 0) { if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch1.freq.length_enable.read() and new.length_enable.read() and ch1.len_dev.timer != 0) {
ch1.len_dev.timer -= 1; ch1.len_dev.timer -= 1;
if (ch1.len_dev.timer == 0 and !new_cnt.trigger.read()) ch1.enabled = false; // If Length Timer is now 0 and trigger is clear, disable the channel
if (ch1.len_dev.timer == 0 and !new.trigger.read()) ch1.enabled = false;
}
} }
} }
/// Obscure NRx4 Behaviour fn updateToneLength(_: *Self, fs: *const FrameSequencer, ch2: *Tone, new: io.Frequency) void {
fn updateToneLength(_: *Self, fs: *const FrameSequencer, ch2: *Tone, new_cnt: io.Frequency) void { // Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.lengthIsNext() and !ch2.freq.length_enable.read() and new_cnt.length_enable.read() and ch2.len_dev.timer != 0) { if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch2.freq.length_enable.read() and new.length_enable.read() and ch2.len_dev.timer != 0) {
ch2.len_dev.timer -= 1; ch2.len_dev.timer -= 1;
if (ch2.len_dev.timer == 0 and !new_cnt.trigger.read()) ch2.enabled = false; // If Length Timer is now 0 and trigger is clear, disable the channel
if (ch2.len_dev.timer == 0 and !new.trigger.read()) ch2.enabled = false;
}
} }
} }
@ -1091,7 +1101,7 @@ const SquareWave = struct {
const timer = (2048 - @as(u64, cnt.frequency.read())) * 4; const timer = (2048 - @as(u64, cnt.frequency.read())) * 4;
self.timer = @truncate(u12, timer); self.timer = @truncate(u12, timer);
self.pos = (self.pos +% 1) & 7; self.pos +%= 1;
self.sched.push(.{ .ApuChannel = if (kind == .Ch1) 0 else 1 }, self.sched.now() + timer * ticks - late); self.sched.push(.{ .ApuChannel = if (kind == .Ch1) 0 else 1 }, self.sched.now() + timer * ticks - late);
} }
@ -1143,13 +1153,29 @@ const Lfsr = struct {
return @truncate(u1, ~self.shift); return @truncate(u1, ~self.shift);
} }
fn updateLength(_: *const Self, fs: *const FrameSequencer, ch4: *Noise, new_cnt: io.NoiseControl) void { fn updateLength(_: *Self, fs: *const FrameSequencer, ch4: *Noise, new: io.NoiseControl) void {
if (!fs.lengthIsNext() and !ch4.cnt.length_enable.read() and new_cnt.length_enable.read() and ch4.len_dev.timer != 0) { // Write to NRx4 when FS's next step is not one that clocks the length counter
if (!fs.isLengthNext()) {
// If length_enable was disabled but is now enabled and length timer is not 0 already,
// decrement the length timer
if (!ch4.cnt.length_enable.read() and new.length_enable.read() and ch4.len_dev.timer != 0) {
ch4.len_dev.timer -= 1; ch4.len_dev.timer -= 1;
if (ch4.len_dev.timer == 0 and !new_cnt.trigger.read()) ch4.enabled = false; // If Length Timer is now 0 and trigger is clear, disable the channel
if (ch4.len_dev.timer == 0 and !new.trigger.read()) ch4.enabled = false;
} }
} }
}
fn reloadTimer(self: *Self, poly: io.PolyCounter) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 3 });
const div = Self.divisor(poly.div_ratio.read());
const timer = @as(u64, div << poly.shift.read());
self.sched.push(.{ .ApuChannel = 3 }, self.sched.now() + timer * ticks);
}
fn handleTimerOverflow(self: *Self, poly: io.PolyCounter, late: u64) void { fn handleTimerOverflow(self: *Self, poly: io.PolyCounter, late: u64) void {
const div = Self.divisor(poly.div_ratio.read()); const div = Self.divisor(poly.div_ratio.read());

View File

@ -57,6 +57,9 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
0x0400_0108 => @as(T, bus.tim._2.cnt.raw) << 16 | bus.tim._2.counter(), 0x0400_0108 => @as(T, bus.tim._2.cnt.raw) << 16 | bus.tim._2.counter(),
0x0400_010C => @as(T, bus.tim._3.cnt.raw) << 16 | bus.tim._3.counter(), 0x0400_010C => @as(T, bus.tim._3.cnt.raw) << 16 | bus.tim._3.counter(),
// Serial Communication 1
0x0400_0128 => unimplementedRead("Read {} from SIOCNT and SIOMLT_SEND", .{T}),
// Keypad Input // Keypad Input
0x0400_0130 => unimplementedRead("Read {} from KEYINPUT", .{T}), 0x0400_0130 => unimplementedRead("Read {} from KEYINPUT", .{T}),