Phase 15: host trusted LADSPA processors with persisted graph state
This commit is contained in:
parent
c3eadee891
commit
f72314b5a3
24 changed files with 1046 additions and 70 deletions
|
|
@ -246,3 +246,14 @@ acceptance sequence and transport semantics.
|
|||
and numeric/layout tests (also included in `make test`). `make quality-preview`
|
||||
builds the C example and writes raw/smoothed listening WAVs into BUILD_DIR without
|
||||
playing them or configuring devices. See [AUDIO_QUALITY.md](AUDIO_QUALITY.md).
|
||||
|
||||
Phase 14/15 checks: `make test-processor test-plugins test-gui-plugins`.
|
||||
The latter two need the system LADSPA development header; real-plugin acceptance
|
||||
is separate (`make test-plugins-real`, paths listed in PLUGIN_HOSTING.md). With no
|
||||
`ladspa.h`, the ordinary core/GUI still builds and plugin operations return
|
||||
UNSUPPORTED; existing plugin metadata can still be loaded/saved. To exercise that
|
||||
configuration explicitly, use a fresh BUILD_DIR with
|
||||
`CPPFLAGS=-DXTC_DISABLE_LADSPA JACK_AVAILABLE=`. No dependency is downloaded.
|
||||
Link static consumers with `-lm -ldl` plus the backend dependencies when used.
|
||||
See LICENSES.md for header/plugin licensing and decision 0009 for rationale.
|
||||
Recording stress/recovery is available with `make test-recording`.
|
||||
|
|
|
|||
|
|
@ -37,3 +37,20 @@ and supply the notices/source/relinking facilities required by those exact
|
|||
licenses and linking choices. FLTK's exception does not remove obligations of
|
||||
other linked libraries. This review covers the current unmodified system-linked
|
||||
build; static all-in-one bundles and external plugins need a separate review.
|
||||
|
||||
## Phase 15 LADSPA review
|
||||
|
||||
The optional external host includes the system LADSPA 1.1 header (`ladspa`
|
||||
1.17-7), copyright Richard W.E. Furse, Paul Barton-Davis and Stefan Westerfeld,
|
||||
LGPL-2.1-or-later. No header, implementation or plugin binary is copied into the
|
||||
repository. The host uses the glibc dynamic loader (`-ldl`, LGPL-2.1-or-later),
|
||||
with no discovery/runtime SDK library or new installed package. LADSPA is mature
|
||||
and stable; it does not provide modern instrument/MIDI/opaque-state services.
|
||||
The original fixture in tests/plugins is MIT repository code.
|
||||
|
||||
Acceptance dynamically loads the user's installed LADSPA sample amp/sine modules
|
||||
(package metadata LGPL-2.1-or-later) and SWH inverter (swh-plugins 0.4.17-7,
|
||||
GPL-2.0-or-later). Testing/loading a user-installed module does not grant the right
|
||||
to bundle or relicense it. Redistributors must review each chosen module and the
|
||||
combined distribution's obligations separately; xtc's MIT grant covers only its
|
||||
original code. No third-party plugin is included in source archives or installs.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# External plugin hosting evaluation
|
||||
|
||||
Decision: defer external hosting after Phase 9. The native source API is useful
|
||||
Historical Phase 9 decision: defer external hosting. The native source API is useful
|
||||
for proving processor contracts without adding an SDK or scanning/loading code.
|
||||
|
||||
| Candidate | Benefits | Work required before adoption |
|
||||
|
|
@ -22,3 +22,84 @@ Native ABI tests cover some ownership mechanics but do not prove third-party
|
|||
code safe. Event graph ports and custom-instrument GUI selection remain gaps.
|
||||
Choose one format with concrete user needs after those contracts are specified;
|
||||
do not introduce a general multi-format framework now. See decision 0008.
|
||||
|
||||
## Phase 15 implementation (supersedes the Phase 9 deferral)
|
||||
|
||||
Decision [0009](decisions/0009-parameter-processors-and-ladspa.md) selects a bounded
|
||||
LADSPA 1.1 host. `xtc_plugin.h` is the C API; no C++ engine or FLTK type is needed.
|
||||
|
||||
1. Choose **File → Load LADSPA plugin** and a trusted absolute `.so` path. Loading
|
||||
executes native code immediately, including constructors during discovery.
|
||||
2. The dialog lists compatible descriptor indices/labels. Select an index.
|
||||
3. In the Modular Graph, connect the new source/effect output to an output or
|
||||
other effect; effects have an optional mono input. Unconnected effect inputs
|
||||
receive external input, as other graph processors do.
|
||||
4. Select its node and use **Edit → Selected plugin control**. The dialog shows
|
||||
actual port IDs, names, values and rate-adjusted ranges. Enter a port and value.
|
||||
Toggled controls require 0/1 and integer controls require integral values.
|
||||
5. Save normally, reopen, reconnect external JACK playback if needed, and Play.
|
||||
Plugin changes stop/rebuild playback and participate in ordinary undo/redo.
|
||||
|
||||
The GUI uses only public graph/model/plugin APIs. Discovery probes explicit module
|
||||
files by descriptor index, bounded to 1024 descriptors. It never recursively scans
|
||||
system directories. The C API exposes probe, parameter metadata, instance
|
||||
create/process/destroy, and add/get/set graph configuration. Missing header means
|
||||
hosting returns UNSUPPORTED; metadata persistence continues to work.
|
||||
|
||||
Supported: 0/1 audio input, 1 output, <=64 total ports and <=16 input controls.
|
||||
Stereo graph channels are two independent mono instances. Stereo/multichannel
|
||||
LADSPA descriptors are rejected, rather than downmixed silently. Control output
|
||||
ports have private storage; no output-control GUI is implemented. No plugin UI,
|
||||
MIDI/events, sample automation, worker interface, latency compensation or opaque
|
||||
state is supplied by this first format. Parameter configuration is persisted;
|
||||
oscillator phase, held voices, delay history and tails are not. No smoothing is
|
||||
added around plan replacement. Integer LADSPA sample rates are obtained from the
|
||||
engine rate; practical backend rates are integral.
|
||||
|
||||
A new plan opens and owns its own modules and instances. Failed loading,
|
||||
incompatible identity/range/layout, or instantiate returning NULL unwinds the
|
||||
candidate and leaves active/pending plans intact. Only run and bounded host
|
||||
buffer copies execute in audio. Destruction and unloading happen after callback
|
||||
quiescence or retirement acknowledgement on the control thread. Transport reset
|
||||
retains LADSPA history; compile/reopen starts fresh. A LADSPA run callback has no
|
||||
error return. Nonfinite/subnormal samples become zero, but native crashes,
|
||||
corruption, runaway CPU, allocations or I/O inside a plugin cannot be contained
|
||||
or prevented. Use trusted plugins; hard-realtime properties are declarations,
|
||||
not proof. This is an in-process host, not a crash-isolation service.
|
||||
|
||||
Project v5 stores absolute module path, index, label, LADSPA unique ID, mono input
|
||||
count and ordered input-control port/value pairs. Restore verifies identity,
|
||||
layout, parameter ports and ranges against the loaded module. No pointers are
|
||||
serialized. Relocating a module requires restoring it at its saved absolute path
|
||||
or deliberately modifying its metadata through a C client. GUI Open reports the
|
||||
missing/incompatible identity/path and retains the current document. Metadata
|
||||
load/save and `xtc-check --validate` do not execute plugin code and do not certify
|
||||
module availability. LADSPA IDs are not cryptographic binary/version identities.
|
||||
|
||||
## Repeatable acceptance
|
||||
|
||||
```sh
|
||||
make all test gui test-plugins test-gui-plugins analyze
|
||||
make test-plugins-real
|
||||
```
|
||||
|
||||
The first plugin target builds an original MIT shared-library fixture with gain,
|
||||
source, unsupported stereo and failed-initialization descriptors. Tests exercise
|
||||
identity/range errors, exact in-place gain, save/reopen/exact rendering, missing
|
||||
module metadata preservation and 100 plan replacements with callback guards
|
||||
against host allocation/free/dlopen/dlclose. GUI commands cover add, control edit,
|
||||
invalid-value rollback, undo/redo and save/New/reopen. ASan/UBSan/leak checks cover
|
||||
host/fixture lifetimes; plugin code from system packages is not instrumented.
|
||||
|
||||
`test-plugins-real` requires these separately installed modules (nothing is
|
||||
bundled or installed by the target):
|
||||
|
||||
| Module / descriptor | Tested behavior |
|
||||
| --- | --- |
|
||||
| `/usr/lib/ladspa/amp.so`, 0 `amp_mono` | One input, gain control, finite nonzero render, exact save/reopen render |
|
||||
| `/usr/lib/ladspa/inv_1429.so`, 0 `inv` | One input, no controls, exact save/reopen render |
|
||||
| `/usr/lib/ladspa/sine.so`, 3 `sine_fcac` | No input, frequency/amplitude controls, exact save/reopen render |
|
||||
|
||||
Tested packages: ladspa 1.17-7 and swh-plugins 0.4.17-7. These zero-latency
|
||||
processors define the initial compatible set; other plugins remain unqualified.
|
||||
Physical listening and plugin CPU/endurance qualification remain pending.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -294,6 +294,15 @@ state are explicitly outside this parameter-only interface.
|
|||
- Plugin loading and destruction do not occur in the real-time callback.
|
||||
- The chosen format's dependencies and licensing are documented.
|
||||
|
||||
Implemented: bounded LADSPA 1.1 C host with explicit file/index discovery,
|
||||
control-thread loading/lifecycle, mono source/effect graph mapping, independent
|
||||
stereo instances, metadata/range checks and v5 identity/control persistence.
|
||||
Public-API GUI load/configure commands support ordinary routing/save/reopen and
|
||||
undo. Fixture/error/retirement/GUI tests and installed amp/inverter/sine acceptance
|
||||
pass. See decision 0009, PLUGIN_HOSTING.md and LICENSES.md. MIDI, true multichannel
|
||||
layouts, opaque state, editors, latency compensation and crash isolation are
|
||||
unsupported; plugin realtime behavior and physical listening are unqualified.
|
||||
|
||||
## Phase 16 — Automation and Expressive Control
|
||||
|
||||
**Goal:** Make automation and MIDI control reliable, sample-timed, and usable.
|
||||
|
|
|
|||
52
docs/decisions/0009-parameter-processors-and-ladspa.md
Normal file
52
docs/decisions/0009-parameter-processors-and-ladspa.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# 0009 — Parameter processors and bounded LADSPA hosting
|
||||
|
||||
Status: accepted, Phases 14–15 (2026-10-27).
|
||||
|
||||
Native ABI 1 remains compatible. ABI 2 adds explicitly parameter-only configuration,
|
||||
metadata, validation and sample-offset parameter/MIDI ordering. It is a public C
|
||||
facility; opaque state and automatic native descriptor registration in project
|
||||
files remain unsupported. Native instrument and gain examples exercise the
|
||||
interface before adding a dynamically loaded module lifetime.
|
||||
|
||||
Select LADSPA 1.1 for the first external host. Reconsidering the Phase 9 candidates,
|
||||
LV2 needs RDF/features/URIDs/workers, CLAP needs host scheduling/extension
|
||||
negotiation, and VST3 needs a separate C++ SDK adapter and release-specific license
|
||||
review. LADSPA adds a mature Linux C ABI, installed real plugins, straightforward
|
||||
port negotiation and no RDF/SDK runtime library. Its specification is old and
|
||||
stable, not an actively expanding instrument/event ecosystem. This trades MIDI,
|
||||
editors, opaque state and modern automation for a small testable first host.
|
||||
No general multi-format abstraction is introduced.
|
||||
|
||||
Use the installed LGPL-2.1-or-later header, system dynamic loader (`-ldl`), and
|
||||
user-selected, trusted module files. Neither the header nor plugin binaries are
|
||||
vendored. Header absence or `XTC_DISABLE_LADSPA` yields unsupported hosting stubs;
|
||||
metadata can still be loaded/saved. Plugin licenses are independent of the host
|
||||
and require review before distribution. See LICENSES.md.
|
||||
|
||||
Support zero/one mono audio input and exactly one mono output, at most 64 ports
|
||||
and 16 input controls. Graph stereo consists of independent instances, not a
|
||||
stereo LADSPA layout. Reject incompatible layouts and identities explicitly.
|
||||
Control outputs are connected to private storage, not exposed as graph events.
|
||||
LADSPA has no MIDI or opaque state API: save control values only. Parameter
|
||||
changes rebuild plans on the control thread; sample automation is deferred to
|
||||
Phase 16. Rate-scaled bounds are recalculated at compile. Instance history/tails
|
||||
reset on recompile, not every transport seek (LADSPA has no realtime reset rule).
|
||||
Latency compensation is unsupported: the supported set is zero-latency amp,
|
||||
inverter and sine source, not arbitrary lookahead/delay/routing plugins.
|
||||
|
||||
Discovery, dlopen, descriptor calls, instantiate, connect, activate, deactivate,
|
||||
cleanup and dlclose are control-thread operations. Only preallocated copies and
|
||||
run execute in audio; the usual pending/retired plan handoff owns module handles.
|
||||
Partial initialization unwinds on the control thread. The host cannot enforce
|
||||
third-party callback behavior, bound CPU time, catch native crashes/hangs, or
|
||||
undo memory corruption. There is no sandbox or crash recovery inside the audio
|
||||
process. A fault may kill xtc; previously saved documents/recording recovery files
|
||||
remain subject to their ordinary durability guarantees. Test only trusted modules.
|
||||
|
||||
Project v5 stores plain identity/layout/control metadata. Parsing never opens
|
||||
modules. Compile failure preserves the source document and active plan. GUI Open
|
||||
rejects missing/incompatible modules, retains its current document, and reports
|
||||
saved module identity/path. Restore the compatible module at that path and retry;
|
||||
a metadata-only C client can preserve/save the document without the module.
|
||||
No silent replacement or deletion occurs. Same-ID/label/ports do not authenticate
|
||||
binaries or guarantee unchanged sound across plugin upgrades.
|
||||
Loading…
Add table
Add a link
Reference in a new issue