Homeentitytool

DUET

m64's tracker: "impulse-tracker and sid-wizard on one grid — two real trackers, one clock, one save button." A complete IT engine and a complete SID-Wizard 1.97 engine in one Zig program, plus two synths of its own (DUST and DUSK) and a studio console over everything. Version 0.1.0-alpha, Zig 0.15; 60 fps in a kitty-class terminal or the same screens in an SDL3 window; macOS/Linux, Windows cross-compiles; GPL-2.0-or-later (reSID is vendored); 909 tests. Not yet published. (source: s-duet-readme; docs ingested at repo commit 85155dd, 2026-08-28.)

The two engines#

  • IT side: plays and edits .it (it-format) — samples, instruments, envelopes, order list — implemented from ITTECH.TXT and held against openmpt renders; a resaved .it renders identically, write-idempotent over a 47-file corpus (sources: s-duet-readme, s-duet-docs-formats).
  • SW side: a pure-Zig semantics port of SID-Wizard 1.97's 6502 player driving reSID — no C64 emulation in the audio path — held to a register scoreboard against hermit's shipped player. The research behind it (s-duet-research-sidwizard, s-duet-research-sidwizard-deep) validated the swm-format byte-exactly: 142/143 of SW 1.97's own modules round-trip byte for byte (the 143rd is refused, not rewritten), 324/324 .swi instruments.
  • Documented divergences from the original, all deliberate: tempo-program FX $12/$15 with operand 00 resolve to program 1 instead of a no-op; playback keeps running when the subtune selector moves (SW stops); a per-track subtune jump is followed only when all tracks agree — otherwise the display shows SUBTUNES SPLIT rather than draw from the wrong orderlist; a multi-subtune .swt/.swq (a file SW itself could not open) is refused on write (source: s-duet-research-sidwizard).

Sessions and files#

DUET has no song format of its own. The badge names the session and what SAVE writes: [IT] → the .it only; [SWM] → the module only; [DUET].zon + .it; [CO].zon + .it + the .swm if changed. The .zon project (VERSION 12, a small ZON text) holds only what no song format can — SID setup, S##/D##/K## banks (bytes inside the file, never links), mixer state, sync markers; newer versions are refused loudly, older upgraded on save (source: s-duet-docs-formats).

The .it is written as IT 2.14 (CWTV $0214) with full-mask packing and no RLE — a layout every loader accepts — and the SID columns are pan-muted (bit 7) so foreign players play silence there instead of garbage (source: s-duet-docs-formats). .wav comes in as PCM 8/16/24/32 or float at any rate; every render goes out 48 kHz stereo, PCM16 or FLOAT32 (source: s-duet-docs-formats).

The grid#

  • SID columns: up to 4 reSID chips (each individually 6581 or 8580, own PAN, QUALITY FAST/INTERP/RESAMPLE), 3 columns per chip (1:1…); arming chip 1 turns [IT] into [DUET]. A column speaks SID-Wizard: S## instruments, $3F legato, instrument-field nibble FX $4x$7F, effects as two hex bytes of SW's list; the IT volume column translates to sustain (17 5x) (source: s-duet-docs-grid-and-sides).
  • SWM sessions: the track column is a phrase (SW01:03↕28); per-track order lists advance independently; tempo programs share the format's 119-byte budget; the 32-chord bank plays by arp $7F, FX $7x or big-FX 07 (orderlist, shuffle-funktempo, chord-arpeggio; source: s-duet-docs-grid-and-sides).
  • Views and blocks: Ctrl+V cycles IT/UNITY/POLY/SWM views; ALT+B marks follow rows and fields; translate paste (ALT+SHIFT+P) respells effects across column kinds with a TRANSLATE REPORT — IT H64 vibrato becomes SW 0825, volume becomes sustain, arpeggio becomes a chord (source: s-duet-docs-grid-and-sides).
  • Sync markers (ALT+G, 0–65535, .zon-only) fire at every occurrence of a reused pattern, for demo/game hosts syncing on rows via libduet (sync; source: s-duet-docs-grid-and-sides).
  • Undo is per pattern; Ctrl+U opens a travelable history (source: s-duet-docs-grid-and-sides).

Co-play#

A whole .swm can play beside an .it on the IT clock (duet song.it tune.swm), both editable, both saved back as real files; [ EXPORT SID -> .SWM ] writes a duet's SID columns out as a real module. Mute by voice (Ctrl+M/ALT+F9): voice mutes save in the project, channel mutes in the .it, solo never (source: s-duet-docs-grid-and-sides).

DUST and DUSK, the built-in synths#

Both [DUET]-only, with their own pattern-effect dialects (velocity in the volume column, Gxx/Fxx/Exx in cents per tick, SFx+Zxx macros, ad sends) (source: s-duet-docs-dust-dusk):

  • DUST — a SID synth: 4 slots = 4 extra reSID instances on channels 49–52 (eight SIDs all told, model swappable live mid-note). A D## patch has four faces (VOICE/TABLES/MOD/MACRO): four 64-step tables (WAVE/PITCH/PULSE/FILTER) each with its own clock (Hz / per-tick / per-row) and direction, 4 LFOs + 2 envelopes + 8 mod routes at a 250–4000 Hz patch clock, and RESTART OFF/SOFT/FULL over a 1–200 ms pre-note window — hard-restart as a patch parameter with look-ahead, so the attack lands on the beat.
  • DUSK — wavetable synthesis over your own samples: 8 slots on channels 41–48; any P## sample becomes a row of single-cycle frames (FRAME 2048 = "the Polyend standard") morphed continuously (wavetable-programming); a software-6581 ADSR (staircase deliberate), a self-oscillating SVF filter, and VOICES 1–8 — a strummed/detuned chord from one cell, from a 16-voice pool.
  • The patch engine is a frozen contract: engine byte v1 renders the same forever; unknown bytes play silent but survive resave verbatim; golden-pin renders guard it (source: s-duet-docs-dust-dusk).

The console and the bounce#

Every channel, chip and synth gets a strip — born bypassed: untouched, DUET adds nothing to the signal. Three-band EQ (120 Hz / 1.2 kHz / 8 kHz, ±12 dB), master dynamics (THR −18 dB, knee 6 dB, limiter ceiling −0.3 dB), four send units (DELAY/REVERB/CHORUS/DRIVE) with return strips — delay TIME in rows, so it stays musical. Sends automate from the grid: effect letters ad, amount $00–$64 = 0–100 % (b40 = 64 % to reverb); SID columns use $18$1B (reverb-and-compression, sidechain-pump; source: s-duet-docs-sounds-mixer-bounce).

Channel VOL/PAN/mute save into the .it; faders, EQ, dynamics and sends into the .zon. Export (Ctrl+E): mix and/or stems at 48 kHz, never overwrites, with a save report (peak, compression, limiter, clipped samples); stems skip the master dynamics, DRIVE and every DUCK so they sum clean. Block bounce (Ctrl+B) renders a marked block to .wav or straight into a P## sample slot at C5 48000 Hz — the fastest resampling loop in the program (source: s-duet-docs-sounds-mixer-bounce).

Six instrument lists, slots never renumbering: I## (255) · P## (255) · S## (36; a second chip costs 7) · W## (36) · D## (32) · K## (32); [IMPORT] lifts instruments, samples or patches out of any file, a .zon doubling as a preset bank; JAM plays the selected sound from any screen (source: s-duet-docs-sounds-mixer-bounce).

The clipboard#

The system clipboard is text: IT columns copy as openmpt's ModPlug Tracker IT format (readable by schism-tracker too), SID columns as DUET SID (DUET SW is an equivalent spelling) — 9-character cells, NOTE(3) INS(2) FX(2) PARAM(2) — interchangeable across both SID column kinds. A generator like 037-lab writes one of the two texts and pastes with ALT+SHIFT+V; instruments never travel — ALT+S stamps them after. DUET forgives ragged text; OpenMPT and Schism demand all 11 characters of every IT cell (source: s-duet-docs-clipboard).

Headless and libduet#

Everything runs without a terminal: --render, --stems, --bare, --bounce-block P:R0-R1[:C0-C1], register traces, screen dumps, a scripted-keys harness — which is also how it is tested (sources: s-duet-readme, s-duet-docs-sounds-mixer-bounce).

The engine itself is libduet — a Zig module and a C ABI "in libopenmpt's shape": bytes in, samples out; the library opens no file, creates no thread, owns no audio device, and "the tracker is only its first user" (source: s-duet-docs-libduet):

  • Three laws: a render allocates nothing / takes no lock / does no I/O; register writes land on span boundaries (a note starts on the exact sample of its tick, wherever that falls in the host's buffer); and what renders is what the tracker renders — an 8-song A/B (capi-ab.sh) holds C player, Zig player and duet --render byte-identical. Buffer sizes don't matter: "the call pattern is not a property of the sound."
  • The event ring: the render thread stamps every dispatch (row, order, note on/off, effect, marker …) with the output-clock frame of its tick into a 4096-event ring — so a demo gets "row 32 of order 5 started at output frame N" instead of a buffer-late poll. Sync markers (ALT+G, .zon-only, 16-bit value per pattern/row/column) are the author's hand-placed moments; "a demo syncs on the rows' frames … a game reads only the markers" (sync).
  • Instruments as sound effects: note_on by family plays an IT instrument, a SID-Wizard instrument through a chip (the jam road), or a DUST/DUSK patch — a game's SFX without a pattern. The writers are exposed too, so a program can produce .it/swm-format bytes.
  • The web player (duet.wasm): the same engine as a wasm32-wasi reactor in an AudioWorklet, 128-frame quanta, events re-stamped into the AudioContext clock "so a row is drawn the moment it is heard". 2.4 MB (253 KB gzipped) rendering a 2-chip SW module at 22× real time; the wasm-vs-native A/B is byte-identical on six of eight corpus songs — resid's sample path is fully integer, resample included — and within 1 LSB on the two that touch libm floats. Ships a 90-song gallery: DUET's demos, all 70 SID-Wizard examples (hermit's WTFPL folder, credited from their AUTHOR:TITLE headers), and PD/CC0/CC BY .its with per-song provenance (source: s-duet-docs-libduet).

Cheat sheet#

keysdo
F5 / F8play / stop (F7 from cursor)
F2/F4/F11/F12, TABpattern / instruments / mixer / project, next column
Ctrl+V · Ctrl+T · Ctrl+Wgrid view IT/UNITY/POLY/SWM · split · column width
ALT+B / ALT+D / ALT+Lmark block / grow mark (beat→bar→rest→channel→side) / mark channel (twice: side)
ALT+C/Z/P, ALT+SHIFT+Pcopy/cut/paste, translate paste
ALT+Sstamp instrument onto block
ALT+SHIFT+C/Z/V/M/Tsystem clipboard copy/cut/paste (M mix, T types)
Ctrl+M / ALT+F9 / ALT+F10mute (voice) / mute / solo
ALT+Gsync marker
ad $00$64 in FXsend to DELAY/REVERB/CHORUS/DRIVE
Ctrl+E / Ctrl+Bexport mix/stems / bounce block
Ctrl+Uundo history
?key help on any screen

(source: s-duet-docs-grid-and-sides, s-duet-docs-clipboard, s-duet-docs-sounds-mixer-bounce; the full generated key and CLI reference lives un-ingested in raw/duet/docs/keys.md and cli.md.)

sid-wizard · swm-format · impulse-tracker · it-format · openmpt · schism-tracker · 037-lab · sid · resid · hermit · hard-restart · wavetable-programming · sync

Sources#

s-duet-readme · s-duet-docs-grid-and-sides · s-duet-docs-formats · s-duet-docs-dust-dusk · s-duet-docs-clipboard · s-duet-docs-sounds-mixer-bounce · s-duet-docs-libduet · s-duet-research-sidwizard · s-duet-research-sidwizard-deep

source file wiki/entities/duet.md · graph