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 nDC 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