From f84009aeb9453b0ef340780f9c836e484626add3 Mon Sep 17 00:00:00 2001 From: Matthew Pomes Date: Wed, 8 Apr 2026 21:16:54 -0500 Subject: [PATCH] Minor improvements to APU --- .gitignore | 2 + src/apu.rs | 37 +++++++----- src/apu/tests.rs | 98 ++++++++++++++++++++++++++++++++ src/test_roms/apu_pulse_1_test.s | 33 +++++++++++ 4 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 src/apu/tests.rs create mode 100644 src/test_roms/apu_pulse_1_test.s diff --git a/.gitignore b/.gitignore index db7658d..84fa85e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.nes *.zip *.dmp +wasm/ +!wasm/wasm.html diff --git a/src/apu.rs b/src/apu.rs index f96ba61..7863bfe 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -1,7 +1,3 @@ -use std::iter::repeat_n; - -use tracing::debug; - macro_rules! lut { ($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => { const $name: [$ty; $len] = { @@ -264,7 +260,7 @@ Output: {16:>4} ${16:02X} self.sweep.period(), self.sweep.enable(), self.period, - 0, + self.counter.current, self.enabled, self.period_timer, self.cur, // ? @@ -423,6 +419,7 @@ bitfield::bitfield! { } #[derive(Debug, Clone)] +#[allow(dead_code)] struct NoiseChannel { enabled: bool, evelope: NoiseEnvelope, @@ -447,7 +444,7 @@ impl NoiseChannel { } } - pub fn write(&mut self, offset: u16, val: u8) { + pub fn write(&mut self, offset: u16, _val: u8) { match offset { 0x00 => (), 0x01 => (), @@ -505,7 +502,7 @@ impl DeltaChannel { } } - pub fn write(&mut self, offset: u16, val: u8) { + pub fn write(&mut self, offset: u16, _val: u8) { match offset { 0x00 => (), 0x01 => (), @@ -553,12 +550,21 @@ pub struct APU { impl std::fmt::Debug for APU { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // write!( - // f, - // "PPU: f {}, s {}, p {}", - // self.frame_count, self.scanline, self.pixel - // ) - Ok(()) + write!( + f, + "APU +{:#?} +{:#?} +{:#?} +{:#?} +{:#?} + ", + self.pulse_1, + self.pulse_2, + self.triangle, + self.noise, + self.dmc, + ) } } @@ -577,7 +583,7 @@ impl APU { irq: false, }, - samples: vec![0; 100], + samples: Vec::with_capacity(10000), } } @@ -739,3 +745,6 @@ mod apu_iced { } } } + +#[cfg(test)] +mod tests; diff --git a/src/apu/tests.rs b/src/apu/tests.rs new file mode 100644 index 0000000..bb23412 --- /dev/null +++ b/src/apu/tests.rs @@ -0,0 +1,98 @@ +use super::*; + +#[track_caller] +fn run_ppu_cycles(apu: &mut APU, cycles: usize) { + for i in 0..cycles { + apu.run_one_clock_cycle(i); + } +} + +trait Pattern { + fn matches(&mut self, val: u8) -> bool; +} + +impl Pattern for u8 { + fn matches(&mut self, val: u8) -> bool { + val == *self + } +} + +impl> Pattern for I { + fn matches(&mut self, val: u8) -> bool { + self.next().is_some_and(|v| v == val) + } +} + +fn is(samples: &[u8], mut pattern: impl Pattern) -> bool { + for s in samples { + if !pattern.matches(*s) { + return false; + } + } + true +} + +#[test] +fn check_is() { + assert!(&[0; 20], 0..5); +} + +macro_rules! cycle_check { + (|$apu:ident| $init:expr; $([$pat:expr; $count:expr]),* ; [$final:expr; _]) => { + let mut $apu = APU::init(); + $init; + run_ppu_cycles(&mut $apu, 10000); + let mut s = $apu.get_frame_samples(); + let mut cur = 0; + $( + let count = $count; + if !is(&s[..count], $pat) { + panic!("Incorrect samples from {} to {}:\nExpected: {:?}\n Found: {:?}", cur, cur + count, $pat, &s[..count]); + } + s = &s[count..]; + cur += count; + )* + if !is(s, $final) { + panic!("Incorrect samples from {} to {}:\nExpected: {:?}\n Found: {:?}", cur, cur + count, $final, s); + } + }; +} + +const SNDCHN: u16 = 0x15; + +const PULSE_CH1_DLCV: u16 = 0x00; +const PULSE_CH1_SWEEP: u16 = 0x01; +const PULSE_CH1_TLOW: u16 = 0x02; +const PULSE_CH1_LCTH: u16 = 0x03; + +#[test] +fn run_apu() { + let mut apu = APU::init(); + run_ppu_cycles(&mut apu, 10000); + println!("Count: {}", apu.get_frame_samples().len()); + assert_eq!(apu.get_frame_samples(), &[0; 417]); +} + +#[test] +fn run_apu_pulse_1() { + // Base? case + cycle_check!(|apu| { + apu.write_reg(SNDCHN, 0x01); + apu.write_reg(PULSE_CH1_DLCV, 0xBF); + apu.write_reg(PULSE_CH1_SWEEP, 0x7F); + apu.write_reg(PULSE_CH1_TLOW, 0x6F); + apu.write_reg(PULSE_CH1_LCTH, 0xF0); + // 1666 APU cycles (every fourth is a sample) + // Advance in duty cycle every 111 + 1 apu cycle (28 samples) + }; [0; 28], [37; 28*4], [0; 28*4], [37; 28*4] ; [0; _]); + + cycle_check!(|apu| { + apu.write_reg(SNDCHN, 0x01); + apu.write_reg(PULSE_CH1_DLCV, 0xBF); + apu.write_reg(PULSE_CH1_SWEEP, 0x7F); + apu.write_reg(PULSE_CH1_TLOW, 0x60); + apu.write_reg(PULSE_CH1_LCTH, 0xF0); + // 1666 APU cycles (every fourth is a sample) + // Advance in duty cycle every 96 + 1 apu cycle (24.25 samples) + }; [0; 24], [37; 24*4+1], [0; 24*4+1], [37; 24*4+1], [0; 24*4 + 1] ; [37; _]); +} diff --git a/src/test_roms/apu_pulse_1_test.s b/src/test_roms/apu_pulse_1_test.s new file mode 100644 index 0000000..9d30715 --- /dev/null +++ b/src/test_roms/apu_pulse_1_test.s @@ -0,0 +1,33 @@ +.include "testing.s" + +zp_res TIMER_LOW +zp_res SWEEP +zp_res DLCV +; zp_res TIMER_LOW + +reset: + sei + cld + ldx #$FF + txs + + lda #$01 + sta SNDCHN + lda #$BF ; Duty 2, LC halted, evelope enabled, volume = F + sta PULSE_CH1_DLCV + sta DLCV + lda #$7F ; + sta PULSE_CH1_SWEEP + sta SWEEP + lda #$6F + sta PULSE_CH1_TLOW + sta TIMER_LOW + lda #$F0 + sta PULSE_CH1_LCTH + brk +loop: + jmp loop + +irq: +nmi: + rti