SIDin #12 — Tiny Sid 2, part 3: Plaster (512 b) and Back To Basics (256 b) (Stefano Tognon, 2008)
The end of Tognon's series on the Tiny Sid Compo II: the two remaining entries, GRG's 512-byte Plaster — "the tune that take more points over all the others" — and Jaymz Julian's 256-byte Back To Basics, both "done by reverse engineering the code". Two complete SID players that fit in half a kilobyte and a quarter of one, with the tricks that make that possible.
Key claims#
Plaster (GRG, 512 bytes)#
- "Autostarting at $0326 by setting the IRQ": the file loads over the vector at
$0326(bytes$2A,$03→ the code at$032A, then$ED,$F6), so the tune starts on its own. The player then doessei, synchronises to the raster (cmp $D012loop) and never returns — no interrupt at all. - Init clears the zero page with a
sta $0002,yloop and copies per-voice start values there; all voice state lives in zero page ($84–$D8, a commented map in the article: note duration, pattern offset, pattern index, track index, note, pattern address, control index, instrument, transpose, "note duration for control", control value, pulse low byte per voice, plus the filter direction and cutoff high byte). - Filter sweep: the cutoff high byte (
$D416) runs from$3Fto$FFand back, one step per frame, driven by a direction flag;$D417 = $F1(resonance$F, voice 1 filtered),$D418 = $3F(filter-programming). - Song = two tracks: one for voice 1 (
$83,$01,$01,$01,$01,$8A,$01 … $06,$00) and one shared by voices 2 and 3 ($02,$02,$03,$03,$04,$04,$05,$05,$00). A track byte is an index into a pattern table; a negative byte's low five bits are the transpose for the next pattern (orderlist). - Pattern bytes: a value below 8 selects an instrument, otherwise the low 7 bits are the note; a negative byte is the last command of the pattern. Example
patAC: $03, $3A,$33,$3A, $03, $36,$3A,$35, $36, $B3. - Instruments are three parallel 5-entry tables —
controlIndex $00,$04,$02,$02,$0C(an index into a shared list of control-register values),AD_inst $00,$0F,$02,$E0,$08,SR_inst $A4,$FA,$8A,$8A,$E8. The control list is three sequences:$09,$81,$41,$00,$09,$81,$11,$11,$11,$11,$11,$00,$09,$81,$41,$40,$40,$80— every sound starts with$09(test + gate) and a frame of noise before its waveform (wavetable-programming, hard-restart). A paralleluseFreqlist supplies a fixed high frequency byte for some steps ($8B,$89,$86,$84,$8E,$8C,$8A— drum pitches, negative = "7bits=high freq. to use for effect"). - Pulse width is "varied by the player for making better effect":
$18is added to each voice's pulse low byte every frame; the high bytes start at$08,$08,$04(pulse-width-modulation). - "Hardrestart of note is performed by the player": when a note's remaining duration reaches 1 the code writes SR
$01and AD$0F— the0F01value trackers later made a default — one tick before the next note (read from the listing; see To verify). - Notes come from a 12-entry octave table (
loFreq/hiFreq), halved per octave withlsr $C2 / ror $C1; the note byte's low nibble is the semitone, the high nibble the number of halvings. - Voices 2 and 3 play the same track but are kept apart: voice 2's transpose is XORed with
$F0at each track step, voice 3's instrument is XORed with$07at each pattern, voice 3 adds a fixed$1Eto its frequency low byte (detune), and voice 1 forces instrument 5 through a self-modified pattern byte. The voice offset (0, 7, 14) is stepped with the undocumentedsbx #$07.
Back To Basics (Jaymz Julian, 256 bytes)#
- Same
$0326autostart and raster sync;$46bytes of data are copied to zero page$01–$47"for access them with short instructions" — patterns, tables, counters and the three voices' state all sit in one block. - One 8-bit counter (
$47), incremented every four main loops, is written both as the pulse-width high byte and as the filter cutoff high byte (txa; adc $47; sta $D403,x; sta $D416), so each voice gets a different pulse width from the same counter and the filter sweeps with it; pulse low byte fixed at$80;$D418 = $1F,$D417 = $F4(voice 3 filtered). - Voice 2 reads a table of frequency high bytes (
$77,$FF,$28,$2F,$3C,$00,$02,$FF,$0B,$0B,$B5,$FF) and a table of control registers ($0C,$81,$41,$41,$41,$00); voices 1 and 3 read patterns of raw frequency bytes (voice 3 writes the byte to the low register and its low nibble to the high one). - "Note duration are triggered between 2 values during the play": the duration is XORed with a constant (
$39 = $08) after every note, so successive notes alternate between two lengths (shuffle-funktempo). - At every pattern end the voice gets SR
$E9and control$41; every 16 pattern ends the SR and control are cleared and voice 2's frequency high byte is XORed with$0Cto vary the bass.
Conclusion#
"Remember that writing tiny music is a sort of programming art: you have to customize your player according to the instruments to use, with the music effect you want to achieve and the melody of your tune, arranging all the code in a manner that must be the smallest one."
Practical takeaways#
- Sub-kilobyte players are built from: a load address over a system vector for autostart; zero-page state with
,xvoice offsets 0/7/14; one octave table shifted per octave; one shared counter driving pulse width and filter at once; two voices on one track differentiated by XORed transpose, instrument or a fixed detune; alternating note lengths for swing; and the same$09→$81→ waveform start and0F01restart write that full trackers use. - GRG — credited elsewhere in the wiki with turning JCH's hard restart into a "soft restart" (hard-restart) — wins a 512-byte compo with a player that still hard-restarts.
Notable quotes#
"All the engines present here are done by reverse engineering the code."
Relevance to the wiki#
Results and player descriptions for tiny-sid-compo; the smallest complete players on sid-player-routine; dated examples of the $09/noise attack, the 0F01 restart, a shared PWM/filter counter and alternating durations for hard-restart, pulse-width-modulation, filter-programming and shuffle-funktempo.
To verify#
- (unverified)
$0326is the KERNAL's CHROUT (character output) vector, so a file loaded over it starts on the next character printed — the article only says "by setting the IRQ". - The
0F01write is read from a listing whose comment lines were shifted by the PDF text extraction; the order "lda $84,x(note duration) —cmp #$01—sta $D406,x—lda #$0F—sta $D405,x" is the reading that matches the comments.
Pages touched#
tiny-sid-compo · sid-player-routine · hard-restart · pulse-width-modulation · filter-programming · wavetable-programming · shuffle-funktempo · detune · orderlist · sidin