realdmx: Jonathan Dunn's Daley Thompson '88 and Ocean Loader 5 in Paul Hughes's Ocean music driver

author Jonathan Dunn (music: 'Orchestration, blood, sweat & tears'), Paul Hughes (driver: 'Oh wow (paroxism!) programming'), (c) Ocean Software / Imagine Software / Paul Hughes 1988; DASM port by 'Paulie' 2004 and 2006; ACME conversion by dmx87
date 2023-06-24
article Dunn_Jonathan_DT88.asm (3372 lines, the six-tune Daley Thompson '88 soundtrack, PSID '1988 Ocean') and Dunn_Jonathan_OceanLoader5.asm (3119 lines, 'LOADING MUSIC IV, THE REMIX!', PSID 'Ocean Loader 5'). Original Ocean sources, not disassemblies; the date is the repository's last push
created 2026-08-30 · updated 2026-08-31

Two original Ocean Software sources from 1988 — not reverse-engineered — in the realdmx collection (s-realdmx-sid-players): the Daley Thompson '88 soundtrack (six tunes) and the loader tune HVSC calls Ocean Loader 5, which the source itself calls "LOADING MUSIC IV, THE REMIX!". The driver is Paul Hughes's, the music jonathan-dunn's, and the file is the only one in the collection where a composer's notation survives: music written as note names, command names and note-value constants (CR2, QV2) straight into the assembler. The cheat sheet is on ocean-music-driver.

Key claims#

Provenance and budget

  • Header (both files): "D.THOMPSON '88 Soundtrack / Orchestration, blood, sweat & tears by Jonathan Dunn / Oh wow (paroxism!) programming by Paul Hughes / (c) Copyright Ocean Software / Imagine Software / Paul Hughes 1988 / Ported to DASM 10/11/2004 by Paulie" (the loader: "LOADING MUSIC IV ,THE REMIX!", "Ported to DASM 15/4/2006 by Paulie"). Who "Paulie" is the file does not say. A 57-byte text sits inside the loader's driver: SILLYMESS !text "LOADER4 THE REMIX! BY JONATHAN DUNN DRIVER BY PAUL HUGHES". The drum system is "DunnyDrums (tm)" in two comments.
  • The memory budget is in the source: ALLOCATION = 5*1024+512, TOGO = ALLOCATION-PROGRAM_LEN — 5632 bytes for driver, data and variables, with the assembler reporting what is left. Assembled, DT88 is DRIVER_LEN 3783 + DATA_LEN 1540 + 191 bytes of variables = 5514, TOGO = 118 bytes to spare; the loader is 3864 + 1009 + 188 = 5061 bytes (measured by re-assembling the sources with 64tass in this session). The driver is about 70 % of the budget; six tunes and 19 patches fit in 1.5 KB.
  • API: "To play the music JSR REFRESH every frame / To select a tune LDX #TUNE_NUMBER / JSR TUNE", tune numbers 0 = DISQUALIFIED, 1 = SUCCESS, 2 = FAIL, 3 = NEXT EVENT, 4 = TITLE MUSIC, 5 = QUALIFY. Init writes 8 then 0 to every SID register — "Set SID test bit so there's no / grotty clicking". Everything runs once per frame; there is no tempo counter, durations are frame counts.

Each voice is a program (orderlist does not apply — there are no patterns)

  • Zero page holds three 16-bit program counters (PC_A/B/C) and three arpeggio-table pointers; each voice has a 5-deep stack (STACKDEPTH = 5) shared by CALL/RET and LOOP/NEXT. Voices are synchronised only by the composer's durations.
  • Bytes $00–$5D are notes (C0 = 0 … A7 = 93, sharps as CS4, FS5), 94 = REST; $80–$97 are commands: JUMP addr, CALL addr, RET (also clears the transpose), LOOP n … NEXT, BEND dir,step,length,offset16, PATCH n, LENGTH frames (auto-duration), VIBON delay,rate,depth, VIBOFF, ARPON addr, ARPOFF, MANUAL (note,duration pairs), PWM n, FILTER/FILTOFF (empty handlers), END, TRANSPOSE n, CT n,addr (transposed call), MODE mask, DRUM n,transpose[,duration] (voice C only), PORT value,sid-offset (write any SID register — used for volume $18), CODE addr (JSR into arbitrary 6502), EFFECT note. Commands cost no time; the handler runs and the next byte is read in the same frame.
  • Durations are named: TEMPO = 5 gives SQ 5, QV 10, CR 20, MN 40, SB 80; TEMPO2 = 6 gives SQ2 6, QV2 12, DQV2 18, CR2 24, DCR2 36, MN2 48, SB2 96 frames — a 4/4 bar at TEMPO2 is 96 frames (BARLENGTH = 96), 125 BPM at 50 Hz. The loader writes raw counts (192, 96, 12).
  • A phrase is a subroutine, and harmony is done by calling it transposed: DITTY2: PATCH,1,PWM,1 / ARPON ARP2 / LENGTH,SQ / CALL MEL1 / CT,-5 MEL1 / CT,-12 MEL1 / CT,-17 MEL1 / D4 / END with MEL1: LOOP,2 / F4,G4,F4,D5 / NEXT / RET; the loader's chord changes are CT,0 BASSSEQ1 / CT,-2 … over BACK1/BACK2 (arrangement).
  • The loader's opening, as written: LOADER1: MANUAL / PATCH,1,PWM,1 / VIBON,0,9,6 / EFFECT,12 / BEND,UP,$10,48 / !wo $10*48 / C2,192 / BEND,UP,$10,48 / !wo $10*48 / AS1,192 / … — each 192-frame bass note is preceded by a 48-frame bend of $10 per frame that lands on the note (the driver pre-offsets the frequency by step × length, so the composer writes the destination pitch), while EFFECT,12 makes every note start an octave up until its release point (pitch-slide-and-portamento).
  • Self-modifying music: fades and ramps are CODE routines that poke operand bytes in the data — FADEPOS !by PORT,$0F,$18 with TFADEAWAY DEC FADEPOS+1 in a loop (fade-out), CURRENTVOL !by PORT,1,$18 with FADEUP INC CURRENTVOL+1 over LOOP,9 (fade-in 3 → 11), and DRUMPOS !by LOOP,16,DRUM,2,-9,SQ2/2,CODE UPDRUM with UPDRUM INC DRUMPOS+4 — a 16-hit snare roll whose pitch rises each hit. Endless tunes end in JUMP to their own label.

Patches = 5 bytes (instrument-tables): gate-on control byte, AD, SR, hold length (bit 7 = GAP mode), gate-off control byte. Two waveforms per instrument: one while gated, another for the release; hold is either "gate off N frames after note-on" or, with GAP, "N frames before the next note". DT88's 19 patches: $51,$00,$C8,3,$40 (triangle+pulse, 3 frames, then pulse off), $01,$00,$88,2,$42 (gate with no waveform for 2 frames, then pulse+sync — heard only in release), $55,$00,$99,3,$40 (triangle+pulse+ring), $81,$00,$A8,3,$10 (3 frames of noise, then triangle — a click transient), $11,$AA,$79,QV2+SQ2+GAP,$10 (the only patch with an attack — the title pad), $41,$00,$88,4+GAP,$14 (pulse, released as triangle+ring). The loader's lead is $43,$00,$7B,96+GAP,$22 (pulse+sync gated, gate off 96 frames before the end of a 192-frame note, released as sawtooth+sync), its bend bass $41,$00,$AD,3,$40. Almost all have AD $00 (adsr-envelope).

Modulation, all inline

  • VIBON delay,rate,depth: after the delay, add/subtract rate to the 16-bit frequency, reversing every depth frames (first leg half) — a triangle in raw units; values VIBON,0,7,6, VIBON,6,$20,6, VIBON,$10,$1A,8, and the loader lead's VIBON,0,$FF,4 (vibrato). VIBON cancels an arpeggio; an active arpeggio suppresses vibrato and bend.
  • PWM n: 5-byte entries rate, frames up, frames down, initial width (word)$80,$00,$02 / $0F50, $F0,$04,$04 / $0800, loader $40,48,42 / $0000; one rate both ways (pulse-width-modulation).
  • ARPON addr: table = frames per step, number of steps, offsets…ARP1 !by 3,4,0,4,7,9, ARP2 !by 1,3,24,12,0 (the two-octave "chip" arpeggio), FARP1 !by 2,4,0,3,7,12, TARP1/2/3 !by 2,3,0,3,7 / 0,3,8 / 0,3,10 — chord changes under a held note by swapping tables (chord-arpeggio).
  • Drums (DRUM, voice C only): 7-byte entries wave-table ptr, freq-table ptr, length, SR, pulse-hi, AD forced $00; per frame one control byte and one frequency-high byte (+ transpose in coarse units) — BASS DRUM: DRUM1V $41,$41,$10,$80 / DRUM1F $0E,$06,$0F,$10; SNARE: DRUM2V $41,$41,$40,$80,$80,$80,$10,$80,$10,$80,$80,$10,$80 / DRUM2F $11,$0C,$08,$70,$70,$2F,$0C,$2E,$0C,$2D,$BC,$0C,$2B; HI-HAT: DRUM3V $11,$11,$80,$80,$80 / DRUM3F $10,$10,$70,$20,$30 (wavetable-programming). When a drum ends the driver writes 0 to $D417.
  • MODE: a bit mask (bit 7 of the argument = set, else clear) for gate writes, pulse retrigger per note and the "pluck" effect mode — commented "i'll be buggered if i know what this does !"; EFFECT,C7,MODE,%00001000 in the title makes every note attack on an absolute C-7.

Two builds: DT88 pauses all modulators on the last frame of every note (MOD_OFF, "Shut off modulation for this frame") and has bend only on voice A and effect only on voice C; the loader has no MOD_OFF, bend on A and B, effect on all three — handlers are compiled in or out per tune to fit the budget. Unused leftovers (RATEDOWN, FILT_TOGGLE, BARS = $44 ; Bars to skip, BARLENGTH) point at a filter, a separate pulse-down rate and a bar-skipping tester that are not in these files.

Practical takeaways#

  • The "instrument" is two control bytes and a gate length; everything else is a command in the voice's stream. To copy a Dunn patch in a tracker: waveform A while the gate is on, waveform B in release, AD $00, and a fixed gate time — hard-restart-era thinking about attacks.
  • "Bend into the note" is the ergonomic form of portamento: write the target note and a (step, length) pair; the driver computes the start pitch.
  • Dunn's snare is the SID recipe in the raw: noise frames at a high frequency byte ($70) alternated with gate-off triangle/pulse frames at a low one ($0C).
  • A 5.5 KB budget with a 3.8 KB driver: the data has to be subroutines and transposed calls, which is why the music sounds like variations on short cells.

Notable quotes#

  • "Orchestration, blood, sweat & tears by Jonathan Dunn / Oh wow (paroxism!) programming by Paul Hughes" (header).
  • "Set SID test bit so there's no grotty clicking" (init); "Suprisingly enuf this handles the vibrato"; "Process DunnyDrums (tm)"; "MODE_A - Sets triggering mode, i'll be buggered if i know what this does !" (Hughes's comments).

Relevance to the wiki#

Primary source for ocean-music-driver and jonathan-dunn; the third "macro-language" driver beside Galway's (s-realdmx-galway-rambo-player) for the comparison on sid-player-routine; concrete Ocean-house instrument, drum, arpeggio and bend data for the technique pages; the first source in the wiki that shows a game's music memory budget in numbers.

Pages touched#

ocean-music-driver · jonathan-dunn · martin-galway · galway-player · sid-player-routine · sidid · instrument-tables · instrument-design · wavetable-programming · chord-arpeggio · vibrato · pulse-width-modulation · pitch-slide-and-portamento · hard-restart · adsr-envelope · arrangement · orderlist · octave-bass

source file wiki/summaries/s-realdmx-ocean-dunn-hughes-driver.md · graph