1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/audio_demo.ne
Claude 33537a32a9
tests/emulator: record audio goldens alongside screenshots
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
2026-04-12 22:33:48 +00:00

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