SIDin #7 — Inside Modules: Ivan Del Duca's music engine (Stefano Tognon, 2005)

author Stefano Tognon (ice00), SIDin magazine
date 2005-01-09
article SIDin issue 7 (version 1.00, 2005-01-09 — the PDF prints 2004, a typo), pp. 20–47: 'Inside Modules' — sections Structure, Instrument, Pattern, Code (commented reverse-engineered listing, $C000–$CFF0, of the PSID 'Modulus / Ivan Del Duca / 1988 System'), Conclusion
created 2026-08-30 · updated 2026-08-30

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, $02 runs pat00 for $3D bytes three times, >pat01, <pat01, $51, $00 once for $51 bytes, >pat03, <pat03, $FF, $00 once 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 is octave-note: high nibble the octave "(from 0 to 7)", low nibble the note 1–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–$E0 index 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"): $80 end of pattern · $83 set control for release (gate off with the instrument's off byte) · $86 n flag new instrument (flag ≠ 0 reloads the filter parameters) · $89 v volume $0–$F · $8C step delay portamento add · $8F step delay portamento down · $92 stop all effects on the voice · $95 src dst copy one SID register to another ("for copying the oscilator 3 value to one other register for putting a random value in it") · $98 unused · $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; indType 0 filter cutoff high, 1 pulse width high, 2 frequency high = vibrato); $9B $03 return; $9B $05 note play a note "No action is taken about duration of this note"; $9B lo gosub 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 phrases pat38/pat39 at $C900/$C930, which switch drum instruments $0D–$0F around $9B,$05 play-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 $80 per tick, then down; pat34: $86,$0B,$00 / $95,$1B,$0F copies oscillator 3 ($D41B) into voice 3's frequency high byte; pat36: $86,$00,$00 / $95,$1C,$16 copies 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 stub playMusic: jsr $C000 / jsr $C000 / rts at $CFE8. One active effect per voice (flagEff/freqEffect: 1 add, 2 subtract, 3 SID copy, 5 vibrato-like) plus a global abilEffect switch; the vibrato-like effect keeps currValue, limit, lowLimit = limit >> 2, step, delay per 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/RET streams (galway-player, ocean-music-driver).
  • Copying $D41B/$D41C into 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) $80 jumps to disableVoice (lda #$01 / sta disVoice,x), and when all three voices are disabled disablePlayer writes an RTS at $C000 — so $80 ends the voice, not just the pattern, and the title tune plays once and stops; the patterns pat0b–pat37 (each starting with $89,$0F and ending with $80) look like jingles/effects the game triggers separately.
  • (unverified) $86 n 0 does 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 $05 jumps to setFreq, skipping makeControlOff, 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 limit steps it adds for the first quarter, subtracts for the second and third, adds for the last (lowLimit = limit >> 2 thresholds), then wraps; period = limit × delay ticks.
  • (unverified) The duration table follows 480 / n ticks ($A1 = 480, $A2 = 240, $A4 = 120, $A8 = 60, $B0 = 30, $C0 = 15) with dotted values from $D8; and since the play stub calls $C000 twice 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

source file wiki/summaries/s-sidin07-inside-modules.md · 7 unverified · graph