Homeentitysong

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–$9B at $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):

bytemeaning
0pattern address, high byte
1pattern address, low byte
2number of pattern bytes to execute; $FF = until an ending command
3extra repeats ($02 = play three times)

Example: >pat00, <pat00, $3D, $02 / >pat00, <pat00, $51, $00 / >pat01, <pat01, $0A, $04 / … / >pat03, <pat03, $FF, $00.

Pattern bytes:

bytemeaning
$00 ddset 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 noteits duration; otherwise the next byte is another note with the previous duration
$A0–$E0duration 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–$9Bcommand (below)

Commands (low byte of the jump table at $C680):

commandparameterswhat it does
$80end of pattern
$83release: write the instrument's control off byte (gate 0) (hard-restart)
$86n flaginstrument n ($00–$0F); flag ≠ 0 reloads its filter parameters
$89vvolume $0–$F
$8Cstep delayportamento up: add step to the frequency every delay ticks (pitch-slide-and-portamento)
$8Fstep delayportamento down
$92stop the voice's effect
$95src dstcopy 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)
$98unused
$9B $02type limit step delayLFO: 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 $03return from a gosub (restores position and instrument)
$9B $05noteplay note "No action is taken about duration of this note"
$9B logosub 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:

#ADSRpulsectrl onctrl offcutoff hires/routemode
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).

sid-player-routine · instrument-tables · orderlist · vibrato · pulse-width-modulation · filter-programming · pitch-slide-and-portamento · dustbin · sidin · hubbard-player · galway-player

Sources#

s-sidin07-inside-modules · s-sidin07-dustbin-interview

source file wiki/entities/modules.md · graph