realdmx: Martin Galway's Rambo II loader — the macro-language player, reverse-engineered by dmx87
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;Playis ajmpthrough a vector (playl/playh) that the music itself can re-point;Initclears$d400–$d417, copies a 16-byte zero-page image to$10–$1fand 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$c4and$d2, voice 2 lacks$cc,$d2,$d4,$d8, voice 3 lacks$c8and$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
$c0is 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+ $60followed by an immediate duration in frames;$5f/$bfis a rest with table / immediate time. The voice's transpose is added to every note. - A byte from
$c0up is an opcode (and #$3findexes 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,p2c9dand+PLAYT Fx4,p2cb4play 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,$05is 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,$05a 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 byARPare semitone offsets,fq3timeholds the note, and the index runs fromfq4timedown 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 bypm1addforp1timeframes and bypm2addforp2timeframes; flag$80then resets the width and restarts, flag$01restarts 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,$00is 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$00and 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$08set in the control byte, when the note's remaining time drops below it), and whenhrtimeframes 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 / …—.morse1writes SR$a0and frequency$224bto 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 toplaymusicwhen it reaches$00. The intro is code, not notes. +ISET $18,$15 ; ctrl: tri + gate + ringmodand theRINGBIT/SYNCBITconstants for the rising notes (ring-modulation-and-sync);+CISET $0d,%00000111 ; fqflagand+CISET $1c,$ff ; hrtimepoked 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/INSTR5left 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