diff --git a/src/apu.zig b/src/apu.zig index 5e78946..96ff52a 100644 --- a/src/apu.zig +++ b/src/apu.zig @@ -41,7 +41,7 @@ pub const Apu = struct { .ch1 = ToneSweep.init(sched), .ch2 = Tone.init(sched), .ch3 = Wave.init(sched), - .ch4 = Noise.init(), + .ch4 = Noise.init(sched), .chA = DmaSound(.A).init(), .chB = DmaSound(.B).init(), @@ -61,7 +61,8 @@ pub const Apu = struct { sched.push(.SampleAudio, sched.now() + apu.sampleTicks()); sched.push(.{ .ApuChannel = 0 }, sched.now() + SquareWave.ticks); // Channel 1 sched.push(.{ .ApuChannel = 1 }, sched.now() + SquareWave.ticks); // Channel 2 - sched.push(.{ .ApuChannel = 2 }, sched.now() + WaveDevice.ticks); // Channel 2 + sched.push(.{ .ApuChannel = 2 }, sched.now() + WaveDevice.ticks); // Channel 3 + sched.push(.{ .ApuChannel = 3 }, sched.now() + Noise.ticks); // Channel 4 sched.push(.FrameSequencer, sched.now() + ((1 << 24) / 512)); return apu; @@ -70,7 +71,8 @@ pub const Apu = struct { fn reset(self: *Self) void { self.ch1.reset(); self.ch2.reset(); - // TODO: Reset the rest of the channels + self.ch3.reset(); + self.ch4.reset(); } pub fn setDmaCnt(self: *Self, value: u16) void { @@ -145,11 +147,17 @@ pub const Apu = struct { 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 mixed_left = ch1_left + ch2_left + ch3_left / 3; - const mixed_right = ch1_right + ch2_right + ch3_right / 3; + // Sample Channel 4 + const ch4_sample = self.highPassFilter(self.ch4.amplitude(), self.ch4.enabled); + 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 mixed_left = ch3_left; - // const mixed_right = ch3_right; + const mixed_left = ch1_left + ch2_left + ch3_left + ch4_left / 4; + const mixed_right = ch1_right + ch2_right + ch3_right + ch4_right / 4; + + // // For Debugging Purposes + // const mixed_left = ch4_left; + // const mixed_right = ch4_right; // Apply NR50 Volume Modifications const nr50_left = (@intToFloat(f32, self.psg_cnt.left_vol.read()) + 1.0) * mixed_left; @@ -180,8 +188,8 @@ pub const Apu = struct { const chB_right = if (self.dma_cnt.chB_right.read()) chB else 0; // Mix all Channels - const left = (chA_left + chB_left + (psg_left * 0.1)) / 3; - const right = (chA_right + chB_right + (psg_right * 0.1)) / 3; + const left = (chA_left + chB_left + (psg_left * 0.05)) / 3; + const right = (chA_right + chB_right + (psg_right * 0.05)) / 3; if (self.sampling_cycle != self.bias.sampling_cycle.read()) { log.warn("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() }); @@ -397,7 +405,7 @@ const ToneSweep = struct { /// NR11 pub fn setDuty(self: *Self, value: u8) void { self.duty.raw = value; - self.len_dev.timer = @intCast(u7, 64 - value); + self.len_dev.timer = @as(u7, 64) - @truncate(u6, value); } /// NR12 @@ -532,7 +540,7 @@ const Tone = struct { /// NR21 pub fn setDuty(self: *Self, value: u8) void { self.duty.raw = value; - self.len_dev.timer = @intCast(u7, 64 - value); + self.len_dev.timer = @as(u7, 64) - @truncate(u6, value); } /// NR22 @@ -620,6 +628,16 @@ const Wave = struct { }; } + fn reset(self: *Self) void { + self.select.raw = 0; + self.length = 0; + self.vol.raw = 0; + self.freq.raw = 0; + + self.sample = 0; + self.enabled = false; + } + pub fn tickLength(self: *Self) void { self.len_dev.tick(self.freq, &self.enabled); } @@ -695,6 +713,7 @@ const Wave = struct { const Noise = struct { const Self = @This(); + const ticks = (1 << 24) / (1 << 22); /// Write-only /// NR41 @@ -712,9 +731,13 @@ const Noise = struct { /// Envelope Functionality env_dev: EnvelopeDevice, - enabled: bool, + // Linear Feedback Shift Register + lfsr: Lfsr, - fn init() Self { + enabled: bool, + sample: u8, + + fn init(sched: *Scheduler) Self { return .{ .len = 0, .envelope = .{ .raw = 0 }, @@ -724,9 +747,22 @@ const Noise = struct { .len_dev = LengthDevice.init(), .env_dev = EnvelopeDevice.init(), + .lfsr = Lfsr.init(sched), + + .sample = 0, }; } + fn reset(self: *Self) void { + self.len = 0; + self.envelope.raw = 0; + self.poly.raw = 0; + self.cnt.raw = 0; + + self.sample = 0; + self.enabled = false; + } + pub fn tickLength(self: *Self) void { self.len_dev.ch4Tick(self.cnt, &self.enabled); } @@ -734,6 +770,77 @@ const Noise = struct { pub fn tickEnvelope(self: *Self) void { self.env_dev.tick(self.envelope); } + + /// NR41 + pub fn setLength(self: *Self, len: u8) void { + self.len = @truncate(u6, len); + self.len_dev.timer = @as(u7, 64) - @truncate(u6, len); + } + + /// NR42 + pub fn setEnvelope(self: *Self, value: u8) void { + self.envelope.raw = value; + if (!self.isDacEnabled()) self.enabled = false; + } + + /// NR41, NR42 + pub fn setSoundCntL(self: *Self, value: u16) void { + self.setLength(@truncate(u8, value)); + self.setEnvelope(@truncate(u8, value >> 8)); + } + + /// NR43, NR44 + pub fn setSoundCntH(self: *Self, fs: *const FrameSequencer, value: u16) void { + self.poly.raw = @truncate(u8, value); + self.setCnt(fs, @truncate(u8, value >> 8)); + } + + /// NR44 + pub fn setCnt(self: *Self, fs: *const FrameSequencer, byte: u8) void { + var new: io.NoiseControl = .{ .raw = byte }; + + if (new.trigger.read()) { + self.enabled = true; // FIXME: Same as ch1, ch2, is this necessary? + + if (self.len_dev.timer == 0) { + self.len_dev.timer = 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 + // self.lfsr.reloadTimer(self.freq.frequency.read()); // TODO: Do we do something here? + self.lfsr.shift = 0x7FFF; + + // Update Envelope and Volume + self.env_dev.timer = self.envelope.period.read(); + self.env_dev.vol = self.envelope.init_vol.read(); + + self.enabled = self.isDacEnabled(); + } + + self.lfsr.updateLength(fs, self, new); + self.cnt = new; + } + + pub fn channelTimerOverflow(self: *Self, late: u64) void { + self.lfsr.handleTimerOverflow(self.poly, late); + + self.sample = 0; + if (!self.isDacEnabled()) return; + self.sample = if (self.enabled) self.lfsr.sample() * self.env_dev.vol else 0; + } + + fn amplitude(self: *const Self) f32 { + return (@intToFloat(f32, self.sample) / 7.5) - 1.0; + } + + fn isDacEnabled(self: *const Self) bool { + return self.envelope.raw & 0xF8 != 0x00; + } }; pub fn DmaSound(comptime kind: DmaSoundKind) type { @@ -1013,3 +1120,52 @@ const SquareWave = struct { return @truncate(u1, result); } }; + +// Linear Feedback Shift Register +const Lfsr = struct { + const Self = @This(); + const ticks = (1 << 24) / (1 << 22); + + shift: u15, + timer: u16, + + sched: *Scheduler, + + pub fn init(sched: *Scheduler) Self { + return .{ + .shift = 0, + .timer = 0, + .sched = sched, + }; + } + + fn sample(self: *const Self) u1 { + return @truncate(u1, ~self.shift); + } + + fn updateLength(_: *const Self, fs: *const FrameSequencer, ch4: *Noise, new_cnt: io.NoiseControl) void { + if (!fs.lengthIsNext() and !ch4.cnt.length_enable.read() and new_cnt.length_enable.read() and ch4.len_dev.timer != 0) { + ch4.len_dev.timer -= 1; + + if (ch4.len_dev.timer == 0 and !new_cnt.trigger.read()) ch4.enabled = false; + } + } + + fn handleTimerOverflow(self: *Self, poly: io.PolyCounter, late: u64) void { + const div = Self.divisor(poly.div_ratio.read()); + const timer = @as(u64, div << poly.shift.read()); + + const tmp = (self.shift & 1) ^ ((self.shift & 2) >> 1); + self.shift = (self.shift >> 1) | (tmp << 14); + + if (poly.width.read()) + self.shift = (self.shift & ~@as(u15, 0x40)) | tmp << 6; + + self.sched.push(.{ .ApuChannel = 3 }, self.sched.now() + timer * ticks - late); + } + + fn divisor(code: u3) u16 { + if (code == 0) return 8; + return @as(u16, code) << 4; + } +}; diff --git a/src/bus/io.zig b/src/bus/io.zig index 91cfbc7..b215e73 100644 --- a/src/bus/io.zig +++ b/src/bus/io.zig @@ -236,11 +236,17 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { 0x0400_0060 => bus.apu.ch1.sweep.raw = @truncate(u8, value), // Channel 1 0x0400_0062 => bus.apu.ch1.setSoundCntH(value), 0x0400_0064 => bus.apu.ch1.setFreq(&bus.apu.fs, value), + 0x0400_0068 => bus.apu.ch2.setSoundCntH(value), // Channel 2 0x0400_006C => bus.apu.ch2.setFreq(&bus.apu.fs, value), + 0x0400_0070 => bus.apu.ch3.setWaveSelect(@truncate(u8, value)), // Channel 3 0x0400_0072 => bus.apu.ch3.setSoundCntH(value), 0x0400_0074 => bus.apu.ch3.setFreq(&bus.apu.fs, value), + + 0x0400_0078 => bus.apu.ch4.setSoundCntL(value), // Channel 4 + 0x0400_007C => bus.apu.ch4.setSoundCntH(&bus.apu.fs, value), + 0x0400_0080 => bus.apu.psg_cnt.raw = value, 0x0400_0082 => bus.apu.setDmaCnt(value), 0x0400_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1), @@ -324,19 +330,23 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { 0x0400_0063 => bus.apu.ch1.envelope.raw = value, 0x0400_0064 => bus.apu.ch1.setFreqLow(value), 0x0400_0065 => bus.apu.ch1.setFreqHigh(&bus.apu.fs, value), + 0x0400_0068 => bus.apu.ch2.duty.raw = value, // Channel 2 0x0400_0069 => bus.apu.ch2.envelope.raw = value, 0x0400_006C => bus.apu.ch2.setFreqLow(value), 0x0400_006D => bus.apu.ch2.setFreqHigh(&bus.apu.fs, value), + 0x0400_0070 => bus.apu.ch3.setWaveSelect(value), // Channel 3 0x0400_0072 => bus.apu.ch3.setLength(value), 0x0400_0073 => bus.apu.ch3.vol.raw = value, 0x0400_0074 => bus.apu.ch3.setFreqLow(value), 0x0400_0075 => bus.apu.ch3.setFreqHigh(&bus.apu.fs, value), - 0x0400_0078 => bus.apu.ch4.len = @truncate(u6, value), - 0x0400_0079 => bus.apu.ch4.envelope.raw = value, + + 0x0400_0078 => bus.apu.ch4.setLength(value), // Channel 4 + 0x0400_0079 => bus.apu.ch4.setEnvelope(value), 0x0400_007C => bus.apu.ch4.poly.raw = value, - 0x0400_007D => bus.apu.ch4.cnt.raw = value, + 0x0400_007D => bus.apu.ch4.setCnt(&bus.apu.fs, value), + 0x0400_0080 => bus.apu.setSoundCntLLow(value), 0x0400_0081 => bus.apu.setSoundCntLHigh(value), 0x0400_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1), diff --git a/src/scheduler.zig b/src/scheduler.zig index f58a4ce..b1856d1 100644 --- a/src/scheduler.zig +++ b/src/scheduler.zig @@ -56,7 +56,7 @@ pub const Scheduler = struct { 0 => cpu.bus.apu.ch1.channelTimerOverflow(late), 1 => cpu.bus.apu.ch2.channelTimerOverflow(late), 2 => cpu.bus.apu.ch3.channelTimerOverflow(late), - else => {}, + 3 => cpu.bus.apu.ch4.channelTimerOverflow(late), } }, .FrameSequencer => cpu.bus.apu.tickFrameSequencer(late),