Modules (Ivan Del Duca, 1988)
The music of Modules, an Italian C64 game of 1988, written by its coder Ivan Del Duca in a music engine of his own — according to Stefano Tognon "probably … the first Italian music engine around". Tognon reverse-engineered the PSID rip (name "Modulus", "1988 System", init $C57B, play $CFE8) in sidin #7 and printed the commented listing; this page documents the engine from that article (source: s-sidin07-inside-modules). Del Duca is one of the Italian musicians dustbin names as talented (source: s-sidin07-dustbin-interview).
Facts#
- Del Duca "was not a true musician (he is a game coder, even today)", per Italian interviews Tognon cites; he wrote the engine "instead of used the one's that were available" (source: s-sidin07-inside-modules).
- The engine is a sid-player-routine of the pattern-list family with three ideas of its own: 4-byte track entries carrying a pattern length and a repeat count, pattern commands that are the low bytes of a jump table (
$80–$9Bat$C680), and a gosub/return command that saves the pattern position and the instrument so drum phrases can be shared (source: s-sidin07-inside-modules). - Tognon's verdict: "not so powerful compared to some player of the same year, but probably only the features needed by Ivan were implemented" — and the music "very-very good" nevertheless (source: s-sidin07-inside-modules).
Cheat sheet: the engine#
All values from the listing in s-sidin07-inside-modules.
Track entry (one table per voice, 4 bytes each; orderlist):
| byte | meaning |
|---|---|
| 0 | pattern address, high byte |
| 1 | pattern address, low byte |
| 2 | number of pattern bytes to execute; $FF = until an ending command |
| 3 | extra repeats ($02 = play three times) |
Example: >pat00, <pat00, $3D, $02 / >pat00, <pat00, $51, $00 / >pat01, <pat01, $0A, $04 / … / >pat03, <pat03, $FF, $00.
Pattern bytes:
| byte | meaning |
|---|---|
$00 dd | set the note duration dd (no new note) |
$on ($01–$7F) | note: high nibble octave 0–7, low nibble note 1–C; pitch from a 12-entry top-octave table ($861E … $FD2E) shifted right once per octave |
a byte > $9F after a note | its duration; otherwise the next byte is another note with the previous duration |
$A0–$E0 | duration index into a 16-bit table: $03C0, $01E0, $F0, $A0, $78, $60, $50, $45, $3C … down to $01, then $02D0, $0168, $B4, $5A, $2D … |
$80–$9B | command (below) |
Commands (low byte of the jump table at $C680):
| command | parameters | what it does |
|---|---|---|
$80 | — | end of pattern |
$83 | — | release: write the instrument's control off byte (gate 0) (hard-restart) |
$86 | n flag | instrument n ($00–$0F); flag ≠ 0 reloads its filter parameters |
$89 | v | volume $0–$F |
$8C | step delay | portamento up: add step to the frequency every delay ticks (pitch-slide-and-portamento) |
$8F | step delay | portamento down |
$92 | — | stop the voice's effect |
$95 | src dst | copy SID register $D4src to $D4dst every tick — $1B,$0F puts oscillator 3 into voice 3's pitch, $1C,$16 envelope 3 into the filter cutoff (randomization-and-probability, filter-programming) |
$98 | — | unused |
$9B $02 | type limit step delay | LFO: add/subtract step every delay ticks, direction governed by limit; type 0 = filter cutoff high byte, 1 = pulse width high byte, 2 = frequency high byte (vibrato) (vibrato, pulse-width-modulation) |
$9B $03 | — | return from a gosub (restores position and instrument) |
$9B $05 | note | play note "No action is taken about duration of this note" |
$9B lo | — | gosub to the pattern at $C9lo (pat38 at $C900, pat39 at $C930 are the shared drum phrases) |
Example: 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 one step on the frequency high byte every six ticks, then notes at duration $A8); pat01: $00,$A8 / $9B,<pat39 / $00,$DB / $9B,<pat39 / $9B,<pat38 / $92.
Instrument = 9 tables × 16 entries (tbAD, tbSR, tbWLo, tbWHi, tbCntOn, tbCntOff, filtCutFH, filterResCtr, filterMode) — ADSR, a fixed 12-bit pulse width, the control register for note-on and for release, and a filter preset; no modulation lives in the instrument (instrument-tables, adsr-envelope). A few rows:
| # | AD | SR | pulse | ctrl on | ctrl off | cutoff hi | res/route | mode |
|---|---|---|---|---|---|---|---|---|
| 0 | $2A | $3C | $060A | $11 | $10 | $80 | $F0 | $10 |
| 1 | $58 | $5C | $077A | $41 | $40 | $30 | $F0 | $10 |
| 2 | $82 | $50 | $0000 | $85 | $84 | $70 | $F0 | $30 |
| 3 | $0C | $00 | $077A | $41 | $40 | $90 | $F0 | $30 |
| 5 | $08 | $00 | $0800 | $15 | $14 | $00 | $00 | $00 |
| C | $3A | $4A | $0000 | $81 | $80 | $90 | $F0 | $10 |
| D | $09 | $00 | $0000 | $81 | $80 | $00 | $00 | $00 |
Instrument 2 (noise + ring modulation, gate) and the noise instruments $C/$D are the drums; $15 is triangle + sync.
Effect engine: one effect per voice (flagEff / freqEffect: 1 add, 2 subtract, 3 SID copy, 5 LFO) and a global enable; the LFO keeps currValue, limit, lowLimit = limit >> 2, step and delay per voice and reaches its register through sidIntType: $16, $03, $01 (cutoff hi, pulse hi, frequency hi — the last two offset by the voice). makeDurEffect counts each voice's 16-bit duration down and runs the effects every tick.
Memory map: code $C000–$C5B5; jump table $C680; variables and the nine instrument tables to $C790; tracks $C800–$C8FF; patterns $C900–$CFD8; playMusic: jsr $C000 / jsr $C000 / rts at $CFE8.
To verify#
Readings of the listing not stated in the article's prose (see the summary's list): $80 calls disableVoice, so it ends the voice and, once all three are off, the player (the title tune is a one-shot; pat0b–pat37 look like game jingles); $86 n 0 clears the voice's routing bit in $D417; $9B $05 skips the gate-off, i.e. a tied note; the LFO is a triangle of period limit × delay; durations follow 480 / n ticks; the play stub runs the engine twice per frame (multispeed); the title is "Modules" in the article and "Modulus" in the PSID (all unverified).
Related#
sid-player-routine · instrument-tables · orderlist · vibrato · pulse-width-modulation · filter-programming · pitch-slide-and-portamento · dustbin · sidin · hubbard-player · galway-player