realdmx: Martin Galway's Rambo II loader — the macro-language player, reverse-engineered by dmx87

author Martin Galway (player and tune, 1986 Ocean); reverse-engineered and commented by dmx87
date 2023-06-24
article Galway_Martin_Rambo_Loader.asm, 2967 lines — the 'Rambo II Loader' (PSID: 1986 Ocean, load $6f00, one song), written out with dmx87's macros (PLAY, PLAYT, REPEAT, NEXT, SETFQ, SETPM, ISET, CISET, EXEC …). The date is the repository's last push
created 2026-08-30 · updated 2026-08-30

The loader tune of Rambo: First Blood Part II (Ocean, 1986) — matt-gray's "favourite C64 tune of all time" (s-c64com-matt-gray-interview) — in martin-galway's own player, reverse-engineered by dmx87 for the realdmx collection (s-realdmx-sid-players). It is the "so large/extensive, with macro-language" player SIDwave describes (s-recollection-brief-history-of-sid): each voice runs a small interpreter over a byte stream of notes and 3-byte opcodes, with subroutine calls, repeat loops, note transposition and even a hook to execute arbitrary 6502 code — which is how the loader's morse-code intro is done. The cheat sheet is on galway-player.

Key claims#

Shape and size

  • Header: "Rambo II Loader / By Martin Galway / Reverse engineered by dmx87". PSID load $6f00; Play is a jmp through a vector (playl/playh) that the music itself can re-point; Init clears $d400–$d417, copies a 16-byte zero-page image to $10–$1f and restores the play vector.
  • Zero page $10–$1f: three track pointers, two opcode parameters, the current value, a voice-enable byte (%00000111), three note counters and three stack pointers (7,7,7). Each voice has an 8-entry stack (v1_stackl/h, v1_repstack), a transpose byte and a 17-entry duration table $00,$04,$08 … $40 (multiples of 4 frames).
  • The interpreter exists three times, once per voice (dovoice1/2/3, called in turn every frame), each with its own opcode table — and not every voice implements every opcode (voice 1 lacks $c4 and $d2, voice 2 lacks $cc, $d2, $d4, $d8, voice 3 lacks $c8 and $d4). The labels keep the original addresses: voice 1's sound update at $2231, voice 3's code ending near $28ce, song data from $2b43, tracks at $2ce0/$2ecc/$302e, the morse data ending near $31c0 — roughly 2.3 KB of code, 0.6 KB of tables and 1.7 KB of music, against about 1 KB of code in Hubbard's driver (s-realdmx-hubbard-player).

The byte stream (orderlist is the wrong word — there are no patterns, only streams with calls)

  • A byte below $c0 is a note or rest: $00–$58 (C-0 … E-7, an 89-entry table from $0112 "16.09 hz = C0" to $acd2 "2598.12 hz = E7") followed by an index into the duration table; the same note + $60 followed by an immediate duration in frames; $5f / $bf is a rest with table / immediate time. The voice's transpose is added to every note.
  • A byte from $c0 up is an opcode (and #$3f indexes the table; even codes only) with two parameter bytes: C0 RET (return from a call, or — when the stack is empty — disable the voice), C2 INSTR5 addr (copy 5 bytes: control, AD, SR, release time, "hrtime"), C4 INSTR addr (whole instrument), C6 SETFQ addr (the 14 frequency-program bytes), C8 SETPM addr (the 10 pulse-program bytes), CA PLAY addr (call a sub-track, pushing the return address), CC TRANS n, CE PLAYT addr,n (call with transpose, 4 bytes), D0 ISET off,val (poke one byte of the instrument definition), D6 CISET off,val (poke the running copy), D8 ARP addr (copy 10 arpeggio bytes), DA REPEAT n … DC NEXT (loops, nestable via the stack), D4 EXEC addr (jump to 6502 code, returning into the interpreter).
  • So a phrase is written once and called: p2c9d: +REPEAT 8 / !by 0,1 / +NEXT / +REPEAT 4 / !by 0,2 / +NEXT / +RET, then +PLAYT G4,p2c9d and +PLAYT Fx4,p2cb4 play it in two keys — transposable subroutines instead of pattern numbers (arrangement).

Instrument = 29 bytes, three programs plus the register set (instrument-tables):

  • Offsets $00–$0d, the frequency program: four signed 16-bit adders (fq1add … fq4add), four times (fq1time … fq4time), a delay and flags. After the delay, adder 1 is added to the frequency every frame for time-1 frames, then adder 2 for time-2, adder 3, adder 4 — and the four stages loop. fq2b91: !wo 30, !wo -30, !wo 30, !wo 0 / $03,$06,$03,$00 / $16,$05 is a triangle vibrato of ±90 units with a 12-frame period starting after 22 frames; fq2b79: $f8,$ff,$08,$00,$f8,$ff,$b8,$ff / $03,$06,$03,$00 / $1e,$05 a smaller one (vibrato). Flag bit 1 applies adder 4 during the delay (a slide into the note); flag bit 3 switches the same bytes into arpeggio mode: the 10 bytes set by ARP are semitone offsets, fq3time holds the note, and the index runs from fq4time down to 0 and wraps — one chord note per frame. The tables are four-note stacks with the octave: arp47c: $00,$04,$07,$0c ×2, arp37c: $00,$03,$07,$0c ×2, arp59c, arp27c, arp58c, arp48c, arp368: $00,$03,$06,$08 ×2 (chord-arpeggio). Drums are the same program: +ISET $00,$b0 ; fq1add, +ISET $08,$ff ; fq1time, +ISET $1a,$a9 ; sr, +ISET $1b,$0a ; release — a pitch falling 80 units per frame with a short gate (wavetable-programming).
  • Offsets $0e–$17, the pulse program: p1time, p2time, delay, flags, pm1add (16-bit), pm2add (16-bit), initial pulse width. After the delay the width moves by pm1add for p1time frames and by pm2add for p2time frames; flag $80 then resets the width and restarts, flag $01 restarts the counters without resetting. $14,$14,$00,$05, $14,$00,$ec,$ff, $00,$08 (voice 1's default): 20 frames of +20, 20 frames of −20 from $0800 — the "clean pulsating" chordian hears in Rambo (pulse-width-modulation). pm2b9f: $14,$64,$03,$05,$46,$00,$19,$00,$64,$00 is a slow one-way sweep (+70 for 20 frames, +25 for 100).
  • Offsets $18–$1c: control, AD, SR, release counter and "hrtime". On every note the driver writes $00 and then the five register bytes to $d402–$d406, loads the frequency and restarts both programs; the gate is cleared when the release counter expires (or, with the test bit $08 set in the control byte, when the note's remaining time drops below it), and when hrtime frames have also passed the voice's seven registers are zeroed. Instrument 5-byte sets in the file: $41,$dd,$cc,$82,$ff, $41,$88,$cc,$96,$c8, $41,$01,$e7,$04,$0c, $41 | RINGBIT,$ee,$0f,$05,$32 ; instr5 for rising ringmod effect, $41,$83,$cb,$5a,$64, $41,$cc,$9d,$96,$c8. The gate time is therefore an instrument property, not a note property (hard-restart, adsr-envelope).

Programmed events in the stream (dmx87 annotates the tune with timestamps: "00:31 lead start", "00:45 drums", "00:52 lead", "01:40 rising notes used with sync"):

  • The famous morse-code intro: track1data: +NOOP $32 / +EXEC .morse1 / … — .morse1 writes SR $a0 and frequency $224b to voice 1, then replaces the play vector with a morse player that keys the gate from a data table (.morsedata: $84,$92,$98,$98,$84,$82,$a4 …) and hands control back to playmusic when it reaches $00. The intro is code, not notes.
  • +ISET $18,$15 ; ctrl: tri + gate + ringmod and the RINGBIT/SYNCBIT constants for the rising notes (ring-modulation-and-sync); +CISET $0d,%00000111 ; fqflag and +CISET $1c,$ff ; hrtime poked mid-phrase — the "instrument" is edited while it plays.

Practical takeaways#

  • Galway's vibrato, PWM and arpeggio are one mechanism: a short program of signed adders with durations. In tracker terms it is a pitch table with per-step lengths and a pulse table with two segments — sid-wizard's and goattracker's tables are the same idea with the durations made explicit.
  • Chords here are 4-note stacks including the octave at 50 Hz — the "wibble" SIDwave credits him with (s-recollection-brief-history-of-sid).
  • Notes carry no instrument: the sound is whatever the last ISET/SETFQ/SETPM/INSTR5 left behind, so a "sound change" is a poke in the stream — the opposite of Hubbard's per-note instrument byte.
  • Drums without a drum routine: a frequency adder of −80 per frame and a short release on the lead voice — which is why Galway wanted Hubbard's percussion (s-remix64-galway-interview).

Notable quotes#

  • "Rambo II Loader / By Martin Galway / Reverse engineered by dmx87" (header).
  • "; instr5 for rising ringmod effect"; "; ctrl: tri + gate + ringmod"; "; 01:40 rising notes used with sync" (dmx87's annotations).

Relevance to the wiki#

Primary source for galway-player; confirms SIDwave's "macro-language" description and chordian's "programmed pulsating" on martin-galway from the code; supplies the Galway column of the driver comparison on sid-player-routine and concrete table shapes for vibrato, pulse-width-modulation and chord-arpeggio.

Pages touched#

galway-player · martin-galway · sid-player-routine · sidid · instrument-tables · vibrato · pulse-width-modulation · chord-arpeggio · wavetable-programming · ring-modulation-and-sync · hard-restart · arrangement · matt-gray · hubbard-player

source file wiki/summaries/s-realdmx-galway-rambo-player.md · graph