DUET docs: sessions, formats and round-trips (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

The file-format contract of duet (m64's own tracker, not yet published): what each file kind holds, the four session kinds and what SAVE writes, the byte-identical round-trips, the project container's law, and what .wav takes and gives. From the user page sessions-and-formats.md and the developer page dev/formats-internals.md.

Key claims#

The file kinds#

DUET has no song format of its own. A song is an .it or a SID-Wizard module in those programs' own formats; what DUET saves, those programs open. The kinds:

extensionwhat it iswhere it goes when loaded
.itan Impulse Tracker module: patterns, samples, instruments, order listthe session's song; its I## and P## lists
.swma [[swm-formatSID-Wizard module]] for one SID; .sws/.swt/.swq for two, three, four chipsthe SID-Wizard song: tracks, phrases, W## bank, subtunes, chord tables
.swione SID-Wizard instrumentcopied into the S## list (or a module's W## bank); nothing stays linked to the file
.wava sample — PCM 8/16/24/32-bit or float, mono or stereo, any ratea P## slot, converted to 16-bit; the .it carries it from then on
.zona DUET projectthe whole session (below)

The four session kinds#

The badge at the top right names the kind, and the kind decides what SAVE writes:

badgeyou openedSAVE writes
[IT]an .it alonethe .it, nothing else
[SWM]a module alonethe module, nothing else
[DUET]a project, nothing, or an .it given a SID chipthe project (name.zon) + its song (name.it)
[CO]an .it and a module togetherthe project, the .it, and the .swm if it changed

An [IT] becomes a [DUET] the moment a SID chip is switched on (only a project can keep that); the next SAVE asks for a project name. DUST/DUSK slots arm only in a [DUET]. What a session can load: [IT] takes .wav (.swi needs a chip); [SWM] takes .swi into its W## bank, no samples; [DUET]/[CO] take both. [IMPORT] reads parts out of another file — instruments out of a module, samples out of an .it, DUST/DUSK patches out of a .zon — with SPACE audition before taking.

What a project (.zon) holds#

A small text file in ZON (Zig's data notation), current VERSION 12. It names its song files by file name (they sit beside it and stay real modules) and holds everything with no home in a song file: name/author/comment, the SID setup (chips, models, pans, channel placement, QUALITY), the S## pool (every SID-Wizard instrument's bytes inside the file, labelled with the .swi basename it came from), the DUST/DUSK patch lists and armed slots, the whole mixer, saved mutes, IT CHANNELS, hop sizes, EXPORT AUDIO's last settings, the chord table, and the sync markers (.{ .pattern, .row, .chan, .value }).

  • The writer emits only what a hand has moved (emit_default_optional_fields = false): an untouched mixer writes no .mixer, a blank bank no .swi_bank — but a bank with anything in it writes all 36 slots, holes included. A file serialized, parsed and serialized again is the same bytes.
  • The version gate runs before the grammar: a .zon written by a newer DUET is refused with written by a newer DUET (file v13, this build reads v12) rather than read with parts missing; unknown fields are dropped, unknown enum literals are hard errors. An older file loads and is saved back current — that is the whole migration (resaved co-demo.zon (v2 -> v12)). Rule: new vocabulary ships with a version bump (v12 brought .markers).

The container law#

  • Nothing in the container is a link. A .swi_bank slot holds the .swi writer's own bytes, hex-encoded — one encoding per instrument, so the writer's 324/324 identity is inherited; a slot that does not parse refuses the whole file. .from is a label (a basename), never a path; parse opens no file.
  • Either/or: a project names an .swm (co-play) or holds an .swi_bank, never both (ProjectBothSwmAndSwi); SID notes live in one place.
  • A co-play's module is adopted, not linked: SAVE writes <name>.swm beside the .zon (same bytes) and names that copy; the file it was opened from is left untouched.

What comes back the same#

Saving is not an export:

  • An .swm saved by SID-Wizard loads and saves back byte for bytewrite(load(f)) == f — pinned over tests/corpus/swm/ and the SID-Wizard 1.97 example tree: 142 of 143 modules identical (132 .swm, 4 .sws, 6 .swt, 1 .swq). The one exception, sixpack.swm, carries a 252-byte pattern past the format's 249-byte cap and is refused, not rewritten (error.PatternTooBig).
  • A .swi likewise: 324/324 example instruments byte-identical.
  • An .it saves in DUET's own layout of the same format — OpenMPT and Schism open it — and plays back identically: --render of the original and of its resave are cmp-identical. The first resave normalizes storage (a 4,085-byte foreign file returns as 4,889: 8-bit samples widened to 16, no RLE), but write(load(write(m))) is byte-identical — idempotence pinned over 47 corpus files.
  • A project loads, saves and loads again to the same session; the leading // comment block of a .zon is carried across a save verbatim.
  • duet --resave out.ext in.ext does all four from the command line; the extension picks the writer, and a module's extension must match its chip count (a 2SID module must be written as .sws).

The .it writer's shape#

One shape, always: an IT 2.14 file — CWTV = $0214, CMWT = $0200, 2.x IMPI instrument blocks, samples uncompressed signed 16-bit (stereo stored separated: all left frames, then all right), patterns in full-mask packing with no run-length coding (every loader accepts it). A packed pattern over the format's 64 KB refuses (PatternTooBig). Header counts are derived from the highest non-blank slot, so header and body can never disagree. One save-time step: a duet's .it is written with its SID columns' channel-pan bit 7 set (pan-disabled) so a foreign tracker plays silence there; a load clears the bit again.

The SID-Wizard writer's shape#

The four formats differ by magic and header: .swm = SWM1, 1 chip, 64 header bytes · .sws = SWMS, 2, 64 · .swt = SWMT, 3, 65 · .swq = SWMQ, 4, 69. Modules are parsed backwards from the end of the file, written forward; rows re-encode to the variable-length stream with $7X run-length bytes. Limits enforced on the way out: pattern stream ≤ 249 bytes and ≤ 248 rows, ≤ 127 patterns, sequence ≤ 255 bytes, ≤ 32 subtunes. A .swi is the $A1 $04 load address, the body (≤ 120 bytes), the size byte in the seat of the filter table's $FF terminator, and the 8-character name.

What .wav takes and gives#

  • In: RIFF/WAVE PCM at 8/16/24/32 bits or 32-bit float (extensible header included), mono or stereo, any rate — converted once to interleaved i16 (8-bit shifted up, 24-bit keeps its top 16, float clamps to ±1). More than two channels refuses. An import keeps the file's rate as c5speed and keeps stereo.
  • Out: every render is 48 kHz stereo, PCM16 or FLOAT32; the 16-bit conversion is one law (clamp, × 32767, truncate toward zero). A P## export writes the sample's own bytes at its own c5speed, to a path that never overwrites.

Exports (leave the session as it is)#

EXPORT WAV (Ctrl+E; --render/--stems headless) · EXPORT SID -> .SWM (the SID columns as a stand-alone module; chip count picks the extension; --export-swm) · SAVE on an S## or P## slot (a .swi, a .wav).

Practical takeaways#

  • Songs made in DUET stay portable: the .it opens in openmpt and schism-tracker and renders identically; the .swm opens in sid-wizard unchanged. Only the glue (SID setup, S## pool, mixer, synth patches, markers) needs the .zon.
  • Keep a project's files in one folder — the .zon names its songs by file name, relative to itself.
  • A .zon from a newer DUET refuses loudly instead of playing wrong; an older one silently upgrades on save.
  • The corpus numbers (142/143, 324/324, 47-file idempotence) are the measurable definition of "DUET is a real SID-Wizard and a real Impulse Tracker on disk".

Notable quotes#

  • "DUET has no song format of its own."
  • "A file DUET cannot read back is not written."
  • "Nothing in the container is a link."

Relevance to the wiki#

Defines duet's place among formats: a strict citizen of it-format and the swm-format (whose byte layout agent B's summaries cover), with a text container for the rest. The pattern-caps (249 bytes / 248 rows / 127 patterns / 32 subtunes) and the .swi limits (120-byte body, 8-char name) are SID-Wizard format facts confirmed against 1.97's own example tree.

Pages touched#

duet (maintainer rewrite). Related material for it-format and swm-format is noted in the ingest report rather than rippled.

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