Skip to content

Commit 41f3ec2

Browse files
committed
2026-05-15 16:42:37
1 parent cc53770 commit 41f3ec2

1 file changed

Lines changed: 35 additions & 69 deletions

File tree

src/apu.rs

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -851,18 +851,8 @@ impl Apu {
851851
f32::from(self.nr50 & 0x07) / 7.0 / 15.0 * 0.25
852852
}
853853

854-
// ─── Helpers ───────────────────────────────────────────────────────────
855-
856-
fn xtra_clock(&self) -> bool {
857-
// "First half" of the length period (Pan Docs obscure behaviour): the
858-
// FS step just past an even step. step%2==0 means the *next* clock will
859-
// be a length clock, i.e. we are in the half that triggers extras.
860-
self.fs.s & 1 == 0
861-
}
862-
863-
/// Compute the live wave sample index based on elapsed T-cycles since the
864-
/// last trigger. The wave timer fires once every period T-cycles after an
865-
/// initial `period_at_trigger + 6` T-cycle warm-up.
854+
// Compute the live wave sample index based on elapsed T-cycles since the last trigger. The wave timer fires once
855+
// every period T-cycles after an initial period_at_trigger + 6 T-cycle warm-up.
866856
fn live_wave(&self) -> u64 {
867857
let p_now = u64::from(self.ch3.tmr.p);
868858
let p_trg = u64::from(self.ch3.p1);
@@ -888,9 +878,8 @@ impl Apu {
888878
elapsed - t_last < 2
889879
}
890880

891-
fn power_off(&mut self) {
892-
// Zero every channel register except wave RAM and (on DMG) length
893-
// counters.
881+
fn power_no(&mut self) {
882+
// Zero every channel register except wave RAM and (on DMG) length counters.
894883
for i in 0..5 {
895884
self.ch1.reg.n[i] = 0;
896885
}
@@ -918,9 +907,8 @@ impl Apu {
918907
}
919908

920909
fn power_on(&mut self) {
921-
// Phase-align the frame sequencer with DIV: the next FS clock occurs at
922-
// the next falling edge of sdiv bit 12. If that bit is already high at
923-
// power-on, the very first FS event is skipped (hardware glitch).
910+
// Phase-align the frame sequencer with DIV: the next FS clock occurs at the next falling edge of sdiv bit 12.
911+
// If that bit is already high at power-on, the very first FS event is skipped (hardware glitch).
924912
self.fs.s = 7;
925913
self.ft.n = u32::from(self.sdiv) % (CLOCK_FREQUENCY / 512);
926914
self.fs_skip = self.sdiv & 0x1000 != 0;
@@ -935,10 +923,6 @@ impl Apu {
935923
}
936924
}
937925

938-
// ────────────────────────────────────────────────────────────────────────────
939-
// Memory bus.
940-
// ────────────────────────────────────────────────────────────────────────────
941-
942926
impl Memory for Apu {
943927
fn lb(&self, a: u16) -> u8 {
944928
let v = match a {
@@ -974,9 +958,8 @@ impl Memory for Apu {
974958
}
975959

976960
fn sb(&mut self, a: u16, v: u8) {
977-
// Power gating: all channel/NR50/NR51 writes are blocked while the APU
978-
// is off, except wave RAM and NR52 itself. On DMG, length-counter
979-
// pre-loads via NRx1 remain writable.
961+
// Power gating: all channel/NR50/NR51 writes are blocked while the APU is off, except wave RAM and NR52
962+
// itself. On DMG, length-counter pre-loads via NRx1 remain writable.
980963
if !self.pwr && a != 0xff26 && !(0xff30..=0xff3f).contains(&a) {
981964
if self.glo.borrow().term == Term::DMG {
982965
match a {
@@ -1002,7 +985,9 @@ impl Memory for Apu {
1002985
return;
1003986
}
1004987

1005-
let xtra = self.xtra_clock();
988+
// First half of the length period (Pan Docs obscure behaviour): the FS step just past an even step. step%2==0
989+
// means the next clock will be a length clock, i.e. we are in the half that triggers extras.
990+
let xtra = self.fs.s & 1 == 0;
1006991
match a {
1007992
0xff10 => {
1008993
if self.ch1.fs.nr10di(&self.ch1.reg, v) {
@@ -1034,13 +1019,28 @@ impl Memory for Apu {
10341019
let was = self.pwr;
10351020
self.pwr = v & 0x80 != 0;
10361021
if was && !self.pwr {
1037-
self.power_off();
1022+
self.power_no();
10381023
} else if !was && self.pwr {
10391024
self.power_on();
10401025
}
10411026
}
10421027
0xff27..=0xff2f => {}
1043-
0xff30..=0xff3f => self.write_wave_ram(a, v),
1028+
0xff30..=0xff3f => {
1029+
if self.ch3.on && self.ch3.reg.dac_wv() {
1030+
let fires = self.live_wave();
1031+
let addr = self.wave_addr(fires);
1032+
match self.glo.borrow().term {
1033+
Term::CGB => self.ch3.ram[addr] = v,
1034+
Term::DMG => {
1035+
if self.dmg_wave_window(fires) {
1036+
self.ch3.ram[addr] = v;
1037+
}
1038+
}
1039+
}
1040+
} else {
1041+
self.ch3.ram[(a - 0xff30) as usize] = v;
1042+
}
1043+
}
10441044
_ => unreachable!(),
10451045
}
10461046
}
@@ -1061,46 +1061,22 @@ impl Apu {
10611061
};
10621062

10631063
if trig {
1064-
// Record live-wave snapshot before sb_nrx4 reloads the timer.
10651064
self.ch3.t_s_div = sdiv;
10661065
self.ch3.d1 = 0;
10671066
self.ch3.t_apu_n = self.ft.n;
10681067
self.ch3.t_frame = self.fc;
10691068
}
10701069
self.ch3.nrx4sb(v, xtra, dmg_off);
10711070
if trig {
1072-
// p1 takes the *new* period (post-NR34 high-bits).
10731071
self.ch3.p1 = self.ch3.tmr.p;
10741072
}
10751073
}
1076-
1077-
fn write_wave_ram(&mut self, a: u16, v: u8) {
1078-
if self.ch3.on && self.ch3.reg.dac_wv() {
1079-
let fires = self.live_wave();
1080-
let addr = self.wave_addr(fires);
1081-
match self.glo.borrow().term {
1082-
Term::CGB => self.ch3.ram[addr] = v,
1083-
Term::DMG => {
1084-
if self.dmg_wave_window(fires) {
1085-
self.ch3.ram[addr] = v;
1086-
}
1087-
}
1088-
}
1089-
} else {
1090-
self.ch3.ram[(a - 0xff30) as usize] = v;
1091-
}
1092-
}
10931074
}
10941075

1095-
// ────────────────────────────────────────────────────────────────────────────
1096-
// Clocking.
1097-
// ────────────────────────────────────────────────────────────────────────────
1098-
10991076
impl Ticker for Apu {
11001077
fn tick(&mut self, cycles: u16) {
11011078
// The DIV-APU 512 Hz counter runs regardless of power state.
11021079
let n = self.ft.step(u32::from(cycles));
1103-
11041080
if !self.pwr {
11051081
for _ in 0..n {
11061082
if self.fs_skip {
@@ -1112,20 +1088,17 @@ impl Ticker for Apu {
11121088
}
11131089
return;
11141090
}
1115-
11161091
for _ in 0..n {
11171092
if self.fs_skip {
11181093
self.fs_skip = false;
11191094
self.fc = self.fc.wrapping_add(1);
11201095
continue;
11211096
}
1122-
11231097
let p = self.ft.p;
11241098
self.ch1.step(p);
11251099
self.ch2.step(p);
11261100
self.ch3.step(p);
11271101
self.ch4.step(p);
1128-
11291102
let s = self.fs.step();
11301103
if Fseq::len(s) {
11311104
if self.ch1.lc.step(&self.ch1.reg) {
@@ -1158,7 +1131,6 @@ impl Ticker for Apu {
11581131
}
11591132
self.ch1.tmr.p = self.ch1.reg.per_sq();
11601133
}
1161-
11621134
self.ch1.buf.end(p);
11631135
self.ch2.buf.end(p);
11641136
self.ch3.buf.end(p);
@@ -1169,30 +1141,25 @@ impl Ticker for Apu {
11691141
}
11701142
}
11711143

1172-
// ────────────────────────────────────────────────────────────────────────────
11731144
// DMG wave-RAM corruption timing model (SameBoy-compatible).
11741145
//
1175-
// The internal sample-countdown is loaded with `p1/2 + 2` 2-MHz cycles after
1176-
// the first trigger and reset to `p/2 − 1` on every fire (where p is the
1177-
// current period in T-cycles). Re-triggering while `sample_countdown == 0`
1178-
// corrupts wave RAM at byte offset `((csi + 1) >> 1) & 0xF`.
1146+
// The internal sample-countdown is loaded with p1/2 + 2 2-MHz cycles after the first trigger and reset to p/2 − 1
1147+
// on every fire (where p is the current period in T-cycles). Re-triggering while sample_countdown == 0. Corrupts wave
1148+
// RAM at byte offset ((csi + 1) >> 1) & 0xF.
11791149
//
1180-
// p1_h P1/2 (2-MHz cycles) at first trigger
1181-
// d1 2-MHz cycles between the first trigger and the last NR33 write
1182-
// d2 2-MHz cycles between that NR33 write and the re-trigger
1183-
// p2_h P2/2 (2-MHz cycles) — the current period
1184-
// ────────────────────────────────────────────────────────────────────────────
1150+
// p1_h P1/2 (2-MHz cycles) at first trigger
1151+
// d1 2-MHz cycles between the first trigger and the last NR33 write
1152+
// d2 2-MHz cycles between that NR33 write and the re-trigger
1153+
// p2_h P2/2 (2-MHz cycles) — the current period
11851154

11861155
fn dmg_corrupt_offset(p1_h: u32, d1: u32, d2: u32, p2_h: u32) -> Option<usize> {
11871156
if p1_h == 0 || p2_h == 0 {
11881157
return None;
11891158
}
11901159
let c1 = p1_h - 1;
11911160
let c2 = p2_h - 1;
1192-
11931161
let mut sc: u32 = c1 + 3;
11941162
let mut csi: u32 = 0;
1195-
11961163
// Phase 1.
11971164
if sc >= d1 {
11981165
sc -= d1;
@@ -1211,6 +1178,5 @@ fn dmg_corrupt_offset(p1_h: u32, d1: u32, d2: u32, p2_h: u32) -> Option<usize> {
12111178
csi = csi.wrapping_add(rem / (c2 + 1)) & 0x1f;
12121179
sc = c2.saturating_sub(rem % (c2 + 1));
12131180
}
1214-
12151181
if sc == 0 { Some(((csi as usize + 1) >> 1) & 0xf) } else { None }
12161182
}

0 commit comments

Comments
 (0)