DUET docs: DUST and DUSK, the two synths (summary)

author m64
date 2026-08-28
source url local repo duetracker @ commit 85155dd (2026-08-28, not yet published)
created 2026-08-30 · updated 2026-08-30

duet's two own synths (m64's tracker, not yet published): DUST, a sid synth — one whole reSID per slot, three voices, table programs and a mod matrix on the patch's own clock — and DUSK, wavetable synthesis over the song's own samples with a software copy of the SID envelope and filter. Both live only in a [DUET] project; a session with a SID-Wizard module ([SWM], [CO]) has no synth rows at all — "the module brings its own chips".

Key claims#

DUST — slots and column#

  • Four slots (DUST1DUST4) on the PROJECT page's SETUP; SHIFT+LEFT/RIGHT walk one through off > 8580 > 6581the chip model is the slot's, not the patch's, and swaps live mid-note. An armed slot is a reSID of its own, beyond the SID setup's four chips; it puts a synth column headed DUST1 on the grid and a strip on the mixer. The four slots stand on channels 49–52, so a song with DUST keeps at most 48 IT channels. Switching a slot off parks its column — the notes wait in the song. QUALITY reaches the slots as it reaches the chips.
  • Column fields: note (=== gates off, ~~~ fades) · instrument (a patch number 0120 — "a DUST ins is a PATCH ADDRESS (D01-D20)") · volume ("a DUST cell's volume IS velocity, 0-64 — it has no commands, only the number"; empty = 64) · effect (DUST's own list).
  • The column's effects: Gxx glide to note (cents per tick, no retrigger) · Fxx/Exx pitch slide up/down (cents per tick; 00 repeats the column's last) · Zxx macro value $00$7F · SFx select macro (SF0 = M01, sixteen slots) · SDx note delay (ticks the whole cell waits; the restart follows it) · ad the mixer's send units.

The D## patch — four faces#

The D## list holds patch slots D01D20 (hex — 32; every one born as the default THREE SAWS; Ctrl+D copies, BKSP restores the default). The editor's faces:

  • VOICE: CLOCK (control clock 250–4000 Hz — how often modulation is resolved), VOL (the chip's own volume nibble — "it clicks when it moves"), LEVEL (patch level 0–64, where velocity lands), MODE (UNISON, or CHORD — each voice takes its interval), GATE (WAVE TBL — bit 0 of each wave step is the gate — or GRAIN, gated on the grain clock), RESTART (OFF / SOFT / FULL hard-restart with a 1–200 ms window spent before the note), per-voice WAVE bytes (bits spelled out; "the gate bit is never yours"), PW $000$FFF ($800 is a square), ADSR nibbles, SRC (CHIP: the SID's own ADSR shapes the note; or ENV A/B), DETUNE in cents per voice ("three saws a few cents apart" — see detune), CHORD + STRUM (semitones per voice, ms between gates), GRAIN (RATE/SPREAD/WINDOW), FILTER (CUT $000$7FF, RES $0$F, ROUTE a bit per voice, LP/BP/HP).
  • TABLES: four table programs side by side — WAVE, PITCH, PULSE, FILTER — 64 steps each, each with its own clock (Hz free-running, tk ticks or rw rows per step; per-tick/per-row follow tempo changes), direction fwd/rev/png/rnd, length and loop (0 disables; loop end on the last step = one-shot), and a per-voice read OFFSET — "three grains, one table". A step reads itself back ($41 -> $D404: pulse gate UP); a PITCH step is +3 semitones, = absolute, ~ cents or > glide; a PULSE step =100 sets, +018 sweeps; . is a hold ("the waveform stays where the table left it"). The status row counts the cost live (720 writes/s 272 steps/s peak 22/block).
  • MOD: 4 LFOs (off/tri/saw up/saw down/square/sine/S&H/smooth random; RATE in Hz/ticks/rows, depth %, retrig free/note, quantise, phase, fade-in ms; "an LFO that is off costs nothing") · 2 envelopes (DLY/ATK/HLD/DEC ms, SUS %, REL, curve lin/exp/log, LOOP) · 8 routes SOURCE → DESTINATION (sources: LFOs, envelopes, table positions, note, random, velocity, the last Zxx; destinations: cutoff, resonance, pulse widths, frequencies, detune, ADSR nibbles, the tables' rates and jumps, master volume, grain knobs, sync and ring, amp). "There is no vibrato effect on the column: an LFO on the pitch is the vibrato." A RESOLUTION pane shows BASE/HELD/MOD/FINAL per moved parameter, live.
  • MACRO: VEL binds the volume column to amp between MIN and MAX; M01–M10 (sixteen, hex) are free knobs — LABEL, DESTINATION, MIN (where Z00 lands), MAX (Z7F), curve. "SF0 picks the slot Z00-Z7F sends into it."

DUSK — slots and column#

  • Eight slots (DUSK1DUSK8) on channels 41–48 (IT side stops at 40 when all are armed). The column stands in the synth block between channels and SID columns. Fields as DUST's, except: one note cell can be a whole chord when the patch's VOICES say so; === releases (the tail plays out), ^^^ cuts. Effects: same list as DUST's (Gxx, Fxx/Exx, Zxx, SFx, SDx, ad).
  • K## patches K01K20 (hex: thirty-two), default AFTERGLOW (built-in table, ADSR 00F6, no filter).

The K## patch#

  • VOICE: SAMPLE — BUILT-IN ($00, a saw > pulse > sine morph table, 32 frames × 512) or a P## number: the song's own sample read as a wavetable — frames of FRAME samples each (8–4096; "2048 is the Polyend standard"). MORPH is the position across the frames in %, always a blend of the two nearest. ALIAS clean (band-limited copies for high notes) or raw ("the aliasing on purpose"). ADSR is the 6581's own times (attack 2 ms–8 s, decay/release 6 ms–24 s, sustain level; release 6 ≈ 204 ms — see adsr-envelope). AMP SRC: the nibbles, or ENV A–D as the VCA. FILTER $000$7FF = 20 Hz–20 kHz, RES $00$FF ($B4 rings, $F0 is the edge — past it the loop self-oscillates), LP/BP/HP summable outputs (LP+HP = notch; all off = no filter), DRV drive into the filter.
  • VOICES — the chord from one cell: 1–8 oscillators per note; STRUM (ms between voice starts), WIDTH (stereo spread 0–64), PHASE (spread random / align "the coherent punch"), INT (semitones per voice — a chord from one cell), DET (cents; "the default ladder is DUST's own, grown outward"). The slot's pool: 16 voices; a new note releases the last; when full, the oldest note is stolen whole.
  • TABLES: MORPH, PITCH and FILTER, "scanned per NOTE: every note reads them from the top" (a released tail keeps walking its own copies). Same clock/direction/loop grammar as DUST; a MORPH step is - hold, = set %, + add (a sweep); the status row converts every clock into steps per second at the song's tempo.
  • MOD: 4 LFOs (slot-wide, shared by every sounding note) + 4 envelopes (per note) + 8 routes. Sources add morph_pos, note (centred on C-5), velocity, random (per note, seeded), chan_fx; destinations: cutoff, resonance, drive, morph ("a slow LFO here is the classic wavetable sweep" — see wavetable-programming), pitch (cents, summed — vibrato), amp. RESOLUTION panel shows BASE/HELD/MOD/FINAL/FROM live.
  • MACRO: VEL default amp -64 .. 0 (full note untouched); M01–M10 (sixteen hex) driven by SFx + Zxx on the column, walked MIN→MAX in the destination's own units on a lin/exp/log curve.

Internals in brief — the frozen contract#

  • Both synths are a pure core (no chip, no allocator, no clock) beside a rack owning hardware and memory; DUST's core only ever emits (reg, val, src) writes, every one shadow-compared (write-on-change over 25 registers) and attributed. DUSK's envelope is the 6581's in software — rate counter periods in PAL cycles, an 8-bit level, the ladder's slowing thresholds: "the staircase on a long release is deliberate". Wavetables get band-limited mip halvings (down to 16 samples, at most 9 levels); raw pins level 0. Modulation runs on a 1000 Hz control grid; a render is split-invariant.
  • Every patch carries an engine byte first (ENGINE_DUST_V1 = 1, ENGINE_DUSK_V1 = 1). A patch with any other value is left alone: it makes no sound — never the wrong one — the editor refuses to touch it, and the project writes it back verbatim. The contract: a v1 patch renders the same forever; a change that alters an existing patch's sound is a new engine version with an explicit migration. Renders are pure functions of the file (fixed per-slot seeds, never a clock).
  • Verification: no external reference exists, so golden pins are the oracle — nine corpus projects per synth, each pinned by wav SHA-256, peak and a gzipped trace; tools/dust-ab.sh / tools/dusk-ab.sh must close at 9/9 + 9/9; a moved pin is a regression unless explicitly re-blessed with a note. --dust-test / --dusk-test render the default patches (THREE SAWS, 3 voices, detune 0/−9/+9 cents; AFTERGLOW, 32 frames × 512, ADSR 00F6). The DUST trace is the attributed register stream (960 $D404=$21 wave_tab); the DUSK trace records note-engine decisions (note/steal/glide/gate…), since it has no registers.

Practical takeaways#

  • DUST is table-driven sid sound design with the frame-tick removed: each of WAVE/PITCH/PULSE/FILTER runs at its own rate (up to 4 kHz), free-running or tempo-locked — instrument-tables thinking, decoupled from 50 Hz.
  • DUST's RESTART OFF/SOFT/FULL with a millisecond window is hard-restart as a synth parameter; the engine even looks ahead one row so the attack, not the restart, lands on the beat.
  • DUSK turns any P## sample into a morphing wavetable — set FRAME to the single-cycle length (2048 for Polyend-style tables), sweep MORPH from a table or a slow LFO; the SID-true ADSR and self-oscillating filter keep it in the chip idiom.
  • Velocity is the volume column on both synth columns; SFx + Zxx are per-column macro automation into any destination.

Notable quotes#

  • "There is no vibrato effect on the column: an LFO on the pitch is the vibrato."
  • "The staircase on a long release is deliberate." (DUSK's software 6581 envelope)
  • "A v1 patch renders the same forever."

Relevance to the wiki#

Documents the two synth halves the duet entity only names, with the concrete ranges a sound designer needs (clock rates, table grammar, filter and resonance edges, the velocity and macro bindings). The engine-byte / golden-pin contract is a rare written-down policy for keeping a tracker synth's old songs rendering identically.

Pages touched#

duet (maintainer rewrite). Suggested (report only): hard-restart, wavetable-programming.

source file wiki/summaries/s-duet-docs-dust-dusk.md · graph