[{"data":1,"prerenderedAt":36},["ShallowReactive",2],{"project-plugin-hostd-own-host":3},{"page":4,"others":20},{"slug":5,"src":6,"title":7,"source":8,"toc":9,"html":19},"own-host","docs\u002Fown-host.md","the highest SCHED_FIFO priority, which the probe asks for, and a lock as large as the probe's memory limit","https:\u002F\u002Fgithub.com\u002FFreeMixer\u002Fplugin-hostd\u002Fblob\u002Fb4ea6a9\u002Fdocs\u002Fown-host.md",[10,13,16],{"id":11,"title":12},"lv2-plugins","LV2 plugins",{"id":14,"title":15},"measuring-plugins-at-real-time-priority","Measuring plugins at real-time priority",{"id":17,"title":18},"what-a-zynthian-integration-still-needs-from-us","What a Zynthian integration still needs from us","\u003Ch1 id=\"use-plugin-hostd-in-your-own-host\">Use plugin-hostd in your own host\u003C\u002Fh1>\n\u003Cp>This page takes a Debian machine (Raspberry Pi OS, Zynthian, any Debian 12 or 13, arm64 or amd64) from nothing to a\nplugin running under plugin-hostd, driven over its socket, in nine commands, and then runs an LV2 plugin the same way in eight more. CI runs every command below, as written, in\na clean Debian container on Debian 12 and 13, on both architectures; a step that stops working fails that job.\u003C\u002Fp>\n\u003Cp>The plugin is a CLAP effect from the \u003Ccode>omx-plugins-clap\u003C\u002Fcode> package, so nothing else has to be installed to try it. Any\nhost that already speaks mod-host's socket protocol can send the same lines.\u003C\u002Fp>\n\u003Cp>The first line of the block is a helper, not counted: one protocol message in, the reply out (a message ends in a NUL\nbyte, which \u003Ccode>printf\u003C\u002Fcode> adds). A trailing \u003Ccode># resp ...\u003C\u002Fcode> or \u003Ccode># has ...\u003C\u002Fcode> is the reply or output CI expects; to you it is a\ncomment.\u003C\u002Fp>\n\u003Cp>The commands, in order. Run them as a normal user with \u003Ccode>sudo\u003C\u002Fcode>, or as root:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sh\">send() { printf '%s\\0' &quot;$1&quot; | nc -w3 127.0.0.1 5555 | tr '\\0' '\\n'; }\ncurl -fsSLO https:\u002F\u002Fwww.openmixer.org\u002Fdeb\u002Fopenmixer-release.deb\nsudo apt-get install -y .\u002Fopenmixer-release.deb\nsudo apt-get update &amp;&amp; sudo apt-get install -y plugin-hostd omx-clap-host omx-plugins-clap jackd2 netcat-openbsd\njackd -r -d dummy &gt;\u002Fdev\u002Fnull 2&gt;&amp;1 &amp; sleep 2\nplugin-hostd -n -p 5555 &gt;plugin-hostd.log 2&gt;&amp;1 &amp; sleep 2\nomx-clap-scan \u002Fusr\u002Flib\u002Fclap\u002Fomx-comp.clap | grep 'param 1 '  # has Ratio [1 .. 20] default 4\nsend &quot;add clap:\u002Fusr\u002Flib\u002Fclap\u002Fomx-comp.clap#org.openmixer.comp 0&quot;   # resp 0\nsend &quot;param_set 0 1 8&quot;   # resp 0\nsend &quot;param_get 0 1&quot;   # resp 0 8.0000\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>What each step did:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Ccode>curl\u003C\u002Fcode> fetches the repository key of the FreeMixer channel. It signs every package, for x86_64 and arm64.\u003C\u002Fli>\n\u003Cli>The \u003Ccode>echo\u003C\u002Fcode> adds the channel for your Debian release (\u003Ccode>bookworm\u003C\u002Fcode> and \u003Ccode>trixie\u003C\u002Fcode> are published).\u003C\u002Fli>\n\u003Cli>\u003Ccode>apt-get install\u003C\u002Fcode> takes the daemon, the CLAP worker \u003Ccode>omx-clap-host\u003C\u002Fcode>, a set of CLAP plugins to try, and JACK. On a\nmachine that already runs JACK (Zynthian does) leave \u003Ccode>jackd2\u003C\u002Fcode> out.\u003C\u002Fli>\n\u003Cli>\u003Ccode>jackd -r -d dummy\u003C\u002Fcode> starts a JACK server that needs no sound card. On a real machine use the one you have.\u003C\u002Fli>\n\u003Cli>\u003Ccode>plugin-hostd -n -p 5555\u003C\u002Fcode> starts the daemon on the command port, 5555. It prints \u003Ccode>plugin-hostd ready!\u003C\u002Fcode> once the port\naccepts. No worker exists until the first \u003Ccode>add\u003C\u002Fcode>. The ports listen on 127.0.0.1; a controller on another machine needs\n\u003Ccode>--bind-address\u003C\u002Fcode> (\u003Ccode>man plugin-hostd\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Ccode>omx-clap-scan\u003C\u002Fcode> reads the plugin's layout: every parameter, its number, range and default. The number is the symbol\nthe protocol takes, so the compressor's Ratio is \u003Ccode>1\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>add\u003C\u002Fcode> loads the plugin as instance \u003Ccode>0\u003C\u002Fcode>. The daemon starts a worker of its own for it, a JACK client named\n\u003Ccode>effect_0\u003C\u002Fcode>, and puts it back if it dies.\u003C\u002Fli>\n\u003Cli>\u003Ccode>param_set\u003C\u002Fcode> sets Ratio to 8.\u003C\u002Fli>\n\u003Cli>\u003Ccode>param_get\u003C\u002Fcode> reads it back.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"lv2-plugins\">LV2 plugins\u003C\u002Fh2>\n\u003Cp>Zynthian's plugins are mostly LV2. \u003Ccode>omx-clap-host\u003C\u002Fcode>, which you already installed, also runs an LV2 plugin through its LV2\nadapter; the LV2 build of the same plugins is the package \u003Ccode>omx-plugins-lv2\u003C\u002Fcode>. Continue in the same shell, with the\n\u003Ccode>send\u003C\u002Fcode> helper of the first block and JACK still running. \u003Ccode>lv2_worker\u003C\u002Fcode> names the program that runs LV2 plugins, and\n\u003Ccode>mod-host\u003C\u002Fcode> is the default, so the daemon is restarted with \u003Ccode>omx-clap-host\u003C\u002Fcode> named in its configuration file. An LV2\nplugin is added by its URI with an \u003Ccode>lv2:\u003C\u002Fcode> prefix, and its parameters are named by their LV2 symbol (\u003Ccode>ratio\u003C\u002Fcode>), not by a\nnumber.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sh\">send &quot;quit&quot;; sleep 1   # resp 0\nsudo apt-get install -y omx-plugins-lv2\necho &quot;lv2_worker omx-clap-host&quot; &gt; plugin-hostd.conf\nplugin-hostd -n -p 5555 -c plugin-hostd.conf &gt;plugin-hostd-lv2.log 2&gt;&amp;1 &amp; sleep 2\nsend &quot;add lv2:urn:openmixer:comp 0&quot;   # resp 0\nsend &quot;param_set 0 ratio 8&quot;   # resp 0\nsend &quot;param_get 0 ratio&quot;   # resp 0 8.0000\nsend &quot;worker_list&quot;   # has :lv2-clap:\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n\u003Cli>\u003Ccode>quit\u003C\u002Fcode> stops the daemon and the worker of the first block.\u003C\u002Fli>\n\u003Cli>\u003Ccode>apt-get install\u003C\u002Fcode> adds the LV2 bundles under \u003Ccode>\u002Fusr\u002Flib\u002Flv2\u003C\u002Fcode>, where the daemon searches.\u003C\u002Fli>\n\u003Cli>The configuration file makes \u003Ccode>omx-clap-host\u003C\u002Fcode> the worker of LV2 plugins.\u003C\u002Fli>\n\u003Cli>The daemon starts again with it.\u003C\u002Fli>\n\u003Cli>\u003Ccode>add\u003C\u002Fcode> loads the plugin by its URI as instance \u003Ccode>0\u003C\u002Fcode>, in a worker of its own.\u003C\u002Fli>\n\u003Cli>and 7. \u003Ccode>param_set\u003C\u002Fcode> and \u003Ccode>param_get\u003C\u002Fcode> set and read back the compressor's Ratio by its symbol.\u003C\u002Fli>\n\u003Cli>\u003Ccode>worker_list\u003C\u002Fcode> names the worker of each instance; \u003Ccode>lv2-clap\u003C\u002Fcode> is \u003Ccode>omx-clap-host\u003C\u002Fcode> running an LV2 plugin.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>CI also sends a sine into the instance's JACK ports and measures what comes out, with the Makeup gain set to 6 dB and\nRatio to 1: the output peak must be 1.995 times the input's.\u003C\u002Fp>\n\u003Cp>Pins need nothing from you. A plugin has to be pinned (its files' hashes and its parameter layout) before the daemon\nlets it run; in the default \u003Ccode>first-use\u003C\u002Fcode> mode the first \u003Ccode>add\u003C\u002Fcode> records the pin in \u003Ccode>$XDG_STATE_HOME\u002Fplugin-hostd\u002Fpins\u003C\u002Fcode>, and\na later \u003Ccode>add\u003C\u002Fcode> of a changed file is refused with a \u003Ccode>pin_changed\u003C\u002Fcode> event. A host that pins by itself sets \u003Ccode>pin_mode strict\u003C\u002Fcode>.\nSee Pins in the \u003Ca href=\"\u002Fprojects\u002Fplugin-hostd\">README\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>What to read next: \u003Ccode>man plugin-hostd\u003C\u002Fcode>; the verbs and error codes in the README; \u003Ccode>remote_pages\u003C\u002Fcode> and \u003Ccode>remote_page_get\u003C\u002Fcode> for\nthe plugin's own pages of controls; the machine-readable protocol at\n\u003Ccode>\u002Fusr\u002Fshare\u002Fplugin-hostd-protocol\u002Fplugin-hostd.json\u003C\u002Fcode>; a C consumer can include \u003Ccode>plugin-hostd\u002Fprotocol.h\u003C\u002Fcode>\n(\u003Ccode>pkg-config --cflags plugin-hostd-protocol\u003C\u002Fcode>, package \u003Ccode>libplugin-hostd-protocol-dev\u003C\u002Fcode>).\u003C\u002Fp>\n\u003Ch2 id=\"measuring-plugins-at-real-time-priority\">Measuring plugins at real-time priority\u003C\u002Fh2>\n\u003Cp>\u003Ccode>plugin-hostd-probe\u003C\u002Fcode> measures a plugin on a SCHED_FIFO thread at the highest priority it is granted, with its memory\nlocked, as JACK or PipeWire runs it; without a grant the figures are taken at ordinary priority, and the record says so\n(\u003Ccode>run.realtime.class: none\u003C\u002Fcode> and the reason). On Zynthian and on the console, plugin-qualify's sync service\n(\u003Ccode>plugin-qualify-sync.service\u003C\u002Fcode>) runs the probe; it passes the grant with a drop-in,\n\u003Ccode>\u002Fetc\u002Fsystemd\u002Fsystem\u002Fplugin-qualify-sync.service.d\u002Frealtime.conf\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-ini\">[Service]\n# the highest SCHED_FIFO priority, which the probe asks for, and a lock as large as the probe's memory limit\nLimitRTPRIO=99\nLimitMEMLOCK=infinity\n# the unit forbids real time to everything it starts; the probe bounds it instead (RLIMIT_RTPRIO, RLIMIT_RTTIME)\nRestrictRealtime=no\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>then \u003Ccode>sudo systemctl daemon-reload\u003C\u002Fcode>. Where the distribution lets a service hold capabilities,\n\u003Ccode>AmbientCapabilities=CAP_SYS_NICE\u003C\u002Fcode> grants the same priority without the rlimit; a CI job whose container holds CAP_SYS_NICE\nand no CAP_SYS_RESOURCE runs the probe's real-time arm on that route, so the record's \u003Ccode>grantedBy: cap\u003C\u002Fcode> is measured on every\npush. A unit without \u003Ccode>User=\u003C\u002Fcode> runs the probe as root: the measured child then has every capability and the bounding set dropped, and \u003Ccode>run.realtime.capabilityBound\u003C\u002Fcode> says whether that held (\u003Ccode>false\u003C\u002Fcode> when the runner lacks \u003Ccode>CAP_SETPCAP\u003C\u002Fcode>, where only a \u003Ccode>User=\u003C\u002Fcode> with \u003Ccode>AmbientCapabilities=\u003C\u002Fcode> keeps the bound). \u003Ccode>plugin-qualify --all\u003C\u002Fcode>, run by hand,\ngets the grant of the user's login session: the \u003Ccode>rtprio\u003C\u002Fcode> and \u003Ccode>memlock\u003C\u002Fcode> of \u003Ccode>\u002Fetc\u002Fsecurity\u002Flimits.d\u002F\u003C\u002Fcode>, as for JACK (for\nexample the \u003Ccode>audio\u003C\u002Fcode> group's). \u003Ccode>plugin-hostd-probe run --list-limits\u003C\u002Fcode> prints what a run would get: the \u003Ccode>realtime\u003C\u002Fcode> line\nnames the grant and the priority, or why there is none.\u003C\u002Fp>\n\u003Cp>The probe's memory limit is \u003Ccode>memory.max\u003C\u002Fcode> of a cgroup it makes for the measured child. A service running as root needs nothing\nfor that. A service under another user needs \u003Ccode>Delegate=memory\u003C\u002Fcode> in the same drop-in; without it the limit is \u003Ccode>RLIMIT_DATA\u003C\u002Fcode>, and\nthe document says so under \u003Ccode>limits.unavailable\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"what-a-zynthian-integration-still-needs-from-us\">What a Zynthian integration still needs from us\u003C\u002Fh2>\n\u003Cp>Checked in a clean Debian container against published packages. The first item against 0.4.0 of the daemon and 0.10.0 of\nthe worker and plugins (the page's CI); the others against 0.2.1 of the daemon and 0.8.0 of the worker and plugins, on\ntrixie, and not re-checked since:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>An LV2 plugin that the LV2 adapter of \u003Ccode>omx-clap-host\u003C\u002Fcode> refuses needs mod-host, which neither Debian nor the FreeMixer\nchannel packages. The add answers \u003Ccode>-512\u003C\u002Fcode>; \u003Ccode>lv2_worker auto\u003C\u002Fcode> runs such a plugin in mod-host when one is installed.\u003C\u002Fli>\n\u003Cli>\u003Ccode>param_info\u003C\u002Fcode> answers \u003Ccode>-511\u003C\u002Fcode> (the worker holds no checked unit and scale) for every parameter of a CLAP instance with\nthe released worker, 0.8.0; the daemon passes the answer through. A host reads ranges and defaults from\n\u003Ccode>omx-clap-scan\u003C\u002Fcode> (or \u003Ccode>omx-clap-scan --json\u003C\u002Fcode>) and not from the socket, until a worker answers \u003Ccode>resp 0\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Parameters are named by their CLAP number (\u003Ccode>0\u003C\u002Fcode>, \u003Ccode>1\u003C\u002Fcode>, ...), not by a name; a host maps names to numbers from the scan.\u003C\u002Fli>\n\u003Cli>No Zynthian image was available to CI: the check is on plain Debian arm64, not on Zynthian's own userland (its jackd,\nits audio configuration, its realtime limits).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each of these is an issue of its own.\u003C\u002Fp>\n",[21,24,27,30,33],{"slug":22,"title":23},"","README.md",{"slug":25,"title":26},"formats","docs\u002Fformats.md",{"slug":28,"title":29},"panel","docs\u002Fpanel.md",{"slug":31,"title":32},"placement","docs\u002Fplacement.md",{"slug":34,"title":35},"presets","Presets in the host",1793954996698]