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

David Robillard d at drobilla.net
Fri Apr 17 12:43:51 PDT 2020


On Tue, 2020-04-14 at 18:05 +0100, Rui Nuno Capela wrote:
> 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?

Yeah, I increasinly find the separation annoying and often wish we just
jammed everything in the same namespace these days.  There are some
clashing names because of this too...

This feature... maybe parameters?  I have been thinking that that
extension is a bit silly in its current form, but it would be a good
place to actually document the semantics for message-based controls (or
whatever else vaguely about "parameters"), so this would fit in
relatively well there.

> 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;
>         lv2_atom_object_get(obj,
>             _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);
>                 else
>                 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,
> _urid_map(LV2_ATOM__portEvent));

Types (classes) are capitalized, PortEvent in this case.

>     lv2_atom_forge_key(forge, forge->Tuple);

Similarly, atom:Tuple is not a property (key), it is a type.  You will
need to invent a property for this.

>     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);
> 
> //  ...

It is a bit strange to use a tuple with implicit structure in this way,
but it's more efficient and in this particular very narrow case where
no extensibility is really even possible I guess I'm okay with it.

It would be conceptually easier to just send patch:Set or something,
though.  We would need to relax things so you can refer to a control
port, but it would maybe be worth it because this is also a mechanism
you could use to tinker your own minimum/maximum/etc which is also a
thing that has come up from time to time.

Regardless, in the short term, please fix the type/property confusion
mentioned above, it is quite bad to have implementations in the wild
using atom:Tuple as a key and such.

Most importantly however, thanks for prototyping this, but PLEASE DO
NOT RELEASE IMPLEMENTATIONS THAT USE URIS IN LV2 NAMESPACES THAT DO NOT
EXIST IN LV2 MASTER!  Use URIs in some other namespace at first for
things like this, people can't invent whatever URIs in the LV2
namespaces they want, that has caused real problems in the past.

Cheers,

-- 
dr



More information about the Devel mailing list