Phase 15: host trusted LADSPA processors with persisted graph state

This commit is contained in:
Johannes Findeisen 2026-10-27 04:00:27 +01:00
commit f72314b5a3
24 changed files with 1046 additions and 70 deletions

View file

@ -1,6 +1,6 @@
# Project format v4 (reads v1/v2/v3)
# Project format v5 (reads v1/v2/v3/v4)
Phases 5–8 use a bounded text format implemented in C99 without a parser library.
Phases 5–15 use 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.
@ -12,7 +12,7 @@ Fields use ASCII spaces. Integers are unsigned decimal, without signs.
Counts determine the number of subsequent records:
```text
XTC_PROJECT 4
XTC_PROJECT 5
APP 0 1 0
TEMPO 120000
IDS 1 1
@ -39,30 +39,30 @@ END
- `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.
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output, 5 audio source, 6 instrument), and exactly eight
- `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.
- `TRACK`: ID and hex-encoded name bytes. In v4 each is immediately followed
- `TRACK`: ID and hex-encoded name bytes. In v4/v5 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.
- `CLIP`: ID, track ID, start tick, length tick, source offset tick, source
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; "-" means no audio asset, otherwise
- `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; 0 for silent/audio regions, 1 for
- `MIDI`: required after each ASSET in v3/v4/v5; 0 for silent/audio regions, 1 for
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.
- `AUTOMATION`: global point count, after the last NOTE and before END in v4.
- `AUTOMATION`: global point count, after the last NOTE and before END in v4/v5.
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.
@ -82,8 +82,8 @@ 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 v4. Unknown versions are rejected without replacing the destination; older
applications cannot read v4. Migration and maximum-path roundtrips are tested.
Saving always writes v5. Unknown versions are rejected without replacing the destination; older
applications cannot read v5. Migration and maximum-path roundtrips are tested.
## Ownership and failures
@ -108,9 +108,33 @@ changing its IDs or arrangement. See AUDIO_FILES.md for recovery and limitations
## Phase 9 extension and recovery policy
Format remains v4. Native processor descriptors are process-local and have no
Phase 9 kept format 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.
## Phase 15: version 5 plugin metadata
The writer now emits `XTC_PROJECT 5`; versions 1–4 still load. 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:
```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
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.