[{"data":1,"prerenderedAt":51},["ShallowReactive",2],{"project-omx-panel-skin-package":3},{"page":4,"others":29},{"slug":5,"src":6,"title":7,"source":8,"toc":9,"html":28},"skin-package","docs\u002Fskin-package.md","The skin package","https:\u002F\u002Fgithub.com\u002FFreeMixer\u002Fomx-panel\u002Fblob\u002Fedaf4f48de7e57496ead1b949f754b7e111867e3\u002Fdocs\u002Fskin-package.md",[10,13,16,19,22,25],{"id":11,"title":12},"skin-json","skin.json",{"id":14,"title":15},"applying-it","Applying it",{"id":17,"title":18},"refusals","Refusals",{"id":20,"title":21},"the-licence","The licence",{"id":23,"title":24},"importing-a-mod-gui","Importing a MOD GUI",{"id":26,"title":27},"what-the-validator-checks","What the validator checks","\u003Ch1 id=\"the-skin-package\">The skin package\u003C\u002Fh1>\n\u003Cp>A skin package is a directory named after the plugin it draws. The plugin id is the name. An id that holds a byte\noutside \u003Ccode>A-Z a-z 0-9 . _ -\u003C\u002Fcode> (an LV2 URI) has each such byte written as \u003Ccode>%XX\u003C\u002Fcode>, and \u003Ccode>.\u003C\u002Fcode> and \u003Ccode>..\u003C\u002Fcode> alone are never a\nname, so the name is always safe in a path. One function writes the name, \u003Ccode>encodeSkinId\u003C\u002Fcode> (\u003Ccode>src\u002Fskin-package.ts\u003C\u002Fcode>), and\n\u003Ccode>decodeSkinId\u003C\u002Fcode> reads it back. This package creates \u003Ccode>\u002Fusr\u002Fshare\u002Fomx\u002Fskins\u003C\u002Fcode> for packages to be installed into; which\ndirectories a host reads, and in what order, is the host's.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;plugin id&gt;\u002F\n  skin.json          the override file, required, even if it only names the licence\n  view\u002F              optional: a view of the skin's own\n    index.mjs        an ES module, the entry\n  i18n\u002F&lt;lang&gt;.json   optional: label key to text, one file per language\n  art\u002F               optional: the images the override file names\n  modgui\u002F            optional: a MOD GUI, imported from a bundle or generated\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"skin-json\">skin.json\u003C\u002Fh2>\n\u003Cp>One schema, declared twice in one place: \u003Ccode>schema\u002Fskin-package.schema.json\u003C\u002Fcode> (JSON Schema, what \u003Ccode>omx-panel validate\u003C\u002Fcode>\nchecks) and \u003Ccode>parseSkinOverride\u003C\u002Fcode> (\u003Ccode>src\u002Fskin-package.ts\u003C\u002Fcode>, what a reader applies). A test holds the two to the same\nverdict on every broken copy of the example. A file holds:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>schema\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>1\u003C\u002Fcode>, the file's schema version; any other value refuses the file\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>plugin\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the plugin id; a reader refuses a file whose id is not the directory's decoded name\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>licence\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the SPDX expression of everything in the package; required\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>source\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>where the package came from: \u003Ccode>own\u003C\u002Fcode>, \u003Ccode>generated\u003C\u002Fcode> or \u003Ccode>modgui\u003C\u002Fcode>, with the \u003Ccode>bundle\u003C\u002Fcode> and the \u003Ccode>licenceEvidence\u003C\u002Fcode> an importer read; an imported MOD GUI may name its own icon template and stylesheet, \u003Ccode>modgui: { html, css }\u003C\u002Fcode>, both under \u003Ccode>modgui\u002F\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>fingerprint\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the panel description fingerprint the file was written against; a mismatch is a warning, never a refusal\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>family\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a panel family; replaces the description's\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>roles\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>role (\u003Ccode>time\u003C\u002Fcode>, \u003Ccode>band_freq[2]\u003C\u002Fcode>) to control symbol; replaces the description's roles, each checked against the family\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sections\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>[{ key, labelKey?, label?, controls }]\u003C\u002Fcode>; replaces the sections and their order; a control named nowhere goes to a last section, never hidden\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>labels\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>control symbol to \u003Ccode>{ labelKey?, label? }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>visibleWhen\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>control symbol to \u003Ccode>{ param, values }\u003C\u002Fcode>, the integer values 0 to 63 that show it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>displays\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>[{ kind, section, source, ... }]\u003C\u002Fcode>, the displays the panel draws\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>houseView\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the family of the house view to use\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>look\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the default look; a client's own choice still wins\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>backdrop\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>an image under \u003Ccode>art\u002F\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>view\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ entry, api }\u003C\u002Fcode>: the view's entry under \u003Ccode>view\u002F\u003C\u002Fcode> (\u003Ccode>view\u002Findex.mjs\u003C\u002Fcode>) and the skin API version it was written for, \u003Ccode>MAJOR.MINOR\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>modgui\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ entry }\u003C\u002Fcode> under \u003Ccode>modgui\u002F\u003C\u002Fcode>: the opt-in for a MOD GUI's own script, never beside a \u003Ccode>view\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>A path in a skin is relative to the package and stays inside it: no absolute path, no \u003Ccode>..\u003C\u002Fcode> segment, no backslash.\u003C\u002Fp>\n\u003Cp>The skin restates no range, unit or scale. The plugin's own description holds those, and the skin binds to a\ncontrol by its symbol and its meaning.\u003C\u002Fp>\n\u003Ch2 id=\"applying-it\">Applying it\u003C\u002Fh2>\n\u003Cp>\u003Ccode>applySkinOverride(description, override)\u003C\u002Fcode> applies the file to a panel description field by field. An entry that\nnames an unknown symbol, a role the family has no place for or whose unit it does not take, or a display in no\nsection is dropped and listed with its reason (\u003Ccode>unknown-symbol\u003C\u002Fcode>, \u003Ccode>unknown-section\u003C\u002Fcode>, \u003Ccode>role-not-in-family\u003C\u002Fcode>,\n\u003Ccode>role-unit\u003C\u002Fcode>, \u003Ccode>role-repeats\u003C\u002Fcode>); the rest of the file stands. The role check reads \u003Ccode>ROLES\u003C\u002Fcode> of\n\u003Ccode>tools\u002Fpanel-description.mjs\u003C\u002Fcode>; a caller may pass its own. That table is wider than the console's own at five\nroles: \u003Ccode>threshold\u003C\u002Fcode> of gate, dynamics, limiter and deesser and \u003Ccode>ceiling\u003C\u002Fcode> of limiter take \u003Ccode>db, dbfs, dbu, lufs\u003C\u002Fcode> here and\n\u003Ccode>db, dbfs\u003C\u002Fcode> in the console. A console that keeps its own table passes it as the check; one that adopts this table\naccepts a threshold in dBu or LUFS.\u003C\u002Fp>\n\u003Ch2 id=\"refusals\">Refusals\u003C\u002Fh2>\n\u003Cp>Each refusal is a code with named arguments (\u003Ccode>SKIN_REFUSAL_CODES\u003C\u002Fcode>): \u003Ccode>SKIN_OVERRIDE_INVALID\u003C\u002Fcode> (the file does not parse\nor fails the schema, \u003Ccode>args.field\u003C\u002Fcode> the first field that broke it), \u003Ccode>SKIN_OVERRIDE_ENTRY_DROPPED\u003C\u002Fcode>, \u003Ccode>SKIN_API_MISMATCH\u003C\u002Fcode>,\n\u003Ccode>SKIN_VIEW_ERROR\u003C\u002Fcode>, \u003Ccode>SKIN_VIEW_LINT\u003C\u002Fcode>, \u003Ccode>SKIN_MODGUI_LICENCE\u003C\u002Fcode>, and the importer's own ten, \u003Ccode>MODGUI_*\u003C\u002Fcode> (below).\u003C\u002Fp>\n\u003Ch2 id=\"the-licence\">The licence\u003C\u002Fh2>\n\u003Cp>\u003Ccode>SKIN_LICENCE_ALLOW_LIST\u003C\u002Fcode> is the one list a MOD GUI's licence must be on: GPL-2.0-or-later, GPL-3.0-or-later,\nLGPL-2.1-or-later, LGPL-3.0-or-later, MIT, BSD-2-Clause, BSD-3-Clause, ISC, Zlib, CC0-1.0 and CC-BY-4.0.\n\u003Ccode>spdxFromDoapLicence\u003C\u002Fcode> reads a plugin's \u003Ccode>doap:license\u003C\u002Fcode> as an SPDX id; mapping is not permission.\u003C\u002Fp>\n\u003Ch2 id=\"importing-a-mod-gui\">Importing a MOD GUI\u003C\u002Fh2>\n\u003Cp>\u003Ccode>omx-panel import &lt;bundle.lv2&gt; --out &lt;skins dir&gt; [--plugin &lt;uri&gt;]\u003C\u002Fcode> (\u003Ccode>tools\u002Fmodgui-import.mjs\u003C\u002Fcode>) reads a bundle's\n\u003Ccode>modgui.ttl\u003C\u002Fcode> and the plugin's \u003Ccode>doap:license\u003C\u002Fcode> and writes a package: one section in \u003Ccode>modgui:port\u003C\u002Fcode> order, each port's\n\u003Ccode>lv2:name\u003C\u002Fcode> as its label, the screenshot as the backdrop under \u003Ccode>art\u002F\u003C\u002Fcode>, the whole resources directory and the TTL\nunder \u003Ccode>modgui\u002F\u003C\u002Fcode>, and \u003Ccode>source\u003C\u002Fcode> \u003Ccode>{ kind: &quot;modgui&quot;, bundle, licenceEvidence }\u003C\u002Fcode>. It writes no view, no family and no\nroles. It refuses, with a code that names the file it read and nothing written: \u003Ccode>MODGUI_NOT_FOUND\u003C\u002Fcode>,\n\u003Ccode>MODGUI_TTL_INVALID\u003C\u002Fcode>, \u003Ccode>MODGUI_NO_LICENCE\u003C\u002Fcode>, \u003Ccode>MODGUI_LICENCE_UNMAPPED\u003C\u002Fcode>, \u003Ccode>MODGUI_LICENCE_REFUSED\u003C\u002Fcode> (outside the\nallow-list), \u003Ccode>MODGUI_UNKNOWN_PROPERTY\u003C\u002Fcode>, \u003Ccode>MODGUI_PORT_UNKNOWN\u003C\u002Fcode> (a \u003Ccode>modgui:port\u003C\u002Fcode> or \u003Ccode>modgui:monitoredOutputs\u003C\u002Fcode>\nsymbol the plugin does not carry), \u003Ccode>MODGUI_IMAGE_NOT_FOUND\u003C\u002Fcode> (a template image the bundle does not hold),\n\u003Ccode>MODGUI_PATH_ESCAPE\u003C\u002Fcode> (a resource, or a template reference, outside the bundle) and \u003Ccode>MODGUI_TARGET_EXISTS\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>The known \u003Ccode>modgui:gui\u003C\u002Fcode> properties are mod-ui's whole list, not only the ones a reviewed bundle used:\n\u003Ccode>resourcesDirectory\u003C\u002Fcode>, \u003Ccode>iconTemplate\u003C\u002Fcode>, \u003Ccode>settingsTemplate\u003C\u002Fcode>, \u003Ccode>javascript\u003C\u002Fcode>, \u003Ccode>stylesheet\u003C\u002Fcode>, \u003Ccode>screenshot\u003C\u002Fcode>, \u003Ccode>thumbnail\u003C\u002Fcode>,\n\u003Ccode>documentation\u003C\u002Fcode>, \u003Ccode>discussionURL\u003C\u002Fcode>, \u003Ccode>brand\u003C\u002Fcode>, \u003Ccode>label\u003C\u002Fcode>, \u003Ccode>model\u003C\u002Fcode>, \u003Ccode>panel\u003C\u002Fcode>, \u003Ccode>color\u003C\u002Fcode>, \u003Ccode>knob\u003C\u002Fcode>, \u003Ccode>port\u003C\u002Fcode> and \u003Ccode>monitoredOutputs\u003C\u002Fcode>\n(\u003Ccode>MODGUI_GUI_PROPERTIES\u003C\u002Fcode>). Any other \u003Ccode>modgui:\u003C\u002Fcode> property is \u003Ccode>MODGUI_UNKNOWN_PROPERTY\u003C\u002Fcode>, as is an \u003Ccode>lv2:\u003C\u002Fcode> property on a\n\u003Ccode>modgui:port\u003C\u002Fcode> other than \u003Ccode>index\u003C\u002Fcode>, \u003Ccode>symbol\u003C\u002Fcode> and \u003Ccode>name\u003C\u002Fcode>. The file-valued ones (\u003Ccode>iconTemplate\u003C\u002Fcode>, \u003Ccode>settingsTemplate\u003C\u002Fcode>,\n\u003Ccode>javascript\u003C\u002Fcode>, \u003Ccode>stylesheet\u003C\u002Fcode>, \u003Ccode>screenshot\u003C\u002Fcode>, \u003Ccode>thumbnail\u003C\u002Fcode>, and \u003Ccode>documentation\u003C\u002Fcode> when it names a \u003Ccode>file:\u003C\u002Fcode> IRI;\n\u003Ccode>MODGUI_FILE_PROPERTIES\u003C\u002Fcode>) must resolve inside the bundle and are copied with the package; \u003Ccode>discussionURL\u003C\u002Fcode>, and a\n\u003Ccode>documentation\u003C\u002Fcode> that names a web address, are kept as text and never fetched.\u003C\u002Fp>\n\u003Cp>Every \u003Ccode>modgui:port\u003C\u002Fcode> is checked against the plugin's own \u003Ccode>lv2:port\u003C\u002Fcode> declarations in the bundle, the same TTL the\nimporter reads for the licence: a \u003Ccode>modgui:port\u003C\u002Fcode> whose \u003Ccode>lv2:symbol\u003C\u002Fcode> names no \u003Ccode>lv2:symbol\u003C\u002Fcode> of the plugin's own ports\nis \u003Ccode>MODGUI_PORT_UNKNOWN\u003C\u002Fcode>, naming the file and the symbol, so a renamed or stale modgui entry is never imported as if\nit controlled a real parameter. A \u003Ccode>modgui:monitoredOutputs\u003C\u002Fcode> entry is checked the same way: its \u003Ccode>lv2:symbol\u003C\u002Fcode> (or the\nliteral symbol it names) must be one of the plugin's own ports.\u003C\u002Fp>\n\u003Cp>Every image a template names (\u003Ccode>MODGUI_TEMPLATE_PROPERTIES\u003C\u002Fcode>: the icon template and the settings template; an HTML\n\u003Ccode>src=&quot;...&quot;\u003C\u002Fcode> or \u003Ccode>src='...'\u003C\u002Fcode> or a CSS \u003Ccode>url(...)\u003C\u002Fcode> inside it) is checked. A Mustache suffix like \u003Ccode>{{{ns}}}\u003C\u002Fcode> (mod-ui's\n\u003Ccode>?uri=...\u003C\u002Fcode>), a query and a fragment are dropped, and the path is percent-decoded once and its dot segments resolved,\nas a browser and mod-ui's server do, before anything else is decided. Under mod-ui's \u003Ccode>\u002Fresources\u002F\u003C\u002Fcode> route\n(\u003Ccode>MODGUI_RESOURCES_ROUTE\u003C\u002Fcode>, the bundle's \u003Ccode>resourcesDirectory\u003C\u002Fcode>) a path that climbs out of the resources directory is\n\u003Ccode>MODGUI_PATH_ESCAPE\u003C\u002Fcode>, whether its target exists or not; one that names no file there, the directory itself\nincluded, is \u003Ccode>MODGUI_IMAGE_NOT_FOUND\u003C\u002Fcode>, naming the template file and the path it names. A web address, any other\nabsolute path, or a relative path that climbs out of the bundle is \u003Ccode>MODGUI_PATH_ESCAPE\u003C\u002Fcode>, so a drawn template never\nreaches outside the package. The absolute paths kept are mod-ui's own routes, the directories its static handler\nserves from its html directory, \u003Ccode>\u002Ffonts\u002F\u003C\u002Fcode>, \u003Ccode>\u002Fimg\u002F\u003C\u002Fcode>, \u003Ccode>\u002Fjs\u002F\u003C\u002Fcode>, \u003Ccode>\u002Fcss\u002F\u003C\u002Fcode> and \u003Ccode>\u002Finclude\u002F\u003C\u002Fcode> (\u003Ccode>MODGUI_SERVER_ROUTES\u003C\u002Fcode>): a file\nthe drawing supplies or replaces, kept as text and never fetched. A path holding a Mustache tag anywhere cannot be\nchecked before mod-ui fills it in, so it is kept; a \u003Ccode>data:\u003C\u002Fcode> address and a value that is only a Mustache tag are left\nas they are too. The stylesheet is not checked this way: MOD's shared pedal stylesheets are one theme file covering\nevery knob count and colour a style offers, most of it unused by any one plugin, so most of its \u003Ccode>url()\u003C\u002Fcode>s name no file\nthe bundle carries, on purpose.\u003C\u002Fp>\n\u003Ch2 id=\"what-the-validator-checks\">What the validator checks\u003C\u002Fh2>\n\u003Cp>The shape. That every control id exists in the plugin's description and that a role fits its control is\n\u003Ccode>applySkinOverride\u003C\u002Fcode>'s, against the description it is applied to.\u003C\u002Fp>\n",[30,33,36,39,42,45,48],{"slug":31,"title":32},"","omx-panel",{"slug":34,"title":35},"compare","Compare A\u002FB\u002FC\u002FD and undo",{"slug":37,"title":38},"group-inference","Port-group inference for LV2 plugins",{"slug":40,"title":41},"lv2-complement","The LV2 complement: a replacing bundle with the inferred port groups",{"slug":43,"title":44},"modgui","The MOD GUI generator",{"slug":46,"title":47},"panel-layout","Panel layout from a descriptor",{"slug":49,"title":50},"worked-example","Worked example: a third-party LV2 and CLAP plugin to a generated panel, adapted to Zynthian",1793954996081]