SIDin #7 — Inside Modules: Ivan Del Duca's music engine (Stefano Tognon, 2005)
Tognon's reverse-engineering of the music engine of Modules, an Italian C64 game of 1988 whose coder, Ivan Del Duca, "probably wrote the first Italian music engine around, instead of used the one's that were available" — a small driver with 4-byte track entries, 16 instruments spread over nine tables, patterns whose commands are jump-table addresses, a gosub/return mechanism for reusable phrases, and one generic triangle-LFO effect that serves as vibrato, pulse sweep or filter sweep. Documented on modules.
Key claims#
- Provenance: the PSID header reads name "Modulus", author Ivan Del Duca, copyright "1988 System", init
$C57B, play$CFE8, one song. Tognon: Del Duca "has declared in some Italian interview that he was not a true musician (he is a game coder, even today)". "All the right remain to Ivan." - Structure: tracks, patterns, music commands. One track table per voice; a track entry is 4 bytes — pattern address high, pattern address low, "max index in pattern to execute (FF=max, can be terminate by instruction)", and the number of extra repeats:
>pat00, <pat00, $3D, $02runs pat00 for$3Dbytes three times,>pat01, <pat01, $51, $00once for$51bytes,>pat03, <pat03, $FF, $00once until an ending command (orderlist). Tracks live from$C800(track1, 23 entries, then track2) with track3 at$C8A0. - Instrument = 9 tables × 16 entries: attack/decay (
tbAD), sustain/release (tbSR), pulse width low (tbWLo) and high (tbWHi) — "Wave duty cycle (fixed)" —, control register when on (tbCntOn), control register when off, i.e. at release (tbCntOff), filter cutoff high byte (filtCutFH), filter resonance/routing (filterResCtr) and filter mode (filterMode). "One instrument has very limited capability: ADSR, Wave duty cycle (fixed) and control values for the voice (two types). This means that al the effects that the player can reproduce are not associated with the instrument, with one exception: the filter"; 256 instruments would be possible by extending the tables. Sample rows (AD, SR, pulse width, control on, control off, cutoff hi, resonance, mode): instrument 0 =$2A, $3C, $060A, $11, $10, $80, $F0, $10; instrument 2 =$82, $50, $0000, $85, $84, $70, $F0, $30(noise + ring modulation, gate on/off — a drum, low+band-pass); instrument 3 =$0C, $00, $077A, $41, $40, $90, $F0, $30(instrument-tables, adsr-envelope). - Pattern bytes (Tognon's pseudocode): read a value;
0→ the next byte is a note duration;$80–$FF→ a command; otherwise a note, after which the next byte is a duration if it is above$9F, else another note with the previous duration. A note isoctave-note: high nibble the octave "(from 0 to 7)", low nibble the note1–C; pitch comes from a 12-entry top-octave table (freqTableLo/Hi:$861E, $8E18, $968B, $9F7E, $A8FA, $B306, $BDAC, $C8F3, $D4E6, $E18F, $EEF8, $FD2E) shifted right once per octave. Durations$A0–$E0index a 16-bit table (durationHiTab/durationTab:$03C0, $01E0, $F0, $A0, $78, $60, $50, $45, $3C …down to$01, then$02D0, $0168, $B4, $5A, $2D …) — "we can have very-very long sound". - Commands are low bytes of a jump table at
$C680(high byte fixed at$C6, "so, the code cannot be relocated easily"):$80end of pattern ·$83set control for release (gate off with the instrument's off byte) ·$86 n flagnew instrument (flag ≠ 0 reloads the filter parameters) ·$89 vvolume$0–$F·$8C step delayportamento add ·$8F step delayportamento down ·$92stop all effects on the voice ·$95 src dstcopy one SID register to another ("for copying the oscilator 3 value to one other register for putting a random value in it") ·$98unused ·$9B"various action":$9B $02 indType limit step delay= the vibrato-like effect (limit inverts the direction, step is the amount, delay the ticks between steps;indType0filter cutoff high,1pulse width high,2frequency high = vibrato);$9B $03return;$9B $05 noteplay a note "No action is taken about duration of this note";$9B logosub to the pattern at$C9lo(pitch-slide-and-portamento, vibrato, pulse-width-modulation, filter-programming). - Example patterns from the listing:
pat00: $86,$00,$00 / $9B,$02,$02,$04,$01,$06 / $83 / $2A / $A8 / $15,$11,$13 / $2A,$15,$11,$13 …— instrument 0, a vibrato of ±1 on the frequency high byte every 6 ticks over 4 steps, then notes at duration$A8;pat01: $00,$A8 / $9B,<pat39 / $00,$DB / $9B,<pat39 / $9B,<pat38 / $92— rests and gosubs into the phrasespat38/pat39at$C900/$C930, which switch drum instruments$0D–$0Faround$9B,$05play-notes;pat0d: $89,$0F / $86,$09,$00 / $8C,$80,$01 / $41,$B4 / $92 / $00,$B0 / $8F,$80,$01 / $00,$B4 / $92— a slide up by$80per tick, then down;pat34: $86,$0B,$00 / $95,$1B,$0Fcopies oscillator 3 ($D41B) into voice 3's frequency high byte;pat36: $86,$00,$00 / $95,$1C,$16copies envelope 3 ($D41C) into the filter cutoff high byte. - Engine layout: code from
$C000(per-voice loop,processMusic, track reading,setFrequency, the command handlers, the effect engine,makeDurEffect,initMusic) to about$C5B5; the jump table at$C680; variables and the nine instrument tables to$C790; tracks$C800–$C8FF; patterns$C900–$CFD8; the play stubplayMusic: jsr $C000 / jsr $C000 / rtsat$CFE8. One active effect per voice (flagEff/freqEffect: 1 add, 2 subtract, 3 SID copy, 5 vibrato-like) plus a globalabilEffectswitch; the vibrato-like effect keepscurrValue,limit,lowLimit = limit >> 2,step,delayper voice and reaches the register through a per-type pointer (sidIntType: $16, $03, $01= cutoff hi, pulse hi, frequency hi, the latter two offset by the voice). - Conclusion: "The engine is not so powerful compared to some player of the same year, but probably only the features needed by Ivan were implemented"; "wonderful sid music can be produced even with simple player (look at 'Lazy Jones' in SIDin #2) and so I think that with this player, Modulus is resulted very-very good."
Practical takeaways#
- A driver design where all modulation is pattern-side: the instrument is only ADSR, a fixed pulse width, two control bytes and a filter preset; vibrato, pulse and filter sweeps are one shared LFO command with a type byte — cheap to implement, verbose to write.
- Gosub/return with the instrument saved and restored makes drum fills reusable across patterns — the same idea as Galway's and Ocean's
CALL/RETstreams (galway-player, ocean-music-driver). - Copying
$D41B/$D41Cinto other registers from the play loop is hardware modulation for free: random pitch from oscillator 3, an envelope-shaped filter from envelope 3.
Notable quotes#
"This means that al the effects that the player can reproduce are not associated with the instrument, with one exception: the filter."
Relevance to the wiki#
Creates modules (song page with the engine's tables); a row for the driver comparison on sid-player-routine; new 1988 examples for instrument-tables, orderlist, vibrato, pulse-width-modulation, filter-programming, pitch-slide-and-portamento and randomization-and-probability; an Italian counterpart to the British and Dutch drivers already documented.
To verify#
Readings of the listing that the article's prose does not state:
- (unverified)
$80jumps todisableVoice(lda #$01 / sta disVoice,x), and when all three voices are disableddisablePlayerwrites anRTSat$C000— so$80ends the voice, not just the pattern, and the title tune plays once and stops; the patternspat0b–pat37(each starting with$89,$0Fand ending with$80) look like jingles/effects the game triggers separately. - (unverified)
$86 n 0does more than "currents will be used": it clears the voice's bit in$D417(bitWiseTab eor #$FF and actFiltRes), i.e. takes the voice out of the filter. - (unverified)
$9B $05jumps tosetFreq, skippingmakeControlOff, and then consumes the previous duration — a tied note without gate retrigger rather than a "free" note. - (unverified) The vibrato-like effect is a triangle: over
limitsteps it adds for the first quarter, subtracts for the second and third, adds for the last (lowLimit = limit >> 2thresholds), then wraps; period =limit × delayticks. - (unverified) The duration table follows
480 / nticks ($A1= 480,$A2= 240,$A4= 120,$A8= 60,$B0= 30,$C0= 15) with dotted values from$D8; and since the play stub calls$C000twice per frame, a tick is half a frame (about 100 Hz on PAL) — see multispeed. - (unverified) The game's title is "Modules" in the article and "Modulus" in the PSID name and the conclusion; the publisher "System" in the copyright is not otherwise identified.
- (unverified) Octave nibble 0 is the highest octave (no shift), 7 the lowest.
Pages touched#
modules · sid-player-routine · instrument-tables · orderlist · vibrato · pulse-width-modulation · filter-programming · pitch-slide-and-portamento · randomization-and-probability · hard-restart · multispeed · dustbin · sidin