# 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 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.