01 · Music
Music: five tunes from the game's own sound code
The player below runs the sound code that sits in every load of the game, the note player $253F and the event engine $1C2D, on the kit's 6502 simulator. It calls both once a frame, as the raster interrupt $113A does, and plays what they write to the SID.
Each tune was checked against the same code run in the simulator, a frame at a time: every write to the sound chip, in order, with no difference. The game sets the SID filter in all five tunes, so the player models a 6581's, and real chips differ from each other in cutoff by large factors. The sound effects have no player yet.
A tune starts when a routine calls $2517 with the address of its header. The header holds the low and high halves of an instrument directory, then groups of three voice pointers. Each voice reads pairs of a note and an instrument, and a pair whose instrument is zero ends that voice's sequence. The next group of pointers then starts, and a group whose first pointer has a zero high byte ends the tune. $25FA turns each note into a frequency and writes it into the instrument's event template, and $1C9F starts that template as a sound stream.
Where each tune plays
- The jingle is the short tune built into the resident code, header
$2679. The ADUMBRATE JARETH handler starts it through $0869, and other area files call the same entry.
- The opening menu tune has its header at
$C05F, and the opening's menu code starts it at $BC5C.
- The gate tune has its header at
$9200, and the entrance gate starts it at $8D23.
- The final duel tune has its header at
$790D, loaded over the start of the costume block, and a starter at $7900 waits for it to end.
- The win ending tune has its header at
$92D1, and the win ending starts it.
Sound effects are short event streams queued by request number
The game's other sounds, such as a door opening or a coin refused, are not tunes. A handler loads a request number into the accumulator and calls $1C0C, which does nothing while a tune plays. Otherwise it looks the number up in the selector table at $1FA9, whose complement picks the first pending slot, and files the request there. A positive selector in the next byte files that following request in its own slot too, so request 5 starts streams 5 and 6 together. The pointer tables $1F33 and $1F6E give each stream's address, and $1C2D reads the streams once a frame in the same way as it reads a tune's events.
The 36 requests that can be queued alone, each started through the reset $1C6C and the request, and each checked against the game's code a frame at a time. Requests 0 and 55 to 58 are not requests, and no code asks for them. Stream 0 is a filter stream, but request 0 files it in voice slot 0, which expects a voice stream, and the simulator ends up running code at $0000. The selector table ends at request 54, so a number from 55 up would read the bytes of the streams at $1FE0 as selectors. Requests that a stream pairs with another, such as 6 after 5, are heard with the first. The last eight entries are pitched effects: the helper $0CE8, reached through the stub at $0860, adds 7 times A to Y, writes the note that many semitones below note 99 into the request's stream, and then queues the request. Every caller asks for request 49 or 53. The pitch inputs listed are the two fixed calls and the lowest, middle and highest values scene 24's wall hit can build from its row and cell band.