Martin Galway's player (the Ocean "macro-language" driver)
The 6502 music system martin-galway wrote at Ocean from 1984 ("there never was an editor … It was always assembler source files"; source: s-sidmusic-galway-interviews), known from two reverse-engineered tunes: the Rambo: First Blood Part II loader (1986; dmx87) and the whole Arkanoid program (1986 Imagine; Stefano Tognon, sidin #4) (sources: s-realdmx-galway-rambo-player, s-realdmx-galway-arkanoid-player). SIDwave's description holds up in the code: a player "so large/extensive, with macro-language" because it manipulates MIDI-converted data (source: s-recollection-brief-history-of-sid) — each voice runs a small interpreter over a stream of notes and opcodes, with subroutine calls, loops, transposition and even native-code hooks, and every modulation (vibrato, slide, pulse sweep, filter envelope, arpeggio) is one generic engine of timed 16-bit adders.
Facts#
- Size: the Rambo loader is about 2.3 KB of code (the interpreter and effect engine exist three times, once per voice), 0.6 KB of tables and 1.7 KB of music; Arkanoid about 4.5 KB of code, 2.5 KB of music, 1.1 KB of sound effects and 0.5 KB of tables — against about 1 KB of code in hubbard-player, which is why "his player was so cleverly coded, that it needs little RAM and raster consumption (compared to Galway)" (sources: s-realdmx-galway-rambo-player, s-realdmx-galway-arkanoid-player, s-recollection-brief-history-of-sid).
- Two builds: the Rambo loader (opcodes
$c0–$dc, no filter) and the Arkanoid program (opcodes$c0–$f0, a filter envelope retriggered per note of one voice, a sound-effect bank of twelve 3-voice effects, a CIA-timer IRQ at two rates — about 104 Hz for jingles and effects, 52 Hz for the two tunes with the "sample" track — and the volume-register percussion). Arkanoid is an RSID: the init never returns for those two songs because the foreground synthesizes the drums (source: s-realdmx-galway-arkanoid-player; multispeed, chip-samples). - Samples that are not samples: the "digitized" percussion of Arkanoid is six routines writing a stepped 4-bit value into
$D418with busy-wait delay tables — Galway's "wiggling the volume register … the flatulence stuff" and Newman's "a series of farts and burps" (sources: s-realdmx-galway-arkanoid-player, s-sidmusic-galway-interviews, s-newman-driving-the-sid-chip). - What it lacks: a drum routine — drums are a frequency adder of −80 per frame and a short gate on a normal voice, which is why he wanted Hubbard's percussion ("Perhaps we should have swapped drivers for a game"; source: s-remix64-galway-interview). chordian's "clean pulsating with the instantaneously triggered notes" is the two-stage pulse program plus the per-note register reset (sources: s-chordian-sid-musicians, s-realdmx-galway-rambo-player). matt-gray: "His Rambo loader is my favourite C64 tune of all time" (source: s-c64com-matt-gray-interview).
- SIDId:
Martin_Galway,Martin_Galway_Digi(source: s-sidid-player-list).
Cheat sheet#
All from the two reverse-engineered files (sources: s-realdmx-galway-rambo-player, s-realdmx-galway-arkanoid-player).
The stream — one byte-code program per voice, no patterns, no order list:
| bytes | Rambo loader | Arkanoid |
|---|---|---|
$00–$58 / $00–$5e + nn | note (C-0 … E-7) with nn = index into a 17-entry duration table (multiples of 4 frames) | note with nn = 1-based index into a 32-entry table rebuilt per tune as multiples of its minimum duration |
note + $60 + nn | note with an immediate duration in frames | note with a raw tick count |
$5f / $bf | rest (table / immediate time) | $5f nn rest |
≥ $c0 | opcode + 2 parameter bytes (3 bytes; CE has 4) | opcode + parameters (1–4 bytes) |
Opcodes
| Rambo loader | Arkanoid | meaning |
|---|---|---|
C0 RET | C0 RTS | return from a call; on an empty stack, stop the voice |
CA addr PLAY | C2 addr JSR | call a sub-stream (8-level stack per voice) |
CE addr,n PLAYT | C6 n,addr JSRT | call with a note transpose ($f4 = −12) |
| — | C4 addr JMP | jump (endless tunes; voices can jump into each other's streams — a delayed echo) |
DA n … DC REPEAT/NEXT | CC n … CE FOR/NEXT | loops, nestable |
CC n TRANS | (in JSRT) | set the transpose |
C2 addr INSTR5 | D4 addr INSTR | copy 5 bytes: control, AD, SR, gate time, "hrtime" |
C4 addr INSTR | D2 n,addr SETNI | copy a whole instrument (29 bytes) |
C6 addr SETFQ | F0 addr SETFI | copy the 14-byte frequency program |
C8 addr SETPM | — | copy the 10-byte pulse program |
D8 addr ARP | (DC $08 addr) | load a 10-byte / 4-byte arpeggio table |
D0 off,val ISET | CA id,val SET, DC id,v1,v2 SET2I | poke the instrument definition |
D6 off,val CISET | D6 id,val SETCI, DE id,v1,v2 SET2CI | poke the running instrument — change the sounding note |
D4 addr EXEC | D8 addr EXCT | execute 6502 code from the song (Rambo's morse-code intro; Arkanoid's filter switches) |
| — | E0 LF3, E2 addr FILTA | filter on voice 3 (resonance $f, low-pass); load a 16-byte filter envelope |
Reading a phrase: p2c9d: REPEAT 8 / $00,$01 / NEXT / REPEAT 4 / $00,$02 / NEXT / RET then PLAYT G4,p2c9d and PLAYT Fx4,p2cb4 (Rambo); sub07: JSRT $1f sub06 / JSRT $1d sub06 / JSRT $1b sub06 / JSRT $1a sub06 / RTS (Arkanoid).
Instrument = 29 bytes ($00–$1c):
| offset | content |
|---|---|
$00–$07 | four signed 16-bit frequency adders |
$08–$0b | their durations in frames/ticks (0 = skip) |
$0c | initial delay |
$0d | flags: $01 reload the stages (loop), $80 reload from the note's own frequency after stage 4, $02 apply adder 4 during the delay (a slide into the note), $08 arpeggio mode (Arkanoid: voice 2 only) |
$0e/$0f | two pulse-width stage lengths |
$10 | pulse delay |
$11 | pulse flags ($80 reset the width and restart, $01 restart the counters) |
$12–$15 | two signed 16-bit pulse adders |
$16/$17 | initial pulse width |
$18 | control byte (the test bit here is a mode flag: release the gate $1b ticks before the next note instead of after the note-on) |
$19/$1a | AD, SR |
$1b | gate time ($ff = never release) |
$1c | "hrtime": frames after gate-off at which the voice's 7 registers are zeroed ($ff = never) — Tognon's "; hard restart" |
A vibrato is three adders: !wo 30, !wo -30, !wo 30, !wo 0 / $03,$06,$03,$00 / delay $16, flags $05 (Rambo fq2b91); $14,$00 / $ec,$ff / $14,$00 / $00,$00 / $03,$06 / $03,$00 / $1e,$05 (Arkanoid ins01). A drum: ISET $00,$b0 ; fq1add / ISET $08,$ff ; fq1time / ISET $1a,$a9 ; sr / ISET $1b,$0a ; release. A pulse: $14,$14,$00,$05, $14,$00,$ec,$ff, $00,$08 = 20 frames +20, 20 frames −20 around $0800. An arpeggio table is a 4-note stack including the octave — $00,$04,$07,$0c (Rambo, doubled to 8 entries), arp37: $0c,$00,$03,$07 (Arkanoid, read backwards). Every note starts by writing $00 and then the five register bytes (Rambo) or $08 (test) and then the registers (Arkanoid).
Arkanoid's sample track (own language, opcodes = 6502 opcodes): 81–86 xx play synthesized drum n, wait xx ticks; 87 xx nothing; 20 lo hi JSR; 40 NEXT; 49 xx FOR; 4C lo hi JMP; 60 RTS.
Its fingerprint#
- Because the byte-code values
≥ $C0are commands, the player's fetch loop is a seven-byte signature —LDY #$00 / LDA ($xx),Y / CMP #$C0 / BCC, in Perl'\xA0\x00'.'\xB1.'.'\xC9\xC0'.'\x90'— which Tognon offered in 2003 with the caveat that "7 bytes are very little" and another engine might share them "with other meanings" (source: s-sidin03-pattern-searching-1; see player-identification).
Tognon's explanation of the Arkanoid engine (SIDin #4, 2003)#
The prose around the listing (s-sidin04-galway-arkanoid) adds what the code alone does not say:
- Why three copies of everything: "each voice has its code that are perfectly equal each other" and could have been one indexed routine, but "as all effects are coded into tables, this made the Galway engine very powerful"; the per-voice opcode sets —
JSRTonly on voices 1 and 3,EXCT,LF3andFILTAonly on voice 3,SET2Ion 2 and 3 — look to Tognon like instructions "added … when needed", or unused handlers removed to save memory (source: s-sidin04-galway-arkanoid). - Song header and note forms: three stream pointers plus a minimum note duration —
$03–$0Dacross the eight tunes of the listing; raising it slows only the notes timed through the table, not those written+ $60with an immediate count ($12 nnfrom the table ≡$72 nnimmediate). Notes run$00–$5E;$5F/$BFare rests (source: s-sidin04-galway-arkanoid). - Two tables per voice: the 29-byte definition and a 34-byte running copy (
$00–$22) thatSETCI/SET2CIedit — the same bytes to$17, then the current frequency at$18–$19, control$1A, gate-off and wipe times$1B–$1C, and the live phase counters ($1D–$20for the four frequency phases,$21–$22for the two pulse phases); "reloading the cycles" copies$08–$0B/$0E–$0Finto them.SETFIcopies$00–$0D,INSTRfive bytes ("control/ADSR"),SETNIthe firstnnbytes (source: s-sidin04-galway-arkanoid). - The flag byte in his words (frequency
$0D, pulse$11, filter$0D): zero = effect off; bit 7 = at the end of the last phase reload the cycles and the stored start value; bit 0 = reload the cycles but continue from the current value; any other bit = hold the current value, no more phases. So the vibrato flag$05loops from wherever the pitch is, and a portamento$01,$00 × $35cycles with flag$08climbs 53 units and stays. During the initial delay the engine also tests bit 1 — adder 4 applied every tick, a slide into the note — which Tognon's text does not separate out (sources: s-sidin04-galway-arkanoid, s-realdmx-galway-arkanoid-player). - Sound effects need no stream: an effect is an instrument table "with two extra bytes at the end: the low/high value of the frequency", pushed straight into
makeTimbreVx; it lasts as long as its phases; "all the 3 voices can be used for generating the sound effect (and else, this is the way used in the game)" (source: s-sidin04-galway-arkanoid). - The hidden sample synth: a
PSampleroutine's timbre "is governed by two parameters: a repeat value and a table of delays" (ldx #$0Crepeats per delay, six delays read from the end); thesta $D418operand is stored masked ($9418,$DD18inPSample1) and unmasked by$94 + $40on entry; the sample stream's opcodes are real 6510 opcodes; routines and pointer tables sit in "very sparse memory" — "maybe some suspicious of a way to hidden the sample generation". Zero page: 8-deep call stacks at$31/$39/$40(voice 1),$A9/$B1/$B9(2),$61/$69/$71(3),$78/$80/$89(sample stream);$E0–$E5stream pointers,$E6–$E8durations,$EC–$EEtransposes,$F1–$F6pulse width,$F7–$FCfrequency,$FFthe current note (source: s-sidin04-galway-arkanoid). - Why it sounds wrong in old players: the PSID sample extension "try to emulated this kind of sound generation", so the tune "will play so different into a sidplayer (with extended sid register) instead of the real machine (or today RSID rip)" (sid-format, arkanoid; source: s-sidin04-galway-arkanoid).
Related#
martin-galway · hubbard-player · ocean-music-driver · musicfile · matt-gray-player · sid-player-routine · chip-samples · multispeed · instrument-tables · vibrato · pulse-width-modulation · chord-arpeggio · filter-programming · sidin · sidid
Sources#
s-realdmx-galway-rambo-player · s-realdmx-galway-arkanoid-player · s-recollection-brief-history-of-sid · s-sidmusic-galway-interviews · s-newman-driving-the-sid-chip · s-chordian-sid-musicians · s-c64com-matt-gray-interview · s-sidid-player-list · s-remix64-galway-interview · s-sidin03-pattern-searching-1 · s-sidin04-galway-arkanoid