SIDin #8 — Tiny Sid Compo, the 512-byte entries (Stefano Tognon, 2005)
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, ADSR0291/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
$07or$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
$0314IRQ; 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;$1Fto$D418(volume, low-pass),$F1to$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 runsseiand 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) holdpatN-pat1offsets from the first pattern; a zero-page pointer topat1makeslda orderList1,y / tay / lda (zp1),ythe whole fetch — "Using 1 byte instead of 2 for each pointer is a clear way for save lot of space" (orderlist). - Pattern bytes:
$FFend;$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 byeor #speedEoreach 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 …) andchords(frequency high bytes:$28,$2f,$3c,$00,$01 …,$ff,$08,$06,$03,$09,$09,$00,$19 …); a$00entry 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$e9and 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 = 3topwmMax = 6every 4 frames (pwmSpeed = 3 ; must be (n^2)-1!!!!) and wraps;$1f→$D418,$f4→$D417,$40→$D416, theninc fltr / sta $d416every 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 $d020around 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/$ffffpointed at anrti; the main loop waits for raster$50and 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 intofreqtab— "82 bytes instead of 188 bytes for a typical one" (sid-player-routine). - Sequence bytes:
$00–$7Enote,$7Fend,$80–$BFduration,$C0–$FFwave 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:speedcntreloads with 2 (three frames per step). - Instruments = two parallel wave tables,
wavetab1(control byte) andwavetab2(positive = semitones added to the note, negative = absolute note,$00ends): 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
$03for 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,pulseloincremented bypulseadd $00,$10,$3dat every SID write; transpose tabletp: 0,0,0,0,5,7,0,0applied to channel 2 at each end mark.
Empty (4mat, 1st)#
- "For PAL machines, tested on 6581 chip. Source compiled with C64ASM."
seiis "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$d012with 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) andupdate(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-bytevoicedataimage. - Song data (
songstartoffsets per voice):≤ $7fposition inpattdatato play from,$80–$8ftranspose,$ffend — "I removed the 'repeat pattern' command and made the maximum patterndata 128 bytes to save some memory". Pattern data:≤ $7fnote (0 = keep the previous note playing),$80–$8fnote length,$e0–$efinstrument,$ffend. 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-entryinstdata/instwaverows: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/$15is "a classical arpeggio with 3 values"; instrument 2$10,$af,$06/$41,$81,$40is 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
$0eto$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 withSEIinstead of installing an interrupt; loading at$0326so 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$07and$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