2026-11-06 15:20:20 +01:00
|
|
|
|
# Project Format v17 (Reads v1–v17)
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
The project uses a bounded text format implemented in C99 without a parser
|
|
|
|
|
|
library. The extension is `.xtc`. File operations run exclusively on the control
|
|
|
|
|
|
thread. The project owns its graph, tracks and silent/audio arrangement regions.
|
|
|
|
|
|
Audio samples remain in external WAV files; MIDI notes are stored inline.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
2026-10-27 00:10:45 +01:00
|
|
|
|
## Schema
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
2026-10-27 00:10:45 +01:00
|
|
|
|
Records occur in this exact order, separated by LF, including the final record.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Fields use ASCII spaces. Integers are unsigned decimal, without signs. Counts
|
|
|
|
|
|
determine the number of subsequent records:
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
|
|
|
|
|
```text
|
2026-10-27 17:34:46 +01:00
|
|
|
|
XTC_PROJECT 11
|
2026-10-27 00:10:45 +01:00
|
|
|
|
APP 0 1 0
|
|
|
|
|
|
TEMPO 120000
|
|
|
|
|
|
IDS 1 1
|
|
|
|
|
|
GRAPH 1 2 2 1
|
|
|
|
|
|
NODE 0 1 3e800000
|
|
|
|
|
|
NODE 1 4 00000000
|
|
|
|
|
|
EDGE 0 1
|
|
|
|
|
|
TRACKS 1
|
|
|
|
|
|
TRACK 0 547261636b
|
2026-10-27 01:06:57 +01:00
|
|
|
|
MIX 3f800000 00000000 0 0
|
2026-10-27 16:37:59 +01:00
|
|
|
|
ROUTE 0 18446744073709551615
|
2026-10-27 00:10:45 +01:00
|
|
|
|
CLIPS 1
|
|
|
|
|
|
CLIP 0 0 3840 2880 960 3840 526567696f6e
|
2026-10-27 00:31:03 +01:00
|
|
|
|
ASSET -
|
2026-10-27 00:53:41 +01:00
|
|
|
|
MIDI 0
|
2026-10-27 16:37:59 +01:00
|
|
|
|
AUDIO 3f800000 0 0
|
2026-10-27 00:53:41 +01:00
|
|
|
|
NOTES 0 0
|
2026-10-27 01:06:57 +01:00
|
|
|
|
AUTOMATION 0
|
2026-10-27 16:37:59 +01:00
|
|
|
|
CONTROL_MAPS 16
|
|
|
|
|
|
MAP 0 7 10 64
|
|
|
|
|
|
MAP 1 7 10 64
|
|
|
|
|
|
MAP 2 7 10 64
|
|
|
|
|
|
MAP 3 7 10 64
|
|
|
|
|
|
MAP 4 7 10 64
|
|
|
|
|
|
MAP 5 7 10 64
|
|
|
|
|
|
MAP 6 7 10 64
|
|
|
|
|
|
MAP 7 7 10 64
|
|
|
|
|
|
MAP 8 7 10 64
|
|
|
|
|
|
MAP 9 7 10 64
|
|
|
|
|
|
MAP 10 7 10 64
|
|
|
|
|
|
MAP 11 7 10 64
|
|
|
|
|
|
MAP 12 7 10 64
|
|
|
|
|
|
MAP 13 7 10 64
|
|
|
|
|
|
MAP 14 7 10 64
|
|
|
|
|
|
MAP 15 7 10 64
|
2026-10-27 00:10:45 +01:00
|
|
|
|
END
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `APP`: producer major/minor/patch; informational, never executed.
|
|
|
|
|
|
- `TEMPO`: constant milli-BPM (20000..300000, default 120000), fixed 4/4.
|
|
|
|
|
|
- `IDS`: next track ID and next clip ID. IDs are uint64, monotonic and never
|
|
|
|
|
|
reused; UINT64_MAX is reserved. Track and clip ID namespaces are separate.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
- `GRAPH`: output-node policy (0/1), next node ID, node count, edge count. Nodes
|
|
|
|
|
|
retain stable IDs (limited to the host's size_t range). No pointers or
|
2026-10-27 00:10:45 +01:00
|
|
|
|
compiled state are serialized.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output, 5 audio source, 6
|
|
|
|
|
|
instrument, 8 LADSPA plugin), and exactly eight hexadecimal digits encoding
|
|
|
|
|
|
the IEEE binary32 parameter bits, independent of locale and host byte order.
|
|
|
|
|
|
Nonfinite values are rejected.
|
|
|
|
|
|
- `EDGE`: source and destination node IDs. Port directions are implied by node
|
|
|
|
|
|
type; endpoints, duplicates and cycles are validated by the graph API.
|
2026-10-27 04:00:27 +01:00
|
|
|
|
- `TRACK`: ID and hex-encoded name bytes. In v4/v5 each is immediately followed
|
2026-10-27 01:06:57 +01:00
|
|
|
|
by `MIX gain_bits pan_bits mute solo`: finite binary32 gain 0..4, pan -1..1
|
|
|
|
|
|
encoded as eight hex digits, mute and solo unsigned 0/1.
|
2026-10-27 16:37:59 +01:00
|
|
|
|
- `ROUTE`: since v9, required after MIX: bus flag and destination bus ID;
|
2026-11-06 14:12:22 +01:00
|
|
|
|
UINT64_MAX denotes master. See the mix routing constraints below.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
- `CLIP`: ID, track ID, start tick, length tick, source offset tick, source
|
2026-11-06 15:20:20 +01:00
|
|
|
|
length tick, hex-encoded name bytes. Trimming preserves source bounds, so a
|
|
|
|
|
|
region can re-extend only within its original length. Overlaps are allowed and
|
|
|
|
|
|
audio clips mix through the audio source node.
|
|
|
|
|
|
- `ASSET`: required after each CLIP in v2/v3/v4/v5; "-" means no audio asset,
|
|
|
|
|
|
otherwise hex-encoded absolute path bytes, <=511 bytes after decoding, no
|
|
|
|
|
|
embedded NUL or ASCII control characters. Each clip directly references a WAV;
|
|
|
|
|
|
no sample data, pointers or DSP state are embedded. Paths can contain spaces.
|
|
|
|
|
|
- `MIDI`: required after each ASSET in v3/v4/v5; 0 for silent/audio regions, 1
|
|
|
|
|
|
for MIDI clips. MIDI clips must have no asset.
|
2026-10-27 00:53:41 +01:00
|
|
|
|
- `NOTES`: next stable note ID and global note count, followed by that many
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`NOTE id clip_id start_tick length_tick pitch velocity channel` records. Notes
|
|
|
|
|
|
reference MIDI clips; start/length are source-relative, positive length within
|
|
|
|
|
|
source bounds. Pitch is 0..127, velocity 1..127, channel 0..15. Note IDs are
|
|
|
|
|
|
unique, monotonic uint64 IDs with UINT64_MAX reserved.
|
2026-10-27 04:00:27 +01:00
|
|
|
|
- `AUTOMATION`: global point count, after the last NOTE and before END in v4/v5.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Follow with `POINT track_id parameter tick value_bits step` in v10 (v4–v9 omit
|
|
|
|
|
|
step and migrate to linear). Step is 0 linear or 1 hold for the next segment.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
Parameter 0 is gain (0..4), 1 pan (-1..1); values use finite binary32 hex.
|
|
|
|
|
|
Track must exist; tick <= MAX_TICK; duplicate track/parameter/tick is invalid.
|
|
|
|
|
|
Record order is preserved; rendering sorts a separate prepared copy.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
- Names decode to nonempty valid UTF-8 of at most 63 bytes; ASCII control
|
|
|
|
|
|
characters and embedded NULs are forbidden. Hex avoids quoting ambiguity.
|
|
|
|
|
|
|
|
|
|
|
|
Ticks use 960 per quarter note, 3840 per bar. All region endpoints are bounded
|
|
|
|
|
|
by 38400000 ticks (10000 bars). Limits: 64 tracks, 1024 clips, 64 graph nodes,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
4096 edges, 4096 notes, 1024 automation points, 4 MiB input, 1535 bytes per
|
|
|
|
|
|
record, 12 tokens per record. The reader rejects invalid references, duplicate
|
|
|
|
|
|
IDs, inconsistent next IDs, ranges, truncated records, raw control/non-ASCII
|
|
|
|
|
|
bytes, and trailing data.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
|
|
|
|
|
Unsupported format versions return UNSUPPORTED. Other malformed data returns
|
2026-11-06 15:20:20 +01:00
|
|
|
|
FORMAT. The reader explicitly accepts v1 records without ASSET lines, with node
|
|
|
|
|
|
types 1..4, migrating their clips to empty asset paths. Version 2 accepts node
|
|
|
|
|
|
types 1..5 and ASSET records. Both migrate to MIDI=0 and no notes. Version 3
|
|
|
|
|
|
accepts instrument nodes and MIDI/NOTES records. Versions 1–3 migrate all tracks
|
|
|
|
|
|
to unity gain, centered pan, mute/solo off and no automation. Saving always
|
|
|
|
|
|
writes v15. Unknown versions are rejected without replacing the destination;
|
|
|
|
|
|
older applications without v12 support cannot read current saves. Migration and
|
|
|
|
|
|
maximum-path roundtrips are tested.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Ownership and Failures
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
|
|
|
|
|
Load builds and validates a separate candidate, closes the input, then replaces
|
|
|
|
|
|
the destination. Any failure retains the complete previous project. Saving
|
|
|
|
|
|
validates the project, creates a mode-0600 temporary in the destination
|
|
|
|
|
|
directory, writes, flushes, fsyncs, closes and renames over the destination.
|
|
|
|
|
|
Failures before rename retain the previous destination and remove the temporary.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
After rename, the parent directory is fsynced. A directory-sync failure reports
|
|
|
|
|
|
I/O error even though the new complete destination may already be present.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Storage and filesystem behavior still determine power-loss durability. Save
|
|
|
|
|
|
replaces symlinks themselves and does not preserve existing file permissions.
|
|
|
|
|
|
POSIX I/O is private to the Linux-first persistence module; public headers
|
|
|
|
|
|
contain no POSIX types.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Saved data reproduces the arrangement, graph, track mix and automation,
|
|
|
|
|
|
including ID allocation state. Undo history is not saved. See MIXER.md for
|
|
|
|
|
|
pan/interpolation. Graph node positions, timeline zoom/scroll/selection,
|
|
|
|
|
|
transport position and DSP oscillator phase are GUI/session state and are not
|
|
|
|
|
|
saved. Asset references are absolute in memory; saves use relative paths for
|
|
|
|
|
|
media inside the project folder, otherwise absolute paths. Media are not copied
|
|
|
|
|
|
on Save As. Metadata loading preserves missing references; audio-plan
|
|
|
|
|
|
preparation reports unreadable/invalid assets and renders them silently.
|
|
|
|
|
|
Relinking replaces a selected clip's path without changing its IDs or
|
|
|
|
|
|
arrangement. See AUDIO_FILES.md for recovery and limitations.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
## Extension and Recovery Policy
|
2026-10-27 04:00:27 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Format v4 was retained. Native processor descriptors are process-local and have
|
|
|
|
|
|
no serialized representation; saving a native graph returns UNSUPPORTED and
|
|
|
|
|
|
retains the existing destination. Unknown file versions/types remain rejected.
|
|
|
|
|
|
See NATIVE_API.md for the extension boundary and RECOVERY.md for write-failure,
|
|
|
|
|
|
killed writer and unfinished-recording behavior. Metadata validation is
|
|
|
|
|
|
available via `xtc-check --validate PROJECT` without a GUI or server; it does
|
|
|
|
|
|
not load assets.
|
2026-10-27 04:00:27 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 5: Plugin Metadata
|
|
|
|
|
|
|
|
|
|
|
|
`XTC_PROJECT 5` was introduced; its reader also supported versions 1–4. All v4
|
|
|
|
|
|
records and order remain the same. Node type 8 is a LADSPA plugin; native
|
|
|
|
|
|
pointer-bearing type 7 is still unsupported in files. Immediately after each
|
|
|
|
|
|
type-8 `NODE id 8 00000000` record come:
|
2026-10-27 04:00:27 +01:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
PLUGIN unique_id descriptor_index mono_inputs control_count hex_label
|
|
|
|
|
|
MODULE hex_absolute_module_path
|
|
|
|
|
|
CONTROL port_index float32_hex_bits
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
There are exactly `control_count` CONTROL records. Label length is 1–63 bytes,
|
|
|
|
|
|
module path 1–511 bytes (absolute), both hex-encoded with no embedded NUL. Index
|
|
|
|
|
|
is <1024, mono_inputs 0/1, controls <=16, control port indices <64 strictly
|
2026-11-06 15:20:20 +01:00
|
|
|
|
increasing, and values finite. Unique ID fits uint32. The one output is
|
|
|
|
|
|
implicit. Structural validation never opens libraries. On graph compilation the
|
|
|
|
|
|
host checks identity, layout, input control port IDs and rate-adjusted legal
|
|
|
|
|
|
ranges against the module. Unknown nodes/versions fail without mutating the
|
|
|
|
|
|
current project. Missing modules remain preserved by metadata load/save; GUI
|
|
|
|
|
|
installation rejects them with a repair message. No transient DSP state, pointer
|
|
|
|
|
|
or plugin binary is stored. See PLUGIN_HOSTING.md and decision 0009 for
|
|
|
|
|
|
supported processors/limits.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-10-27 06:25:30 +01:00
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
## Version 6
|
2026-10-27 06:25:30 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Version 6 was introduced and its reader accepted v1–v6. After each plugin's
|
|
|
|
|
|
CONTROL records, `PLUGIN_AUTOMATION count` is followed by exactly count (<=32)
|
|
|
|
|
|
records: `PARAM tick port mode float32_hex_bits`. Mode is 0 linear or 1 step.
|
|
|
|
|
|
Duplicate port/tick pairs, nonfinite values, unknown control ports and ticks
|
|
|
|
|
|
beyond the project limit are rejected. Loaded plugin tempo comes from TEMPO.
|
|
|
|
|
|
Range and discrete-mode compatibility are checked on control-thread compilation.
|
|
|
|
|
|
Versions 1–5 migrate with empty plugin lanes. See CONTROL.md for
|
|
|
|
|
|
timing/ordering.
|
2026-10-27 06:31:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
2026-10-27 06:31:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
## Version 7 and Project Folders
|
|
|
|
|
|
|
|
|
|
|
|
Version 7 was introduced; its readers migrated v1–v6. Record ordering and field
|
|
|
|
|
|
bounds are otherwise unchanged from v6. v7 ASSET may contain a relative path,
|
|
|
|
|
|
interpreted against the actual containing directory of the named project file
|
|
|
|
|
|
(resolved on the control thread), never the launch working directory. Opening a
|
|
|
|
|
|
symlink to a project uses the resolved target project directory; Save replaces
|
|
|
|
|
|
the named entry as before. After loading, all core asset references are
|
|
|
|
|
|
absolute, including missing media. Media inside the save folder are written
|
|
|
|
|
|
relative; references outside remain absolute. Save As resolves against its new
|
|
|
|
|
|
folder and preserves media identity rather than rebasing it by accident.
|
|
|
|
|
|
Copy/move a project folder together with its referenced media to retain
|
2026-10-27 06:31:59 +01:00
|
|
|
|
portability. Saving does not copy assets: import/record into that folder first.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Plugin binaries remain trusted absolute module references, not bundled
|
|
|
|
|
|
resources.
|
2026-10-27 06:31:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Empty assets still use `-`. Paths exclude embedded NUL/control bytes and must
|
|
|
|
|
|
fit 511 bytes after resolution; overly long resolved references fail
|
|
|
|
|
|
transactionally. Relative references can name outside the folder using ordinary
|
|
|
|
|
|
POSIX semantics; this is not a sandbox. Symlinks follow filesystem behavior, so
|
|
|
|
|
|
portability requires moving their targets too. Old formats continue to require
|
|
|
|
|
|
absolute media paths. No unknown version/node/record is silently discarded;
|
|
|
|
|
|
unsupported versions/types or malformed/trailing data reject the candidate and
|
|
|
|
|
|
retain the current project. Parsing never opens media/plugins or constructs an
|
|
|
|
|
|
audio engine.
|
2026-10-27 06:31:59 +01:00
|
|
|
|
|
|
|
|
|
|
Save validates the complete model, writes/fsyncs a same-folder temporary, closes
|
|
|
|
|
|
it, atomically renames it, then fsyncs the containing directory. Failures before
|
2026-11-06 15:20:20 +01:00
|
|
|
|
rename retain the old destination. A directory sync failure *after* rename
|
|
|
|
|
|
reports I/O error but the new complete file may already be present:
|
|
|
|
|
|
inspect/reopen it; there is no rollback across that durability boundary.
|
|
|
|
|
|
Filesystem/hardware power loss guarantees still depend on the underlying
|
|
|
|
|
|
storage. Recording destinations have their separate no-clobber policy; ordinary
|
|
|
|
|
|
project Save intentionally replaces the selected document. In-memory models and
|
|
|
|
|
|
the existing destination are preserved on load failure. No automatic
|
|
|
|
|
|
unknown-field stripping or media deletion occurs.
|
2026-10-27 06:31:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`make test-portability test test-plugins test-recovery test-recording` covers
|
|
|
|
|
|
checked-in v1–v10 fixtures (including missing v5/v6 plugins), current exact
|
|
|
|
|
|
roundtrips, moved-folder playback from a different cwd, missing media retention,
|
|
|
|
|
|
unknown records, every truncation and deterministic byte mutations, malformed
|
|
|
|
|
|
plugin metadata, interrupted writers and write-limit failures. ASan/UBSan/static
|
|
|
|
|
|
analysis additionally audit the bounded parser. This is
|
|
|
|
|
|
regression/fault-injection coverage, not exhaustive fuzzing or a claim of
|
|
|
|
|
|
power-cut qualification.
|
2026-10-27 15:46:41 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 8: Audio Clip Settings
|
2026-10-27 15:46:41 +01:00
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
Version 8 adds audio clip gain/fade settings and reads v1–v7 with unity clip
|
2026-11-06 15:20:20 +01:00
|
|
|
|
gain and no explicit clip fades. After **every** clip's `MIDI` record (including
|
|
|
|
|
|
silent and MIDI clips), v8 requires:
|
2026-10-27 15:46:41 +01:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
AUDIO gain_float32_hex_bits fade_in_ticks fade_out_ticks
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Gain is finite, linear and within 0..4. Fade durations are unsigned decimal,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
0..source_length_tick; zero disables that explicit fade. MIDI clips require gain
|
|
|
|
|
|
exactly 1 and both fades zero. Invalid or missing AUDIO records reject the whole
|
|
|
|
|
|
candidate. All v7 media-folder rules and atomic-save guarantees remain. Older
|
|
|
|
|
|
applications reject v8 rather than silently stripping clip settings. Rebuild
|
|
|
|
|
|
C/C++ consumers against the extended `xtc_clip_info` public struct; this
|
|
|
|
|
|
development release does not promise an unchanged struct binary layout.
|
2026-10-27 15:46:41 +01:00
|
|
|
|
|
|
|
|
|
|
Fades follow the audible trimmed clip boundaries, in current-tempo ticks, with
|
|
|
|
|
|
sample endpoints defined in AUDIO_FILES.md. Duplicating retains settings and
|
|
|
|
|
|
source references; splitting keeps the original ID on the left, gives the right
|
2026-11-06 15:20:20 +01:00
|
|
|
|
a fresh ID/source offset, retains outer explicit fades and clears inner ones. No
|
|
|
|
|
|
waveform cache or source audio is serialized. Checked-in v1–v7 migrations, v8
|
|
|
|
|
|
exact roundtrips, malformed new metadata and byte-mutation tests cover the
|
2026-10-27 15:46:41 +01:00
|
|
|
|
format change (`make test test-portability test-audio-editing`).
|
2026-10-27 16:15:34 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
2026-10-27 16:15:34 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
## Version 9: Mix Routing
|
2026-10-27 16:37:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Version 9 adds `ROUTE is_bus bus_id` immediately after each TRACK's MIX record.
|
|
|
|
|
|
`is_bus` is 0 or 1; `bus_id` is an existing bus track ID, or decimal UINT64_MAX
|
|
|
|
|
|
(18446744073709551615) for master. Buses must route to master and cannot contain
|
|
|
|
|
|
clips. Invalid destinations, flags and nested routing are rejected. v1–v8
|
|
|
|
|
|
migrate all tracks to ordinary direct-to-master tracks. Bus controls and
|
|
|
|
|
|
automation use existing MIX and POINT records. Older readers reject v9 rather
|
|
|
|
|
|
than discard routing. The public `xtc_track_info` grew fields; API consumers
|
|
|
|
|
|
must rebuild. Fixtures cover v1–v8 migration and tests exercise v9 exact
|
|
|
|
|
|
roundtrips and malformed routing.
|
2026-10-27 16:37:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 10: Interpolation and Controller Bindings
|
|
|
|
|
|
|
|
|
|
|
|
Version 10 adds interpolation modes and migration from v1–v9. Track/bus
|
|
|
|
|
|
automation points are `POINT track_id parameter tick value_bits step`, where
|
|
|
|
|
|
step is 0 linear or 1 hold for the segment starting there. v1–v9 points migrate
|
|
|
|
|
|
to step=0. LADSPA `PARAM` encoding remains unchanged (already has interpolation
|
|
|
|
|
|
mode).
|
2026-10-27 16:37:59 +01:00
|
|
|
|
|
|
|
|
|
|
Immediately after all POINT records, before END, v10 requires `CONTROL_MAPS 16`
|
|
|
|
|
|
and exactly 16 records in ascending channel order:
|
|
|
|
|
|
`MAP channel volume_CC pan_CC sustain_CC`. Channels are 0..15. Controllers are
|
|
|
|
|
|
0..127 or 128 (disabled); enabled numbers must be distinct within a row and may
|
2026-11-06 15:20:20 +01:00
|
|
|
|
not be 120 or 123. Duplicate/missing/out-of-order channels, invalid CCs,
|
|
|
|
|
|
reserved bindings and invalid interpolation modes reject the complete candidate.
|
|
|
|
|
|
v1–v9 supply default 7/10/64 bindings on every channel. No event history/current
|
|
|
|
|
|
live controller values are serialized. Older readers reject v10.
|
2026-10-27 16:37:59 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Fixtures cover v1–v10 loading and migration. Exact roundtrips, malformed
|
|
|
|
|
|
metadata, step/linear renders and remapped live events cover the new
|
|
|
|
|
|
writer/reader. Public clients must rebuild and initialize the appended
|
|
|
|
|
|
`xtc_automation_point.step` field.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Compatibility and Diagnostic Contract
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
2026-11-05 23:50:14 +01:00
|
|
|
|
The current reader accepts versions 1–15 and the writer always emits version 15.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Compatibility diagnostics made no format change; plugin format identity is added
|
|
|
|
|
|
in v11. Saving an older document upgrades it; retain an original backup if an
|
|
|
|
|
|
older application must still open it. APP is informational; compatibility
|
|
|
|
|
|
depends on the format version, not the producer's application version. A future
|
|
|
|
|
|
incompatible schema requires a new format number. Public C/C++ consumers must
|
|
|
|
|
|
rebuild when public structs change.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
|
|
|
|
|
| Version | Added representation | Defaults when loading earlier versions |
|
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
|
| 1 | Graph, tracks, silent regions | Baseline |
|
|
|
|
|
|
| 2 | External WAV references | Empty asset references |
|
|
|
|
|
|
| 3 | MIDI clips and notes | Non-MIDI clips, no notes |
|
|
|
|
|
|
| 4 | Track mix and automation | Unity gain, center pan, mute/solo off, no points |
|
|
|
|
|
|
| 5 | LADSPA metadata | No plugin nodes |
|
|
|
|
|
|
| 6 | Plugin automation | Empty plugin lanes |
|
|
|
|
|
|
| 7 | Relative media references | Earlier media paths must be absolute |
|
|
|
|
|
|
| 8 | Clip gain and fades | Unity gain, zero explicit fades |
|
|
|
|
|
|
| 9 | Bus routing | Ordinary tracks routed directly to master |
|
|
|
|
|
|
| 10 | Track interpolation and controller bindings | Linear points; CC 7/10/64 on every channel |
|
|
|
|
|
|
|
|
|
|
|
|
The grammar has no optional extension namespace. Unknown records, extra fields,
|
|
|
|
|
|
node types or trailing data cannot be preserved by this model and are rejected
|
|
|
|
|
|
rather than silently removed. Supported older records migrate in a separate
|
|
|
|
|
|
candidate and pass complete model validation before adoption. Failed parsing,
|
|
|
|
|
|
reference validation or close retains the existing destination model. GUI Open
|
|
|
|
|
|
also prepares the candidate before replacing the live document; missing or
|
|
|
|
|
|
incompatible plugins retain the current document and identify the module.
|
|
|
|
|
|
Missing media references remain available for relinking and render silently.
|
|
|
|
|
|
|
|
|
|
|
|
`xtc_project_load_diagnostic` adds a copied result, parsed format version (zero
|
2026-11-06 15:20:20 +01:00
|
|
|
|
if unavailable), expected record/stage and message. Syntax locations are
|
|
|
|
|
|
1-based; line zero denotes I/O, size, path resolution or whole-model validation.
|
|
|
|
|
|
MODEL errors do not identify the original line of a cross-reference. The
|
|
|
|
|
|
original `xtc_project_load` remains available with identical transactional
|
|
|
|
|
|
semantics.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`xtc-check --validate PROJECT` checks bounded metadata without loading
|
|
|
|
|
|
dependencies. `xtc-check --inspect PROJECT` additionally lists resolved media
|
|
|
|
|
|
and module paths, plugin labels, unique IDs and descriptor indices. It checks
|
|
|
|
|
|
readable regular-file availability only. Exit status is 0 when references are
|
|
|
|
|
|
available, 2 when valid metadata has missing/unreadable references, and 1 for
|
|
|
|
|
|
invalid metadata or I/O. Neither command executes plugin code, decodes WAV data,
|
|
|
|
|
|
connects JACK or verifies hardware. A readable file can still be an invalid WAV
|
|
|
|
|
|
or incompatible plugin; actual preparation performs those checks. Paths and
|
|
|
|
|
|
labels are escaped for terminal output. There is no search-path substitution or
|
|
|
|
|
|
automatic plugin relink.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Portability currently targets Linux/POSIX. The text representation is
|
|
|
|
|
|
independent of byte order and locale and requires IEEE binary32 floats. IDs used
|
|
|
|
|
|
by the graph must fit the receiving host's size_t; a 64-bit file is not
|
|
|
|
|
|
guaranteed to fit a 32-bit build. Paths follow POSIX semantics; Windows drive
|
|
|
|
|
|
letters and separators are not translated. Relative media can travel with their
|
|
|
|
|
|
project folder, while absolute references and symlink targets must be restored
|
|
|
|
|
|
separately. LADSPA modules are absolute installed references: match
|
|
|
|
|
|
architecture, ABI, descriptor identity, ports and legal controls on the
|
|
|
|
|
|
destination. Plugin binaries and state are not bundled. JACK availability,
|
|
|
|
|
|
sample rate, block limits and device routing are runtime dependencies; loading
|
|
|
|
|
|
metadata does not restore device connections.
|
2026-10-27 16:58:26 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`make test` includes v1–v10 migration, malformed/truncated/mutated/oversized
|
|
|
|
|
|
input, retained-state diagnostics, CLI dependency inspection, and deterministic
|
|
|
|
|
|
save faults at flush, file sync, close, rename and directory sync. Pre-rename
|
|
|
|
|
|
faults preserve the old bytes and remove the temporary. After rename, a sync
|
|
|
|
|
|
failure reports I/O while leaving a complete new document; inspect it before
|
|
|
|
|
|
retrying. Fault wrappers are linked into tests only. These tests complement
|
|
|
|
|
|
killed-writer coverage in `make test-recovery`; they do not qualify power-cut
|
|
|
|
|
|
behavior.
|
2026-10-27 17:34:46 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 11: Plugin Format Identity
|
2026-10-27 17:34:46 +01:00
|
|
|
|
|
|
|
|
|
|
Immediately after every plugin MODULE record, v11 requires:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
FORMAT format_number hex_identity_or_dash
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
0 = LADSPA, 1 = CLAP, 2 = VST2, 3 = VST3. Identity is `-` for LADSPA/VST2; CLAP
|
|
|
|
|
|
uses its stable plugin ID (1–127 non-NUL bytes); VST3 uses the 32-character
|
|
|
|
|
|
lowercase hex class ID string, itself hex-encoded like other byte strings.
|
|
|
|
|
|
PLUGIN's unique ID remains the VST2/LADSPA integer ID (zero for CLAP/VST3),
|
|
|
|
|
|
descriptor index and label. MODULE is still the actual absolute binary path.
|
|
|
|
|
|
Versions 1–10 migrate to format 0 and empty extra identity. Older readers reject
|
|
|
|
|
|
v11. The public plugin struct has grown; C/C++ consumers must rebuild and
|
|
|
|
|
|
initialize it to zero.
|
2026-10-27 17:34:46 +01:00
|
|
|
|
|
|
|
|
|
|
Modern adapters accept one-input mono effects, default configuration only, with
|
|
|
|
|
|
zero controls and automation points. Unsupported modern control metadata is
|
2026-11-06 15:20:20 +01:00
|
|
|
|
rejected, not discarded. State/editor/preset changes are not exposed, so no
|
|
|
|
|
|
opaque state is serialized. Reloading recreates plugin defaults, which can
|
|
|
|
|
|
change after a plugin update or with its external configuration. Matching
|
|
|
|
|
|
ID/name/layout is not a binary hash or sound-reproducibility guarantee. See
|
|
|
|
|
|
PLUGIN_HOSTING.md.
|
2026-10-27 17:34:46 +01:00
|
|
|
|
|
|
|
|
|
|
Metadata validation and inspection never initialize modules. Actual graph
|
|
|
|
|
|
preparation verifies format, descriptor, stable identity, name and layout before
|
2026-11-06 15:20:20 +01:00
|
|
|
|
publishing. Missing module metadata survives C load/save; GUI preparation
|
|
|
|
|
|
retains the existing document on failure. No automatic path search or plugin
|
|
|
|
|
|
substitution occurs. Current and historical migration fixtures, malformed
|
|
|
|
|
|
fields, identity mismatch, missing-module and exact-render roundtrips cover this
|
|
|
|
|
|
extension.
|
2026-10-27 17:51:48 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 12: Track Instruments
|
2026-10-27 17:51:48 +01:00
|
|
|
|
|
|
|
|
|
|
After CONTROL_MAPS/MAP records and before END:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
TRACK_INSTRUMENTS count
|
|
|
|
|
|
INSTRUMENT track_id format descriptor_index unique_id hex_label
|
|
|
|
|
|
MODULE hex_absolute_binary_path
|
|
|
|
|
|
IDENTITY hex_stable_id_or_dash
|
|
|
|
|
|
LIVE_INSTRUMENT track_id_or_18446744073709551615
|
|
|
|
|
|
END
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Each binding comprises INSTRUMENT/MODULE/IDENTITY. Formats 1/2/3 mean
|
|
|
|
|
|
CLAP/VST2/VST3. Bindings require distinct existing non-bus tracks and compatible
|
|
|
|
|
|
instrument metadata (zero audio inputs). The live target must have a binding;
|
|
|
|
|
|
UINT64_MAX selects built-in sine. Empty bindings are valid. Older documents
|
|
|
|
|
|
migrate to no bindings and built-in live sine. Parsing/inspection never executes
|
|
|
|
|
|
modules; preparing playback verifies binary identity and configuration. Deleting
|
|
|
|
|
|
tracks removes bindings; reordering preserves stable track identities. Undo
|
|
|
|
|
|
snapshots include bindings. Public clients must rebuild for the new spec field.
|
2026-10-28 17:16:01 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 13 Internal Devices
|
2026-10-28 17:16:01 +01:00
|
|
|
|
|
|
|
|
|
|
Format value 4 identifies dependency-free internal devices at `/xtc/internal`.
|
|
|
|
|
|
Indices 0/1 bind fixed Soft Keys/Sub Bass instruments; 2/3 are Delay/Reverb
|
2026-11-06 15:20:20 +01:00
|
|
|
|
effects with three controls and existing automation records. Identity and
|
|
|
|
|
|
control ranges are validated without loading libraries. See
|
|
|
|
|
|
[BUILTIN_DEVICES.md](BUILTIN_DEVICES.md).
|
2026-11-05 23:47:54 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 14 Instrument Parameters
|
2026-11-05 23:47:54 +01:00
|
|
|
|
|
|
|
|
|
|
Each track INSTRUMENT identity is followed by `INSTRUMENT_CONTROLS count`,
|
|
|
|
|
|
`CONTROL port value_bits` records, `INSTRUMENT_AUTOMATION count` and
|
|
|
|
|
|
`PARAM tick port mode value_bits` records. Bounds, duplicate points and metadata
|
2026-11-06 15:20:20 +01:00
|
|
|
|
are validated before destination replacement, just as for graph effects. Earlier
|
|
|
|
|
|
bindings have no controls/automation; v13 internal instruments retain fixed
|
|
|
|
|
|
default sounds. Modern external instruments still require zero controls.
|
2026-11-05 23:47:54 +01:00
|
|
|
|
Instrument tempo follows project tempo. Instrument preset files use this same
|
|
|
|
|
|
schema with exactly one instrument track and no clips; they are metadata only.
|
2026-11-05 23:50:14 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 15 Sample References
|
2026-11-05 23:50:14 +01:00
|
|
|
|
|
|
|
|
|
|
Each instrument parameter block ends with `SAMPLE hex_path_or_dash`. Only the
|
|
|
|
|
|
internal Sample Player (index 4) may carry a nonempty sample path. Paths within
|
|
|
|
|
|
the project directory are relative on disk and resolve against the project
|
|
|
|
|
|
file's directory; other paths are absolute. Metadata loading does not read WAVs.
|
|
|
|
|
|
Missing/invalid samples remain references and become reported silence during
|
|
|
|
|
|
preparation. Controls describe root key, whole-file loop, attack, release and
|
2026-11-06 15:20:20 +01:00
|
|
|
|
gain. Earlier projects have empty sample paths. Rebuild public API consumers for
|
|
|
|
|
|
the additional copied `sample_path` field in `xtc_plugin_spec`.
|
2026-11-06 00:29:48 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Version 16: Arrangement Markers
|
2026-11-06 00:29:48 +01:00
|
|
|
|
|
|
|
|
|
|
After `LIVE_INSTRUMENT` and before `END`, version 16 writes `MARKERS count`
|
2026-11-06 15:20:20 +01:00
|
|
|
|
followed by `MARKER tick hex_name` records. Count is 0..128; ticks must be
|
|
|
|
|
|
strictly increasing and at most MAX_TICK. Names use the existing bounded UTF-8
|
|
|
|
|
|
hex encoding. Versions 1–15 migrate to an empty marker list. Older builds reject
|
|
|
|
|
|
version 16. Markers are copied by snapshots and history; the tick is their
|
|
|
|
|
|
stable key.
|
2026-11-06 01:50:25 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
2026-11-06 01:50:25 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
## Version 17: per-Track Insert Chains
|
2026-11-06 01:50:25 +01:00
|
|
|
|
|
|
|
|
|
|
After MARKERS/MARKER and before END, the writer emits a chain for every track,
|
|
|
|
|
|
in dense track order. Earlier versions migrate to empty chains with unity gain.
|
|
|
|
|
|
Floats use the existing eight-digit IEEE binary32 hex encoding; strings use the
|
|
|
|
|
|
existing bounded UTF-8/byte hex encoding, with `-` for an empty identity.
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
CHAINS track_count
|
|
|
|
|
|
CHAIN track_id slot_count output_gain_bits
|
|
|
|
|
|
INSERT enabled wet_bits format descriptor_index unique_id inputs tempo hex_label
|
|
|
|
|
|
MODULE hex_absolute_binary_path
|
|
|
|
|
|
IDENTITY hex_stable_identifier_or_dash
|
|
|
|
|
|
CONTROLS control_count
|
|
|
|
|
|
CONTROL port value_bits
|
|
|
|
|
|
PARAMETERS point_count
|
|
|
|
|
|
PARAM tick port step value_bits
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Each CHAIN has exactly slot_count INSERT blocks. CONTROLS/PARAMETERS determine
|
|
|
|
|
|
the subsequent CONTROL/PARAM record counts, including zero. Slots are ordered,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
at most eight, and accept mono non-instrument effects only. Enabled is 0/1, wet
|
|
|
|
|
|
is finite 0–1, output gain finite 0–4; inputs must be 1. The format values are
|
|
|
|
|
|
LADSPA=0, CLAP=1, VST2=2, VST3=3 and Internal=4. Existing plugin specification
|
2026-11-06 01:50:25 +01:00
|
|
|
|
validation applies, including descriptor/identity/port/automation bounds and
|
|
|
|
|
|
20,000–300,000 milli-BPM. Bus chains must be empty. No sample reference is
|
|
|
|
|
|
permitted on an effect. Duplicate, missing, reordered or excessive chain
|
|
|
|
|
|
records, malformed strings and nonfinite values reject the whole load.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Loading and dependency inspection only validate metadata; no plugin module is
|
|
|
|
|
|
opened. Enabled plugins are verified when a render plan is prepared. Chain
|
2026-11-06 01:50:25 +01:00
|
|
|
|
preset files reuse this schema with exactly one non-bus track and no clips or
|
|
|
|
|
|
instrument. Track insert APIs are declared in `xtc_plugin.h`; see
|
|
|
|
|
|
[TRACK_INSERTS.md](TRACK_INSERTS.md) for playback restrictions and workflows.
|