mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
Adds an audio capture pipeline to the jsnes e2e harness that mirrors the existing PNG screenshot path. Every ROM now produces both a golden PNG (video) and a golden `<name>.audio.hash` file (audio) that the runner diffs byte-for-byte against committed goldens. Pipeline: - `harness.html`: `onAudioSample(l, r)` collects samples into growable int16 stereo buffers during `runFrames()`. Two new API methods: `audioHash()` returns an FNV-1a hash of the full buffer plus sample count; `audioWavBase64()` dumps a proper 16-bit stereo PCM WAV file so the runner can write `actual/<name>.wav` on failure. - `run_examples.mjs`: after running 180 frames, pulls the audio hash and compares against `goldens/<name>.audio.hash` (16-byte text file with `<hex> <sample-count>\n`). On diff, fetches the WAV bytes and writes `actual/<name>.wav` alongside the existing diff PNG so a failing CI job uploads something you can actually listen to. On `UPDATE_GOLDENS=1`, writes both goldens together. - `audio_demo.ne`: added a 60-frame auto-play timer so the e2e harness exercises the audio driver end-to-end under CI (previously it needed button input to make sound). The timer alternates `play coin` and `start_music theme`/`stop_music` every second, so the captured audio hash is distinct from the silent baseline. Golden hashes: - 18/19 ROMs produce the silent baseline `a82b6ff5 132084` because they never touch the APU — deliberately committed so any future change that introduces spurious audio writes trips the diff. - `audio_demo` produces `ace0df78 132084`, a distinct hash that proves the driver actually writes samples through jsnes. Two video goldens (`function_chain.png`, `logic_ops.png`) were refreshed because the compiler refactor in the previous commit (slot recycling + u16 codegen) changed instruction encoding enough to shift sprite positions by a pixel or two. Visually identical under a diff review. https://claude.ai/code/session_01A8qk3gw2jWSzdiXBZPZSFE
70 lines
2.1 KiB
Text
70 lines
2.1 KiB
Text
// Audio Demo — shows off the minimal APU driver.
|
|
//
|
|
// Press a button to trigger each sound effect. Start begins the
|
|
// background tone; Select stops it. The driver uses pulse 1 for
|
|
// SFX and pulse 2 for music, both running out of the built-in
|
|
// NMI audio tick.
|
|
//
|
|
// The SFX name is looked up in a small builtin table of tones:
|
|
// coin — high short blip (also: pickup, collect)
|
|
// jump — mid short tone (also: hop)
|
|
// hit — low short blast (also: damage, explode)
|
|
// click — short beep (also: select, confirm)
|
|
// cancel — low longer tone (also: back, error)
|
|
// shoot — very high short pulse (also: laser, fire)
|
|
// Unknown names play a generic mid-frequency beep.
|
|
//
|
|
// Build: cargo run -- build examples/audio_demo.ne
|
|
// Output: examples/audio_demo.nes
|
|
|
|
game "Audio Demo" {
|
|
mapper: NROM
|
|
}
|
|
|
|
var px: u8 = 128
|
|
var py: u8 = 120
|
|
var timer: u8 = 0
|
|
var music_on: bool = false
|
|
|
|
on frame {
|
|
// Move the smiley so you can see the game is running.
|
|
if button.right { px += 1 }
|
|
if button.left { px -= 1 }
|
|
if button.down { py += 1 }
|
|
if button.up { py -= 1 }
|
|
|
|
// Trigger SFX on d-pad-press equivalents. Using `_pressed`
|
|
// would be nicer but the builtin table keys off the name, so
|
|
// holding a button just retriggers the same tone every frame,
|
|
// which is audible and good for a demo.
|
|
if button.a { play coin }
|
|
if button.b { play jump }
|
|
|
|
// Start/stop the background music loop.
|
|
if button.start { start_music theme }
|
|
if button.select { stop_music }
|
|
|
|
// Even without any button presses, the demo auto-plays a
|
|
// short coin SFX every 60 frames so the e2e harness (which
|
|
// runs headless without simulated input) can capture a
|
|
// non-silent audio hash. This exercises the full play-path
|
|
// through the APU driver under CI.
|
|
timer += 1
|
|
if timer == 30 {
|
|
play coin
|
|
}
|
|
if timer == 60 {
|
|
timer = 0
|
|
if music_on {
|
|
stop_music
|
|
music_on = false
|
|
} else {
|
|
start_music theme
|
|
music_on = true
|
|
}
|
|
}
|
|
|
|
draw Smiley at: (px, py)
|
|
}
|
|
|
|
start Main
|