2026-10-27 01:06:57 +01:00
|
|
|
|
# Project format v4 (reads v1/v2/v3)
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
2026-10-27 01:06:57 +01:00
|
|
|
|
Phases 5–8 use a bounded text format implemented in C99 without a parser library.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
The extension is `.xtc`. File operations run exclusively on the control thread.
|
2026-10-27 00:31:03 +01:00
|
|
|
|
The project owns its graph, tracks and silent/audio arrangement regions. Audio samples remain in external WAV
|
2026-10-27 00:53:41 +01:00
|
|
|
|
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.
|
|
|
|
|
|
Fields use ASCII spaces. Integers are unsigned decimal, without signs.
|
|
|
|
|
|
Counts determine the number of subsequent records:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
2026-10-27 01:06:57 +01:00
|
|
|
|
XTC_PROJECT 4
|
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 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
|
|
|
|
|
|
NOTES 0 0
|
2026-10-27 01:06:57 +01:00
|
|
|
|
AUTOMATION 0
|
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.
|
|
|
|
|
|
- `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
|
|
|
|
|
|
compiled state are serialized.
|
2026-10-27 00:53:41 +01:00
|
|
|
|
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output, 5 audio source, 6 instrument), and exactly eight
|
2026-10-27 00:10:45 +01:00
|
|
|
|
hexadecimal digits encoding the IEEE binary32 parameter bits, independent
|
|
|
|
|
|
of locale and host byte order. Nonfinite values are rejected.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
- `EDGE`: source and destination node IDs. Port directions are implied
|
2026-10-27 00:10:45 +01:00
|
|
|
|
by node type; endpoints, duplicates and cycles are validated by the graph API.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
- `TRACK`: ID and hex-encoded name bytes. In v4 each is immediately followed
|
|
|
|
|
|
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 00:10:45 +01:00
|
|
|
|
- `CLIP`: ID, track ID, start tick, length tick, source offset tick, source
|
2026-10-27 00:31:03 +01:00
|
|
|
|
length tick, hex-encoded name bytes. Trimming
|
2026-10-27 00:10:45 +01:00
|
|
|
|
preserves source bounds, so a region can re-extend only within its original
|
2026-10-27 00:31:03 +01:00
|
|
|
|
length. Overlaps are allowed and audio clips mix through the audio source node.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
- `ASSET`: required after each CLIP in v2/v3/v4; "-" means no audio asset, otherwise
|
2026-10-27 00:31:03 +01:00
|
|
|
|
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.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
- `MIDI`: required after each ASSET in v3/v4; 0 for silent/audio regions, 1 for
|
2026-10-27 00:53:41 +01:00
|
|
|
|
MIDI clips. MIDI clips must have no asset.
|
|
|
|
|
|
- `NOTES`: next stable note ID and global note count, followed by that many
|
|
|
|
|
|
`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 01:06:57 +01:00
|
|
|
|
- `AUTOMATION`: global point count, after the last NOTE and before END in v4.
|
|
|
|
|
|
Follow with `POINT track_id parameter tick value_bits` for each point.
|
|
|
|
|
|
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-10-27 01:06:57 +01:00
|
|
|
|
4096 edges, 4096 notes, 1024 automation points, 4 MiB input, 1535 bytes per record, 12 tokens per record. The reader
|
2026-10-27 00:10:45 +01:00
|
|
|
|
rejects invalid references, duplicate IDs, inconsistent next IDs, ranges,
|
|
|
|
|
|
truncated records, raw control/non-ASCII bytes, and trailing data.
|
|
|
|
|
|
|
|
|
|
|
|
Unsupported format versions return UNSUPPORTED. Other malformed data returns
|
2026-10-27 00:31:03 +01:00
|
|
|
|
FORMAT. The reader explicitly accepts v1 records without ASSET lines, with
|
2026-10-27 00:53:41 +01:00
|
|
|
|
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.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
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 v4. Unknown versions are rejected without replacing the destination; older
|
|
|
|
|
|
applications cannot read v4. Migration and maximum-path roundtrips are tested.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
|
|
|
|
|
|
## Ownership and failures
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
The parent directory is not fsynced: survival of the rename through sudden power
|
|
|
|
|
|
loss is not guaranteed. 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 01:06:57 +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.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
Graph node positions, timeline zoom/scroll/selection, transport position and DSP
|
2026-10-27 00:31:03 +01:00
|
|
|
|
oscillator phase are GUI/session state and are not saved. Asset references are
|
|
|
|
|
|
external absolute paths, 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
|
|
|
|
|
|
|
|
|
|
## Phase 9 extension and recovery policy
|
|
|
|
|
|
|
|
|
|
|
|
Format remains v4. 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.
|