SIDin #13 — Inside JITT64: the player routine (Stefano Tognon, 2010)

author Stefano Tognon (ice00), SIDin magazine
date 2010-06-28
article SIDin issue 13 (version 1.00, 28 June 2010), pp. 33–50: 'Inside JITT64' — sections Init & Play (IRQ) routine, Play Pattern, Instruments definition, Play Instrument, Play Command, Play Hard-Restart, Conclusion; the author's own commented source (assembler macros), with two block diagrams not in the text
created 2026-08-30 · updated 2026-08-30

The player behind jitt64, explained by its author with the source: how init and the IRQ routine are organised (and why the SID is written at the start of the interrupt), how a pattern row is decoded into command bits, how an instrument's sixteen table pointers are defined and stepped, how each pattern command is computed, and how the hard restart is timed. A rare case of a driver documented by the person who wrote it (sid-player-routine).

Key claims#

  • Init clears every player variable, reads the first pattern of each track (so that the IRQ code is smaller) and sets the volume to maximum. Play loops over the voices from 3 to 1, computing values into shadow_xx variables (one per SID register: FL, FH, PL, PH, CTRL, AD, SR, and the globals FCH, RES, TYPE, VOL); "only at the end of player calculation those values are putted into the sid chip … all in one passed, so there is not distortion in sound generation for that voice", then the four global registers (filter, volume).
  • Three blocks per voice: Play Pattern (only when the pattern's tempo has elapsed — "a tempo of 7 … the routine is executed only after 7 IRQ calling"), Play Instrument and Play Command (every IRQ). Play Pattern must run first, "otherwise there will be a delay of one frame when a new note/instrument/command is to be activated".
  • Why the SID is written first: with the linear structure "the 3 voices are not outputted at the same time" because the blocks take variable time, "and this can cause some hard-restart of note problem too". The revised IRQ writes all shadow registers at its start — "at every frames the sid registers are updated all together" — then runs the blocks, and a fourth block, Play Hard-Restart, runs last so that an HR "had to take control on sid output over the others commands". Side effect: "sound output is always one frame later his calculation … JITT64 is born for write only music, so this is not a problem" (hard-restart).
  • Play Pattern (pl_play_pattern) decodes four bytes per row from four pointer tables (pattPoint1–4): note (PAT_END → next pattern in the track via pl_read_track, PAT_NULL, PAT_KON, PAT_KOFF, PAT_REST, or a note: add trackTransp, look up frequencyLo/Hi, and clear activeCommand — "stop all pattern command"), instrument (pl_new_instr if non-zero), command, parameter. Command state is bit fields: activeCommand bits 0–6 = arpeggio, portamento up, down, tone portamento, vibrato, pulse slide up, down (continuous); actCommand2 bits 0–7 = AD, SR, key on, key off, rest, filter type, resonance, cut-off (one-shot); actCommand3 bit 0 = gate SR. The TBLR macro reads (pointer table)_L/H,x into ADDR_LOW/HIGH and loads (ADDR_LOW),y.
  • Command decoding (from the listing): Tempo 0 reads the pattern's own tempo from row 0 of pattPoint1; AD/SR/Gate-SR store cmdADSR and a flag; Volume writes shadow_VOL directly; Arpeggio splits the parameter into bits 7–6 = speed (0–3), bits 5–3 = first offset added to the note, bits 2–0 = second offset added to the first arpeggio note (note2 = note1 + offset2), cycling positions 0 (note), 1, 2 (chord-arpeggio); Portamento up/down store the amount; Tone portamento takes the new row's note frequency (already in the shadow) as the target, restores the previous frequency (copy_FL/FH) as the start and sets the direction from a 16-bit compare — so the command sits on the same row as the target note; Vibrato: speed = (param & $0F) + 1, depth = (param & $F0) + $10, with vibSpeed1 = 0 and vibSpeed2 = speed so the first leg is a half period; Slide up/down set pulse amounts; Auto fade out stores a delay; Filter type/resonance/cut-off store cmdFilter and a one-shot flag (pitch-slide-and-portamento, vibrato).
  • Instrument definition (ins_instr macro, conditional on NUM_INSTR from the packer): 5 bytes — INSTR_HR, INSTR_AD, INSTR_SR, INSTR_CTRL1, INSTR_CTRL2 (the hard-restart ticks/flag, HR ADSR and the two HR control bytes) — then 16 pointers: instrAD, instrSR, instrWave, instrFreq, instrPulse, instrFilter, instrRes, instrType, instrFixFreq, instrRelFreq, instrFixPulse, instrRelPulse, instrRelFilter, instrFixFilter, instrDelay, instrRepeat. On a new instrument they are copied to instrPtr*_L/H per voice (instrument-tables).
  • Play Instrument (pl_intr_core) calls eight table cores — AD, SR, Wave, Freq, Pulse, Filter, Res, Type — generated by one macro (pl_instr_core_) with an OUTV macro choosing the output: AD/SR/Type to the shadow, Wave to shadow_CTRL unless zero, Freq/Pulse/Filter/Res through putFreq, putPulse, putFilterFreq, putFilterRes. A table starts with three bytes — dimension, allowed repeats (0 = infinite), step position — then pairs of (value, D/R index); the index selects a delay from instrDelay and a repeat count from instrRepeat. Each tick: count the delay down, else re-output while repeats remain, else advance two bytes; at the end jump to the step position while repeats remain (actNumRepeat vs allowRepeat).
  • Play Command (pl_play_command), every tick: arpeggio (position 0/1/2 → actNote, note1Arpeggio, note2Arpeggio; speedArpeggio counts down and reloads); portamento up/down add or subtract the amount PORT_MULT times to the 16-bit shadow frequency; tone portamento the same until the target is reached, then snaps to it and clears bit 3; vibrato adds or subtracts vibFreq with two counters and a direction flag; pulse slides add/subtract to shadow_PL/PH; auto fade decrements shadow_VOL every autoFadeOut ticks until 0; then the one-shot commands: AD, SR, key on (ora #$01), key off (and #$FE), rest (frequency 0), filter type → shadow_TYPE, resonance → shadow_RES, cut-off → shadow_FCH, gate SR → shadow_SR.
  • Play Hard-Restart (pl_play_hr): only if hrActive,x is set for the row; ticks = instr_HR & $0F; with pattDelay = ticks remaining in the row: if pattDelay is 0 (the last tick before the new note) and instr_HR bit 7 is set, write instr_CTRL2 to the control shadow; else if pattDelay ≤ ticks: with hrActive bit 7 (full HR) write instr_AD, instr_SR and instr_CTRL1 to the shadows, otherwise only clear the gate bit (hard-restart).
  • Conclusion: the shipping code (JITT64 1.03) adds conditional compilation to drop unused features but keeps this structure; because everything is macros, a faster idiom ("maybe using undocumented instructions") can be dropped in once and applies everywhere. "The use of so freedom into instrument implementation have make the code to manage it very complex, and so raster time usage can be high."

Practical takeaways#

  • Write the SID from shadow registers at the start of the interrupt when voices must change together and hard restarts must land on exact frames; accept one frame of latency, or keep the end-of-routine write if the music must sync with graphics.
  • Decode the pattern row before running the per-tick engines, or new notes get a one-frame smear of old and new settings.
  • A per-table (value, delay/repeat) pair with a header of length, repeat count and loop point is a complete "table engine" in about a screen of 6502 — the same idea as SID-Wizard's and GoatTracker's tables, but with the delay in its own field.

Notable quotes#

"It is very important that Play Pattern is executed before the others blocks, otherwise there will be a delay of one frame when a new note/instrument/command is to be activated."

Relevance to the wiki#

Primary source for jitt64's player and a row in the driver comparison on sid-player-routine; the timing rationale for buffered ("shadow") writes and the HR-last ordering on hard-restart; the arpeggio, vibrato, portamento and pulse-slide arithmetic on chord-arpeggio, vibrato, pitch-slide-and-portamento and pulse-width-modulation; a table format for instrument-tables.

To verify#

  • (unverified) PORT_MULT = 2, matching the user article's "amount (*2)"; the constant's definition is not in the printed excerpt.
  • The article gives no rastertime figure; "very rastertime consuming" is the only statement.

Pages touched#

jitt64 · stefano-tognon · sid-player-routine · hard-restart · instrument-tables · chord-arpeggio · vibrato · pitch-slide-and-portamento · pulse-width-modulation · filter-programming · adsr-envelope · sidin

source file wiki/summaries/s-sidin13-inside-jitt64.md · 1 unverified · graph