The measured gap
On a Zynthian image (ZynthianOS Vangelis-2610.1) 1243 installed LV2 plugins have controls, and only 105 of them declare port groups. 623 have no groups and more than four controls, so the touchscreen cuts them into screens of four, in port order, whatever the controls are.
Where each Zynthian consumer reads
- MOD-UI finds a MOD GUI in any loaded file whose subject is the plugin's URI. That is why a generated, separate GUI bundle already works.
- The touchscreen engine reads ports through lilv. On each port it reads
pg:group, and on the group nodelv2:symbol(required: without it every port falls into one "params" group),lv2:name(falling back tordfs:label) andlv2:displayPriority. Screens of four are cut per group, sorted by display priority. It never readspg:mainInput,lv2:portGroups,units:unitorrdfs:commenton a port, and no layout file keyed by plugin URI or port symbol exists. The plugin's name, type, category and description are cached per URI after the first scan, so a changed description needs a regenerate from the web configuration.
Why an added bundle cannot complement a plugin
lilv ignores a second bundle that declares a plugin it already loaded ("Duplicate plugin ... ignored"), and a plugin's
ports are anonymous nodes, so a separate file has nothing stable to attach pg:group to. Tried on a small envelope plugin,
the groups stayed empty.
The channel that works: a replacing bundle
lilv's duplicate rule is a version comparison, not a flat refusal: a second bundle for the same URI replaces the one
already loaded when its lv2:minorVersion or lv2:microVersion is newer; an equal or older one is dropped. The test that
found empty groups had used an equal version.
So, for a plugin OpenMixer does not own, the complement is a generated bundle that:
- restates the plugin's own description in full (every port, every parameter, as the plugin states it), read through the Turtle parser, never retyped;
- keeps
lv2:binarypointing at the plugin's installed library: no shim; - raises
lv2:microVersionby one over the installed value, so lilv loads it and drops the original; - adds group nodes, each with
lv2:symbol,lv2:nameandlv2:displayPriority, attached to the restated ports by port symbol: symbols are stable, blank nodes are not; - is marked with a file beside it, so a later run tells its own output from the plugin's bundle and never touches the plugin's installed files.
Deleting the marked bundle restores the original: lilv has nothing newer to prefer and loads the plugin's own files again. A plugin that already declares its own groups is left alone, with no marker and nothing generated.
Which groups
The groups are inferred automatically, over every installed plugin at install time, never laid out by hand per plugin. The
rules, in order: the plugin's own partial groups; lv2:designation; a shared name or symbol prefix ("Band 1 …", b1_);
a shared unit; a contiguous run of port indexes; the plugin's own MOD GUI layout when it has one. A plugin the rules cannot
group with confidence gets no complement at all, never a guessed layout.
The rules and the bundle writer are specified by omx-panel: group inference and the LV2 complement.
When a proxy is needed instead
A proxy bundle, with its own URI, ports and groups and a shim forwarding audio, parameters and state to plugin-hostd, is only the exception: for a plugin whose description the Turtle reader cannot parse, whose version field is already at its ceiling, or whose files declare more than one subject for the same URI.
What proves it on the device
- lilv, loaded after the generator, sees the inferred groups on a matched plugin, and the original bundle lost the replace.
- Zynthian's port reader returns those groups, and the touchscreen shows the expected screens.
- A plugin that declares its own full groups loads exactly as before: no marker, the same bytes.
- Deleting a marked bundle brings back the plugin's original files.
- A complemented plugin still runs through plugin-hostd unchanged: add, parameters and state behave as for any LV2 plugin.