206 lines
13 KiB
Markdown
206 lines
13 KiB
Markdown
# External plugin hosting evaluation
|
|
|
|
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 |
|
|
| --- | --- | --- |
|
|
| LV2 | Open specifications, established Linux hosts/plugins | RDF discovery and feature negotiation, URIDs, state, UI lifetime, worker requests and event mapping; likely additional discovery dependency |
|
|
| CLAP | C ABI, explicit lifecycle and event model | Extension negotiation, parameter/state persistence, host callbacks, main/audio-thread scheduling and UI integration |
|
|
| VST3 | Broad plugin availability | SDK/license review for the selected SDK release, C++ adapter outside the C core, component/controller and editor lifecycle, state and event translation |
|
|
|
|
No candidate was linked, scanned or loaded in this phase. API/SDK and plugin
|
|
licenses must be checked at the revision actually adopted; they are separate
|
|
from xtc's MIT license. Supporting a format does not grant redistribution rights
|
|
to individual plugins. Do not search user plugin directories from the callback.
|
|
|
|
Before implementation, require versioned module identity and state, a missing-
|
|
plugin recovery policy, preparation failure/partial cleanup tests, bounded events
|
|
and parameter publication, latency/tail handling, callback-safe unload after
|
|
retirement, a GUI/main-thread boundary and an explicit crash-containment decision.
|
|
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 descriptor browser lists names, labels, makers and source/effect roles.
|
|
Select a row and Choose. Counts report compatible and skipped descriptors.
|
|
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 editor shows
|
|
name, maker, copyright, module path, descriptor index and unique ID, then
|
|
actual port IDs, editable values, rate-adjusted bounds, defaults and discrete/
|
|
logarithmic hints. Apply validates all values and candidate initialization;
|
|
Cancel and window close preserve the document. 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.
|
|
|
|
## Phase 25: discovery, editing and graph lifecycle
|
|
|
|
Discovery remains explicit-file only, bounded to 1024 descriptor indices; no
|
|
recursive scan, plugin database or new dependency is introduced. Unsupported
|
|
layouts are skipped with counts; source/effect roles describe audio ports, not
|
|
MIDI instrument capabilities. Built-in MIDI synthesis remains the only GUI MIDI
|
|
instrument. LADSPA sources can be identified, edited, replaced and removed in
|
|
the graph but cannot receive MIDI events.
|
|
|
|
**Edit → Insert LADSPA on selected wire** replaces one existing connection with
|
|
`source -> effect -> destination`, as one undo step. Only one-input effects are
|
|
accepted; stale wires, capacity failures and initialization failures retain the
|
|
original graph. **Edit → Replace selected plugin** accepts the same audio input
|
|
layout, keeps the node ID and all wires, and installs the new descriptor's default
|
|
controls with no inherited automation. It can replace from another module.
|
|
A source/effect layout mismatch is rejected. Undo restores the old identity,
|
|
values and automation. Every candidate is compiled before adoption even when
|
|
the backend is disconnected; no intermediate graph is published.
|
|
|
|
Ordering is graph signal flow, not descriptor order, creation order or a flat
|
|
rack. Use the graph's connection/disconnection tools to place effects in series
|
|
or parallel. Insertion on a wire preserves its endpoints and changes only that
|
|
stage. Select a plugin and Delete to remove it and incident wires, then reconnect
|
|
as desired; deletion does not automatically bridge or sum routes. A now-unwired
|
|
effect receives external audio, following existing graph semantics. Undo/redo
|
|
restores removal and routing. Independent bus inserts are still outside the
|
|
shared-source graph model.
|
|
|
|
Reset defaults in the editor previews every control's declared/default-derived
|
|
value. Apply also clears that plugin's automation; Cancel preserves it. The
|
|
**Edit → Reset selected plugin** command does the same in one undo step. The
|
|
public control-thread `xtc_plugin_reset` can reset one port (removing only its
|
|
lane) or UINT32_MAX for all controls. Automation otherwise overrides static
|
|
values, so editing a static control does not remove an existing lane.
|
|
|
|
`xtc_plugin_describe` returns copied name/maker/copyright (bounded/truncated),
|
|
parameter ranges/defaults and integer/toggle/logarithmic/sample-rate/boundedness
|
|
flags. Missing maker/copyright receive explicit fallback labels. LADSPA has no
|
|
standard unit metadata: unit stays empty and the editor says unspecified rather
|
|
than guessing Hz/dB from names. Numeric inputs use linear parameter values;
|
|
logarithmic is a descriptor hint, not an extra host conversion. Unbounded ends
|
|
are labeled unbounded; finite float extrema remain the validation bounds.
|
|
Sample-rate hints scale bounds and defaults once at the engine's actual rate;
|
|
nonrepresentable scaled bounds are rejected. Discrete defaults are rounded and
|
|
validated; impossible defaults fail discovery rather than reaching the engine.
|
|
|
|
The generic modal editor owns copied metadata and temporary widget values only.
|
|
It never owns or retains module handles, descriptors or audio instance pointers.
|
|
Closing it cannot unload active DSP; Apply builds a new immutable plan before
|
|
installation and existing retirement/quiescence rules release old instances.
|
|
No embedded third-party editor UI is supported.
|
|
|
|
### Compatibility and recovery
|
|
|
|
LADSPA 1.1 descriptor identity is module path + index + label + unique ID, with
|
|
exact supported port/control layout verified on restore. A unique ID is not a
|
|
binary version/hash guarantee. Parameter values and bounded automation persist
|
|
in existing project v9 records; this phase adds no file format change. Transient
|
|
history/tails and opaque state remain unsupported. Native processor ABI 2 and
|
|
its exact `struct_size` contract are unchanged; it remains parameter-only state.
|
|
Consumers rebuild against public headers; the new copied details API adds no
|
|
callbacks or GUI types to the C core.
|
|
|
|
Missing/incompatible modules reject GUI Open with the offending label/path and
|
|
leave the current project intact. Restore a compatible module at that path and
|
|
retry; use metadata-only validation/save to preserve an unavailable module's
|
|
records. No dummy replacement silently removes its controls. Failed discovery,
|
|
replacement or instantiate reports an error and retains the current document.
|
|
A LADSPA `run` callback has no error return; finite-sample sanitation does not
|
|
contain crashes, hangs, memory corruption or prohibited work inside a plugin.
|
|
Only trusted in-process plugins are supported. See RECOVERY.md.
|
|
|
|
### Phase 25 verification
|
|
|
|
Fixture tests cover copied identity/hints, integer and toggle defaults and range
|
|
rejection, logarithmic/sample-rate hints, scaled-range overflow rejection, reset
|
|
and unchanged-on-error behavior, parameter/automation save/reopen and exact
|
|
rendering. Noncommutative gain/offset effects prove that graph order changes
|
|
output as expected. GUI tests drive the actual chooser/editor, reset/Apply and
|
|
Cancel, replacement layout/init/missing-module rollback, insertion/stale-wire
|
|
failure, deletion and undo. Existing fixture tests exercise 100 live plan
|
|
replacements with host allocation/free/dlopen/dlclose guards. Native ABI tests
|
|
cover process-error silence/reset. The separately installed amp/inverter/sine
|
|
modules remain the qualified LADSPA set. Human listening, arbitrary third-party
|
|
editors/plugins and extended realtime endurance remain unverified.
|
|
|
|
## Phase 26 parameter targets
|
|
|
|
`xtc_control.h` supplies common stable owner/parameter IDs and control-thread
|
|
metadata/edit APIs for track/bus gain/pan and LADSPA input ports. The GUI's plugin
|
|
automation command uses this contract. Linear/hold LADSPA encoding and its
|
|
integer/toggle restrictions remain unchanged; v10 adds mixer interpolation and
|
|
live-sine CC maps without changing plugin state records. Direct static edits do
|
|
not override existing lanes. Live CCs do not map to plugins; missing targets or
|
|
modules fail without installing partial state. See CONTROL.md for timing,
|
|
precedence, reset and backward-jump behavior.
|