mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
audio: triangle and noise sfx channels
Adds `channel: triangle` / `channel: noise` to the `sfx` declaration form. The existing pulse-1 / pulse-2 driver is unchanged (and is still byte-identical for programs that don't use the new channels) — when a program declares a triangle or noise sfx the runtime splices in an additional per-channel slot that writes to $4008- $400B (triangle) or $400C-$400F (noise) on play. Includes a new `examples/noise_triangle_sfx.ne` demo with committed golden PNG + audio hash. https://claude.ai/code/session_01MaNVcDmK9gsspRkdxowQAM
This commit is contained in:
parent
8610aecdac
commit
201664ea04
18 changed files with 1116 additions and 52 deletions
1
tests/emulator/goldens/noise_triangle_sfx.audio.hash
Normal file
1
tests/emulator/goldens/noise_triangle_sfx.audio.hash
Normal file
|
|
@ -0,0 +1 @@
|
|||
a82b6ff5 132084
|
||||
BIN
tests/emulator/goldens/noise_triangle_sfx.png
Normal file
BIN
tests/emulator/goldens/noise_triangle_sfx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 843 B |
|
|
@ -813,6 +813,54 @@ fn program_with_user_declared_sfx_and_music() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_with_noise_sfx_writes_400c_at_play_site() {
|
||||
// A program that declares a noise sfx and plays it should
|
||||
// end up with a trigger sequence that touches $400E (noise
|
||||
// period) and $400C (volume pre-mute). The exact register
|
||||
// sequence is:
|
||||
// LDA #$30; STA $400C; LDA #idx; STA $400E; LDA #len; STA $400F;
|
||||
// LDA #$0B; STA $4015
|
||||
// plus an envelope pointer load. We check the two channel-
|
||||
// specific stores ($400E + $400C) explicitly so a regression
|
||||
// that routes to pulse 1 by mistake will fail loud.
|
||||
let source = r#"
|
||||
game "Noise Test" { mapper: NROM }
|
||||
sfx Crash {
|
||||
channel: noise
|
||||
pitch: 4
|
||||
volume: [15, 12, 8, 4]
|
||||
}
|
||||
on frame { play Crash }
|
||||
start Main
|
||||
"#;
|
||||
let rom_data = compile(source);
|
||||
let prg = &rom_data[16..16 + 16384];
|
||||
// Search for any `STA $400C` instruction byte sequence
|
||||
// (opcode 0x8D, lo=0x0C, hi=0x40). 6502 absolute store is
|
||||
// always 3 bytes.
|
||||
let sta_envelope_reg: [u8; 3] = [0x8D, 0x0C, 0x40];
|
||||
assert!(
|
||||
prg.windows(sta_envelope_reg.len())
|
||||
.any(|w| w == sta_envelope_reg),
|
||||
"noise play sequence should include STA $400C"
|
||||
);
|
||||
let sta_period_reg: [u8; 3] = [0x8D, 0x0E, 0x40];
|
||||
assert!(
|
||||
prg.windows(sta_period_reg.len())
|
||||
.any(|w| w == sta_period_reg),
|
||||
"noise play sequence should include STA $400E"
|
||||
);
|
||||
// The noise envelope bytes (derived from the user `volume` list
|
||||
// masked with 0x30 | vol) must be in ROM too.
|
||||
let env = |v: u8| 0x30u8 | v;
|
||||
let crash_env: [u8; 5] = [env(15), env(12), env(8), env(4), 0x00];
|
||||
assert!(
|
||||
prg.windows(crash_env.len()).any(|w| w == crash_env),
|
||||
"noise envelope blob must live in PRG"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_without_audio_has_no_audio_driver_in_prg() {
|
||||
// Programs that never touch audio should pay zero ROM cost:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue