SIDin #9 — Tiny Sid Compo, the 256-byte entries (Stefano Tognon, 2006)

author Stefano Tognon (ice00), SIDin magazine; code by Agemixer, Stefano Tognon, Jaymz Julian (A Life in Hell), freakyDNA, Tapio Viitanen, Frantic, Laxity, Aleksi Eeben, Peter Weighill, Steve Judd
date 2006-01-14
article SIDin issue 9 (version 1.00, 14 January 2006), pp. 15–51: 'Tiny Sid Compo 256b entries' — sections Myblock... One Block, Random Ninja, Crue Gurl Freestyle, 128 Byte Blues, Imperial March, New Kid On The Block, Repeat Me, Electronic, Repetitive Tune BASIC, Splatform256, Conclusion. Tognon reverse-engineered the sources of Imperial March, Electronic and Splatform256 himself
created 2026-08-30 · updated 2026-08-30

The ten 256-byte entries of the 2005 tiny-sid-compo, with code and Tognon's notes — the counterpart of s-sidin08-tiny-sid-512b. At this size the players stop looking like drivers: code is copied into the zero page and self-modified, the raster line replaces the interrupt, notes are read from screen memory or the SID's noise output, BASIC ROM routines do the filter sweep, and instruments shrink to two or three bytes. The placings are from the result table in s-sidin08-tiny-sid-512b.

Key claims#

Myblock... One Block (Agemixer, joint 1st)#

  • After sei the program copies its 23 code bytes to $0000–$0016 and jumps into the zero page: "now all the variables can be accessed from zero page and so using only one byte for the address!" The SID is initialised from a 23-byte initTable written to $D401 upward (voice 1 pulse $0A20, control $41, AD $09, SR $E9; voice 2 frequency $FFFE, pulse $0909, control $08, AD $02, SR $35; voice 3 pulse $0909, control $41, AD $FF, SR $7F; filter $C1 = resonance $C on voice 1, $1F = low-pass + full volume). Sync on raster $80.
  • Y is the song. Y is a free-running counter masked to 32 values; its bits drive everything: Y 0–15 read voice 1's note from an 8-entry table (v1LoFreq/v1HiFreq, e.g. $0800, $0556, $02FF, $047C …), Y 16–31 write (Y EOR $0F) LSR to both frequency bytes of voice 1 (a descending "15, 14 … 8, doubled" sweep); bit 2 of Y toggles voice 2's drum (and #$04 / lsr / lsr / eor #$81 → control $81 or $80, so "every 4 values of Y, drum state is inverted"); bits of Y set the cutoff high byte (and #$07, four asl, eor #$7F$D416) and voice 3's pulse (lsr lsr → $D411; ror $D410). Voice 3 plays 8-note patterns from a 4-entry pattern table indexed by two bits of Y.
  • Self-modifying code changes the rules every 8 pattern passes: the mask #$07 becomes #$0F and an asl becomes nop (Pos3 eor #$A0), the voice-3 index bits shift (Pos2 masks eor #$08/eor #$E0). "Maybe you will find more behaviors by looking at the player more carefully…"

Random Ninja (Stefano Tognon, 7th)#

  • A two-voice remix of the end of Last Ninja II – Central Park "in the Chris Holm edition" (last-ninja-2); the instruments were first found in a goattracker test tune — voice 2 drum $81 C8 / $41 A8 / $40 00, voice 1 $41 00 / $21 00 (GoatTracker wavetable notation) — "with 2 voices I can have a good feeling about the generated music".
  • One pattern for both voices: each byte's bit 7 = play the drum on voice 2 (fixed pitch, so a flag suffices), low 7 bits = note for voice 1 (0 = keep the previous note); fixed duration; 6 notes (G3 A3 C4 D4 E4 G4).
  • Timing without an IRQ: fakeSyncro waits for $D012 = 0 then calls the KERNAL delay at $EEB3 to step past it (raster 0 "can come double in a frame"); the note loop is a fixed tick script — tick 0 read the byte, start voice 2 ($81, SR $b8, frequency 157/69) and/or voice 1 (gate off, frequency, $41, AD $76, SR $9B); tick 1 voice 1 → $21 and voice 2 frequency high 9; tick 2 voice 2 frequency high 2; tick 9 (RDELAY = 9) voice 2 hard restart with $09 — "this is needed, otherwise no sound is produced" (hard-restart). "IRQ … too many code is needed for making the dinamic waveform change at each tick".
  • Randomness: voice 3 runs at voice 1's pitch with waveform $81 and $8F in $D418 (voice 3 off); after every 64-byte pattern one bit of $D41B chooses $21 or $11 as voice 1's second waveform by self-modifying lda #$21 — "This gives some kind of life to the music" (randomization-and-probability).

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

  • The 512-byte player of s-sidin08-tiny-sid-512b with different flags: loads at $0326 (KERNAL vector), sei, sync on the raster line (lda #80); the song data is copied to the zero page ("This is a good technique for saving space"); useOrderList = 0 (one 8-byte pattern per voice), noFilter = 1, usePwm = 0, useRestart = 0, defaultWave = $21; useRandomNotes = 1: after each note lda $d41b / and #7 / sta pos picks the next position of voice 1 from the noise output — "a random way for generating the notes for save space"; chordEor = 1 flips a chord byte with eor #2 at every wrap so the chord alternates; stx sid_ctrl with X = 14 sets the volume each frame ("save two bytes by doing this each frame… heh!").

128 Byte Blues (freakyDNA, 9th)#

  • A 128-byte entry ("April 9, 2005", ACME and Relaunch64) whose notes come from screen memory: the IRQ is installed with the BASIC stub POKE 789,8 (IRQ at $0831, "as seen in xxlarge in SIDin #7" — s-sidin07-xxlarge); each interrupt rotates one byte of $0400 left (rol, carry into bit 0, also written to colour memory) and its low bit steps the note index up or down within a blues scale — "Somewhat of a 1-bit DPCM technique except for successive music notes in a scale instead of successive samples". Two scales (C-4 D#4 F-4 G-4 A#4 C-5 D#5 F-5 and F-4 G#4 A#4 B-4 C-5 F-5 A#5, as low/high tables) alternate halfway through the sequence; the tempo is jiffy AND cursor_blink ($A2 and $CD), so "it is possible to change the tempo while moving the cursor" and typing at the top of the screen changes the notes; after eight passes the bytes are back to their original form. One voice: SR $F1, control $15, $1F to $D418 (Tognon: "triangular waveform"). Dropping the frequency low byte made it "too out of tune or too high"; a second voice did not fit (randomization-and-probability).

Imperial March (Tapio Viitanen, 6th)#

  • A Star Wars cover; Tognon's own disassembly. sei, sync on raster $64; a 25-byte sidTable is written to all SID registers every frame (voice 1 pulse $0810, $21, AD $0A, SR $20; voice 2 pulse $0520, $11, $0A, $50; voice 3 pulse $0240, $11, $2A, $00; volume $0F, no filter).
  • Packed notes: voices 1 and 2 share a length table and a note table whose two nibbles hold both voices' notes (the code sends the low nibble to voice 1 and the high nibble to voice 2; Tognon's prose says the reverse); voice 3 has its own pat3 with note in the high nibble and duration in the low; one decodeFreq routine serves all (note 0 = leave the voice alone); 8 pitches. At every pass of pat3 the voice-3 control byte is rotated (rol ctrl3 … ora #$01) and the gate-off value shifted, so the waveform changes each loop — "This made the tune more various like you can hear".

New Kid On The Block (Frantic / Hack'n'Trade, joint 1st)#

  • "Chip: 6581 C64 used when composing." Only $11 and $21 waveforms, yet a winner — "so it is great made".
  • Instrument = 3 bytes: AD (SR stays $00 — "Using AD only to save space"), control byte, and duration (S*1 … S*8 with S = $10; a pattern is S*8 ticks): adtab $1c,$1b,$cd,$2b,$1a,$00,$ad / ctrltab $11,$11,$21,$21,$21,$00,$21 / durtab S*1,S*3,S*6,S*2,S*1,S*8,S*8. "This trick of use duration inside the instruments is very interesting, as with one instruction you set the duration of note and the instrument to use … one instruments cost you only 3 bytes"; instrument 5 ($00, silent, S*8) is a whole empty pattern in one byte, and a S*1 instrument could change the waveform every tick "which means we can also make drums" (adsr-envelope, instrument-tables).
  • Pattern byte: high nibble instrument, low nibble note; $00 = jump, followed by a relative destination ("data may not be larger than $80 bytes"); three voices start at offsets 0, 7, 14.
  • Tricks: loads at $0326 ("Four byte init code. Shamelessly ripped from Alihs entry, who ripped it from Steven Judd"; "Ninjas method, used in xxxlarge was nice too, but still some bytes larger" — s-sidin07-xxlarge); jsr $b5ff — a BASIC ROM fragment inc $23 / ldx $23 / ldy #$00 / rts — does the filter sweep for free ("hijack some BASIC ROM shit"; stx $d416), which makes the tune 13*3*4*128*0.02 = 399.36 seconds long; the undocumented sbx #7 (.byte $cb,7) steps X across the three voices; before each note lda #8 / sta $d404,x (test bit); $5f to $D418 (high-pass + low-pass) and $a2 to $D417 (filter on the middle voice) rewritten every loop so the player cannot run twice on one raster line; debug builds point the NMI at Groepaz's Retro Replay stub (filter-programming, hard-restart).

Repeat Me (Laxity, 3rd) — "TinyPlayer 02.g0"#

  • "Coded on 13th of April 2005"; one instrument hard-coded: waveform $21, AD = SR = $55, gate dropped at the end of every note ("a sort of hardrestart"); sei, sync on raster $55 (A already holds $55 from the ADSR write); speed counter reloads with 8.
  • Pattern bytes: positive = note in the low nibble, octave in the high nibble; $00 rest; negative = duration in the low 7 bits; $FF repeat. Only 12 frequencies are stored (top octave frqlo/frqhi $45A0 … $8370); lower octaves are made at run time by lsr zp+1 / ror zp per octave. Three sequences (s1 drums $83, $40,$30,$43,$33 …, s2 bass, s3 melody) reached through a sofs offset table.

Electronic (Aleksi Eeben, 4th)#

  • Tognon's disassembly, "the code is more articulated and the description of all his behaviors will need more time for sure!" It relocates itself to page 0 like Agemixer's, sei, syncs on raster $81, and keeps a full SID register image (sidTable, 25 bytes: voice 1 pulse $0400, $40, AD $08, SR $18; voice 2 pulse $001A, $80, $04, $04; voice 3 $20, $06, $06; $F4 = resonance on voice 3, $1F) written in a loop each frame; indices live in self-modified operands (IndZ, IndW, IndA, IndB, IndC).
  • Sound: 7 notes (loFreq/hiFreq), voice 1's pulse low byte grows by $0C every frame with carry into the high byte; voice 2's frequency high byte is toggled with eor #$40 every frame; noise is switched into voices 1, 2 and 3 by counters ("according to certain rules"); the low-pass cutoff on voice 3 comes from voice 3's envelope outputlda $D41C / lsr / adc #$20 → cutoff high byte (filter-programming, sid); tables note (11 entries), dur ($02 … $06, $08) and pulse ($92,$90,$92,$94 …) are stepped every 7 frames. See aleksi-eeben.

Repetitive Tune BASIC (Peter Weighill, 10th)#

  • Found "in some very old stuffs", trimmed to 256 bytes ($0801–$08B1): six BASIC lines — m=54272:poke m+24,15:poke m+5,9:poke m+6,15; p=2226; read h, l, d from memory; poke m+1,h:poke m,l:poke m+4,33:for t=1 to d:next:poke m+4,32:for t=1 to 300:next; restart on the second d=0. One sawtooth voice (33 = $21), durations by delay loops — "you can see what is needed for programming sound using BASIC program, and so the polling like technique to simulate note duration" (c64-programmers-reference-guide). Data: triplets such as $03,$f4,$18 / $04,$b4,$18 / $05,$47,$90 ….

Splatform256 (Steve Judd, 5th)#

  • The tune of the Splatform minigame rewritten to fit 256 bytes; Tognon's disassembly. Loads at $0326 (.byte $2A,$03,$ED,$F6), sei, sync on raster $FF; a 14-byte Values block is copied to $D402…, $D409… and zero page $AB… in one loop (voice 1 pulse $0700, $41, AD $31, SR $FB; $0D to $D418); voice 3 unused.
  • Notes are stored as nibbles (decript) and unpacked into zero page ($02, $22, $34) so "only 8 bits" address them; two self-modifying spots use the BIT trick (.byte $2C hides the following LDY/LDA inside a bit operand) — one makes voice 1 play a fixed noise note ($81, note $0C) as a drum when a note's duration is over, the other reloads the pattern address at a 0 note; voice 2's "drill" effect on note $0e ramps a temporary note up to $0D then holds $0A; voice 2's pulse high byte grows by 2 each frame (chip-samples is not involved — the drum is noise).

Conclusion#

"You can now choose from a 256 bytes, 512 bytes and even 1KB, so maybe you will find your right size for competing" — the second compo (Tiny Sid 2) was running when the issue appeared (2006-01-14); its entries are in SIDin #10–#12.

Practical takeaways#

  • Address bytes are the enemy: copy the player (Agemixer, Eeben) or at least its data (A Life in Hell, Judd) into the zero page; keep indices in self-modified operands; store two notes or note + duration per byte.
  • Let the machine be the sequencer: a free-running counter's bits (Agemixer), screen RAM (freakyDNA), the SID's noise register (Tognon, A Life in Hell), the envelope output as filter modulator (Eeben), a BASIC ROM inc/ldx/rts as a sweep (Frantic), the KERNAL delay as a timer (Tognon).
  • Instruments that are nothing but a waveform and a duration still make a winner: Frantic's 3-byte AD/control/duration rows, Laxity's single hard-coded $21/$55 instrument. Where an entry does have a drum, it is one noise frame at a fixed pitch.
  • Every one of them gates cleanly: test bit before the note (Frantic, Random Ninja's $09), gate off at the end (Laxity), or an explicit hard restart tick — the adsr-envelope bug does not care how small the player is.
  • Two starting tricks worth copying for any tiny C64 program: load at $0326 to run on load, and sync on $D012 after sei instead of installing an IRQ.

Notable quotes#

"This is a big solution as now all the variables can be accessed from zero page and so using only one byte for the address!" (on Agemixer)

"with one instruction you set the duration of note and the instrument to use" (on Frantic's 3-byte instruments)

Relevance to the wiki#

Completes tiny-sid-compo; the richest source in the wiki on sub-kilobyte SID players for sid-player-routine; miniature instrument and envelope formats for adsr-envelope, wavetable-programming and chord-arpeggio; noise-register and screen-memory sequencing for randomization-and-probability; laxity's second tiny player; aleksi-eeben's entry.

To verify#

  • (unverified) Tognon spells the Imperial March author "Tapio Vitanen"; the result table in issue 8 has "Tapio Viitanen" — the page follows the result table.
  • (unverified) In Repeat Me the speed counter is added into the octave calculation (adc spdcnt before the shift count), which would make the octave wobble within a note; Tognon does not describe this and it may be a deliberate effect or a reading error of the extracted listing.
  • (unverified) freakyDNA's control byte $15 is triangle + ring modulation + gate by the register map; Tognon writes only "triangular waveform".

Pages touched#

tiny-sid-compo · sid-player-routine · adsr-envelope · hard-restart · chord-arpeggio · wavetable-programming · filter-programming · randomization-and-probability · instrument-tables · laxity · aleksi-eeben · goattracker · last-ninja-2 · c64-programmers-reference-guide · sid · sidin

source file wiki/summaries/s-sidin09-tiny-sid-256b.md · 3 unverified · graph