Minor improvements to APU
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
|||||||
*.nes
|
*.nes
|
||||||
*.zip
|
*.zip
|
||||||
*.dmp
|
*.dmp
|
||||||
|
wasm/
|
||||||
|
!wasm/wasm.html
|
||||||
|
|||||||
37
src/apu.rs
37
src/apu.rs
@@ -1,7 +1,3 @@
|
|||||||
use std::iter::repeat_n;
|
|
||||||
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
macro_rules! lut {
|
macro_rules! lut {
|
||||||
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
||||||
const $name: [$ty; $len] = {
|
const $name: [$ty; $len] = {
|
||||||
@@ -264,7 +260,7 @@ Output: {16:>4} ${16:02X}
|
|||||||
self.sweep.period(),
|
self.sweep.period(),
|
||||||
self.sweep.enable(),
|
self.sweep.enable(),
|
||||||
self.period,
|
self.period,
|
||||||
0,
|
self.counter.current,
|
||||||
self.enabled,
|
self.enabled,
|
||||||
self.period_timer,
|
self.period_timer,
|
||||||
self.cur, // ?
|
self.cur, // ?
|
||||||
@@ -423,6 +419,7 @@ bitfield::bitfield! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct NoiseChannel {
|
struct NoiseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
evelope: NoiseEnvelope,
|
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 {
|
match offset {
|
||||||
0x00 => (),
|
0x00 => (),
|
||||||
0x01 => (),
|
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 {
|
match offset {
|
||||||
0x00 => (),
|
0x00 => (),
|
||||||
0x01 => (),
|
0x01 => (),
|
||||||
@@ -553,12 +550,21 @@ pub struct APU {
|
|||||||
|
|
||||||
impl std::fmt::Debug for APU {
|
impl std::fmt::Debug for APU {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// write!(
|
write!(
|
||||||
// f,
|
f,
|
||||||
// "PPU: f {}, s {}, p {}",
|
"APU
|
||||||
// self.frame_count, self.scanline, self.pixel
|
{:#?}
|
||||||
// )
|
{:#?}
|
||||||
Ok(())
|
{:#?}
|
||||||
|
{:#?}
|
||||||
|
{:#?}
|
||||||
|
",
|
||||||
|
self.pulse_1,
|
||||||
|
self.pulse_2,
|
||||||
|
self.triangle,
|
||||||
|
self.noise,
|
||||||
|
self.dmc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +583,7 @@ impl APU {
|
|||||||
irq: false,
|
irq: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
samples: vec![0; 100],
|
samples: Vec::with_capacity(10000),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,3 +745,6 @@ mod apu_iced {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|||||||
98
src/apu/tests.rs
Normal file
98
src/apu/tests.rs
Normal file
@@ -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<I: Iterator<Item = u8>> 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; _]);
|
||||||
|
}
|
||||||
33
src/test_roms/apu_pulse_1_test.s
Normal file
33
src/test_roms/apu_pulse_1_test.s
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user