[LV2] Writing to control input ports w/o UI (DSP -> host; via atom output port) - RFC

Rui Nuno Capela rncbc at rncbc.org
Tue Apr 14 10:05:22 PDT 2020

Hi all,

(as briefly discussed on #lv2 irc...)

This is all about tackling an ages old long and overdue problem, eg. one
of LV2 instrument plugins that wish to support some kind of
not-initiated-by-the-GUI-ever preset/state change: standard MIDI
bank+program_change channel messages comes to mind...

Hopefully, this proposal tries to solve one controllers conundrum:
plugins are not allowed to override their own input control port
values--formerly regarded as parameters on LADSPA and now *legacy* for
LV2--which are, as ever were *read-only*, of course: ownership and value
is host's prerogative and sole responsibility...

So, how do we make a host to update on over a plugin's control input
ports you ask?

Until now that was not possible at all; of course, control input ports
are read-only from the plugin's POV; only the host may write to it on
its own premises.

This rather simple protocol being here proposed should help on letting
the plugin tell the host that it wishes to update or write to one or
more input control ports--the host may or may not concur on its own.

And how does the plugin tells so? It does it through one of its atom
output port(s), in the very same and similar fashion like it used to do
with patch properties/parameters all the way down: via an LV2 Atom
object that is.

TL;DR: This proposal boils down to add just one single URI to the LV2
reportoire (possibly to `lv2/lv2plug.in/ns/ext/atom/atom.h`):
#define LV2_ATOM__portEvent LV2_ATOM_PREFIX "portEvent"
Just not sure about nomenclature: should it be under `LV2:Atom` class,
like `LV2_ATOM__portEvent` suggested above? or `LV2_ATOM__portWrite`? Or
should it belong to `LV2:Patch` realm (in
`lv2/lv2plug.in/ns/ext/patch/patch.h`) as `LV2_PATCH__portEvent`,
considering that it's similar in semantics to `LV2_PATCH__Put` for
LV2:Patch properties/parameters but applying to control input ports instead?

Ontology and design specification looks fine to me as is and the good
news are that it's implemented already on yours truly **qtractor** (as
host) and the *Vee-One-Suite*, **synthv1**, **samplv1**, **drumkv1** and
**padthv1** (as plugins).

Below you may find some code snippets, as example implementations to
either side, host and plugin.

Host: ** read_port_event **
#include "lv2/lv2plug.in/ns/ext/atom/util.h"

//  LV2_Atom *atom;
//  `atom` is a `LV2_ATOM__Object`, read from the plugin's atom output
//  port buffer, while on the real-time processing thread and then
//  transferred to the main host (non-DSP) thread, possibly via a
//  ring-buffer, wrapped in a `LV2_ATOM__eventTransfer` container.
    const LV2_Atom_Object *obj = (const LV2_Atom_Object *) atom;

    if (obj->body.otype == _urid_map(LV2_ATOM__portEvent)) {
        const LV2_Atom_Tuple *tup = nullptr;
            _urid_map(LV2_ATOM__Tuple), (const LV2_Atom *) &tup, 0);
        uint32_t port_index = 0;
        if (tup) {
            uint32_t port_index = 0;
            LV2_ATOM_TUPLE_FOREACH(tup, iter) {
                if (iter->type == g_lv2_urids.atom_Int)
                    port_index = *(uint32_t *) (iter + 1);
                if (iter->type == g_lv2_urids.atom_Float) {
                    const uint32_t buffer_size = iter->size;
                    const void *buffer = iter + 1;
                    _control_port_write(port_index, buffer_size, 0, buffer);
//  ...

Plugin: ** write_port_event(s) **
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"

//  LV2_Atom_Forge *forge;
//  `forge` should be bound to the plugin's atom output port buffer;
//  nb. remember to declare an minimum adequate port buffer size (via
//  `LV2_RESIZE_PORT__minimumSize` property) if you intend to notify in
//  bulk ie. more than one tuple or pairs (`port_index`, `port_value`)
//  in contiguous sequence.
//  uint32_t port_index;
//  float port_value;
//  `port_index` (int) is the control input port index that is desired
//  to get updated on the host-side with `port_value` (float), forming
//  one or more tuples wrapped in a LV2_ATOM__portEvent object and sent
//  out to host.
    lv2_atom_forge_frame_time(forge, 0);

    LV2_Atom_Forge_Frame obj_frame;
    lv2_atom_forge_object(forge, &obj_frame, 0,
    lv2_atom_forge_key(forge, forge->Tuple);

    LV2_Atom_Forge_Frame tup_frame;
    lv2_atom_forge_tuple(forge, &tup_frame);

    // for each (port_index, port_value) tuple ...
    lv2_atom_forge_int(forge, port_index);
    lv2_atom_forge_float(forge, port_value);
    // ...

    lv2_atom_forge_pop(forge, &tup_frame);
    lv2_atom_forge_pop(forge, &obj_frame);

//  ...

Enjoy && Thanks.
rncbc aka. Rui Nuno Capela

More information about the Devel mailing list