[{"data":1,"prerenderedAt":33},["ShallowReactive",2],{"note-the-lv2-adapter-against-mod-host-and-jalv-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":32},"the-lv2-adapter-against-mod-host-and-jalv",160,"\"The LV2 adapter against mod-host and jalv: size and structure\"","The LV2 adapter that presents an LV2 plugin as a CLAP plugin makes the same lilv calls in the same order as mod-host and jalv. Its code is smaller and less branched than mod-host's core and 1.4 times jalv's, and nearly all of that difference is features the CLAP contract requires and jalv does not offer.","omx-clap-core's LV2 adapter, mod-host 0.10.6 and jalv 1.10.0 (jalv 1.8.0 for the coverage run), October 2026; sizes by scc 3.5.0 and lizard 1.24.1.","core",[11,14,17,20,23,26,29],{"id":12,"title":13},"what-is-compared","What is compared",{"id":15,"title":16},"size","Size",{"id":18,"title":19},"complexity","Complexity",{"id":21,"title":22},"the-same-tasks-in-the-three","The same tasks in the three",{"id":24,"title":25},"where-the-adapter-is-bigger-and-why","Where the adapter is bigger, and why",{"id":27,"title":28},"coverage","Coverage",{"id":30,"title":31},"in-short","In short","\u003Ch2 id=\"what-is-compared\">What is compared\u003C\u002Fh2>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>subject\u003C\u002Fth>\n\u003Cth>what is measured\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>the adapter\u003C\u002Ftd>\n\u003Ctd>omx-clap-core's \u003Ccode>lv2\u002Fcore\u003C\u002Fcode> and \u003Ccode>lv2\u002Fclap\u003C\u002Fcode>; &quot;with plumbing&quot; adds the six headers of the library it includes\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>mod-host\u003C\u002Ftd>\n\u003Ctd>its \u003Ccode>src\u002F\u003C\u002Fcode>, without the socket protocol, the command line, the completer and the monitor client, and without the libraries it vendors\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>jalv\u003C\u002Ftd>\n\u003Ctd>its \u003Ccode>src\u002F\u003C\u002Fcode>, without \u003Ccode>main.c\u003C\u002Fcode>, the JACK and PortAudio backends and the user-interface frontends\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>mod-host and jalv are applications, each with one audio backend (JACK) and a socket or user-interface front. The adapter is\na library: it presents one LV2 plugin as a CLAP plugin and has no backend. Measuring the host cores without their fronts and\nbackends keeps the three comparable. The script that measures them, \u003Ccode>tools\u002Fhosting-compare.sh\u003C\u002Fcode> in omx-clap-core, clones the\nsubjects and prints every table below.\u003C\u002Fp>\n\u003Ch2 id=\"size\">Size\u003C\u002Fh2>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>subject\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">files\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">lines\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">code\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">comments\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">functions\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">external dependencies\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">public functions\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>the adapter\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">8\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">7657\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">5648\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1160\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">273\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">4\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">90\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>with plumbing\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">14\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">9224\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">6591\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1609\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">316\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">5\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">90\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>mod-host\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">20\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">11248\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">8164\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1341\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">165\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">4\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">84\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>jalv\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">47\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">5492\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">3970\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">631\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">127\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">5\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">90\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>External dependencies are the distinct include roots: the adapter \u003Ccode>clap lilv lv2 omxcontract\u003C\u002Fcode>; mod-host \u003Ccode>jack lilv lv2 mach\u003C\u002Fcode>; jalv \u003Ccode>lilv lv2 serd sratom zix\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"complexity\">Complexity\u003C\u002Fh2>\n\u003Cp>Cyclomatic complexity per function (lizard):\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>subject\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">functions\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">mean CCN\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">p95 CCN\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">max CCN\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">CCN over 15\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">mean lines per function\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>the adapter\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">273\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">6.38\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">19\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">86\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">20\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">17.2\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>with plumbing\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">316\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">6.60\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">19\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">86\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">26\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">17.2\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>mod-host\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">165\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">9.08\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">28\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">127\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">18\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">40.9\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>jalv\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">127\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">3.62\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">9\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">21\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">19.4\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>The most branched functions are the adapter's \u003Ccode>instance_make\u003C\u002Fcode> (CCN 86), \u003Ccode>plugin_process\u003C\u002Fcode> (62), \u003Ccode>read_ports\u003C\u002Fcode> (57) and\n\u003Ccode>read_side\u003C\u002Fcode> (50); mod-host's \u003Ccode>effects_add\u003C\u002Fcode> (127, 736 lines), \u003Ccode>ProcessPlugin\u003C\u002Fcode> (122), \u003Ccode>effects_remove\u003C\u002Fcode> (70) and\n\u003Ccode>RunPostPonedEvents\u003C\u002Fcode> (68); jalv's \u003Ccode>jalv_open\u003C\u002Fcode> (21).\u003C\u002Fp>\n\u003Cp>Lines of code by area (functions matched by name, the count in brackets; a heuristic, since code inside one large function\nis filed under that function's area):\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>area\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">the adapter\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">mod-host\u003C\u002Fth>\n\u003Cth style=\"text-align:right\">jalv\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>worker\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">88 (8)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">78 (6)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">153 (11)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>state, presets and state paths\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">522 (34)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">456 (17)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">177 (8)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>CV\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">8 (2)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">197 (2)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">0 (0)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>port groups, layouts, aux\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">236 (14)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">0 (0)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">0 (0)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>CLAP contract, parameters, descriptor\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">413 (30)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">201 (6)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">0 (0)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>latency, rate, block change\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">157 (17)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">7 (1)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">46 (1)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>atoms, events, MIDI\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">945 (51)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1820 (50)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">599 (41)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>ports and connection\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">386 (14)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">287 (11)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">313 (13)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>errors and refusals\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">114 (9)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">141 (3)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">64 (4)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>lifecycle and shutdown\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">291 (14)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">402 (10)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">188 (12)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>run, process\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">164 (2)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">678 (3)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">54 (2)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>instantiate\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1103 (44)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1339 (12)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">470 (12)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>other\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">278 (34)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">1141 (44)\u003C\u002Ftd>\n\u003Ctd style=\"text-align:right\">399 (23)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"the-same-tasks-in-the-three\">The same tasks in the three\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Instantiate and connect a stereo effect.\u003C\u002Fstrong> All three read the plugin through lilv, instantiate it and connect its\nports. The adapter judges the plugin from its description and refuses it with a code before any binary is opened, and it\nconnects every port once, to memory the instance owns, copying audio in and out each block. mod-host copies too; jalv\nconnects the JACK buffer in place every cycle.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A MIDI instrument on an atom port.\u003C\u002Fstrong> All three write the incoming events into an event buffer before \u003Ccode>run\u003C\u002Fcode> and read the\noutgoing ones after. The adapter's output is bounded by its own capacity.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The worker extension.\u003C\u002Fstrong> The same design in all three: a request ring, a thread, a response ring drained before \u003Ccode>run\u003C\u002Fcode>,\nthen \u003Ccode>end_run\u003C\u002Fcode>. The adapter's is 88 lines against 78 and 153.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>State save and restore.\u003C\u002Fstrong> All three go through \u003Ccode>lilv_state_*\u003C\u002Fcode>. The adapter restores live for a plugin that declares\n\u003Ccode>state:threadSafeRestore\u003C\u002Fcode> and otherwise holds the state and applies it at the next activate; jalv pauses the audio thread\ninstead. The adapter confines a plugin's \u003Ccode>make_path\u003C\u002Fcode> to its state directory and refuses a path outside it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A sample-rate change.\u003C\u002Fstrong> CLAP may activate a plugin again at another rate, so the adapter tells the plugin through\n\u003Ccode>options:interface\u003C\u002Fcode> when it can and instantiates it again when it must. Neither JACK host handles a rate change at run\ntime: both read the rate once. All three handle a change of block length.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>CV and aux ports.\u003C\u002Fstrong> The adapter groups aux, CV and surround ports by \u003Ccode>pg:group\u003C\u002Fcode> and designation and offers them as CLAP\naudio ports. mod-host exposes CV ports as JACK ports and as a control source; jalv as audio ports with CV metadata. Neither\nturns a port group into a bus.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"where-the-adapter-is-bigger-and-why\">Where the adapter is bigger, and why\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Refusal before instantiation\u003C\u002Fstrong>, with a code for each reason: every plugin is judged from its description before its\nbinary is opened. This is the hosting contract.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The CLAP face\u003C\u002Fstrong>: the parameter table, its text and value maps, the contract and the descriptor (413 lines), which the\nothers have no need of.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A live rate or block change\u003C\u002Fstrong> (157 lines against 7 and 46).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Layouts, aux, sidechain and surround\u003C\u002Fstrong> as CLAP audio ports (236 lines against none).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>State\u003C\u002Fstrong>: live and held restore, confined paths and CLAP preset discovery (522 lines against 456 and 177).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A run path with no lock and no allocation\u003C\u002Fstrong>, proven by an allocation witness in the tests; jalv's run path is the same\nin this respect.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Against jalv, the adapter has 1.42 times the code and 1.76 times the mean complexity. The four areas jalv does not have (the\nCLAP face, layouts and aux, a rate change, and the part of state beyond jalv's) come to 1151 lines, which covers most of\nthe 1678-line gap.\u003C\u002Fp>\n\u003Cp>The accidental complexity is in three functions: \u003Ccode>instance_make\u003C\u002Fcode> binds ports by testing every role for every port index,\n\u003Ccode>plugin_process\u003C\u002Fcode> repeats one &quot;host channel or silence&quot; loop for each kind of leg, and \u003Ccode>read_ports\u003C\u002Fcode> and \u003Ccode>read_side\u003C\u002Fcode> hold the\nrule of every port kind in one function each. Building a role table when the description is read, one helper per leg, and\none classifier per port kind would remove most of it, with the existing refusal and allocation tests as the guard.\u003C\u002Fp>\n\u003Ch2 id=\"coverage\">Coverage\u003C\u002Fh2>\n\u003Cp>The same question put to the three hosts: of 979 LV2 plugins installed on one Fedora 44 machine, how many does each host\ninstantiate and activate on a live graph? A private namespace with its own PipeWire at 48 kHz, one plugin at a time, ten\nseconds per reply. Hosted means admitted, instantiated and activated; it is not a claim of identical audio.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>host\u003C\u002Fth>\n\u003Cth>hosted\u003C\u002Fth>\n\u003Cth>not hosted\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>the adapter, through omx-clap-host\u003C\u002Ftd>\n\u003Ctd>979 of 979\u003C\u002Ftd>\n\u003Ctd>none\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>mod-host 0.10.6\u003C\u002Ftd>\n\u003Ctd>975 of 979\u003C\u002Ftd>\n\u003Ctd>drumkv1 (SIGABRT), samplv1 and synthv1 (the host stopped on \u003Ccode>add\u003C\u002Fcode>), zynadd (\u003Ccode>resp -102\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>jalv 1.8.0\u003C\u002Ftd>\n\u003Ctd>977 of 979\u003C\u002Ftd>\n\u003Ctd>zynadd (a MIDI port of a type it does not know), synthv1 (SIGABRT at instantiate)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"in-short\">In short\u003C\u002Fh2>\n\u003Cp>The three hosts make the same lilv calls in the same order: instantiate, connect, a worker of two rings and a thread, and\nstate through \u003Ccode>lilv_state_*\u003C\u002Fcode>. The adapter is smaller and less branched than mod-host's core on every measure above, and\n1.4 times jalv, where the difference is almost all features the CLAP contract requires.\u003C\u002Fp>\n",1793954995938]