1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 01:16:12 +00:00

audio: always enable all four tone channels on sfx trigger

Writing $07 in `emit_play_triangle` and $0B in `emit_play_noise`
meant that a noise play following an in-progress triangle note
would clear bit 2 of $4015 and cut the triangle off mid-envelope
(and vice versa). Write $0F from both paths so every trigger keeps
pulse1, pulse2, triangle, and noise enabled; channels with no
active envelope stay silent via the runtime's per-channel counter
gating. Also fixes the attribute-byte packing comment in
`png_to_nametable` — the code was correct, the doc string had the
quadrant order backwards.

The only observable ROM change is `examples/noise_triangle_sfx.nes`
(two immediate operands shift) and its audio hash golden; the
committed PNG golden is byte-identical. Found in independent code
review after the section landed.

https://claude.ai/code/session_01MaNVcDmK9gsspRkdxowQAM
This commit is contained in:
Claude 2026-04-14 12:09:46 +00:00
parent 359c945c30
commit 9b54ff83c0
No known key found for this signature in database
4 changed files with 14 additions and 8 deletions

Binary file not shown.

View file

@ -92,7 +92,7 @@ pub fn png_to_nametable(path: &std::path::Path) -> Result<([u8; 960], [u8; 64]),
// Attribute table: 8×8 bytes, each covering a 32×32 region made
// up of four 16×16 quadrants. Each quadrant gets 2 bits
// (0..=3) packed into the byte as `TR<<6 | TL<<4 | BR<<2 | BL`
// (0..=3) packed into the byte as `BR<<6 | BL<<4 | TR<<2 | TL`
// per the PPU's documented layout. The 15-row nametable only
// half-fills the last attribute byte-row (rows 8..10 of the
// bottom attribute byte are unused and stay at 0, matching the

View file

@ -1407,10 +1407,15 @@ impl<'a> IrCodeGen<'a> {
self.emit(STA, AM::Absolute(0x400A));
self.emit(LDA, AM::Immediate(period_hi));
self.emit(STA, AM::Absolute(0x400B));
// Enable pulse-1 + pulse-2 + triangle in the APU status
// register. We don't bother reading $4015 first — overwriting
// with $07 keeps all currently-used channels enabled.
self.emit(LDA, AM::Immediate(0x07));
// Enable all four tone channels in the APU status register.
// We always write $0F (pulse1+pulse2+triangle+noise) instead
// of just the channel we're triggering, because per-play
// writes use immediate values and a later noise play with
// $0B would otherwise silence an in-progress triangle note
// by clearing bit 2. Channels with no active envelope stay
// silent via the runtime's per-channel counter gating, so
// enabling them blindly is harmless.
self.emit(LDA, AM::Immediate(0x0F));
self.emit(STA, AM::Absolute(0x4015));
// Main-RAM envelope pointer.
self.emit(LDA, AM::SymbolLo(label.to_string()));
@ -1439,8 +1444,9 @@ impl<'a> IrCodeGen<'a> {
// $400F: length counter load.
self.emit(LDA, AM::Immediate(period_hi));
self.emit(STA, AM::Absolute(0x400F));
// Enable pulse-1 + pulse-2 + noise channels.
self.emit(LDA, AM::Immediate(0x0B));
// Enable all four tone channels — see the equivalent write
// in `emit_play_triangle` for why $0F rather than $0B.
self.emit(LDA, AM::Immediate(0x0F));
self.emit(STA, AM::Absolute(0x4015));
// Main-RAM envelope pointer.
self.emit(LDA, AM::SymbolLo(label.to_string()));

View file

@ -1 +1 @@
a82b6ff5 132084
8fd9f7d2 132084