[{"data":1,"prerenderedAt":33},["ShallowReactive",2],{"note-why-lv2-plugins-on-zynthian-need-inferred-port-groups-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":32},"why-lv2-plugins-on-zynthian-need-inferred-port-groups",60,"Why most LV2 plugins on a Zynthian need inferred port groups","Half the LV2 plugins installed on a Zynthian have more than four controls and no port groups, so the touchscreen cuts them into screens of four in port order. A generated bundle that replaces the plugin's own description, never its files, can add groups that every Zynthian consumer reads.","A Raspberry Pi 5 running ZynthianOS Vangelis-2610.1, October 2026.","zynthian",[11,14,17,20,23,26,29],{"id":12,"title":13},"the-measured-gap","The measured gap",{"id":15,"title":16},"where-each-zynthian-consumer-reads","Where each Zynthian consumer reads",{"id":18,"title":19},"why-an-added-bundle-cannot-complement-a-plugin","Why an added bundle cannot complement a plugin",{"id":21,"title":22},"the-channel-that-works-a-replacing-bundle","The channel that works: a replacing bundle",{"id":24,"title":25},"which-groups","Which groups",{"id":27,"title":28},"when-a-proxy-is-needed-instead","When a proxy is needed instead",{"id":30,"title":31},"what-proves-it-on-the-device","What proves it on the device","\u003Ch2 id=\"the-measured-gap\">The measured gap\u003C\u002Fh2>\n\u003Cp>On a Zynthian image (ZynthianOS Vangelis-2610.1) 1243 installed LV2 plugins have controls, and only 105 of them declare\nport groups. 623 have no groups and more than four controls, so the touchscreen cuts them into screens of four, in port\norder, whatever the controls are.\u003C\u002Fp>\n\u003Ch2 id=\"where-each-zynthian-consumer-reads\">Where each Zynthian consumer reads\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>MOD-UI\u003C\u002Fstrong> finds a MOD GUI in any loaded file whose subject is the plugin's URI. That is why a generated, separate GUI\nbundle already works.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The touchscreen engine\u003C\u002Fstrong> reads ports through lilv. On each port it reads \u003Ccode>pg:group\u003C\u002Fcode>, and on the group node\n\u003Ccode>lv2:symbol\u003C\u002Fcode> (required: without it every port falls into one &quot;params&quot; group), \u003Ccode>lv2:name\u003C\u002Fcode> (falling back to \u003Ccode>rdfs:label\u003C\u002Fcode>)\nand \u003Ccode>lv2:displayPriority\u003C\u002Fcode>. Screens of four are cut per group, sorted by display priority. It never reads \u003Ccode>pg:mainInput\u003C\u002Fcode>,\n\u003Ccode>lv2:portGroups\u003C\u002Fcode>, \u003Ccode>units:unit\u003C\u002Fcode> or \u003Ccode>rdfs:comment\u003C\u002Fcode> on a port, and no layout file keyed by plugin URI or port symbol exists.\nThe plugin's name, type, category and description are cached per URI after the first scan, so a changed description\nneeds a regenerate from the web configuration.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"why-an-added-bundle-cannot-complement-a-plugin\">Why an added bundle cannot complement a plugin\u003C\u002Fh2>\n\u003Cp>lilv ignores a second bundle that declares a plugin it already loaded (&quot;Duplicate plugin ... ignored&quot;), and a plugin's\nports are anonymous nodes, so a separate file has nothing stable to attach \u003Ccode>pg:group\u003C\u002Fcode> to. Tried on a small envelope plugin,\nthe groups stayed empty.\u003C\u002Fp>\n\u003Ch2 id=\"the-channel-that-works-a-replacing-bundle\">The channel that works: a replacing bundle\u003C\u002Fh2>\n\u003Cp>lilv's duplicate rule is a version comparison, not a flat refusal: a second bundle for the same URI \u003Cstrong>replaces\u003C\u002Fstrong> the one\nalready loaded when its \u003Ccode>lv2:minorVersion\u003C\u002Fcode> or \u003Ccode>lv2:microVersion\u003C\u002Fcode> is newer; an equal or older one is dropped. The test that\nfound empty groups had used an equal version.\u003C\u002Fp>\n\u003Cp>So, for a plugin OpenMixer does not own, the complement is a generated bundle that:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>restates the plugin's own description in full (every port, every parameter, as the plugin states it), read through the\nTurtle parser, never retyped;\u003C\u002Fli>\n\u003Cli>keeps \u003Ccode>lv2:binary\u003C\u002Fcode> pointing at the plugin's installed library: no shim;\u003C\u002Fli>\n\u003Cli>raises \u003Ccode>lv2:microVersion\u003C\u002Fcode> by one over the installed value, so lilv loads it and drops the original;\u003C\u002Fli>\n\u003Cli>adds group nodes, each with \u003Ccode>lv2:symbol\u003C\u002Fcode>, \u003Ccode>lv2:name\u003C\u002Fcode> and \u003Ccode>lv2:displayPriority\u003C\u002Fcode>, attached to the restated ports \u003Cstrong>by port\nsymbol\u003C\u002Fstrong>: symbols are stable, blank nodes are not;\u003C\u002Fli>\n\u003Cli>is marked with a file beside it, so a later run tells its own output from the plugin's bundle and never touches the\nplugin's installed files.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Deleting the marked bundle restores the original: lilv has nothing newer to prefer and loads the plugin's own files again.\nA plugin that already declares its own groups is left alone, with no marker and nothing generated.\u003C\u002Fp>\n\u003Ch2 id=\"which-groups\">Which groups\u003C\u002Fh2>\n\u003Cp>The groups are inferred automatically, over every installed plugin at install time, never laid out by hand per plugin. The\nrules, in order: the plugin's own partial groups; \u003Ccode>lv2:designation\u003C\u002Fcode>; a shared name or symbol prefix (&quot;Band 1 …&quot;, \u003Ccode>b1_\u003C\u002Fcode>);\na shared unit; a contiguous run of port indexes; the plugin's own MOD GUI layout when it has one. A plugin the rules cannot\ngroup with confidence gets no complement at all, never a guessed layout.\u003C\u002Fp>\n\u003Cp>The rules and the bundle writer are specified by omx-panel: \u003Ca href=\"\u002Fprojects\u002Fomx-panel\u002Fgroup-inference\">group inference\u003C\u002Fa> and\n\u003Ca href=\"\u002Fprojects\u002Fomx-panel\u002Flv2-complement\">the LV2 complement\u003C\u002Fa>.\u003C\u002Fp>\n\u003Ch2 id=\"when-a-proxy-is-needed-instead\">When a proxy is needed instead\u003C\u002Fh2>\n\u003Cp>A proxy bundle, with its own URI, ports and groups and a shim forwarding audio, parameters and state to plugin-hostd, is\nonly the exception: for a plugin whose description the Turtle reader cannot parse, whose version field is already at its\nceiling, or whose files declare more than one subject for the same URI.\u003C\u002Fp>\n\u003Ch2 id=\"what-proves-it-on-the-device\">What proves it on the device\u003C\u002Fh2>\n\u003Col>\n\u003Cli>lilv, loaded after the generator, sees the inferred groups on a matched plugin, and the original bundle lost the\nreplace.\u003C\u002Fli>\n\u003Cli>Zynthian's port reader returns those groups, and the touchscreen shows the expected screens.\u003C\u002Fli>\n\u003Cli>A plugin that declares its own full groups loads exactly as before: no marker, the same bytes.\u003C\u002Fli>\n\u003Cli>Deleting a marked bundle brings back the plugin's original files.\u003C\u002Fli>\n\u003Cli>A complemented plugin still runs through plugin-hostd unchanged: add, parameters and state behave as for any LV2\nplugin.\u003C\u002Fli>\n\u003C\u002Fol>\n",1793954995840]