DUET docs: sessions, formats and round-trips (summary)
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:
| extension | what it is | where it goes when loaded | |
|---|---|---|---|
.it | an Impulse Tracker module: patterns, samples, instruments, order list | the session's song; its I## and P## lists | |
.swm | a [[swm-format | SID-Wizard module]] for one SID; .sws/.swt/.swq for two, three, four chips | the SID-Wizard song: tracks, phrases, W## bank, subtunes, chord tables |
.swi | one SID-Wizard instrument | copied into the S## list (or a module's W## bank); nothing stays linked to the file | |
.wav | a sample — PCM 8/16/24/32-bit or float, mono or stereo, any rate | a P## slot, converted to 16-bit; the .it carries it from then on | |
.zon | a DUET project | the whole session (below) |
The four session kinds#
The badge at the top right names the kind, and the kind decides what SAVE writes:
| badge | you opened | SAVE writes |
|---|---|---|
[IT] | an .it alone | the .it, nothing else |
[SWM] | a module alone | the module, nothing else |
[DUET] | a project, nothing, or an .it given a SID chip | the project (name.zon) + its song (name.it) |
[CO] | an .it and a module together | the 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
.zonwritten by a newer DUET is refused withwritten 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_bankslot holds the.swiwriter'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..fromis a label (a basename), never a path;parseopens 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>.swmbeside 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
.swmsaved by SID-Wizard loads and saves back byte for byte —write(load(f)) == f— pinned overtests/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
.swilikewise: 324/324 example instruments byte-identical. - An
.itsaves in DUET's own layout of the same format — OpenMPT and Schism open it — and plays back identically:--renderof the original and of its resave arecmp-identical. The first resave normalizes storage (a 4,085-byte foreign file returns as 4,889: 8-bit samples widened to 16, no RLE), butwrite(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.zonis carried across a save verbatim. duet --resave out.ext in.extdoes 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
c5speedand 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 ownc5speed, 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
.itopens in openmpt and schism-tracker and renders identically; the.swmopens 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
.zonnames its songs by file name, relative to itself. - A
.zonfrom 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.