[LV2] Parameters, groups, and dynamicism

Hanspeter Portner ventosus at airpost.net
Mon Sep 26 02:32:04 PDT 2016


On 17.09.2016 18:58, David Robillard wrote:
> Hello all,
> 
> I have been working on implementing parameters (event-based controls)
> more completely in Jalv, with an eye towards the general effort of
> bringing this up to a/the first-class way to control LV2 plugins,
> possibly dragging support code up into Lilv to make it as simple as
> possible for hosts, and so on.
> 
> I've come pretty far, but one thing is conspicuously missing from the
> spec: grouping.  For ports, we implemented grouping by necessity by
> adding a pg:group property to ports.  This was necessary because the way
> of describing ports was already to put them directly on the plugin
> description, and we needed to add groups without breaking backwards
> compatibility.  It is a bit tedious, though.
> 
> For parameters, it is more problematic to do it this way, because the
> idea is that a parameter is described somewhere and that description
> should apply wherever.  For example:
> 
> param:gain
> 	a lv2:Parameter ;
> 	rdfs:range atom:Float ;
> 	lv2:default 0.0 ;
> 	lv2:minimum -20.0 ;
> 	lv2:maximum 20.0 ;
> 	units:unit units:db ;
> 	rdfs:label "gain" ;
> 	rdfs:comment "Gain in decibels." .
> 
> If we used the same scheme for grouping as for ports, then you would
> need a different description of the gain parameter *for every group*
> solely to describe the group:
> 
> param:ampGain
> 	...
> 	pg:group :ampControls .
> 
> param:filterGain
> 	...
> 	pg:group :filterControls .
> 
> This sucks.  We could use inheritance to eliminate most of the redundant
> description, but it still sucks, because part of the idea with parameter
> descriptions is that host (or UI library, or...) can understand commonly
> used parameters and implement them specially.

I feel your pain, indeed. It is really cumbersome to define each parameter
individually.

Out of curiosity. How would such an inheritance scheme look like?

> So, we need to do something different, and I'm thinking describing the
> groups on the plugin directly is the way.  This is particularly nice
> because you can re-use entire group descriptions.  For example, we have
> params:EnvelopeControls which describe the controls for a DAHDSR
> envelope.  If a plugin could do something like:
> 
> :myplug
> 	...
> 	foo:parameterGroup [
> 		a param:EnvelopeControls ;
> 		lv2:symbol "amp_env" ;
> 	] .
> 
> Then you can describe an entire set of established controls with just
> that description, and re-use the same group description for many
> controls (e.g. for plugins with many envelopes).

Sounds reasonable to me.

> We would need rules to make the plugin-unique symbol for groups
> parameters, probably "groupsymbol_paramsymbol", so this plugin would
> e.g. have a control amp_env_attack.
> 
> So, issues:
> 
> * This is not compatible with the current way of using patch:readable
> and patch:writable to discover parameters.  I don't think this is
> terribly important since this isn't well-established anyway.  Perhaps we
> should even move to parameters being *always* groups with some "default
> group" defined.

Mandatory groups would be a nice thing to have as it would tremendously improve
representation in automatic/generic UIs. Plugins that define port groups are
much nicer to interact with in automatic UIs than those without.

> * Not sure how this works with the general concepts of
> readable/writable.  Can a single group have parameters that are
> differently readable/writable?  The current group description can not
> support this.

I can think of a single group having both readable and writable parameters, yes.

> * We need symbols to support the general LV2 design constraint that all
> controls have a unique symbol.  These are not useful at run-time,
> though, so it might be better to enforce URIs for parameter groups (like
> we probably should have for ports, even though it makes the ttl
> description a bit uglier), like:
> 
> :ampEnvControls
> 	a param:EnvelopeControls ;
> 	lv2:symbol "amp_env" .
> 
> :myplug
> 	...
> 	foo:parameterGroup :ampEnvControls .
> 
> Which lets you get a URID for ampEnvControls.  Setting grouped
> parameters would use the subject property of patch messages, so:
> 
> []
> 	a patch:Set ;
> 	patch:subject :ampEnvControls ;
> 	patch:property params:attack ;
> 	patch:value 0.15 .

I like that.

> This is a bit worrying, however, because claiming patch:subject means it
> is impossible to tinker grouped parameters on different "subjects" than
> the plugin instance.  We could mandate some things about what the
> *instance* URI is to get around this and make rules that the group URI
> must be a fragment of it and so on, though mandating URIs syntactically
> is generally frowned upon in some circles...

I've run into the same issue with my current way of dynamic properties (see below).

What about referencing the target plugin via a separate property instead of a
naming rule?

[]
	a patch:Set ;
	patch:plugin :myplug ;
	patch:subject :ampEnvControls ;
	patch:prperty params:attack ;
	patch:value 0.15 .

> (Sidenote: reason #8347289 URID probably should have been a general
> symbol interning interface instead)
> 
> * Dynamicism would be achieved by emitting a description much like the
> one above about new parameters showing up.  To make this realistic,
> probably something like lilv_controls_message_is_relevant(atom_msg),
> which if returns true, the hosts should ringbuffer it to somewhere and
> later call lilv_controls_update(controls, atom_msg) which will update
> the (hypothetical) LilvControls which emits callbacks to notify the host
> of new stuff and so on.  This is pretty nice because you can do things
> like describe an entire new set of envelope controls in a terse message
> without enumerating all the specifics.

Yes, I'd like to have an 'official' way for dynamicism.

I'm doing my own experiments with that already.

It's roughly the same thing as you're proposing, instead of custom parameter
groups, I use patch:writable and patch:readable as the only available parameter
groups.

Followingly the jalv dump of registering a single dynamic parameter
<urn:moony:bender#bendrange> to the UI.

## UI => Plugin (32 bytes) ##
[]
	a patch:Get ;
	patch:sequenceNumber "0"^^xsd:int .

## Plugin => UI (104 bytes) ##
[]
	a patch:Patch ;
	patch:remove [
		patch:writable patch:wildcard ;
		patch:readable patch:wildcard
	] ;
	patch:add [
		
	] .


## Plugin => UI (104 bytes) ##
[]
	a patch:Patch ;
	patch:remove [
		patch:writable <urn:moony:bender#bendrange>
	] ;
	patch:add [
		patch:writable <urn:moony:bender#bendrange>
	] .


## Plugin => UI (448 bytes) ##
[]
	a patch:Patch ;
	patch:subject <urn:moony:bender#bendrange> ;
	patch:remove [
		<http://www.w3.org/2000/01/rdf-schema#label> patch:wildcard ;
		<http://www.w3.org/2000/01/rdf-schema#range> patch:wildcard ;
		<http://www.w3.org/2000/01/rdf-schema#comment> patch:wildcard ;
		<http://lv2plug.in/ns/lv2core#minimum> patch:wildcard ;
		<http://lv2plug.in/ns/lv2core#maximum> patch:wildcard ;
		<http://lv2plug.in/ns/extensions/units#unit> patch:wildcard ;
		<http://lv2plug.in/ns/lv2core#scalePoint> patch:wildcard
	] ;
	patch:add [
		<http://www.w3.org/2000/01/rdf-schema#label> "Pitch Bend Range" ;
		<http://www.w3.org/2000/01/rdf-schema#range> <http://lv2plug.in/ns/ext/atom#Int> ;
		<http://www.w3.org/2000/01/rdf-schema#comment> "set pitch bend range of
receiving instrument" ;
		<http://lv2plug.in/ns/lv2core#minimum> "0"^^xsd:int ;
		<http://lv2plug.in/ns/lv2core#maximum> "9600"^^xsd:int ;
		<http://lv2plug.in/ns/extensions/units#unit>
<http://lv2plug.in/ns/extensions/units#cent>
	] .


## UI => Plugin (56 bytes) ##
[]
	a patch:Get ;
	patch:property <urn:moony:bender#bendrange> ;
	patch:sequenceNumber "0"^^xsd:int .


## Plugin => UI (56 bytes) ##
[]
	a patch:Set ;
	patch:property <urn:moony:bender#bendrange> ;
	patch:value "200"^^xsd:int .

> * Should we provide an index mechanism?  URIDs let you do it, but
> frankly the requirement to map a ton of URIDs is already pretty
> burdensome, and having to do it for all your parameters seems like it
> could be the straw that breaks plugin developer's backs...

A lot of URIDs can be burdensome, indeed. Still, I'd prefer URIDs over indexes.

Some 'official' utility functions to (un)map URIDS to/from enums would be nice,
though.

> This is a bit of a meandering rant about this stuff and I'm not sure
> what I'm asking concretely, but since I know others are interested in
> finally realizing controls that don't suck like ControlPort, here's
> where I'm at right now.  Opinions welcome.


More information about the Devel mailing list