realdmx: Martin Galway's Arkanoid — Tognon's SIDin #4 reverse-engineering, with the 'sample' synthesis through $D418
The full Arkanoid (Imagine, 1986) music and sound-effect program of martin-galway, reverse-engineered by Stefano Tognon for sidin #4 and converted to ACME by dmx87 (s-realdmx-sid-players). It is the later, larger sibling of the Rambo loader player (s-realdmx-galway-rambo-player): the same per-voice byte-code with subroutines and loops and the same piecewise-linear modulation engine, now with a filter envelope, two engine rates, a sound-effect bank — and the famous "digitized samples", which the code shows to be synthesized, not sampled: six routines that wiggle the $D418 volume nibble with busy-wait delay tables. Cheat sheet on galway-player.
Key claims#
Provenance and shape
- Header: "Arkanoid reverse enginnering source / all copyright for this code remains to Martin Galway / Reverse Engineered by Stefano Tognon (SIDIn Magazine #4) / Converted to ACME by dmx87 / The classic loader tune was a conversion from Martin's previous work on Cobra for the ZX Spectrum / One of the first, if not the first, game music featuring digitized samples."
- RSID, not PSID: init at
$0801, play$0000, 20 songs. Init installs its own IRQ on CIA timer A —$49F0for songs 0–1,$24F8for the rest — and for the two sample tunes never returns: the foreground loops inSLooprunning the sample generator while the IRQ plays the music. On a PAL clock the two timer values are about 52 Hz and 104 Hz (inference from the values), so the jingles and effects run at roughly twice the frame rate — a 2× player in 1986 (multispeed). - Song map (read from the code): 0–1 the title/loader and in-game tunes with the sample track, 2–7 six jingles, 8–19 twelve sound effects stored as 31-byte blocks (three per effect, one per voice) at
$2000. - Sizes (counted from the listing): player code ≈ 4.5 KB (core ≈ 3.6 KB — everything exists three times, once per voice — plus init, dispatch and the ≈ 0.5 KB sample engine), music ≈ 2.5 KB (1889 bytes of voice programs + 582 bytes of sample patterns), sound effects 1116 bytes, tables ≈ 0.5 KB. The whole thing spans
$0801to about$4454with gaps.
A program per voice (the file's own format block, verbatim)
xx=00..5F play note xx for time from table(nn)
xx=60..BF play note xx-60h for time nn
C0 RTS · C2 lo hi JSR · C4 lo hi JMP · C6 ht lo hi JSRT (1,3) · CA id va SET · CC nn FOR · CE NEXT
D2 nn lo hi SETNI · D4 lo hi INSTR · D6 nn va SETCI · D8 lo hi EXCT (3) · DC id v1 v2 SET2I (2,3)
DE id v1 v2 SET2CI · E0 LF3 (3) · E2 lo hi FILTA (3) · F0 lo hi SETFI
- A track-table entry is three voice-program pointers plus a minimum note duration;
setTracksrebuilds a 32-entry duration table as multiples of it, and a note's second byte is a 1-based index into that table ($1F,$20= note$1Ffor 32 × min); notes written+ $60carry a raw tick count instead;$5Fis a rest. Each voice has an 8-level stack forJSR/FOR;JSRTcalls with a transpose ($F4= −12);JMPmakes endless tunes (tune2_voice1ends inJMP t2v1_) and lets voices 2 and 3 of tune 6 jump into voice 1's stream after a staggered rest — one pattern played by three voices as a delayed echo (fake-echo).EXCTruns native code from the song (two tiny routines that flip the filter-mode nibble). The comments say "instruction", never "macro". - Transposed reuse, verbatim:
sub06: FOR 2 / $00,$02 / $00,$02 / $0C,$02 / $0C,$02 / NEXT / RTSthensub07: JSRT $1F sub06 / JSRT $1D sub06 / JSRT $1B sub06 / JSRT $1A sub06 / RTS(arrangement). - Seven instructions edit the instrument — including the one that is sounding:
tune1_voice1startsSETNI $1C ins01(load a whole instrument), plays, thenSET2CI $0C,$80,$07andSET2CI $06,$3C,$00turn the held note$1Finto a 128-tick upward slide of$003Cper tick, thenJSRT $F4 sub01(an octave down),SET $1A,$8D(change the SR byte),SETCI $1B,$01— annotated by Tognon "(??????????????????)".
Instrument = 29 bytes, one generic engine (instrument-tables): $00–$07 four signed 16-bit frequency adders, $08–$0B their durations in ticks, $0C initial delay, $0D flags ($80 reload from the note's frequency after stage 4, $01 reload the stages but keep the current frequency, $02 apply adder 4 every tick during the delay, $08 arpeggio mode — voice 2 only); $0E/$0F two pulse-width stage lengths, $10 delay, $11 flags, $12–$15 two 16-bit pulse adders, $16/$17 initial width; $18 control byte, $19 AD, $1A SR; $1B gate-off time (with the test bit set in the control byte: release when the remaining duration drops below it, else that many ticks after note-on; $FF never); $1C a countdown after gate-off at whose end the voice's seven registers are zeroed — Tognon's comment: "; hard restart" (hard-restart). The test bit in $18 is a mode flag, never written to the SID; a separate $08 write precedes every note.
ins01(the lead of the title tune):$14,$00 / $EC,$FF / $14,$00 / $00,$00adders,$03,$06 / $03,$00cycles,$1E,$05delay/flags,$32,$32 / $0A,$05pulse cycles and delay/flags,$0A,$00 / $F6,$FFpulse adders,$00,$08width,$41"control to rectangular",$14,$C8AD/SR,$FF,$FA— after 30 ticks a vibrato of +20 × 3, −20 × 6, +20 × 3 ticks, repeating, over a pulse sweep of ±10 × 50 ticks around$0800(vibrato, pulse-width-modulation).ins02:$19"control to triangular + test bit", AD/SR$A4,$F9, gate-off$14, wipe$FE.ins0B:$49"rectangular and test bit on", AD/SR$06,$98, gate-off$05, wipe$1E. Other control bytes seen:$29(sawtooth + test),$11,$15(an effect: triangle + ring + sync).- Arpeggio (voice 2 only): a 4-byte table read backwards, the instrument bytes
$08–$0Brepurposed as pointer, base note and index:arp39: $0C,$00,$03,$09,arp37: $0C,$00,$03,$07,arp5b: $0C,$00,$05,$0B,arp59: $0C,$00,$05,$09— octave, root, third, sixth/seventh (chord-arpeggio). - Filter: bound to one voice (
voiceNumber) and retriggered with a 16-byte cutoff envelope on every note of that voice —fil02: $4D,$01 / $D3,$FF / $FB,$FF / $FF,$FF / $03,$14 / $0A,$32 / $00 / $04 / $01 / $00= cutoff up by$014Dfor 3 ticks, then down by$2D× 20,$05× 10,$01× 50 (a filter "pluck");LF3= "max resonance/filter on voice 3" ($D417 = $F4) and "low pass filter" ($D418 = $1F) in one byte; the mode nibble is kept inTEMPso it survives the sample writes (filter-programming). - Sound effects: 31-byte blocks = a 29-byte instrument plus a raw SID frequency, pushed straight into the modulation engine with no note — an effect is an instrument with its own pitch program.
The "samples" (chip-samples, sid)
- There is no PCM data in the file. Six routines
PSample1–6each run a loop: busy-waitdelayTab[Y]iterations, add a constant to a 4-bit accumulator in$DE($98,$65,$DD,$DD,$0E,$01) and write its low nibble to the master volume register$D418, for X steps ($14,$19,$0C…) per delay value, Y counting the delay table down from the end:delayTabP3: $20,$10,$08,$04,$02,$01— each stage twice as slow, a falling drum;delayTabP1: $40,$1E,$3E,$19,$3C,$14. Steps of +8 make a square, +5 / −3 scramble all 16 levels (noise-like), −2 an 8-step falling saw, +1 a 16-step rising saw (inference from the constants). Galway's own description — "wiggling the volume register … the flatulence stuff that shipped in 'Arkanoid'" (s-sidmusic-galway-interviews) and Newman's "synthesized the drums as a series of farts and burps" (s-newman-driving-the-sid-chip) — is exactly this code. - The sample track has its own language, whose opcodes are 6502 opcodes:
81–86 xxplay sample n then wait xx ticks,87 xxnothing,20 lo hiJSR,40NEXT,60RTS,49 xxFOR,4C lo hiJMP — with its own 8-level stack.Sample_Tune2: $87,$01 / FOR 7 / $85,$18 / NEXT / $85,$0C / $85,$06 / $85,$06 / FOR 6 / $85,$02 / $85,$16 / NEXT / $82,$02 / $82,$16 …, looping withJMP. The IRQ only schedules; the foreground plays, so the CPU is fully used and the music IRQ jitters the percussion. After each hit:lda #$0F ; turn the volume to max / ora TEMP ; turn filter to the store value / sta $D418. The$D418operand is masked to$9418($DD18inPSample1, the routine SIDin prints — source: s-sidin04-galway-arkanoid) while dormant and "unmask"-ed on entry — purpose not stated.
Practical takeaways#
- Galway's 1986 percussion is a volume-register waveform synth: a handful of bytes of code and a 6-entry delay table per drum, no memory for samples. The equivalent today is a 4-bit sample played through
$D418— but the original had no sample at all. - The modulation engine is the same as the Rambo loader's: vibrato = three adders (+, −, +) with durations, PWM = two adders, filter = four adders; the arpeggio is a 4-note stack including the octave. Instruments are edited by the song while they sound, so a "patch change" can be one byte.
- Two timer rates in one player: run the effects at 2× when the CPU is free, at 1× when the sample synthesis needs the foreground.
Notable quotes#
- "One of the first, if not the first, game music featuring digitized samples." (header — the code shows synthesis, not digitizing.)
- "; hard restart" (Tognon, on the timed voice wipe); "Excecute a code gave by paramethers" (on
EXCT); "SETCI instr (??????????????????)".
Relevance to the wiki#
Primary source, with s-realdmx-galway-rambo-player, for galway-player; settles what "samples" in Arkanoid means on martin-galway and chip-samples; the earliest 2×-rate player in the wiki (multispeed); a filter-envelope-per-note ancestor for filter-programming; the sample-track language and the sound-effect bank for sid-player-routine; the SIDin #4 listing for sidin.
Pages touched#
galway-player · martin-galway · sidin · sid-player-routine · chip-samples · multispeed · filter-programming · instrument-tables · vibrato · pulse-width-modulation · chord-arpeggio · hard-restart · fake-echo · arrangement · sid · sound-effects