mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
audio: per-frame pitch envelopes for pulse SFX
Pulse-channel sfx with a multi-byte `pitch:` array used to silently ignore everything past the first byte — the runtime audio tick latched the period at trigger time and never updated it. Programs that wanted a frequency sweep had no way to express it. The compiler now compiles a per-frame pitch envelope blob alongside the existing volume envelope when `decl.pitch` has more than one distinct value. The blob is padded (or truncated) to the volume envelope's length and ends in a zero sentinel so the runtime walker stops both pointers on the same NMI. Sfx with a single scalar pitch (or an array where every byte is the same) keep their historical "no pitch blob, latch once" path and emit byte-identical ROM bytes. The runtime gains two new pieces, both gated on a new `__sfx_pitch_used` codegen marker so programs without varying-pitch sfx pay zero bytes: 1. `gen_audio_tick` emits a per-frame pitch update block inside the SFX tick: read a byte through `(AUDIO_SFX_PITCH_PTR),Y`, write it to `$4002` (pulse-1 period low), advance the pointer. The block bails on a zero high-byte pointer so a single program can mix scalar-pitch and varying-pitch sfx without one clobbering the other. 2. `emit_play_pulse` seeds `AUDIO_SFX_PITCH_PTR_LO/HI` with the pitch-blob label for varying-pitch sfx and zeros it for scalar-pitch sfx. The per-call branch is skipped entirely when the program has no varying-pitch sfx anywhere. The new `examples/sfx_pitch_envelope.ne` exercises the path with a 16-frame siren sweep. Triangle and noise per-frame pitch are deferred — they share the same data shape but the runtime ticks for those channels still write only their volume registers, see docs/future-work.md for the gap. https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
parent
db3a4adc57
commit
9878b7d87d
12 changed files with 449 additions and 31 deletions
|
|
@ -31,6 +31,7 @@ Open any `.nes` file in an NES emulator ([Mesen](https://www.mesen.ca/), [FCEUX]
|
|||
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
|
||||
| `friendly_assets.ne` | named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the `palette` uses `bg0..sp3` + a shared `universal:`, the sprite is authored as ASCII pixel art, the background uses a `legend { ... } + map:` tilemap with a `palette_map:` for attributes, the sfx uses a scalar `pitch:` + `envelope:` alias, and the music uses note names (`C4, E4 40, rest 10`) with a `tempo:` default. |
|
||||
| `noise_triangle_sfx.ne` | `channel: noise`, `channel: triangle` on `sfx` blocks | Demonstrates the noise and triangle sfx channels. Declares one noise burst and one triangle bass note, plays each on a timer so the emulator harness captures both the pixel output and the APU state. |
|
||||
| `sfx_pitch_envelope.ne` | varying-pitch pulse SFX | A 16-frame frequency sweep written as a per-frame `pitch:` array on a Pulse-1 sfx. The compiler emits a separate `__sfx_pitch_<name>` blob and gates the audio tick's pitch update path on the `__sfx_pitch_used` marker, so programs that stick to the scalar `pitch:` form still get byte-identical ROM output. |
|
||||
| `nested_structs.ne` | nested struct fields, array struct fields, chained literals | Two `Hero` instances each carry a `Vec2` position and a `u8[4]` inventory. Exercises `hero.pos.x` chained access, `hero.inv[i]` array-field access, and chained struct-literal initializers (`Hero { pos: Vec2 { x: ..., y: ... }, inv: [...] }`). |
|
||||
| `platformer.ne` | **every subsystem** | End-to-end side-scrolling demo: custom CHR tileset, full 32×30 nametable with per-region attribute palettes, 2×2 metasprite hero with gravity/jump physics, wrap-around horizontal scrolling, stomp-or-die enemy collisions with a live stomp-count HUD, coin pickups, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness cycles through stomp, stomp, die, and retry inside six seconds. Regenerate the tile art with `cargo run --bin gen_platformer_tiles`. |
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue