SIDin #15 — Inside Matt Gray's Serpent Demo player (the 4-bit sample voice, reverse-engineered, 2015)

author Stefano Tognon (ice00), SIDin magazine
date 2015-10-31
article SIDin issue 15 (version 1.00, 2015-10-31), pp. 15–41: 'Inside Matt Gray Serpent Demo player' — sections Sample (how the fourth voice works), Source (the reverse-engineered listing in Dominator notation, pp. 17–41, with 4 KB of sample data), Conclusion (the three engines in order). The listing was read for the sample routine, the tables and the pattern data
created 2026-08-30 · updated 2026-08-30

Answering Gray's remark in the same issue that the Serpent Demo drums were "too loud" (s-sidin15-matt-gray-interview), Tognon reverse-engineers the tune's player: the Driller-generation matt-gray-player plus a fourth, sample track that streams 4-bit values into the SID volume register from a CIA-timer NMI — the wiki's first complete listing of a 1980s chip-samples ("digi") voice inside a music driver.

Key claims#

  • Which engine: the Serpent Demo — "spread as a demo with a cobra serpent images" — uses an engine "younger of the one in Dominator, and it is about the same of Driller one": tracks, patterns, commands and instruments are the Driller set ($FD kk = duration, arpeggio from instrument byte 5, hard-coded pulse limits $08/$0E, the EOR #$23 noise effect), "what is new is the sample management". Tognon's reading order of the three engines: DrillerSerpent DemoDominator; the Vendetta build "that use filter" is left for "maybe another issue".
  • A fourth voice: every song has VOICE4LO/HI pointers next to VOICE1–3; the sample track is a list of pattern numbers with $FF restart / $FE end like the others, and its patterns sit in the same BARLO/BARHI table — but with their own command set:
bytemeaning
NNsample speed: written to CIA 2 Timer A low byte $DD04 (high byte fixed at 1) — "more little is this value and more often the NMI is triggered", i.e. the note/transpose of the sample
$FD VVsample length (duration in tempo ticks)
$FA BIbank B (0 or 1, high nibble) and sample index I (low nibble)
$FFend of pattern
  • How it plays: the raster IRQ still runs the three SID voices once a frame (MUSICROUTINEMAIN ×3, then MAINEXTRA for the sample track); SETNMI points $0318/$0319 at NMI, starts Timer A ($DD0E = $11) and enables its NMI ($DD0D = $81). Each NMI calls SAMPLEROUTINE, which reads one byte from a self-modified address (MEMPOINT), outputs the high nibble first and the low nibble on the next NMI to $D418, then advances the pointer; when the high byte of the pointer reaches BANKEND the sample stops (SAMPLEVOL = 0) or restarts from a second start page. MAIN writes volume $09 to $D418 whenever no sample is playing ("volume to middle"), $0E at init.
  • Sample data: 4-bit, "two samples to play in sequences" per byte; seven samples in labels SM10 … SM1F from STARTADD = $1000 (4 KB before the code), described by BANKLO (start low byte, $00 or $FF), BANKHI0 (start page), BANKEND (end page) and BANKHI1 (bank-1 start page): e.g. index 3 = SM18SM1A, bank 1 from SM19. "16x2 max samples area … even if using bank 0 or bank 1 gives different behavior". Put a sample at a page boundary ($1100$12FF → start $11, end $13).
  • Pattern examples: T29 .BYTE $FD,$00, $FA,$03, $01,$02,$03 … $40, $FF — the first pattern of TUNE4: fastest duration, sample 3, and a run of speed values $01$40 "with a fasting increasing frequency … that you can easy heart in the drums"; T6/T7 alternate sample 1 at speed $0A and sample 2 at $40; T14 ends with $35,$30,$25,$20,$15,$10,$05,$01 (a falling-pitch tail). TUNE4 .BYTE $1D,$09,$0A,$0A,$0A,$0B,$0A … $FF. A second song, EMPTY, plays no SID voices and only sample pattern T16 — "maybe a way to test only the sample engine".
  • Leftovers: undeclared patterns T2912, T28A9 and three unused tracks T27DB, T2836, T285F "are from Hunter's Moon tune (Serpent Demo was spread across the Hunter's Moon game)" (hunters-moon); many declared patterns are unused.
  • The SID side, as listed: 19 instruments (VDATA/VDATA2), speed 4 (TDATA .BYTE $00,$04,$04), and eight four-note PLEX tables — P0 $13,$0C,$07,$00, P1 $00,$07,$0A,$0C, P2 $00,$03,$07,$0C, P3 $00,$04,$07,$0C, P4 $00,$05,$09,$0C, P5 $00,$05,$07,$0C, P6 $00,$04,$09,$0C, P7 $00,$03,$08,$0C — selected from instrument byte 5 ($40, $41, $43$47: four notes, table 0–7) (chord-arpeggio). The header Tognon reconstructs reads "PLAYER <V4.2 (C)1987 MATT GRAY"; the copyright string (C)1987 MATT GRAY appears twice in the data.
  • Rights: "The source code presented here is Copyright by Matt Gray", obtained by reverse-engineering "and applying all the notations of Dominator engine when possible" — the labels and comments of Tognon's Dominator edition (s-sidin14-dominator-player).

Practical takeaways#

  • The classic C64 digi recipe in one place: samples as packed nibbles, an NMI from a CIA timer whose low byte is the "note", one nibble to $D418 per interrupt, the music IRQ untouched (chip-samples, sid); compare Galway's synthesised $D418 percussion in s-realdmx-galway-arkanoid-player and Hülsbeck's NMI streaming noted on chip-samples.
  • Treat the sample track as a fourth pattern channel with three commands — pitch, length, sample — and write drum fills as pattern data (T29's rising $01 … $40 sweep is a pitch envelope written by hand).
  • Level matching: the sample nibble replaces the whole volume register, while the music runs at $09 — the loudness Gray regrets is structural, not a sample-normalisation slip.

Notable quotes#

"The only secret is that you have to use a NMI routine over the logic governed by the IRQ to performs the sample generation. The other trick is to store the 4 bit value of each sample in one byte (8 bits)."

"At this point we have seen 3 Matt Gray engines, that has to be read in this sequences (as features maturated from one to another): Driller, Serpent Demo, Dominator."

Relevance to the wiki#

Adds the sample voice (fourth track, three commands, NMI nibble streaming, bank tables) to matt-gray-player and a complete 1980s digi routine to chip-samples; dates the engine between Driller and Dominator (driller); links the demo to hunters-moon; four-note chord tables for chord-arpeggio.

To verify#

  • Bank semantics (my reading of SETSAMPLESPEED/SETSAMPLEVOL): with bank 0 the end of the area stops playback and the volume returns to $09; with bank 1 the pointer restarts at the BANKHI1 page, so BANKHI1 looks like a loop start inside the sample (the table values lie between each BANKHI0 and BANKEND). Tognon only says bank 0 "set[s] the volume to middle before the sample playback" (unverified).
  • Nibble rate: Timer A = $01NN cycles, so roughly 985 kHz ÷ (256 + NN) NMIs per second on PAL — about 3.7 kHz at $0A, 3.1 kHz at $40 (computed, unverified).
  • "Younger" in Tognon's English means earlier here (his own ordering is Driller → Serpent → Dominator).
  • Tognon calls T29's $FA,$03 "index 3 of bank 1"; the high nibble is 0, so by his own table it is bank 0; the address range he gives ($1800$19FF) matches index 3.
  • The demo's year, group and the "Robin Levy" credit for the serpent picture (from the interview) are not otherwise documented here; no song page was created.

Pages touched#

matt-gray-player · chip-samples · matt-gray · driller · dominator · hunters-moon · chord-arpeggio · sid · sid-player-routine · sidin

source file wiki/summaries/s-sidin15-serpent-demo-player.md · 1 unverified · graph