SIDin #8 — Tiny Sid Compo, the 512-byte entries (Stefano Tognon, 2005)

author Stefano Tognon (ice00), SIDin magazine; code by Stefano Tognon, Jaymz Julian (A Life in Hell), Laxity, 4mat
date 2005-07-30
article SIDin issue 8 (version 1.00, 30 July 2005), pp. 21–46: 'Tiny Sid Compo 512b entries' — sections 15BB, 15BB Code, Crue Gurl, Crue Gurl Code, Twone Five, Twone Five Code, Empty, Empty Code, Conclusion. The result table quoted below is from the issue's News section (pp. 9–10)
created 2026-08-30 · updated 2026-08-30

Tognon prints and comments the four entries of the 512-byte category of his tiny-sid-compo (2005): his own 15BB, Crue Gurl (A Life in Hell), Twone Five (laxity) and the winner Empty (4mat). Each is a complete sid-player-routine plus song in under 512 bytes, and the article is a catalogue of size tricks: one-byte pattern pointers, note tables computed at run time, raster-loop timing instead of interrupts, loading over the KERNAL vector table to start without a BASIC stub, and instruments that are two or three bytes.

Key claims#

The compo (from the News pages 9–10 of the same issue)#

  • "Running from 15 January to 8 May 2005, the compo where only 256 and 512 bytes music were allowed." Results — 256 bytes: 1. My Block .. one block (Agemixer, 186 points) and 1. New Kid on the block (Frantic, 186); 3. Repeat me (Laxity, 152); 4. Electronic (Aleksi Eeben, 151); 5. Splatform256 (Steve Judd, 145); 6. Imperial March (Tapio Viitanen, 118); 7. Random Ninja (Stefano Tognon, 117); 8. Crue Gurl Freestyle (Jaymz Julian / A Life in Hell, 105); 9. 128 Byte Blues (Freaky DNA, 91); 10. Repetitive Tune BASIC (Peter Weighill, 75). 512 bytes: 1. Empty (4mat, 206); 2. Twone Five (Laxity, 171); 3. 15BB (Stefano Tognon, 154); 4. Crue Gurl (Jaymz Julian / A Life in Hell, 118). "More details at http://digilander.iol.it/ice00/tsid/tinysid". The 256-byte entries are analysed in s-sidin09-tiny-sid-256b; the same table is digested with the other SIDin news in s-sidin-news-digest, and the compo was announced in #7 (s-sidin07-xxlarge).

15BB (Tognon, 3rd)#

  • "a 99% cover" of the first seconds of Mike's Bat of Basses, "one of the my most listened sid ever"; analysed with sid2midi (register dump printed: frequencies, pulse widths, waveforms $41/$51, ADSR 0291/0938, the low-pass cutoff ramp $180 … $220 … $018).
  • What the dump shows: voices 2 and 3 share an instrument — $51, slight vibrato, a 10-value pulse cycle reset at each note, equal durations with voice 3 starting half a note later; voice 1 is $41, its frequency doubled for the first 3 ticks of every note ("the timbre of instrument is given even from this effect"), pulse from 2048 upward in steps of 16, and a low-pass filter whose cutoff rises then falls to a minimum; voice 1's durations vary.
  • Simplifications for size: no vibrato; voice 1's pulse bounces where the high byte hits $07 or $0A ("Testing a byte instead of a word is much simple"; the original sweeps $0800$0A30 — the text prints "$A030"/"$A000"); the filter is a 17-byte table of cutoff high bytes ($35,$3A,$3F,$44,$3F … $08,$03) stepped once per tick; voices 2/3 take their pulse from a 6-entry table walked up and down with a ±1 direction flag (pulse-width-modulation, filter-programming).
  • Timing: an ordinary $0314 IRQ; every tick runs the timbre code, "only 1 time onto 7" (RDELAY = 7) the note code.
  • Pattern format: negative byte = duration in the low nibble ("half of the one needed", doubled by asl), positive = note index into a 10-note frequency table (F2, G2, G#2, A#2, C4, D4, D#4, F4, G4, G#4 — e.g. F2 = 207/5), 0 = end of pattern; pat01: DUR+14, Gd2, DUR+1, G2, F2, DUR+16, G2 …; the bass and the two 16-note arpeggio patterns (Gd4, C4, Dd4, Gd4 …) are the whole song.
  • Instruments: voice 1 AD $02, SR $91, control $41, pulse $0800; voices 2/3 AD $09, SR $38, control $51, pulse $0FF0; $1F to $D418 (volume, low-pass), $F1 to $D417 (resonance max, filter on voice 1). Voice 3 starts with a longer initial duration so it is "muted as in original" at first.

Crue Gurl (A Life in Hell, 4th) — "Tiny Player v0.4"#

  • Header: "Player by A Life in Hell / Additional optimizations by Jockstrap and Sorex / Music by A Life in Hell / (c) 2004-2005, Warriors of the Wasteland". The source is full of compile-time flags (useRealPulse, useTranspose, useSpeedEor, noFilter, filterSweep, stupidlyCompact, lotsOfZpage …) so "you can so enable only the features you really need for your tune"; Tognon prints unused branches in blue. The 256-byte Crue Gurl Freestyle is the same player (v0.5).
  • Start without a stub: *=$326 / .word entry ; BSOUT vector / .byte $ed,$f6 ; STOP vector — the file loads over the KERNAL vector table so the code starts on load ("thanks to steve judd's xip for the tip on starting smally"); the player then runs sei and busy-waits on raster line 80 (lda #80 / cmp $d012 / bne *-3), "another best way to obtain IRQ timing for the player using few bytes of code".
  • One-byte pattern pointers: three order lists (orderList1–3, $FF = repeat) hold patN-pat1 offsets from the first pattern; a zero-page pointer to pat1 makes lda orderList1,y / tay / lda (zp1),y the whole fetch — "Using 1 byte instead of 2 for each pointer is a clear way for save lot of space" (orderlist).
  • Pattern bytes: $FF end; $00 = keep the SID as it is and reload the duration; otherwise a note as $XY → frequency high byte $0Y, low byte $X0 (four octaves, coarse pitch: "$86 is a note that is a G2 (~98 Hz)"), then waveform $41. Durations alternate 5 and 13 by eor #speedEor each note (defaultSpeed = 5, speedEor = 8) — a shuffle for free.
  • Voice 2 is the chord voice: its note byte indexes two tables walked one entry per frame — wave ($21,$21,$21,$00,$01 …, $81,$41,$41,$41,$11,$08,$00,$19 …) and chords (frequency high bytes: $28,$2f,$3c,$00,$01 …, $ff,$08,$06,$03,$09,$09,$00,$19 …); a $00 entry ends the list and the next byte is the loop index ("chords -1 / wave -1 — both of these are to save memory, since 0 is silence!") (chord-arpeggio, wavetable-programming).
  • Envelope and restart: SR always $e9, AD never written ("no Attack/Decay"); gateEnd = 3: three frames before the next note the gate is dropped ($40), two frames before the control register gets $08 (test bit), one frame before the frequency high byte is $ff, SR $e9 and control $81 — "Set to different (non-zero) values to get various restart types" (hard-restart, adsr-envelope).
  • Pulse and filter: pulse high byte for voices 1 and 3 steps from pwmMin = 3 to pwmMax = 6 every 4 frames (pwmSpeed = 3 ; must be (n^2)-1!!!!) and wraps; $1f$D418, $f4$D417, $40$D416, then inc fltr / sta $d416 every frame — a cutoff that rises and wraps ("A filter with incremented (high byte) of cut off frequency is added to one voice").
  • Debug aid: inc $d020 … dec $d020 around the play code shows the rastertime.

Twone Five (Laxity, 2nd) — "TPlayer 01.g0"#

  • Written in TASM on the C64 ("Coded on 25th of March 2005"; "Contains tune: Five Twone"); Laxity's own notes: "extreemly simple but still handles wave tables for instrument. AD is fixed for all channels, SR is set per channel. Hard restart is always enabled. No vibrato, no slide, no tie notes. Only one sequence per channel. Channel two has a transpose table for the bass ostinate."
  • IRQ off by RTI: sei, $01 = $35 (BASIC and KERNAL ROM out), the hardware vector $fffe/$ffff pointed at an rti; the main loop waits for raster $50 and toggles $d020.
  • Run-time frequency table: 24 bytes of top-octave values (frqsrc: $a0,$45, $b8,$49 … $70,$83) are shifted right for each lower octave into freqtab — "82 bytes instead of 188 bytes for a typical one" (sid-player-routine).
  • Sequence bytes: $00–$7E note, $7F end, $80–$BF duration, $C0–$FF wave pointer; seq01 .byte $83,$ca,$24,$df,$30, $c4,$3c,$df,$30, $7f = duration 3, Kick, C3, thing, C4, Snare, C5, thing, C4, end. Speed: speedcnt reloads with 2 (three frames per step).
  • Instruments = two parallel wave tables, wavetab1 (control byte) and wavetab2 (positive = semitones added to the note, negative = absolute note, $00 ends): Bass $81,$81,$41,$00 / $c0,$c0,$00,$00 ("2 ticks with noise and E5 note, follow by rectangular waveform and relative note 0"); Snare $81,$81,$11,$40,$80,$00 / $d0,$d0,$b2,$ac,$ca,$00 ("A classical snare implementation"); Kick $09,$81,$11,$10,$10,$10,$10,$00 / $d2,$d2,$ae,$a6,$a2,$9e,$96,$00; Crystal sound $41,$41,$11,$42,$20,$20,$40,$00 / $0c,$0c,$00,$00,$0c,$0c,$00,$00; Sine plug $81,$81,$15,$10,$00; thing $81,$81,$12,$00 / $c0,$c6,$00,$00 — "if you had composed with some real editor around, you are familiar with this kind of instrument implementation" (wavetable-programming, instrument-tables).
  • AD $03 for all voices; SR per voice $e6,$d6,$e7; hard restart two frames before a note ends: AD $0f, SR $00, control $00; pulse: pulsehi $08,$01,$04, pulselo incremented by pulseadd $00,$10,$3d at every SID write; transpose table tp: 0,0,0,0,5,7,0,0 applied to channel 2 at each end mark.

Empty (4mat, 1st)#

  • "For PAL machines, tested on 6581 chip. Source compiled with C64ASM." sei is "the best way to disable interrupt using only 1 byte of code".
  • Frequency table from 12 bytes (freqsource 12,28,45,62,81,102,123,145,169,195,221,250), 58 bytes of code in all; the raster wait compares $d012 with whatever is in A — after the first frame that is the last SR written (voice 3's $6F), so the loop syncs once per frame "only one time (low of raster position)".
  • Two halves, playframe (tempo $06, note lengths, pattern reads) and update (instruments and SID writes; placed above the first "to avoid branch out of range errors"); the SID is written in one loop from a 21-byte voicedata image.
  • Song data (songstart offsets per voice): ≤ $7f position in pattdata to play from, $80–$8f transpose, $ff end — "I removed the 'repeat pattern' command and made the maximum patterndata 128 bytes to save some memory". Pattern data: ≤ $7f note (0 = keep the previous note playing), $80–$8f note length, $e0–$ef instrument, $ff end. Voice 2 entire: songdata $13,$ff, pattdata $e2,$55,$e5,$30,$35,$3a,$41,$35,$3a,$29,$ff (orderlist).
  • Instruments (16 possible, 7 used), five bytes each: instadsr (SR only: $6f,$6f,$95,$ec,$a9,$79,$6e), instadd (added to the pulse low byte each frame: $1c,$0c,$00,$15,$00,$74,$a5), instpuls (low nibble = pulse high byte, high nibble = loop position 1–3: $15,$18,$38,$30,$38,$3b,$36), and three-entry instdata / instwave rows: instdata ≤ $ef = value for the pitch high byte ("low byte isn't cleared"), $f0–$ff = arpeggio offset ($f0 = the pattern note). Instrument 0 $f3,$f7,$f0 / $41,$41,$41 / $15 is "a classical arpeggio with 3 values"; instrument 2 $10,$af,$06 / $41,$81,$40 is a drum (fixed pitch pulse, noise, gate off). "I only have one counter for both data & waveform to save memory, so any instrument that loops can't use release value" (chord-arpeggio, wavetable-programming, pulse-width-modulation). Every note starts with $09 (test + gate) in the control byte (hard-restart).
  • Volume $0e to $D418; no filter.

Conclusion#

"Maybe it is more simple to understand a 512 bytes engine, as it could have a more linear structure over a 256 bytes one" — hence the 512-byte category first; "next time we will see the 256 bytes entries, where the code will be more intricate…".

Practical takeaways#

  • Size tricks that transfer to any small player: pattern pointers as one-byte offsets from a base pattern; a note byte whose nibbles are the raw frequency bytes; alternating durations by EOR; a frequency table computed by shifting the top octave (12 or 24 bytes instead of 188); waiting on a raster line with SEI instead of installing an interrupt; loading at $0326 so the KERNAL jumps into the code; a single SID register image written in a loop; a test-bit frame ($09) as the whole hard restart.
  • Instrument formats seen here — Laxity's two-column wave table, 4mat's three-entry pitch/wave loop with a loop point, Crue Gurl's per-frame wave+chord lists — are the tracker wavetable in miniature; they show which parts of an instrument definition are essential (waveform per frame, a pitch or offset per frame, an SR, a pulse) and which are luxuries (AD, vibrato, slides).
  • What made the winner: Empty has proper song/pattern structure, 7 instruments with pulse sweeps, arpeggios and drums, and a 128-byte pattern space — structure, not effects, is what fits in 512 bytes.

Notable quotes#

"Using 1 byte instead of 2 for each pointer is a clear way for save lot of space" (on Crue Gurl's order lists)

"The total space used for generating the frequency table is of 82 bytes instead of 188 bytes for a typical one." (on Twone Five)

Relevance to the wiki#

Primary source for tiny-sid-compo (rules, dates and results) and for the "minimal players" material on sid-player-routine; concrete miniature instrument formats for wavetable-programming, chord-arpeggio, hard-restart and adsr-envelope; laxity's and 4mat's own player code.

To verify#

  • (unverified) The original Bat of Basses pulse range "from $0800 to $A030" and the cover's "$07F0 to $A000" read like typos for $0A30/$0A00 — the code compares the high byte with $07 and $0A.
  • (unverified) Tognon writes "raster position $80" for Crue Gurl, but the source has lda #80 — decimal 80 = $50.

Pages touched#

tiny-sid-compo · sid-player-routine · laxity · wavetable-programming · chord-arpeggio · hard-restart · adsr-envelope · pulse-width-modulation · filter-programming · orderlist · instrument-tables · sidin

source file wiki/summaries/s-sidin08-tiny-sid-512b.md · 2 unverified · graph