parameter via messages system. Input control ones work. Output ones not yet

This commit is contained in:
Paolo Marrone 2025-02-24 23:07:06 +01:00
parent ecded4fd65
commit 2d66e616cd
6 changed files with 275 additions and 15 deletions

View File

@ -62,7 +62,7 @@ else
endif
endif
CFLAGS := -O3 -Wall -Wpedantic -Wextra
CFLAGS := -O0 -g -Wall -Wpedantic -Wextra
CFLAGS_ALL := -I$(DATA_DIR)/src -I$(PLUGIN_DIR) -I$(API_DIR) $(shell pkg-config --cflags lv2) -fPIC $(CFLAGS_EXTRA) $(CFLAGS)
LDFLAGS :=

View File

@ -36,6 +36,11 @@
{{?(it.product.state && it.product.state.dspCustom)}}
lv2:extensionData state:interface ;
{{?}}
{{~it.tibia.lv2.parameters:p:i}}
patch:writable plugin:{{=p.id}};
{{~}}
lv2:port [
{{~it.tibia.lv2.ports :p:i}}
{{?p.isBypass}}
@ -68,6 +73,22 @@
lv2:portProperty lv2:connectionOptional ;
lv2:portProperty lv2:integer ;
lv2:portProperty lv2:reportsLatency ;
{{??p.isInputParameterMessage}}
a lv2:InputPort ,
atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message ;
lv2:designation lv2:control ;
lv2:symbol "InputParameterMessage" ;
lv2:name "InputParameterMessage" ;
{{??p.isOutputParameterMessage}}
a lv2:OutputPort ,
atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message ;
lv2:designation lv2:control ;
lv2:symbol "OutputParameterMessage" ;
lv2:name "OutputParameterMessage" ;
{{??}}
a {{?p.type == "control"}}lv2:ControlPort{{??p.type == "midi"}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} ,
{{?p.direction == "input"}}lv2:InputPort{{??}}lv2:OutputPort{{?}} ;
@ -129,6 +150,20 @@
{{?}}
{{~}}
{{~it.tibia.lv2.parameters:p:i}}
plugin:{{=p.id}}
a lv2:Parameter ;
rdfs:label "{{=p.name}}" ;
rdfs:range {{?p.toggled}}atom:Bool{{??p.integer}}atom:Int{{??}}atom:Float{{?}} ;
{{?p.unit && p.unit in it.tibia.lv2.units}}
units:unit {{=it.tibia.lv2.ttlURI(it.tibia.lv2.units[p.unit])}} ;
{{?}}
lv2:minimum {{=p.minimum.toExponential()}} ;
lv2:maximum {{=p.maximum.toExponential()}} ;
lv2:default {{=p.defaultValue.toExponential()}} .
{{~}}
{{?it.product.ui}}
plugin:ui
a ui:@UI_TYPE@ ;

View File

@ -37,12 +37,56 @@ static uint32_t midi_in_index[DATA_PRODUCT_MIDI_INPUTS_N] = {
};
#endif
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
# define DATA_PARAM_BYPASS 1
# define DATA_PARAM_TOGGLED (1<<1)
# define DATA_PARAM_INTEGER (1<<2)
{{?it.lv2.use_parameters}}
#define DATA_PRODUCT_USE_PARAMETERS 1
{{?it.tibia.lv2.ports.find(p => p.isInputParameterMessage)}}
#define DATA_PRODUCT_IPM {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.isInputParameterMessage))}}
{{?}}
{{?it.tibia.lv2.ports.find(p => p.isOutputParameterMessage)}}
#define DATA_PRODUCT_OPM {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.isOutputParameterMessage))}}
{{?}}
#if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
static struct {
uint32_t index;
float min;
float max;
float def;
uint32_t flags;
const char *id;
} param_data[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
{{~it.tibia.lv2.parameters :p}}
{
/* .index = */ {{=p.paramIndex}},
/* .min = */ {{=p.minimum.toExponential()}}f,
/* .max = */ {{=p.maximum.toExponential()}}f,
/* .def = */ {{=p.defaultValue.toExponential()}}f,
/* .flags = */ {{?p.isBypass}}DATA_PARAM_BYPASS{{??p.isLatency}}DATA_PARAM_INTEGER{{??}}0{{?p.toggled}} | DATA_PARAM_TOGGLED{{?}}{{?p.integer}} | DATA_PARAM_INTEGER{{?}}{{?}},
/* .id = */ DATA_LV2_URI"#{{=p.id}}"
},
{{~}}
};
#endif
#if DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
static uint32_t param_out_index[DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
{{~it.tibia.lv2.parameters.filter(x => x.direction == "output") :p}}{{=p.paramIndex}}, {{~}}
};
#endif
{{??}}
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
static struct {
uint32_t index;
float min;
@ -69,6 +113,8 @@ static uint32_t param_out_index[DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
};
#endif
{{?}}
{{?it.product.ui}}
#define DATA_UI
#define DATA_LV2_UI_URI "{{=it.tibia.CGetUTF8StringLiteral(it.tibia.lv2.expandURI(it.lv2.uri + '#ui'))}}"

View File

@ -49,6 +49,10 @@
#ifdef DATA_STATE_DSP_CUSTOM
# include <lv2/state/state.h>
#endif
#if DATA_PRODUCT_USE_PARAMETERS
# include "lv2/lv2plug.in/ns/ext/patch/patch.h"
# include <lv2/lv2plug.in/ns/ext/atom/forge.h>
#endif
#include <string.h>
@ -131,6 +135,25 @@ typedef struct {
LV2_State_Store_Function state_store;
LV2_State_Handle state_handle;
#endif
#if DATA_PRODUCT_USE_PARAMETERS
LV2_URID uri_atom_Blank;
LV2_URID uri_atom_Object;
LV2_URID uri_atom_URID;
LV2_URID uri_atom_Float;
LV2_URID uri_atom_Bool;
LV2_URID uri_patch_Set;
LV2_URID uri_patch_Get;
LV2_URID uri_patch_property;
LV2_URID uri_patch_value;
LV2_URID uri_parameters[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N]; // TODO: Fix 0 params case
# if DATA_PRODUCT_IPM
const LV2_Atom_Sequence *controlIn;
# endif
# if DATA_PRODUCT_OPM
const LV2_Atom_Sequence *controlOut;
LV2_Atom_Forge forge;
# endif
#endif
} plugin_instance;
static const char * get_bundle_path_cb(void *handle) {
@ -203,6 +226,20 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s
instance->uri_state_data = instance->map->map(instance->map->handle, DATA_LV2_URI "#state_data");
}
#endif
#if DATA_PRODUCT_USE_PARAMETERS
instance->uri_atom_Blank = instance->map->map (instance->map->handle, LV2_ATOM__Blank);
instance->uri_atom_Object = instance->map->map (instance->map->handle, LV2_ATOM__Object);
instance->uri_atom_URID = instance->map->map (instance->map->handle, LV2_ATOM__URID);
instance->uri_atom_Float = instance->map->map (instance->map->handle, LV2_ATOM__Float);
instance->uri_atom_Bool = instance->map->map (instance->map->handle, LV2_ATOM__Bool);
instance->uri_patch_Set = instance->map->map (instance->map->handle, LV2_PATCH__Set);
instance->uri_patch_Get = instance->map->map (instance->map->handle, LV2_PATCH__Get);
instance->uri_patch_property = instance->map->map (instance->map->handle, LV2_PATCH__property);
instance->uri_patch_value = instance->map->map (instance->map->handle, LV2_PATCH__value);
for (int i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++) {
instance->uri_parameters[i] = instance->map->map (instance->map->handle, param_data[i].id);
}
#endif
plugin_callbacks cbs = {
/* .handle = */ (void *)instance,
@ -260,6 +297,7 @@ err_instance:
}
static void connect_port(LV2_Handle instance, uint32_t port, void * data_location) {
const uint32_t port0 = port;
plugin_instance * i = (plugin_instance *)instance;
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
if (port < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N) {
@ -289,8 +327,24 @@ static void connect_port(LV2_Handle instance, uint32_t port, void * data_locatio
}
port -= DATA_PRODUCT_MIDI_OUTPUTS_N;
#endif
#if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
#if DATA_PRODUCT_USE_PARAMETERS
# if DATA_PRODUCT_IPM
if (port0 == DATA_PRODUCT_IPM) {
i->controlIn = (const LV2_Atom_Sequence*) data_location;
return;
}
# endif
# if DATA_PRODUCT_OPM
if (port0 == DATA_PRODUCT_OPM) {
i->controlOut = (const LV2_Atom_Sequence*) data_location;
return;
}
# endif
#else
# if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
i->c[port] = data_location;
# endif
#endif
}
@ -314,6 +368,60 @@ static void activate(LV2_Handle instance) {
plugin_reset(&i->p);
}
static inline int getParamIndexByURI(plugin_instance *instance, LV2_URID uri) {
for (int i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++)
if (instance->uri_parameters[i] == uri)
return i;
return -1;
}
static inline bool parse_property (plugin_instance *self, const LV2_Atom_Object *obj) {
const LV2_Atom* property = NULL;
lv2_atom_object_get (obj, self->uri_patch_property, &property, 0);
/* Get property URI.
*
* Note: Real world code would only call
* if (!property || property->type != self->uris.atom_URID) { return; }
* However this is example and test code, so..
*/
if (!property) {
lv2_log_error (&self->logger, "PropEx.lv2: Malformed set message has no body.\n");
return false;
}
if (property->type != self->uri_atom_URID) {
lv2_log_error (&self->logger, "PropEx.lv2: Malformed set message has non-URID property.\n");
return false;
}
/* Get value */
const LV2_Atom* val = NULL;
lv2_atom_object_get (obj, self->uri_patch_value, &val, 0);
if (!val) {
lv2_log_error (&self->logger, "PropEx.lv2: Malformed set message has no value.\n");
return false;
}
/* NOTE: This code errs towards the verbose side
* - the type is usually implicit and does not need to be checked.
* - consolidate code e.g.
*
* const LV2_URID urid = (LV2_Atom_URID*)property)->body
* PropExURIs* urid = self->uris;
*
* - no need to lv2_log warnings or errors
*/
int index = getParamIndexByURI(self, ((LV2_Atom_URID*)property)->body);
if (index < 0)
return false;
float f = *((float*)(val + 1));
lv2_log_note (&self->logger, "PropEx.lv2: Received %d = %f\n", index, f);
return true;
}
static void run(LV2_Handle instance, uint32_t sample_count) {
plugin_instance * i = (plugin_instance *)instance;
@ -391,6 +499,52 @@ static void run(LV2_Handle instance, uint32_t sample_count) {
}
#endif
#if DATA_PRODUCT_USE_PARAMETERS
// Initially, self->out_port contains a Chunk with size set to capacity
// Set up forge to write directly to output port
const uint32_t out_capacity = i->controlOut->atom.size;
lv2_atom_forge_set_buffer(&i->forge, (uint8_t*)i->controlOut, out_capacity);
if (!i->controlIn) {
lv2_log_error (&i->logger, "controlIn is not.\n");
}
else if (i->map) {
LV2_ATOM_SEQUENCE_FOREACH (i->controlIn, ev) {
if (ev->body.type != i->uri_atom_Object) {
continue;
}
const LV2_Atom_Object *obj = (LV2_Atom_Object*)&ev->body;
const LV2_Atom_URID *property = NULL;
if (obj->body.otype == i->uri_patch_Set) {
parse_property (i, obj);
}
else if (obj->body.otype == i->uri_patch_Get) {
lv2_atom_object_get(obj, i->uri_patch_property, (const LV2_Atom**)&property, 0);
if (!property) {
lv2_log_error (&i->logger, "PropEx.lv2: Malformed set message has no body.\n");
continue;
}
if (property->atom.type != i->uri_atom_URID) {
lv2_log_error(&i->logger, "Get property is not a URID\n");
continue;
}
const LV2_URID key = property->body;
lv2_log_note(&i->logger, "AAAAAAAAAAAAAAAAA key: %d, getParamIndexByURI: %d \n", key, getParamIndexByURI(i, key));
}
}
}
#endif
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
const float ** x = i->x;
#else

View File

@ -37,7 +37,8 @@ module.exports = function (data, api, outputCommon, outputData) {
{ id: "state", uri: "http://lv2plug.in/ns/extensions/state#" },
{ id: "ui", uri: "http://lv2plug.in/ns/extensions/ui#" },
{ id: "units", uri: "http://lv2plug.in/ns/extensions/units#" },
{ id: "urid", uri: "http://lv2plug.in/ns/ext/urid#" }
{ id: "urid", uri: "http://lv2plug.in/ns/ext/urid#" },
{ id: "patch", uri: "http://lv2plug.in/ns/ext/patch#"}
],
units: {
"bar": "@units:bar",
@ -115,16 +116,38 @@ module.exports = function (data, api, outputCommon, outputData) {
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, audioPorts);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, midiPorts);
var ports = [];
for (var i = 0; i < data.product.parameters.length; i++) {
var p = data.product.parameters[i];
var e = Object.create(p);
e.type = "control";
e.paramIndex = i;
ports.push(e);
if (data.lv2.use_parameters) {
let parameters = [];
for (var i = 0; i < data.product.parameters.length; i++) {
let e = Object.create(data.product.parameters[i]);
e.paramIndex = i;
parameters.push(e);
}
data.tibia.lv2.parameters = parameters;
if (data.product.parameters.find(p => p.direction == "input")) {
data.tibia.lv2.ports.push({
isInputParameterMessage: true
});
}
if (data.product.parameters.find(p => p.direction == "output")) {
data.tibia.lv2.ports.push({
isOutputParameterMessage: true
});
}
}
else {
var ports = [];
for (var i = 0; i < data.product.parameters.length; i++) {
var p = data.product.parameters[i];
var e = Object.create(p);
e.type = "control";
e.paramIndex = i;
ports.push(e);
}
ports.sort((a, b) => a.direction != b.direction ? (a.direction == "input" ? -1 : 1) : 0);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, ports);
}
ports.sort((a, b) => a.direction != b.direction ? (a.direction == "input" ? -1 : 1) : 0);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, ports);
}
api.generateFileFromTemplateFile(`data${sep}manifest.ttl.in`, `data${sep}manifest.ttl.in`, data);

View File

@ -6,6 +6,8 @@
"uri": "@example:tibia_test",
"project": "@example:project",
"types": [ "@lv2:AmplifierPlugin" ],
"version": "1.0"
"version": "1.0",
"use_parameters": true
}
}