plugin-hostd-protocol

doc/protocol.md

The project's own text, in English. The source of this page

The plugin-hostd protocol

The plugin-hostd protocol is a superset of mod-host's text protocol: any mod-host client works unchanged.

Protocol plugin-hostd, version 1 of its JSON declaration (protocol/plugin-hostd.json); mod-host's protocol as the daemon models it is protocol/mod-host.json, version 1, written from the mod-host headers mod-host.h, protocol.h, host-errors.h the daemon is built against. The verbs the daemon adds are declared in data/verbs.json (since 0.12.0, requiring omx-contract-schema >= 3.3.0, omx-clap-core >= 0.23.0).

  1. Transport

what value meaning
command port TCP, 5555 without -p the command port without -p; the feedback port is the next one, unless -n is given
feedback port TCP the next port after the command port, unless -n is given; -f <port> names it
daemon ready plugin-hostd ready! printed on stdout once both ports accept
worker ready ready! the end of the line a worker prints once its socket accepts
worker command line -n -p <command port> -f <feedback port> how the daemon starts every worker
line_max 4096 bytes the protocol socket's buffer: the longest pin_set line the daemon takes
line_words_max 64 words the words of one command line the daemon reads, the verb included; a word past them is not read
string_max 255 bytes the longest string word

A command is one line of words, the verb first; a reply is resp <code> [...]: the words after the code are the verb's reply grammar below, and a negative code is a refusal, named in Reply codes. Events go out on the feedback port, one line each (4. Events and feedback).

Discovery: the daemon declares itself in files, not on the socket. The verb tables below are installed as:

file in this tree installed as
grammar/plugin-hostd-protocol.abnf /usr/share/plugin-hostd-protocol/plugin-hostd-protocol.abnf
protocol/mod-host.json /usr/share/plugin-hostd-protocol/mod-host.json
protocol/plugin-hostd-protocol.json /usr/share/plugin-hostd-protocol/plugin-hostd-protocol.json
protocol/plugin-hostd.json /usr/share/plugin-hostd-protocol/plugin-hostd.json
protocol/plugin-hostd.schema.json /usr/share/plugin-hostd-protocol/plugin-hostd.schema.json

pkg-config --variable=protocoldir plugin-hostd-protocol names that directory, and --variable=table, grammar and protocoljson name the files. The protocol and version keys of the protocol JSON identify it; no verb below answers a version.

The verb table and the grammar

protocol/plugin-hostd-protocol.json is the one table of the protocol: every command, each once, with its origin, its argument types, its reply and its refusals, and every feedback line. It is merged from protocol/mod-host.json, protocol/plugin-hostd.json, data/verbs.json, include/plugin-hostd/omx_clap_core_host_verbs.h by tools/protocol-table.py; 48 commands of origin mod-host, 27 commands of origin plugin-hostd, 11 commands of origin omx-clap-core, 21 feedback lines.

grammar/plugin-hostd-protocol.abnf is the grammar of a line, in ABNF (RFC 5234, with RFC 7405's case-sensitive strings): the lexical core, written once by hand (grammar/core.abnf), and a rule per command, per feedback line and per parse refusal generated from the table by tools/abnf-gen.py. Every line of the scenario corpus and every reply the library writes in the golden transcript is held to it (make test-grammar).

A word of another origin that mod-host also has either extends mod-host's command (takes its arguments, in its types, first, and more only where mod-host's command takes a tail), or is a second form of the word told apart from mod-host's by its number of arguments, so that a line of mod-host's form is always answered as mod-host answers it. Any other is refused: the table is not written, and there is no list of exceptions. The words with more than one form:

verb form origin reply codes
preset_show preset_show word mod-host as mod-host answers it -
preset_show preset_show int word plugin-hostd resp 0 <json> -523 PRESET_NOT_OFFERED; -524 PRESET_INVALID; -3 reply.host.instance-non-exists; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation
  1. mod-host's verbs on plugin-hostd

required is answered as its grammar says; optional is answered, or refused with -902, and never misread; absent is refused on purpose, and no client of the profile may need it.

Answer: same is relayed to the worker that holds the instance (any other verb to every worker) and answered as that worker answers it; differs is a verb plugin-hostd declares a grammar or a behaviour of its own for, or one the Path A run observed answered otherwise on the LV2 worker; refused is absent of every profile. The argument types are mod-host's templates (int %i, word %s, float %f, ... an optional tail); the declarations carry no unit per argument, and a range or a unit a verb has is in its meaning.

mod-host's templates name no reply codes per verb: a relayed verb answers what its worker answers, or one of -503 WORKER_SPAWN (the worker is not up: the spawn failed, or it is in backoff; ask again after instance_restored) or -507 VERB_DROPPED (the worker died on this very command and the daemon dropped it: it is not replayed, and the instance_verb_dropped event names it; or it died on, or did not answer in rpc_timeout_ms, the pin_expect of an add, which is then never forwarded).

Required of every profile

verb mod-host arguments levels answer what the data says
add word int ... required (every profile) differs plugin-hostd declares it: <uri> <instance> [own | pool:<name> | chain:<chain> | default] [client_name] → resp <instance>; mod-host's add with a placement; a token after the instance that is not a placement is the jack client name<br>Path A: none; no
remove int required (every profile) same Path A: none; no
preset_load int word required (every profile) differs Path A: LV2: user presets fail after a pedalboard reload; CLAP: yes, presets
preset_save int word word word required (every profile) same Path A: CLAP: user presets cannot be saved
preset_show word required (every profile) same Path A: CLAP: no<br>a second form, preset_show int word (plugin-hostd), told apart by its number of arguments: The verb table and the grammar
connect word word required (every profile) differs Path A: LV2: 2 replies differ, and the pedalboard's output path through mod-monitor hangs on one worker; CLAP: yes, no audio routing
disconnect word word required (every profile) same -
bypass int int required (every profile) same Path A: none; no
param_set int word word required (every profile) same Path A: CLAP: yes, every restored and moved parameter refused (pedalboard load ignores the code, mod/host.py:3849 has no callback)
set_bpm float required (every profile) same a host setting (TEMPO), kept and handed to every new worker: Host settings
set_bpb float required (every profile) same a host setting (TEMPO), kept and handed to every new worker: Host settings
cpu_load none required (every profile) same Path A: not compared beyond the code (a load figure is timing, OWED on hardware)
bundle_add word required (every profile) same Path A: CLAP: no (mod-ui ignores it)
bundle_remove word word required (every profile) same Path A: as bundle_add
state_load word required (every profile) same Path A: CLAP: yes, plugin state not restored with the pedalboard
state_save word required (every profile) same Path A: none; no
state_tmpdir word required (every profile) same Path A: CLAP: no
feature_enable word int required (every profile) differs Path A: LV2: a worker started later never hears it; CLAP: processing on/off refused
transport int float float required (every profile) same Path A: none, but the transport feedback comes once per worker (6 against 1 in the session)<br>a host setting (TEMPO), kept and handed to every new worker: Host settings
transport_sync word required (every profile) same Path A: none; no<br>a host setting (TEMPO), kept and handed to every new worker: Host settings
quit none required (every profile) same -

Optional

verb mod-host arguments levels answer what the data says
param_get int word optional (every profile) same -
param_monitor int word word float optional (every profile) same relay rule SUBSCRIPTION: monitor_output is in the ledger, one line per output, and param_monitor as it was sent; a respawn replays them after the add, so a respawned instance reports the same outputs, each once, starting again from its first value
patch_get int word optional (every profile) same Path A: none for the probe plugin; a plugin with patch parameters is untested; no
patch_set int word word optional (every profile) same Path A: as patch_get
licensee int optional (every profile) same -
monitor word int int optional (every profile) same -
monitor_output int word optional (every profile) same Path A: CLAP: no meters, and no refusal<br>relay rule SUBSCRIPTION: monitor_output is in the ledger, one line per output, and param_monitor as it was sent; a respawn replays them after the add, so a respawned instance reports the same outputs, each once, starting again from its first value
monitor_midi_program int int optional (every profile) same Path A: CLAP-only rig: pedalboard change by MIDI program lost
midi_learn int word float float optional (every profile) same Path A: CLAP: MIDI learn through mod-ui lost
midi_map int word int int float float optional (every profile) same Path A: CLAP: saved MIDI maps not restored
midi_unmap int word optional (every profile) same Path A: as midi_map
cv_map int word word float float word optional (every profile) same Path A: CLAP: CV addressing lost
cv_unmap int word optional (every profile) same Path A: as cv_map
load word optional (every profile) same -
save word optional (every profile) same -
output_data_ready none optional (every profile) same Path A: CLAP: no<br>relay rule HANDSHAKE: output_data_ready names no instance and goes to every worker; each worker's data_finish is relayed
track_info int word word ... optional (every profile) differs plugin-hostd declares it: <instance> <name> <#RRGGBB | -> [bus | return | master] → resp 0; the strip's name ("" for none), colour and kind, absent for an input channel; a worker whose plugin format has a track-info extension passes them on; any other word is -902
remote_pages int optional (every profile) differs plugin-hostd declares it: <instance> → resp <count>; the plugin's remote-control pages, from clap.remote-controls; 0 for an instance of a format whose worker has none
remote_page_get int int optional (every profile) differs plugin-hostd declares it: <instance> <page> → resp 0 <page_id> <section> <page_name> <s1> <s2> <s3> <s4> <s5> <s6> <s7> <s8>; one page, 0 to count - 1: each slot the symbol param_set takes, - for an empty one; -902 for a page that is not one
param_info int word optional (every profile) differs plugin-hostd declares it: <instance> <symbol> → resp 0 <unit> <scale> <min> <max> <default> <step> <stable_symbol>; what a parameter's value means: its unit, its scale, and the symbol it keeps across hosts and versions; -103 for a symbol that is not a parameter, the plugin's own bypass and :bypass included; -511 for a parameter whose unit and scale the worker holds no checked contract for
help none optional (every profile) same -

Absent: refused on purpose

verb mod-host arguments levels answer what the data says
cc_map int word int int word float float float int int word int ... absent (every profile) refused absent of every profile: refused on purpose<br>Path A: none on Zynthian (no Control Chain hardware); no
cc_value_set int word float absent (every profile) refused absent of every profile: refused on purpose<br>Path A: as cc_map
cc_unmap int word absent (every profile) refused absent of every profile: refused on purpose<br>Path A: as cc_map
hmi_map int word int int int int int word float float int absent (every profile) refused absent of every profile: refused on purpose<br>Path A: none on Zynthian (no MOD HMI); no
hmi_unmap int word absent (every profile) refused absent of every profile: refused on purpose<br>Path A: as hmi_map
show_external_ui int absent (every profile) refused absent of every profile: refused on purpose<br>Path A: none; no

Verbs of Zynthian's mod-ui that are no mod-host command, each refused:

verb levels Path A
set_midi_program_change_pedalboard_bank_channel absent (every profile) dead code in Zynthian's mod-ui; the only verbs of mod-ui's source missing from protocol/mod-host.json
set_midi_program_change_pedalboard_snapshot_channel absent (every profile) as above

The Path A evidence

doc/evidence/mod-ui-verbs.tsv: every verb Zynthian's mod-ui sends, replayed against mod-host and against plugin-hostd. The column heads name the versions that ran; the rows are what was observed then, not run again by this page.

verb sent by mod-ui (zynthian/mod-ui 817a672 file:line; times in the session) LV2 worker (plugin-hostd + mod-host 12335c2) CLAP worker (plugin-hostd + omx-clap-host 0.12.0) difference from mod-host (and: breaks a CLAP pedalboard?)
add mod/host.py:2581, :3849 (pedalboard load); 10 resp <n>, as mod-host resp <n> none; no
remove mod/host.py:1083 (remove -1), :2305; 7 resp 0, as mod-host resp 0 none; no
connect mod/host.py:2218, :3409; 24 resp 0 between instance ports; resp -902 for system:capture_N -> mod-monitor:in_N (no instance owns either port; diff.txt); mod-monitor exists only while the first LV2 worker, the one JACK names mod-host, lives (mod-host src/effects.c:3922), -205 once it is removed (monitor-owner.txt) resp -205 for 22 of 24 in the replay: mod-ui writes the LV2 port symbols, the CLAP worker's ports are named otherwise; -205 for mod-monitor (probe.tsv) LV2: 2 replies differ, and the pedalboard's output path through mod-monitor hangs on one worker; CLAP: yes, no audio routing
bypass mod/host.py:2161, :2692; 10 resp 0, as mod-host resp 0 none; no
param_set mod/host.py:2172, :2715; 40 resp 0, as mod-host resp -103 for all 40 (LV2 symbols; the CLAP worker takes the parameter number); resp 0 with a number (probe.tsv) CLAP: yes, every restored and moved parameter refused (pedalboard load ignores the code, mod/host.py:3849 has no callback)
patch_get mod/host.py:2720; 0 resp -902, as mod-host (the probe plugin has no patch parameter) resp -902 none for the probe plugin; a plugin with patch parameters is untested; no
patch_set mod/host.py:2189, :2730; 0 resp -902, as mod-host resp -902 as patch_get
preset_load mod/host.py:2166, :3867; 5 resp 0 in one worker; resp -105 for a user preset when the instance's worker started after the preset's bundle_add (diff.txt, 2 of 5) resp -902 LV2: user presets fail after a pedalboard reload; CLAP: yes, presets
preset_save mod/host.py:2925; 2 resp 0, as mod-host resp -902 CLAP: user presets cannot be saved
preset_show mod/host.py:2829; 2 the preset's Turtle, identical resp -902 CLAP: no
bundle_add mod/host.py:2240; 2 resp 0, as mod-host (not replayed to later workers, see preset_load) resp -902 CLAP: no (mod-ui ignores it)
bundle_remove mod/host.py:2260; 0 resp 0, as mod-host resp -902 as bundle_add
state_load mod/host.py:2145, :3665; 3 resp 0, as mod-host resp -105 (2 of 3 in the replay); resp -105 in the probe CLAP: yes, plugin state not restored with the pedalboard
state_save mod/host.py:4012; 2 resp 0, as mod-host resp 0 none; no
state_tmpdir mod/host.py:1086; 1 resp 0, as mod-host resp -902 in the probe (resp 0 in the replay, sent before any CLAP worker existed) CLAP: no
feature_enable mod/host.py:3489 (aggregated-midi), :5689 (processing); 17 processing: resp 0; aggregated-midi: -202 from a worker, as mod-host here (the pod has no mod-midi-merger internal client); with no worker running the daemon answers 0 and keeps nothing resp -902 LV2: a worker started later never hears it; CLAP: processing on/off refused
transport mod/host.py:2028, :4656; 7 resp 0, as mod-host resp 0 none, but the transport feedback comes once per worker (6 against 1 in the session)
transport_sync mod/host.py:4565; 1 resp 0, as mod-host resp 0 none; no
monitor_output mod/host.py:2180, :2565; 0 resp 1, as mod-host resp 0 for any symbol CLAP: no meters, and no refusal
monitor_midi_program mod/host.py:1126; 0 resp 0, as mod-host resp -902 CLAP-only rig: pedalboard change by MIDI program lost
midi_learn mod/host.py:4922; 0 resp 1, as mod-host resp -902 CLAP: MIDI learn through mod-ui lost
midi_map mod/host.py:764, :2164; 0 resp 1, as mod-host resp -902 CLAP: saved MIDI maps not restored
midi_unmap mod/host.py:807; 0 resp 1, as mod-host resp -902 as midi_map
cc_map mod/host.py:748; 0 resp -401, as mod-host (no Control Chain) resp -902 none on Zynthian (no Control Chain hardware); no
cc_value_set mod/host.py:868; 0 resp -401, as mod-host resp -902 as cc_map
cc_unmap mod/host.py:803; 0 resp -401, as mod-host resp -902 as cc_map
cv_map mod/host.py:780, :2204; 0 resp 0, as mod-host resp -902 CLAP: CV addressing lost
cv_unmap mod/host.py:811; 0 resp 0, as mod-host resp -902 as cv_map
hmi_map mod/host.py:689; 0 resp -403, as mod-host (no HMI) resp -902 none on Zynthian (no MOD HMI); no
hmi_unmap mod/host.py:694; 0 resp -403, as mod-host resp -902 as hmi_map
cpu_load mod/host.py:5736, mod/session.py:354; 0 resp 0 ..., as mod-host resp 0 ... not compared beyond the code (a load figure is timing, OWED on hardware)
output_data_ready mod/host.py:1888; 2 resp 0, as mod-host resp -902 in the probe (resp 0 in the replay, broadcast) CLAP: no
show_external_ui mod/session.py:349; 0 resp -902, as mod-host resp -902 none; no
set_midi_program_change_pedalboard_bank_channel mod/host.py:4735, commented out: never sent - - dead code in Zynthian's mod-ui; the only verbs of mod-ui's source missing from protocol/mod-host.json
set_midi_program_change_pedalboard_snapshot_channel mod/host.py:4739, commented out: never sent - - as above
  1. Verbs mod-host does not have

Each is declared in protocol/plugin-hostd.json; the formats, codes and hosts come from data/verbs.json, or from omx-clap-core's verb table for a verb the two hosts share. A verb whose meaning begins "sent by the daemon to a worker" is the daemon's to a worker, never a controller's.

Placement and supervision

verb arguments types reply codes since (the group's) hosts meaning
worker_list none none resp <n> <worker>:<pid>:<format>:<state>:<core>:<pool>:<place>:<i>,<i>,... -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the workers, one record each
instance_info <instance> int resp 0 <worker> <pid> <state> <crashes> <quarantined> -3 reply.host.instance-non-exists; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required where an instance lives and what it has cost
supervisor_reset [<worker> | all] ... resp 0 -506 NO_SUCH_WORKER; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required re-arm the storm bound and the backoff of a given-up placement
quarantine_clear <instance> | all word resp 0 -3 reply.host.instance-non-exists; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required let a quarantined instance go back where its placement says
policy_set <format | *> own | pool:<name> word word resp 0 -501 PLACEMENT_INVALID; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the placement of an add that says default
pool_config <name> <max_instances> word int resp 0 -501 PLACEMENT_INVALID; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the instances a pool holds before a sibling opens
worker_env <format | *> <cpu-list|-> <nice|-> word word word resp 0 -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the cpu list and the nice value of the workers of a format, at spawn and on the ones running
lv2_worker_set <uri> <mod-host | omx-clap-host | auto | -> word word resp 0 -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the worker of one LV2 plugin, for its adds from now on: it overrides the lv2_worker setting, - takes the override back; -902 for a word that is none of these. Like policy_set it is the controller's, not in the ledger, gone with the daemon

worker_list:

> worker_list
< resp <n> <worker>:<pid>:<format>:<state>:<core>:<pool>:<place>:<i>,<i>,...

instance_info:

> instance_info 0
< resp 0 <worker> <pid> <state> <crashes> <quarantined>

supervisor_reset:

> supervisor_reset
< resp 0

quarantine_clear:

> quarantine_clear 0
< resp 0

policy_set:

> policy_set clap own
< resp 0

pool_config:

> pool_config fx 4
< resp 0

worker_env:

> worker_env clap - -
< resp 0

lv2_worker_set:

> lv2_worker_set lv2:urn:openmixer:comp omx-clap-host
< resp 0

Pins and qualification

verb arguments types reply codes since (the group's) hosts meaning
pin_set <uri> <path>=<sha256>[,<path>=<sha256>...] <scheme>:<sha256> [verdict=<verdict>] [pool=<pool>] word word word ... resp 0 -516 PIN_REFUSED; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the pin of one plugin: the SHA-256 of every file the host loads for it, a path relative to the bundle (the directory of a single-file format's file), its layout fingerprint and, when they are known, the verdict of its qualification and whether its worker gets the CLAP thread pool; it replaces an earlier pin of the uri; -516 for a plugin whose pins_dir pin carries verdict=refused, which nothing overrides. The pin store, a pin_file and a pins_dir file hold the same line, with or without the verb
pin_clear <uri> | all word resp 0 -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required forget the pin of one plugin, or of every one; a pins_dir pin is read-only and holds again
pin_reload none none resp 0 -902 reply.host.invalid-operation 0.12.0 plugin-hostd required read the pin_file and the pins_dir files again and put the pins in at once, without a restart; the daemon also does this by itself when one of those files changes. Running instances keep running: one the new pins refuse is logged and its id in worker_list is followed by !. The pin_set pins of the controller are kept. -902 under pin_mode off or when the pin_file is gone or has a line outside the grammar, and the earlier pins hold
pin_changed [<uri>] [files | layout] ... resp 0 -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the notice the package sync sends to every daemon it knows after it rewrote the pin file: the same as pin_reload, with or without the pin_changed event's words after it; more words than those are -902
pin_accept <uri> word resp 0 -508 PIN_ABSENT; -509 PIN_BINARY_MISMATCH; -516 PIN_REFUSED; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required pin_mode first-use only: take the plugin's files as they are now as its pin, after a pin_changed event; the layout is learned again by the next add and the verdict is dropped; -508 when the plugin has no pin, -509 when its files cannot be collected, -516 when its pins_dir pin carries verdict=refused, -902 under pin_mode strict
pin_layout <instance> int resp 0 <scheme>:<sha256> - 0.12.0 omx-clap-host optional, mod-host unsupported sent by the daemon to a worker of a format that checks layouts, after it accepted the add of a plugin whose layout pin is still unknown: the layout the worker found after init, which the daemon records in the pin
pin_expect <instance> <scheme>:<sha256> int word resp 0 -1 reply.host.instance-invalid; -902 reply.host.invalid-operation; -508 PIN_ABSENT - plugin-hostd unsupported, omx-clap-host required, mod-host unsupported sent by the daemon to a worker of a format that checks layouts, just before the instance's add: the layout pin that add checks after init and before activate; any other reply than resp 0 refuses the add with that code, and the add is never forwarded

pin_set:

> pin_set clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp <path>=<sha256> <scheme>:<sha256>
< resp 0

pin_clear:

> pin_clear clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0

pin_reload:

> pin_reload
< resp 0

pin_changed:

> pin_changed
< resp 0

pin_accept:

> pin_accept clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0

pin_layout:

> pin_layout 0
< resp 0 <scheme>:<sha256>

pin_expect:

> pin_expect 0 <scheme>:<sha256>
< resp 0

Chains and plans

verb arguments types reply codes since (the group's) hosts meaning
chain <chain> <instance> [<instance> ...] int int ... resp 0 -518 CHAIN_UNKNOWN_INSTANCE; -519 CHAIN_DUPLICATE; -520 CHAIN_INSTANCE_BUSY; -521 CHAIN_MIXED_FORMAT; -501 PLACEMENT_INVALID; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required, omx-clap-host required, mod-host unsupported the instances, in this order, are one chain, run in one worker of place chain:<chain>: each member's state is saved, it is removed from the worker it was in and replayed in the chain's. A chain already declared takes the new order, and a member left out of it goes back to a worker of its own; while its worker is up that worker is kept: a new member joins it (its add, the order, its state, its ledger, its connections) and no other member is restarted, unless the new member's format narrows the worker's world, which forms the chain again. Members of clap and lv2-clap form one chain. A worker that runs chains in one client (clap, lv2-clap) is sent this line, with the members it holds, after the adds of its replay; one that does not (mod-host) never is. A worker that refuses the line would not run the members as one: the chain is forgotten and each member runs in a worker of its own, the line answers -501, and the event chain_refused carries the worker's own code, also when the refusal comes later, on a respawn. Every instance verb still names one member by its own number. A member that crashes the worker is quarantined to a worker of its own and the chain goes on without it; quarantine_clear puts it back. -518 for an instance no add made, -519 for one named twice, -520 for one in another chain, -521 for members no one worker program runs together, -501 for a plugin that may not share a worker while require_pins is on (a CLAP plugin whose first-use pin is not in-process; an LV2 member whose bundle is not found, since a chain's worker sees a world narrowed to the union of its members' bundles), -902 for a chain or an instance that is not a number, or fewer than chain_members_min (1) instances
chain_clear <chain> int resp 0 -522 NO_SUCH_CHAIN; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required forget the chain: each member goes to a worker of its own; -522 for a chain no chain declared
chain_get <chain> int resp 0 <worker> <instance>,... <instance>,... -522 NO_SUCH_CHAIN; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required read a chain back: its worker (- when it has none), its members in the declared order, and the members its worker runs now in their order (- for none); a quarantined member is declared and not running; -522 for a chain no chain declared, -902 for a chain that is not a number
plan <chain> <instance> [<instance> ...] int int ... resp 0 <instance>,...;<instance>,...;... <core>,...;<core>,...;... -518 CHAIN_UNKNOWN_INSTANCE; -519 CHAIN_DUPLICATE; -520 CHAIN_INSTANCE_BUSY; -501 PLACEMENT_INVALID; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the instances, in signal order, are planned (the grouping-planner design note): the daemon decides, from the pin and the cost of each, which runs of consecutive members share a worker. A run of two or more is declared as a chain of place chain:<chain>.<k>, k counting segments from 0; a member that shares with no neighbour is placed own. The reply names the segments in order, the members of one joined by commas and the segments by semicolons, then the cores: the segments again with each member replaced by the audio core of the worker that runs it now, - for one the setting placement did not pin (docs/placement.md). A chain planned again keeps the running worker of every segment that did not change, and the daemon plans it again by itself when a member is quarantined and, history_regroup_s after a death that split its group, to gather it. The plan numbers are its own, apart from chain's. While the setting grouping is off the plan is answered and kept for plan_get and nothing moves. A segment that cannot be formed refuses the whole plan with its own code, moves nothing and is told by plan_get as plan-refused:<code>. A segment its worker refuses, which is known only once it has moved, refuses the plan with -501 too, and then every segment ends and each member runs in a worker of its own. -518 for an instance no add made, -519 for one named twice, -520 for one in a chain or in the plan of another chain, -501 for a segment no world can be built for or its worker refuses, -902 for a chain or an instance that is not a number, or fewer than chain_members_min (1) instances
plan_get <chain> int resp 0 <instance>,...;<instance>,...;... <instance>:<reason>,... <core>,...;<core>,...;... -532 NO_SUCH_PLAN; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required read the last plan of a chain back: its segments as plan answered them, then the members that have a reason, instance:reason joined by commas (- for none); the reasons are the words of PLUGIN_HOSTD_GROUPING_REASONS (data/grouping.json), then the cores of the members as plan answers them; -532 for a chain no plan planned, -902 for a chain that is not a number
plan_clear <chain> int resp 0 -532 NO_SUCH_PLAN; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required forget the plan: each member goes to a worker of its own; -532 for a chain no plan planned
estimate <rate> <quantum> <uri> [<uri> ...] int int word ... resp 0 <latency_frames> <cpu_percent> <ram_bytes> <workers> <unknown_count> <member>:<code>,... <ram_used_bytes> <ram_added_bytes> <ram_budget_bytes> <ram_unknown_count> -533 NO_RECORD; -534 NO_CELL; -535 UNKNOWN_PLUGIN; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required what a chain of these plugins, in signal order and named as add names them, would cost at that rate and quantum, read from the qualification records of qualification_dir; no plugin is loaded and nothing moves. latency_frames adds up each member's measured latency of the grid point, else its declared one; cpu_percent adds up each member's median nanoseconds per sample times the rate, percent of one core's cycle (a member's cost per block over the block's duration); ram_bytes adds up the Pss of the worker each member ran alone in, the history's pss_mb_solo of the binary its pin holds on this host class, - unless every member has one, since no qualification record carries a RAM figure; workers is how many workers plan would place the members in (an isolated one alone); unknown_count is how many members have no figure at that rate and quantum, and the latency and the CPU add up only the others. The last word names each of them as its position in the line, from 0, and its code, -533 for no record, -534 for no point of the grid at that rate and quantum, -535 for a uri that names no plugin installed here, joined by commas, - for none. Several records of one plugin (several versions) give the largest figure. -902 for a rate or a quantum that is not a whole number above 0, or no uri

chain:

> chain 1 0 1
< resp 0

chain_clear:

> chain_clear 1
< resp 0

chain_get:

> chain_get 1
< resp 0 <worker> <instance>,... <instance>,...

plan:

> plan 1 0 1
< resp 0 <instance>,...;<instance>,...;... <core>,...;<core>,...;...

plan_get:

> plan_get 1
< resp 0 <instance>,...;<instance>,...;... <instance>:<reason>,... <core>,...;<core>,...;...

plan_clear:

> plan_clear 1
< resp 0

estimate:

> estimate <rate> <quantum> clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0 <latency_frames> <cpu_percent> <ram_bytes> <workers> <unknown_count> <member>:<code>,... <ram_used_bytes> <ram_added_bytes> <ram_budget_bytes> <ram_unknown_count>

Descriptors, panels and the running history

verb arguments types reply codes since (the group's) hosts meaning
descriptor_get <uri> word resp 0 "<path>" -528 DIR_NOT_WRITABLE; -529 DESCRIPTOR_ABSENT; -530 DESCRIPTOR_PENDING; -531 DESCRIPTOR_SCAN_FAILED; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the scanned descriptor of the plugin, answered from descriptor_dir: the file named for its binary as it is now (<id>@<version>-<hash>.json), written by the scan the plugin's add ran. -529 when there is none, -530 while its scan runs, -531 when its last scan wrote nothing, -528 when the directory could not be written
panel_get <uri> word resp 0 "<path>" -528 DIR_NOT_WRITABLE; -529 DESCRIPTOR_ABSENT; -530 DESCRIPTOR_PENDING; -531 DESCRIPTOR_SCAN_FAILED; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required the panel the plugin publishes (org.openmixer.panel-description/1), answered from descriptor_dir: the file panels/<id>@<version>-<hash>.json, named as its descriptor, which the same scan writes once the descriptor is in place, {"plugin": {"format", "id"}, "panel": <the panel object omx-clap-scan --json prints for the plugin, null when it serves none>}; an LV2 plugin's panel is null. The codes are descriptor_get's: -529 when there is none, -530 while its scan runs, -531 when its last scan wrote nothing, -528 when the directory could not be written
history_get <uri> word resp 0 <history> -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required what the daemon kept of the plugin on this host class, for the binary its pin holds now (the running history: the windows it ran alone and in a group, its crashes, restarts and quarantines): one word of comma-joined digest=<sha256>, class=<host class>, crashed=<0|1>, raises_xruns=<0|1> and then every key=value of the record (data/history.json); - when the plugin has no pin or no record, unavailable while the history file is blocked or history_window_s is 0, -902 for a line that is not one word
history_clear <uri> | all word resp 0 -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd required delete the history of one plugin (every binary and host class of it), or all of it, from memory and from the file at once; -902 while the history is blocked or off, or for a line that is not one word

descriptor_get:

> descriptor_get clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0 "<path>"

panel_get:

> panel_get clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0 "<path>"

history_get:

> history_get clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0 <history>

history_clear:

> history_clear clap:/usr/lib/clap/omx-comp.clap#org.openmixer.comp
< resp 0

Presets and notes, by instance

verb arguments types reply codes since (the group's) hosts meaning
preset_list <instance> int resp <count> -515 NO_PRESET_LIST; -3 reply.host.instance-non-exists; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd optional, omx-clap-host optional, mod-host unsupported the plugin's presets, the ones its preset discovery or its bundle lists; (-515) for an instance of a worker that cannot list them
preset_get <instance> <index> int int resp 0 <load_key> <name> -515 NO_PRESET_LIST; -3 reply.host.instance-non-exists; -901 reply.host.memory-allocation; -902 reply.host.invalid-operation 0.12.0 plugin-hostd optional, omx-clap-host optional, mod-host unsupported one preset, 0 to count - 1: the key preset_load takes for it, and its name; -902 for an index that is not one
note_input <instance> int resp 0 <port | -> -3 reply.host.instance-non-exists; -902 reply.host.invalid-operation; -514 NOTES_BY_JACK; -901 reply.host.memory-allocation - plugin-hostd optional, omx-clap-host required, mod-host unsupported whether the instance takes notes: the name of its note input on its worker's jack client, - for a plugin that takes none; (-514) for an instance of a worker that cannot say
note_on <instance> <channel> <key> <velocity> int int int int resp 0 -513 NO_NOTE_INPUT; -514 NOTES_BY_JACK; -517 NOTE_QUEUE_FULL; -3 reply.host.instance-non-exists; -902 reply.host.invalid-operation; -901 reply.host.memory-allocation - plugin-hostd optional, omx-clap-host required, mod-host unsupported play a key: channel 0 to 15, key 0 to 127, velocity 1 to 127, a MIDI note on at the start of the worker's next cycle; (-513) for a plugin that takes no notes, (-514) for an instance of a worker that takes none on its socket, -902 for a word out of its range
note_off <instance> <channel> <key> <velocity> int int int int resp 0 -513 NO_NOTE_INPUT; -514 NOTES_BY_JACK; -517 NOTE_QUEUE_FULL; -3 reply.host.instance-non-exists; -902 reply.host.invalid-operation; -901 reply.host.memory-allocation - plugin-hostd optional, omx-clap-host required, mod-host unsupported release a key, velocity 0 to 127, a MIDI note off; answered as note_on is
all_notes_off <instance> int resp 0 -513 NO_NOTE_INPUT; -514 NOTES_BY_JACK; -517 NOTE_QUEUE_FULL; -3 reply.host.instance-non-exists; -902 reply.host.invalid-operation; -901 reply.host.memory-allocation - plugin-hostd optional, omx-clap-host required, mod-host unsupported release every key of every channel, MIDI's all notes off (controller 123) on each; answered as note_on is

preset_list:

> preset_list 0
< resp <count>

preset_get:

> preset_get 0 0
< resp 0 <load_key> <name>

note_input:

> note_input 0
< resp 0 <port | ->

note_on:

> note_on 0 0 60 100
< resp 0

note_off:

> note_off 0 0 60 100
< resp 0

all_notes_off:

> all_notes_off 0
< resp 0
  1. Events and feedback

mod-host's feedback lines, relayed as the worker wrote them:

line arguments
output_set int word float
remote_pages_changed int

The daemon's events and the worker lines it relays:

event fields meaning
worker_died <worker> <pid> <exit:N | signal:N> <instance>,... a worker died; the instances it held, or - for none
instance_verb_dropped <instance> <the command as sent> the command a worker died on, dropped from the ledger; or the add it died on, the add as the worker was given it, which names the plugin, or the pin_expect of that add
instance_verb_dropped <instance> suspect:<ms> <the command as sent> the last verb a worker answered when it died inside suspect_window_ms, <ms> the age of the reply
worker_backoff <worker> <ms> the respawn waits this long
worker_respawned <worker> <pid> <replayed_count> <ms> a worker is back and its ledger replayed
instance_restored <instance> <worker> an instance is back in a worker
instance_replay_refused <instance> <code> a replayed add was refused, by its pin or by the worker, <code> the refusal's own: no worker holds the instance until the next replay
instance_quarantined <instance> <worker> the culprit of a pool or a chain death, placed own until cleared; a chain goes on without it
chain_formed <chain> <worker> <instance>,... a chain's worker is up with these members, in this order, after its declaration, a respawn or a member's quarantine; - for none. <chain> is the number chain declared, or <plan>.<k> for segment k of a plan
chain_refused <chain> <code> a chain's worker would not run the members as one chain, <code> its own answer, and its stderr says why: the chain is forgotten and each member runs in a worker of its own. <chain> as chain_formed names it
chain_stalled <chain> <instance | -> <step> <code> a chain, or a plan's segment, has not formed within chain_form_timeout_ms of its declaration: the member and the step that held it (spawn, add, pin, state, memory) and the code it answered, 0 for none, - for no one member; said once each time it is declared or gathered again, and the daemon goes on trying
plan_reason <chain> <instance>:<reason>,... a plan of the chain changed: the reasons plan_get answers now, after the segments, - for none
supervisor_gave_up <worker> <deaths> <window_ms> the storm bound is spent; respawning ends
pin_changed <uri> files | layout an add or a replay of a plugin was refused because its files, or the layout the worker found, differ from its pin; the pin is as it was until pin_accept or pin_set
lv2_adapter_refused <uri> <reason> auto | omx-clap-host the LV2 adapter of the lv2-clap worker refused a plugin: <reason> is the hosting.* code after -102 on the worker's reply to that add (- when it named none); the last word is the lv2_worker in force, auto for a plugin the lv2 row runs from now on, omx-clap-host for an add answered (-512)
remote_pages_changed <instance> relayed from a worker: the plugin changed its remote pages; read them again
remote_page_suggest <instance> <page_id> relayed from a worker: the plugin suggests the remote page of that id (a page_id of remote_page_get); a surface may follow it
param_gesture <instance> <symbol> begin | end relayed from a worker: the user took hold of a control of the plugin's own, or let it go; a controller writes no automation of that parameter in between. The values the plugin moves meanwhile, or at any time by itself, are mod-host's param_set feedback lines, relayed as written. A gesture whose worker died, or whose instance was removed, before its end is ended by the daemon, before the worker_died event
xruns <count> relayed from a worker, and read by the daemon: the JACK xruns the worker has counted since it started, written when the count changes and at most once a second; a worker that never writes it counted none. The daemon takes the first count of a worker as its baseline and, at the close of each history window, adds the window to xrun_windows_solo or xrun_windows_grouped of every member of the worker when the count rose in it; a line that is not the word and one whole number is not a count
abi_clobber <instance> <mask> <registers> <call> <block> relayed from a worker (omx-clap-host 0.26.2 or later), and read by the daemon: the plugin returned from a call with a callee-saved register changed, said once per instance. The worker put the registers back, so its own frame is whole; the other members' state in the same process is not covered, so the daemon quarantines the instance (instance_quarantined, and the history keeps it alone from then on) and, when it shares its worker or is in a chain, moves it to a worker of its own with its state saved; one already alone is only marked
param_set <instance> <symbol> <value> relayed from a worker: parameter feedback, a value the plugin moved by itself (its automation, an LFO, a preset load, a MIDI map, the user's knob of the plugin's own), as mod-host writes it; a controller updates its control to it. A worker never writes it for a value a controller's param_set gave it, and a worker that writes none changes nothing: the daemon adds no line of its own

The relay rules:

  • VERBATIM: every line a worker writes on its feedback port goes out on the daemon's feedback port as the worker wrote it: output_set, param_set, data_finish and the rest of mod-host's feedback, and a line the daemon does not know
  • INSTANCE: the instance number in a relayed line is already the controller's: add hands it to the worker unchanged, so nothing in a line is rewritten
  • ORDER: a worker's lines go out in the order it wrote them, and what a worker wrote before it died goes out before its worker_died event; an instance lives in one worker at a time, so its lines keep their order across a respawn; the lines of different workers interleave
  • SUBSCRIPTION: monitor_output is in the ledger, one line per output, and param_monitor as it was sent; a respawn replays them after the add, so a respawned instance reports the same outputs, each once, starting again from its first value
  • HANDSHAKE: output_data_ready names no instance and goes to every worker; each worker's data_finish is relayed
  • NO_CONTROLLER: with no feedback port, or no controller on it, a line is dropped, as mod-host drops it; the daemon reads every worker's feedback all the time, so no worker waits on it

Host settings

  • TEMPO: the tempo, the metre and the transport a controller sets (mod-host's set_bpm, set_bpb, transport and transport_sync) are kept, the latest line of each as it was sent, in the order they were last sent
  • REPLAY: a worker is handed them before anything else: a respawned one before its first add, and a worker started later, of a new placement, as well; a worker's refusal of one is written to stderr and is no failure
  • GRAMMAR: a line whose words are not that command's grammar is passed on and not kept

Reply codes

mod-host's parse refusals (protocol.h): -1 PROTOCOL_NOT_FOUND (not found); -2 PROTOCOL_MANY_ARGUMENTS (many arguments); -3 PROTOCOL_FEW_ARGUMENTS (few arguments); -4 PROTOCOL_INVALID_ARGUMENT (invalid argument).

mod-host's reply codes (host-errors.h): 0 SUCCESS; -1 ERR_INSTANCE_INVALID; -2 ERR_INSTANCE_ALREADY_EXISTS; -3 ERR_INSTANCE_NON_EXISTS; -4 ERR_INSTANCE_UNLICENSED; -101 ERR_HOST_INVALID_URI; -102 ERR_HOST_INSTANTIATION; -103 ERR_HOST_INVALID_PARAM_SYMBOL; -104 ERR_HOST_INVALID_PRESET_URI; -105 ERR_HOST_CANT_LOAD_STATE; -101 ERR_LV2_INVALID_URI; -102 ERR_LV2_INSTANTIATION; -103 ERR_LV2_INVALID_PARAM_SYMBOL; -104 ERR_LV2_INVALID_PRESET_URI; -105 ERR_LV2_CANT_LOAD_STATE; -201 ERR_JACK_CLIENT_CREATION; -202 ERR_JACK_CLIENT_ACTIVATION; -203 ERR_JACK_CLIENT_DEACTIVATION; -204 ERR_JACK_PORT_REGISTER; -205 ERR_JACK_PORT_CONNECTION; -206 ERR_JACK_PORT_DISCONNECTION; -207 ERR_JACK_VALUE_OUT_OF_RANGE; -301 ERR_ASSIGNMENT_ALREADY_EXISTS; -302 ERR_ASSIGNMENT_INVALID_OP; -303 ERR_ASSIGNMENT_LIST_FULL; -304 ERR_ASSIGNMENT_FAILED; -305 ERR_ASSIGNMENT_UNUSED; -401 ERR_CONTROL_CHAIN_UNAVAILABLE; -402 ERR_ABLETON_LINK_UNAVAILABLE; -403 ERR_HMI_UNAVAILABLE; -404 ERR_EXTERNAL_UI_UNAVAILABLE; -901 ERR_MEMORY_ALLOCATION; -902 ERR_INVALID_OPERATION.

The codes omx-clap-host answers too (omx-clap-core's reply.host.*):

resp code meaning
-1 reply.host.instance-invalid mod-host's ERR_INSTANCE_INVALID: an instance number outside the host's table
-3 reply.host.instance-non-exists mod-host's ERR_INSTANCE_NON_EXISTS: an instance no add made
-103 reply.host.param-symbol-invalid mod-host's ERR_LV2_INVALID_PARAM_SYMBOL: a symbol that is not a parameter param_set takes, the plugin's own bypass included
-508 reply.host.pin-absent a layout pin in a scheme this host does not know, refused as no pin at all
-510 reply.host.pin-layout-mismatch the add of an instance whose layout after init is not the one pin_expect named: the plugin is not activated
-511 reply.host.no-param-contract the host holds no checked unit and scale for this parameter: the controller falls back to the layout the scan gives
-513 reply.host.no-note-input the instance's plugin takes no notes
-514 reply.host.notes-by-jack the instance's worker takes no notes on its socket (mod-host): the plugin is played through its jack MIDI input
-517 reply.host.note-queue-full the worker has no room for the line's notes before its next block: nothing of it is kept, and the line may be sent again
-901 reply.host.memory-allocation mod-host's ERR_MEMORY_ALLOCATION: the reply could not be built
-902 reply.host.invalid-operation mod-host's ERR_INVALID_OPERATION: a word outside the verb's grammar, or a verb the host refuses on purpose

The codes the daemon adds:

resp name meaning
-501 PLACEMENT_INVALID placement invalid: not a placement, or not valid here, as a pool:<name> for a plugin of a format whose pin holds only in a world of the plugin's own, while require_pins is on
-502 NO_BACKEND no worker program for the scheme
-503 WORKER_SPAWN the worker is not up: the spawn failed, or it is in backoff; ask again after instance_restored
-504 REPLAY the name of a refused replay, never a reply: a replayed add that its pin or the worker refuses is announced by the instance_replay_refused event with the refusal's own code, and written to stderr with this one and the step that refused it
-505 GAVE_UP the storm bound is spent for that placement
-506 NO_SUCH_WORKER no such worker
-507 VERB_DROPPED the worker died on this very command and the daemon dropped it: it is not replayed, and the instance_verb_dropped event names it; or it died on, or did not answer in rpc_timeout_ms, the pin_expect of an add, which is then never forwarded
-508 PIN_ABSENT pin_mode strict and the plugin has no pin, or its layout pin is in a scheme this version does not know: add is refused and no worker sees it
-509 PIN_BINARY_MISMATCH a pinned file is missing or its SHA-256 differs, or the plugin's manifest names a file the pin does not hold: add is refused and no worker sees it
-510 PIN_LAYOUT_MISMATCH the worker found the parameter layout after init differs from the layout pin: the instance is destroyed before activate
-511 NO_PARAM_CONTRACT the host holds no checked unit and scale for this parameter: fall back to qualification
-512 LV2_ADAPTER_REFUSED the lv2-clap worker's LV2 adapter cannot run this plugin (MIDI it does not feed, a required feature it lacks, a layout it does not take): the worker answered the add -102, the lv2_adapter_refused event carries the hosting.* code of that reply, and under lv2_worker auto the plugin is run by the lv2 row instead and this code is never answered; a plugin no bundle of the worker's world holds is no refusal: the worker's -101 is answered
-513 NO_NOTE_INPUT the instance's plugin takes no notes (an effect): its worker answers note_on, note_off and all_notes_off with this code
-514 NOTES_BY_JACK the instance's worker takes no notes on its socket (mod-host): the daemon answers the note verbs and note_input with this code, and the plugin is played through its jack MIDI input with connect
-515 NO_PRESET_LIST the instance's worker cannot list the plugin's presets (mod-host): the daemon answers preset_list and preset_get with this code; preset_load still takes a preset the controller knows
-516 PIN_REFUSED the plugin's pin carries verdict=refused: its files are the ones that failed qualification, so add is refused and no worker sees it, and a replayed add is refused the same way; files that differ from the pin answer (-509) first. A pins_dir pin of that verdict is never overridden: pin_set and pin_accept of its plugin answer this code
-517 NOTE_QUEUE_FULL the instance's worker has no room for this note_on, note_off or all_notes_off before its next block: the line was not taken and nothing of it is kept; the controller may send it again
-518 CHAIN_UNKNOWN_INSTANCE a member of chain is an instance no add made: the chain is not declared
-519 CHAIN_DUPLICATE an instance is named twice in one chain: the chain is not declared
-520 CHAIN_INSTANCE_BUSY a member of chain is already in another chain: the chain is not declared; chain_clear of the other chain frees it
-521 CHAIN_MIXED_FORMAT the members of chain are not all run by one worker program, or are of two rows of one program and a row does not run chains in one client, so no one worker runs them: the chain is not declared
-522 NO_SUCH_CHAIN chain_clear or chain_get of a chain no chain declared
-523 PRESET_NOT_OFFERED the worker: preset_load or preset_show of a stable name the plugin does not offer, or of a plugin with no preset system and no state
-524 PRESET_INVALID preset_save with a file path that is not absolute or with no name, or preset_load or preset_show of a file that cannot be read, is no JSON, is of a format version other than org.openmixer.preset/1 and org.openmixer.preset/2, or breaks the format (docs/presets.md): the daemon answers it and no worker sees the file
-525 PRESET_NEEDS_STATE the worker: a preset file that carries state for a worker that cannot take state yet
-526 PRESET_PLUGIN_CHANGED the worker: the preset file's plugin id, version or fingerprint is not the instance's; nothing of it is applied
-527 LOAD_REFUSED the worker: the plugin refused the preset or the state its file carries
-528 DIR_NOT_WRITABLE descriptor_dir or qualification_dir cannot be made or written: the daemon does not start, and says which key and which path on stderr with this code; a directory that turns read-only later makes the scan of an add write nothing, and descriptor_get and panel_get of that plugin answer this code
-529 DESCRIPTOR_ABSENT descriptor_get (panel_get) of a plugin that has no descriptor (panel) in descriptor_dir for its binary as it is now, and none being written: no add since it changed, or a binary that cannot be found
-530 DESCRIPTOR_PENDING descriptor_get (panel_get) of a plugin whose scan, started by its add, has not written its descriptor (panel) yet: ask again
-531 DESCRIPTOR_SCAN_FAILED descriptor_get (panel_get) of a plugin whose last scan wrote no descriptor (panel): the scanner refused the plugin, made no dump or no panel, or is not installed; the add it ran for stands, and stderr and the scan's log say why. The next add scans again
-532 NO_SUCH_PLAN plan_get or plan_clear of a chain no plan planned
-533 NO_RECORD a member of estimate that no qualification record in qualification_dir names: never a reply, the code of the member in the reply's list
-534 NO_CELL a member of estimate whose qualification records hold no point of the grid at that rate and quantum with a cost and a latency: never a reply, the code of the member in the reply's list
-535 UNKNOWN_PLUGIN a member of estimate whose uri names no plugin installed here (its format cannot tell the plugin's binary, or the binary is not there): never a reply, the code of the member in the reply's list
  1. JSON documents

schema installed as $id title format description
protocol/plugin-hostd.schema.json /usr/share/plugin-hostd-protocol/plugin-hostd.schema.json https://github.com/FreeMixer/plugin-hostd-protocol/protocol/plugin-hostd.schema.json The protocol surface plugin-hostd owns - Generated from include/plugin-hostd/protocol.h, which is the declaration; this file says the shape of the JSON.

Counts: 48 mod-host commands, 34 same, 8 differs, 6 refused; 32 verbs mod-host does not have.

Appendix: mod-host's command reference

The "Commands (or Protocol)" section of mod-host's README (FreeMixer/mod-host, branch freemixer, 013ac7d), kept as it is. Copyright 2013-2026 Filipe Coelho, MOD Audio UG and the mod-host contributors, GPL-3.0-or-later.

Where it says "mod-host", read "the host": any host built on this library answers these commands. The library parses and dispatches them; what a command does to a plugin is the host's backend (host_backend_t, include/host-backend.h).

Commands (or Protocol)

The commands supported by mod-host are:

add <lv2_uri> <instance_number> [client_name]
    * add an LV2 plugin encapsulated as a jack client
    e.g.: add "http://lv2plug.in/plugins/eg-amp" 0
    instance_number must be any value between 0 ~ 9990, inclusively
    client_name is optional; when omitted the jack client (and so the graph node) is
    named "effect_<instance_number>", same as always. When given, mod-host tries to
    open the jack client under that name instead -- so a host running many plugins
    shows a graph with a name per plugin instead of an undifferentiated effect_0,
    effect_1, effect_2... The name is sanitised for jack (':' is replaced with '_',
    since jack uses ':' as the client:port separator, and it is truncated to the
    running jack backend's client name limit) and, on a collision with an existing
    jack client, disambiguated by appending "_<instance_number>". If the sanitised
    name is empty, or the name still collides after the instance number is appended,
    mod-host falls back to the default "effect_<instance_number>" rather than fail
    the add.
    e.g.: add "http://lv2plug.in/plugins/eg-amp" 0 "channel1-eg-amp"

remove <instance_number>
    * remove an LV2 plugin instance (and also the jack client)
    e.g.: remove 0
    when instance_number is -1 all plugins will be removed

preset_load <instance_number> <preset_uri>
    * load a preset state of an effect instance
    e.g.: preset_load 0 "http://drobilla.net/plugins/mda/presets#JX10-moogcury-lite"

preset_save <instance_number> <preset_name> <dir> <file_name>
    * save a preset state of an effect instance
    e.g.: preset_save 0 "My Preset" "/home/user/.lv2/my-presets.lv2" "mypreset.ttl"

preset_show <preset_uri>
    * show the preset information of requested URI
    e.g.: preset_show "http://drobilla.net/plugins/mda/presets#EPiano-bright"

connect <origin_port> <destination_port>
    * connect two jack ports
    e.g.: connect "system:capture_1" "effect_0:in"

disconnect <origin_port> <destination_port>
    * disconnect two jack ports
    e.g.: disconnect "system:capture_1" "effect_0:in"

bypass <instance_number> <bypass_value>
    * toggle effect processing
    e.g.: bypass 0 1
    if bypass_value = 1 bypass effect
    if bypass_value = 0 process effect

param_set <instance_number> <param_symbol> <param_value>
    * set the value of a control port
    e.g.: param_set 0 "gain" 2.5

param_get <instance_number> <param_symbol>
    * get the value of a control port
    e.g.: param_get 0 "gain"

param_monitor <instance_number> <param_symbol> <cond_op> <value>
    * monitor a control port according to a condition
    e.g: param_monitor 0 "gain" ">" 2.5

patch_set <instance_number> <property_uri> <value>
    * set the value of a control port
    e.g.: patch_set 0 "gain" 2.5

patch_get <instance_number> <property_uri>
    * get the value of a control port
    e.g.: patch_get 0 "gain"

licensee <instance_number>
    * get the licensee name for a commercial plugin
    e.g.: licensee 0

monitor <addr> <port> <status>
    * open a socket port for monitoring parameter changes
    e.g: monitor localhost 12345 1
    if status = 1 start monitoring
    if status = 0 stop monitoring

monitor_output <instance_number> <param_symbol>
    * request monitoring of an output control port (on the feedback port)
    e.g.: monitor_output 0 "meter"

midi_learn <instance_number> <param_symbol> <minimum> <maximum>
    * start MIDI learn for a control port
    e.g.: midi_learn 0 "gain" 0.0 1.0

midi_map <instance_number> <param_symbol> <midi_channel> <midi_cc> <minimum> <maximum>
    * map a MIDI controller to a control port
    e.g.: midi_map 0 "gain" 0 7 0.0 1.0
    a non-standard midi_cc value of 131 (0x83) is used for pitchbend

midi_unmap <instance_number> <param_symbol>
    * unmap the MIDI controller from a control port
    e.g.: midi_unmap 0 "gain"

monitor_midi_program <midi_channel> <enable>
    * listen to MIDI program change messages (on the feedback port)
    e.g.: monitor_midi_program 0 1

set_midi_program_change_pedalboard_bank_channel <enable> <midi_channel>
    * set the MIDI channel which changes pedalboard banks on MIDI program change. <midi_channel> is in the range of [0,15].
    e.g.: set_midi_program_change_pedalboard_bank_channel 1 5 to enable listening for bank changes on channel 6

set_midi_program_change_pedalboard_snapshot_channel <enable> <midi_channel>
    * set the MIDI channel which changes pedalboard snapshots on MIDI program change. <midi_channel> is in the range of [0,15].
    e.g.: set_midi_program_change_pedalboard_snapshot_channel 1 4 to enable listening for preset changes on channel 5

cc_map <instance_number> <param_symbol> <device_id> <actuator_id> <label> <value> <minimum> <maximum> <steps> <extraflags> <unit> <scalepoints_count> <scalepoints...>
    * map a Control Chain actuator to a control port
    e.g.: cc_map 0 "gain" 0 1 "Gain" 0.0 -24.0 3.0 33 0 "dB" 0

cc_unmap <instance_number> <param_symbol>
    * unmap the Control Chain actuator from a control port
    e.g.: cc_unmap 0 "gain"

cc_value_set <instance_number> <param_symbol> <value>
    * set the value of a  mapped Control Chain actuator
    e.g.: cc_value_set 0 "gain" 2.5

cv_map <instance_number> <param_symbol> <source_port_name> <minimum> <maximum> <operational-mode>
    * map a CV source port to a control port, operational-mode being one of '-', '+', 'b' or '='
    e.g.: cv_map 0 "gain" "AMS CV Source:CV Out 1" -24.0 3.0 =

cv_unmap <instance_number> <param_symbol>
    * unmap the CV source port actuator from a control port
    e.g.: cv_unmap 0 "gain"

cpu_load
    * return current jack cpu load

load <file_name>
    * load a history command file
    * dummy way to save/load workspace state
    e.g.: load my_setup

save <file_name>
    * saves the history of typed commands
    * dummy way to save/load workspace state
    e.g.: save my_setup

bundle_add <bundle_path>
    * add a bundle to the running lv2 world
    e.g.: bundle_add "/path/to/bundle.lv2"

bundle_remove <bundle_path> <resource>
    * remove a bundle from the running lv2 world
    e.g.: bundle_remove "/path/to/bundle.lv2" ""

feature_enable <feature> <enable>
    * enable or disable a feature
    * feature can be one of "aggregated-midi", "freewheeling" or "processing"
    * the "aggregated-midi" feature requires the use of jack2 and mod-midi-merger to be installed system-wide
    e.g.: feature_enable link 1

set_bpm <beats_per_minute>
    * set the global beats per minute transport value
    e.g.: set_bpm 120

set_bpb <beats_per_bar>
    * set the global beats per bar transport value
    e.g.: set_bpb 4

transport <rolling> <beats_per_bar> <beats_per_minute>
    * change the global transport state
    e.g.: transport 1 4 120

transport_sync <mode>
    * change the transport sync mode
    * mode can be one of "none", "link" or "midi"
    e.g.: transport_sync "midi"

output_data_ready
    * report feedback port ready for more messages

track_info <instance_number> <name> <color> [bus|return|master]
    * pass the name and color of the mixer track the instance sits on, color as #RRGGBB or - for none
    * an LV2 plugin has no way to receive them, so mod-host answers -902
    e.g.: track_info 0 "Kick In" #FF8000

remote_pages <instance_number>
    * get the number of remote-control pages of the plugin (8 parameters per page); 0 for an LV2 plugin
    e.g.: remote_pages 0

remote_page_get <instance_number> <page_index>
    * get one remote-control page: resp 0 <page_id> <section_name> <page_name> <symbol> x8, - for an empty slot
    e.g.: remote_page_get 0 0

param_info <instance_number> <param_symbol>
    * get what a control means: resp 0 <unit> <scale> <minimum> <maximum> <default> <step> <stable_symbol>
    * unit is an LV2 unit name (db, hz, ms, ...) or none, scale is linear, log or stepped, step 0 is continuous
    e.g.: param_info 0 "gain"

help
    * show a help message

quit
    bye!

For each effect added one client on JACK will be created. The names of clients follow the standard: effect_<instance_number>

If a valid command is executed a response is given as following:

resp <status> [value]

If status is a negative number an error has occurred. The table below shows the number of each error.

status error
-1 ERR_INSTANCE_INVALID
-2 ERR_INSTANCE_ALREADY_EXISTS
-3 ERR_INSTANCE_NON_EXISTS
-4 ERR_INSTANCE_UNLICENSED
-101 ERR_LV2_INVALID_URI
-102 ERR_LV2_INSTANTIATION
-103 ERR_LV2_INVALID_PARAM_SYMBOL
-104 ERR_LV2_INVALID_PRESET_URI
-105 ERR_LV2_CANT_LOAD_STATE
-201 ERR_JACK_CLIENT_CREATION
-202 ERR_JACK_CLIENT_ACTIVATION
-203 ERR_JACK_CLIENT_DEACTIVATION
-204 ERR_JACK_PORT_REGISTER
-205 ERR_JACK_PORT_CONNECTION
-206 ERR_JACK_PORT_DISCONNECTION
-207 ERR_JACK_VALUE_OUT_OF_RANGE
-301 ERR_ASSIGNMENT_ALREADY_EXISTS
-302 ERR_ASSIGNMENT_INVALID_OP
-303 ERR_ASSIGNMENT_LIST_FULL
-304 ERR_ASSIGNMENT_FAILED
-304 ERR_ASSIGNMENT_UNUSED
-401 ERR_CONTROL_CHAIN_UNAVAILABLE
-402 ERR_LINK_UNAVAILABLE
-403 ERR_HMI_UNAVAILABLE
-404 ERR_EXTERNAL_UI_UNAVAILABLE
-901 ERR_MEMORY_ALLOCATION
-902 ERR_INVALID_OPERATION

A status zero or positive means that the command was executed successfully. In case of the add command, the status returned is the instance number. The value field exists for the param_get, remote_page_get and param_info commands.

When the plugin changes its remote-control pages, the feedback port gets:

remote_pages_changed <instance_number>

openmixer

A software mixing console for Linux. The desk is the software; the browser is the surface.

Pages

Licence

OpenMixer is free software under the GPL-3.0-or-later. Every package in the workspace carries the same licence.

Roland, Midas, Behringer, RME and the product names used here belong to their respective owners. OpenMixer is an independent project and is not affiliated with any of them.