lv2 mailto and better uris (no need for ui uri) + expertimental lv2-next

This commit is contained in:
Stefano D'Angelo 2025-01-17 17:13:26 +01:00
parent 10896d7d5b
commit 355cc690fb
9 changed files with 821 additions and 12 deletions

View File

@ -0,0 +1 @@
This is experimental and broken, do not use this.

View File

@ -0,0 +1,95 @@
{{~it.tibia.lv2.prefixes :p}}
@prefix {{=p.id}}: <{{=p.uri}}> .
{{~}}
{{~it.product.parameters :p}}
plugin:{{=p.id}}
a lv2:Parameter ;
rdfs:label "{{=p.name}}" ;
lv2:name "{{=p.name}}" ;
{{?"shortName" in p}}
lv2:shortName "{{=p.shortName.substring(0, 16)}}" ;
{{?}}
lv2:symbol "{{=p.id}}" ;
rdfs:range atom:Float .
{{~}}
{{=it.tibia.lv2.ttlURI(it.lv2.uri)}}
a lv2:Plugin ;
{{~it.lv2.types :t}}
a {{=it.tibia.lv2.ttlURI(t)}} ;
{{~}}
{{?it.lv2.project}}
lv2:project {{=it.tibia.lv2.ttlURI(it.lv2.project)}} ;
{{?}}
lv2:binary <{{=it.product.bundleName}}@DLL_SUFFIX@> ;
doap:name "{{=it.product.name}}" ;
doap:maintainer [
a foaf:Organization ;
foaf:name "{{=it.company.name}}" ;
foaf:mbox <mailto:{{=it.company.email}}> ;
rdfs:seeAlso {{=it.tibia.lv2.ttlURI(it.company.url)}}
] ;
lv2:minorVersion {{=/^([0-9]+)\./.exec(it.lv2.version)[1]}} ;
lv2:microVersion {{=/^[0-9]+\.([0-9]+)/.exec(it.lv2.version)[1]}} ;
{{?it.tibia.lv2.ports.find(p => p.type == "midi" || p.type == "param")}}
lv2:requiredFeature urid:map ;
lv2:optionalFeature log:log ;
{{?}}
{{?it.product.parameters.find(p => p.direction == "input")}}
patch:writable {{=it.product.parameters.filter(p => p.direction == "input").map(p => "plugin:" + p.id).join(" , ")}} ;
{{?}}
{{?it.product.parameters.find(p => p.direction == "output")}}
patch:readable {{=it.product.parameters.filter(p => p.direction == "output").map(p => "plugin:" + p.id).join(" , ")}} ;
{{?}}
lv2:optionalFeature lv2:hardRTCapable ;
{{?it.product.ui}}
ui:ui plugin:ui ;
{{?}}
lv2:port [
{{~it.tibia.lv2.ports :p:i}}
a {{?p.type == "control"}}lv2:ControlPort{{??(p.type == "midi" || p.type == "param")}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} ,
{{?p.direction == "input"}}lv2:InputPort{{??}}lv2:OutputPort{{?}} ;
lv2:name "{{=p.name}}" ;
{{?"shortName" in p}}
lv2:shortName "{{=p.shortName.substring(0, 16)}}" ;
{{?}}
lv2:symbol "{{=p.id}}" ;
{{?p.type == "param"}}
atom:bufferType atom:Sequence ;
atom:supports atom:Object ;
atom:supports patch:Message ;
{{??p.type == "midi"}}
atom:bufferType atom:Sequence ;
atom:supports midi:MidiEvent ;
{{?}}
{{?p.sidechain}}
lv2:portProperty lv2:isSideChain ;
{{?}}
{{?p.control}}
lv2:designation lv2:control ;
{{?}}
{{?p.optional}}
lv2:portProperty lv2:connectionOptional ;
{{?}}
lv2:index {{=i}}
{{?i < it.tibia.lv2.ports.length - 1}}
] , [
{{??}}
] .
{{?}}
{{~}}
{{?it.product.ui}}
plugin:ui
a ui:@UI_TYPE@ ;
ui:binary <{{=it.product.bundleName}}@DLL_SUFFIX@> ;
{{?!it.product.ui.userResizable}}
lv2:optionalFeature ui:noUserResize ; # doesn't work as lv2:requiredFeature, don't ask me why
{{?!it.product.ui.selfResizable}}
lv2:optionalFeature ui:fixedSize ;
{{?}}
{{?}}
lv2:requiredFeature ui:idleInterface ;
lv2:extensionData ui:idleInterface .
{{?}}

View File

@ -0,0 +1,78 @@
/*
* Tibia
*
* Copyright (C) 2024 Orastron Srl unipersonale
*
* Tibia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Tibia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tibia. If not, see <http://www.gnu.org/licenses/>.
*
* File author: Stefano D'Angelo
*/
#define DATA_LV2_URI "{{=it.tibia.CGetUTF8StringLiteral(it.tibia.lv2.expandURI(it.lv2.uri))}}"
#define DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").reduce((s, x) => s += x.channels == "mono" ? 1 : 2, 0)}}
#define DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").reduce((s, x) => s += x.channels == "mono" ? 1 : 2, 0)}}
#define DATA_PRODUCT_MIDI_INPUTS_N {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}}
#define DATA_PRODUCT_MIDI_OUTPUTS_N {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "output").length}}
#define DATA_PRODUCT_CONTROL_INPUTS_N {{=it.product.parameters.filter(x => x.direction == "input").length}}
#define DATA_PRODUCT_CONTROL_OUTPUTS_N {{=it.product.parameters.filter(x => x.direction == "output").length}}
#if DATA_PRODUCT_MIDI_INPUTS_N > 0
static uint32_t midi_in_index[DATA_PRODUCT_MIDI_INPUTS_N] = {
{{~it.tibia.lv2.ports.filter(x => x.type == "midi" && x.direction == "input") :p}}{{=p.busIndex}}, {{~}}
};
#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)
static struct {
uint32_t index;
float min;
float max;
float def;
uint32_t flags;
} param_data[DATA_PRODUCT_CONTROL_INPUTS_N] = {
{{~it.tibia.lv2.ports.filter(x => x.type == "control" && x.direction == "input") :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{{?}}{{?}}
},
{{~}}
};
#endif
#if DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
static uint32_t param_out_index[DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
{{~it.tibia.lv2.ports.filter(x => x.type == "control" && x.direction == "output") :p}}{{=p.paramIndex}}, {{~}}
};
#endif
{{?it.lv2.ui}}
#define DATA_UI
#define DATA_LV2_UI_URI "{{=it.tibia.CGetUTF8StringLiteral(it.tibia.lv2.expandURI(it.lv2.ui.uri))}}"
#define DATA_UI_USER_RESIZABLE {{=it.product.ui.userResizable ? 1 : 0}}
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
static uint32_t index_to_param[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
{{~it.tibia.lv2.ports.filter(x => x.type == "control").map((e, i) => ({ i: i, pi: e.paramIndex })).sort((a, b) => a.pi - b.pi) :p}}{{=p.i + it.tibia.lv2.ports.filter(x => x.type != "control").length}}, {{~}}
};
#endif
{{?}}

View File

@ -0,0 +1,495 @@
/*
* Tibia
*
* Copyright (C) 2024 Orastron Srl unipersonale
*
* Tibia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Tibia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tibia. If not, see <http://www.gnu.org/licenses/>.
*
* File author: Stefano D'Angelo
*/
#include <stdlib.h>
#include <stdint.h>
#include "data.h"
#include "plugin_api.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include "plugin.h"
#ifdef DATA_UI
# include "plugin_ui.h"
#endif
#pragma GCC diagnostic pop
#include "lv2/core/lv2.h"
#include "lv2/core/lv2_util.h"
#include "lv2/log/log.h"
#include "lv2/log/logger.h"
#include "lv2/urid/urid.h"
#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUTS_N > 0
# include "lv2/atom/util.h"
# include "lv2/atom/atom.h"
# include "lv2/midi/midi.h"
#endif
#ifdef DATA_UI
# include "lv2/ui/ui.h"
#endif
#include <string.h>
#if defined(__i386__) || defined(__x86_64__)
# include <xmmintrin.h>
# include <pmmintrin.h>
#endif
static inline float clampf(float x, float m, float M) {
return x < m ? m : (x > M ? M : x);
}
static float adjust_param(size_t index, float value) {
if (param_data[index].flags & DATA_PARAM_BYPASS)
value = value > 0.f ? 0.f : 1.f;
else if (param_data[index].flags & DATA_PARAM_TOGGLED)
value = value > 0.f ? 1.f : 0.f;
else if (param_data[index].flags & DATA_PARAM_INTEGER)
value = (int32_t)(value + (value >= 0.f ? 0.5f : -0.5f));
return clampf(value, param_data[index].min, param_data[index].max);
}
typedef struct {
plugin p;
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
const float * x[DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N];
#endif
#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0
float * y[DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N];
#endif
#if DATA_PRODUCT_MIDI_INPUTS_N > 0
const LV2_Atom_Sequence * x_midi[DATA_PRODUCT_MIDI_INPUTS_N];
#endif
#if DATA_PRODUCT_MIDI_OUTPUTS_N > 0
LV2_Atom_Sequence * y_midi[DATA_PRODUCT_MIDI_OUTPUTS_N];
#endif
#if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
float * c[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N];
#endif
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
float params[DATA_PRODUCT_CONTROL_INPUTS_N];
#endif
void * mem;
char * bundle_path;
LV2_Log_Logger logger;
LV2_URID_Map * map;
#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUTS_N > 0
LV2_URID uri_midi_MidiEvent;
#endif
} plugin_instance;
static const char * get_bundle_path_cb(void *handle) {
plugin_instance *instance = (plugin_instance *)handle;
return instance->bundle_path;
}
static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double sample_rate, const char * bundle_path, const LV2_Feature * const * features) {
(void)descriptor;
(void)bundle_path;
plugin_instance *instance = malloc(sizeof(plugin_instance));
if (instance == NULL)
goto err_instance;
instance->bundle_path = strdup(bundle_path);
if (instance->bundle_path == NULL)
goto err_bundle_path;
// from https://lv2plug.in/book
const char* missing = lv2_features_query(features,
LV2_LOG__log, &instance->logger.log, false,
LV2_URID__map, &instance->map, true,
NULL);
lv2_log_logger_set_map(&instance->logger, instance->map);
if (missing) {
lv2_log_error(&instance->logger, "Missing feature <%s>\n", missing);
goto err_urid;
}
#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUTS_N > 0
instance->uri_midi_MidiEvent = instance->map->map(instance->map->handle, LV2_MIDI__MidiEvent);
#endif
plugin_callbacks cbs = {
/* .handle = */ (void *)instance,
/* .format = */ "lv2",
/* .get_bindir = */ get_bundle_path_cb,
/* .get_datadir = */ get_bundle_path_cb
};
plugin_init(&instance->p, &cbs);
plugin_set_sample_rate(&instance->p, sample_rate);
size_t req = plugin_mem_req(&instance->p);
if (req != 0) {
instance->mem = malloc(req);
if (instance->mem == NULL) {
lv2_log_error(&instance->logger, "Not enough memory\n");
goto err_mem;
}
plugin_mem_set(&instance->p, instance->mem);
} else
instance->mem = NULL;
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
for (uint32_t i = 0; i < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N; i++)
instance->x[i] = NULL;
#endif
#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0
for (uint32_t i = 0; i < DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N; i++)
instance->y[i] = NULL;
#endif
#if DATA_PRODUCT_MIDI_INPUTS_N > 0
for (uint32_t i = 0; i < DATA_PRODUCT_MIDI_INPUTS_N; i++)
instance->x_midi[i] = NULL;
#endif
#if DATA_PRODUCT_MIDI_OUTPUTS_N > 0
for (uint32_t i = 0; i < DATA_PRODUCT_MIDI_OUTPUTS_N; i++)
instance->y_midi[i] = NULL;
#endif
#if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
for (uint32_t i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++)
instance->c[i] = NULL;
#endif
return instance;
err_mem:
plugin_fini(&instance->p);
err_urid:
free(instance->bundle_path);
err_bundle_path:
free(instance);
err_instance:
return NULL;
}
static void connect_port(LV2_Handle instance, uint32_t port, void * data_location) {
plugin_instance * i = (plugin_instance *)instance;
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
if (port < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N) {
i->x[port] = data_location;
return;
}
port -= DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N;
#endif
#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0
if (port < DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N) {
i->y[port] = data_location;
return;
}
port -= DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N;
#endif
#if DATA_PRODUCT_MIDI_INPUTS_N > 0
if (port < DATA_PRODUCT_MIDI_INPUTS_N) {
i->x_midi[port] = data_location;
return;
}
port -= DATA_PRODUCT_MIDI_INPUTS_N;
#endif
#if DATA_PRODUCT_MIDI_OUTPUTS_N > 0
if (port < DATA_PRODUCT_MIDI_OUTPUTS_N) {
i->y_midi[port] = data_location;
return;
}
port -= DATA_PRODUCT_MIDI_OUTPUTS_N;
#endif
#if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0
i->c[port] = data_location;
#endif
}
static void activate(LV2_Handle instance) {
plugin_instance * i = (plugin_instance *)instance;
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
for (uint32_t j = 0; j < DATA_PRODUCT_CONTROL_INPUTS_N; j++) {
i->params[j] = i->c[j] != NULL ? *i->c[j] : param_data[j].def;
plugin_set_parameter(&i->p, param_data[j].index, i->params[j]);
}
#endif
plugin_reset(&i->p);
}
static void run(LV2_Handle instance, uint32_t sample_count) {
plugin_instance * i = (plugin_instance *)instance;
#if defined(__aarch64__)
uint64_t fpcr;
__asm__ __volatile__ ("mrs %0, fpcr" : "=r"(fpcr));
__asm__ __volatile__ ("msr fpcr, %0" :: "r"(fpcr | 0x1000000)); // enable FZ
#elif defined(__i386__) || defined(__x86_64__)
const unsigned int flush_zero_mode = _MM_GET_FLUSH_ZERO_MODE();
const unsigned int denormals_zero_mode = _MM_GET_DENORMALS_ZERO_MODE();
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
#endif
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0
for (uint32_t j = 0; j < DATA_PRODUCT_CONTROL_INPUTS_N; j++) {
if (i->c[j] == NULL)
continue;
float v = adjust_param(j, *i->c[j]);
if (v != i->params[j]) {
i->params[j] = v;
plugin_set_parameter(&i->p, param_data[j].index, v);
}
}
#endif
// from https://lv2plug.in/book
#if DATA_PRODUCT_MIDI_INPUTS_N > 0
for (size_t j = 0; j < DATA_PRODUCT_MIDI_INPUTS_N; j++) {
if (i->x_midi[j] == NULL)
continue;
LV2_ATOM_SEQUENCE_FOREACH(i->x_midi[j], ev) {
if (ev->body.type == i->uri_midi_MidiEvent) {
const uint8_t * data = (const uint8_t *)(ev + 1);
if ((data[0] & 0xf0) != 0xf0)
plugin_midi_msg_in(&i->p, midi_in_index[j], data);
}
}
}
#endif
#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0
const float ** x = i->x;
#else
const float ** x = NULL;
#endif
#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0
float ** y = i-> y;
#else
float ** y = NULL;
#endif
plugin_process(&i->p, x, y, sample_count);
#if DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
for (uint32_t j = 0; j < DATA_PRODUCT_CONTROL_OUTPUTS_N; j++) {
uint32_t k = param_out_index[j];
if (i->c[k] != NULL)
*i->c[k] = plugin_get_parameter(&i->p, k);
}
#else
(void)plugin_get_parameter;
#endif
#if defined(__aarch64__)
__asm__ __volatile__ ("msr fpcr, %0" : : "r"(fpcr));
#elif defined(__i386__) || defined(__x86_64__)
_MM_SET_FLUSH_ZERO_MODE(flush_zero_mode);
_MM_SET_DENORMALS_ZERO_MODE(denormals_zero_mode);
#endif
}
static void cleanup(LV2_Handle instance) {
plugin_instance * i = (plugin_instance *)instance;
plugin_fini(&i->p);
if (i->mem)
free(i->mem);
free(i->bundle_path);
free(instance);
}
static const LV2_Descriptor descriptor = {
/* .URI = */ DATA_LV2_URI,
/* .instantiate = */ instantiate,
/* .connect_port = */ connect_port,
/* .activate = */ activate,
/* .run = */ run,
/* .deactivate = */ NULL,
/* .cleanup = */ cleanup,
/* .extension_data = */ NULL
};
LV2_SYMBOL_EXPORT const LV2_Descriptor * lv2_descriptor(uint32_t index) {
return index == 0 ? &descriptor : NULL;
}
#ifdef DATA_UI
typedef struct {
plugin_ui * ui;
char * bundle_path;
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
LV2UI_Write_Function write;
LV2UI_Controller controller;
char has_touch;
LV2UI_Touch touch;
# endif
} ui_instance;
# define CONTROL_INPUT_INDEX_OFFSET ( \
DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N \
+ DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N \
+ DATA_PRODUCT_MIDI_INPUTS_N \
+ DATA_PRODUCT_MIDI_OUTPUTS_N )
# define CONTROL_OUTPUT_INDEX_OFFSET (CONTROL_INPUT_INDEX_OFFSET + DATA_PRODUCT_CONTROL_INPUTS_N)
static const char * ui_get_bundle_path_cb(void *handle) {
ui_instance *instance = (ui_instance *)handle;
return instance->bundle_path;
}
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
static void ui_set_parameter_begin_cb(void *handle, size_t index) {
ui_instance *instance = (ui_instance *)handle;
if (instance->has_touch) {
index = index_to_param[index];
instance->touch.touch(instance->touch.handle, index, true);
}
}
static void ui_set_parameter_cb(void *handle, size_t index, float value) {
ui_instance *instance = (ui_instance *)handle;
index = index_to_param[index];
value = adjust_param(index - CONTROL_INPUT_INDEX_OFFSET, value);
instance->write(instance->controller, index, sizeof(float), 0, &value);
}
static void ui_set_parameter_end_cb(void *handle, size_t index) {
ui_instance *instance = (ui_instance *)handle;
if (instance->has_touch) {
index = index_to_param[index];
instance->touch.touch(instance->touch.handle, index, false);
}
}
# endif
static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const char * plugin_uri, const char * bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget * widget, const LV2_Feature * const * features) {
(void)descriptor;
(void)plugin_uri;
ui_instance *instance = malloc(sizeof(ui_instance));
if (instance == NULL)
goto err_instance;
instance->bundle_path = strdup(bundle_path);
if (instance->bundle_path == NULL)
goto err_bundle_path;
char has_parent = 0;
void *parent = NULL;
instance->has_touch = 0;
for (size_t i = 0; features[i] != NULL; i++) {
if (!strcmp(features[i]->URI, LV2_UI__parent)) {
has_parent = 1;
parent = features[i]->data;
}
if (!strcmp(features[i]->URI, LV2_UI__touch)) {
instance->has_touch = 1;
instance->touch = *((LV2UI_Touch *)features[i]->data);
}
}
plugin_ui_callbacks cbs = {
/* .handle = */ (void *)instance,
/* .format = */ "lv2",
/* .get_bindir = */ ui_get_bundle_path_cb,
/* .get_datadir = */ ui_get_bundle_path_cb,
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
/* .set_parameter_begin = */ ui_set_parameter_begin_cb,
/* .set_parameter = */ ui_set_parameter_cb,
/* .set_parameter_end = */ ui_set_parameter_end_cb
# else
/* .set_parameter_begin = */ NULL,
/* .set_parameter = */ NULL,
/* .set_parameter_end = */ NULL
# endif
};
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
instance->write = write_function;
instance->controller = controller;
# else
(void)write_function;
(void)controller;
# endif
instance->ui = plugin_ui_create(has_parent, parent, &cbs);
if (instance->ui == NULL)
goto err_create;
*widget = instance->ui->widget;
return instance;
err_create:
free(instance->bundle_path);
err_bundle_path:
free(instance);
err_instance:
*widget = NULL;
return NULL;
}
static void ui_cleanup(LV2UI_Handle handle) {
ui_instance *instance = (ui_instance *)handle;
plugin_ui_free(instance->ui);
free(instance->bundle_path);
free(instance);
}
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
static void ui_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void * buffer) {
(void)buffer_size;
(void)format;
ui_instance *instance = (ui_instance *)handle;
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
if (port_index < CONTROL_OUTPUT_INDEX_OFFSET) {
size_t index = port_index - CONTROL_INPUT_INDEX_OFFSET;
plugin_ui_set_parameter(instance->ui, param_data[index].index, adjust_param(index, *((float *)buffer)));
}
# endif
# if DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
# if DATA_PRODUCT_CONTROL_INPUTS_N > 0
else
# endif
plugin_ui_set_parameter(instance->ui, param_out_index[port_index - CONTROL_OUTPUT_INDEX_OFFSET], *((float *)buffer));
# endif
}
# endif
static int ui_idle(LV2UI_Handle handle) {
ui_instance *instance = (ui_instance *)handle;
plugin_ui_idle(instance->ui);
return 0;
}
static const void * ui_extension_data(const char * uri) {
static const LV2UI_Idle_Interface idle = { ui_idle };
if (!strcmp(uri, LV2_UI__idleInterface))
return &idle;
return NULL;
}
static const LV2UI_Descriptor ui_descriptor = {
/* .URI = */ DATA_LV2_UI_URI,
/* .instantiate = */ ui_instantiate,
/* .cleanup = */ ui_cleanup,
# if DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N > 0
/* .port_event = */ ui_port_event,
# else
/* .port_event = */ NULL,
# endif
/* .extension_data = */ ui_extension_data
};
LV2_SYMBOL_EXPORT const LV2UI_Descriptor * lv2ui_descriptor(uint32_t index) {
return index == 0 ? &ui_descriptor : NULL;
}
#endif

View File

@ -0,0 +1,136 @@
/*
* Tibia
*
* Copyright (C) 2024 Orastron Srl unipersonale
*
* Tibia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Tibia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tibia. If not, see <http://www.gnu.org/licenses/>.
*
* File author: Stefano D'Angelo
*/
var path = require("path");
var sep = path.sep;
module.exports = function (data, api, outputCommon, outputData) {
if (outputData) {
data.tibia.lv2 = {
prefixes: [
{ id: "atom", uri: "http://lv2plug.in/ns/ext/atom#" },
{ id: "doap", uri: "http://usefulinc.com/ns/doap#" },
{ id: "foaf", uri: "http://xmlns.com/foaf/0.1/" },
{ id: "log", uri: "http://lv2plug.in/ns/ext/log#" },
{ id: "lv2", uri: "http://lv2plug.in/ns/lv2core#" },
{ id: "midi", uri: "http://lv2plug.in/ns/ext/midi#" },
{ id: "patch", uri: "http://lv2plug.in/ns/ext/patch#" },
{ id: "pprops", uri: "http://lv2plug.in/ns/ext/port-props#" },
{ id: "rdf", uri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#" },
{ id: "rdfs", uri: "http://www.w3.org/2000/01/rdf-schema#" },
{ 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#" }
],
units: {
"bar": "@units:bar",
"beat": "@units:beat",
"bpm": "@units:bpm",
"cent": "@units:cent",
"cm": "@units:cm",
"coef": "@units:coef",
"db": "@units:db",
"degree": "@units:degree",
"frame": "@units:frame",
"hz": "@units:hz",
"inch": "@units:inch",
"khz": "@units:khz",
"km": "@units:km",
"m": "@units:m",
"mhz": "@units:mhz",
"midiNote": "@units:midiNote",
"mile": "@units:mile",
"min": "@units:min",
"mm": "@units:mm",
"ms": "@units:ms",
"oct": "@units:oct",
"pc": "@units:pc",
"s": "@units:s",
"semitone12TET": "@units:semitone12TET"
},
ports: [],
ttlURI: function (uri) {
if (uri.charAt(0) == "@") {
if (uri.indexOf("#") == -1)
return uri.substring(1);
uri = data.tibia.lv2.expandURI(uri);
}
return "<" + uri + ">";
},
expandURI: function (uri) {
if (uri.charAt(0) != "@")
return uri;
var i = uri.indexOf(":");
var p = uri.substring(1, uri.indexOf(":"));
return data.tibia.lv2.prefixes.find(x => x.id == p).uri + uri.substring(i + 1);
}
};
for (var id in data.lv2.prefixes)
data.tibia.lv2.prefixes.push({ id: id, uri: data.lv2.prefixes[id] });
data.tibia.lv2.prefixes.push({ id: "plugin", uri: data.tibia.lv2.expandURI(data.lv2.uri) + "#" });
var buses = data.product.buses;
var audioPorts = [];
var midiPorts = [];
for (var bi = 0; bi < buses.length; bi++) {
var b = buses[bi];
if (b.type == "audio") {
if (b.channels == "mono") {
var e = { type: "audio", direction: b.direction, name: b.name, sidechain: b.sidechain, cv: b.cv, optional: b.optional, busIndex: bi, id: b.id };
audioPorts.push(e);
} else {
var e = { type: "audio", direction: b.direction, name: b.name + " Left", shortName: b.shortName + " L", sidechain: b.sidechain, cv: b.cv, busIndex: bi, id: b.id + "_l" };
audioPorts.push(e);
var e = { type: "audio", direction: b.direction, name: b.name + " Right", shortName: b.shortName + " R", sidechain: b.sidechain, cv: b.cv, busIndex: bi, id: b.id + "_r" };
audioPorts.push(e);
}
} else {
var e = { type: "midi", direction: b.direction, name: b.name, sidechain: b.sidechain, control: b.control, optional: b.optional, busIndex: bi, id: b.id };
midiPorts.push(e);
}
}
audioPorts.sort((a, b) => a.direction != b.direction ? (a.direction == "input" ? -1 : 1) : 0);
midiPorts.sort((a, b) => a.direction != b.direction ? (a.direction == "input" ? -1 : 1) : 0);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, audioPorts);
data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, midiPorts);
var hasCtrlIn = false;
var hasCtrlOut = false;
for (var i = 0; i < data.product.parameters.length && !(hasCtrlIn && hasCtrlOut); i++) {
var p = data.product.parameters[i];
if (p.direction == "input")
hasCtrlIn = true;
else
hasCtrlOut = true;
}
if (hasCtrlIn)
data.tibia.lv2.ports.push({ type: "param", direction: "input", name: "Control in", id: "control_in", control: true });
if (hasCtrlOut)
data.tibia.lv2.ports.push({ type: "param", direction: "output", name: "Control out", id: "control_out", control: true });
}
api.generateFileFromTemplateFile(`data${sep}manifest.ttl.in`, `data${sep}manifest.ttl.in`, data);
api.copyFile(`src${sep}lv2.c`, `src${sep}lv2.c`);
api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data);
};

View File

@ -15,7 +15,7 @@
doap:maintainer [
a foaf:Organization ;
foaf:name "{{=it.company.name}}" ;
foaf:mbox "{{=it.company.email}}" ;
foaf:mbox <mailto:{{=it.company.email}}> ;
rdfs:seeAlso {{=it.tibia.lv2.ttlURI(it.company.url)}}
] ;
lv2:minorVersion {{=/^([0-9]+)\./.exec(it.lv2.version)[1]}} ;
@ -25,8 +25,8 @@
lv2:optionalFeature log:log ;
{{?}}
lv2:optionalFeature lv2:hardRTCapable ;
{{?it.lv2.ui}}
ui:ui {{=it.tibia.lv2.ttlURI(it.lv2.ui.uri)}} ;
{{?it.product.ui}}
ui:ui plugin:ui ;
{{?}}
lv2:port [
{{~it.tibia.lv2.ports :p:i}}
@ -121,8 +121,8 @@
{{?}}
{{~}}
{{?it.lv2.ui}}
{{=it.tibia.lv2.ttlURI(it.lv2.ui.uri)}}
{{?it.product.ui}}
plugin:ui
a ui:@UI_TYPE@ ;
ui:binary <{{=it.product.bundleName}}@DLL_SUFFIX@> ;
{{?!it.product.ui.userResizable}}

View File

@ -65,9 +65,9 @@ static uint32_t param_out_index[DATA_PRODUCT_CONTROL_OUTPUTS_N] = {
};
#endif
{{?it.lv2.ui}}
{{?it.product.ui}}
#define DATA_UI
#define DATA_LV2_UI_URI "{{=it.tibia.CGetUTF8StringLiteral(it.tibia.lv2.expandURI(it.lv2.ui.uri))}}"
#define DATA_LV2_UI_URI "{{=it.tibia.CGetUTF8StringLiteral(it.tibia.lv2.expandURI(it.lv2.uri + '#ui'))}}"
#define DATA_UI_USER_RESIZABLE {{=it.product.ui.userResizable ? 1 : 0}}
#if DATA_PRODUCT_CONTROL_INPUTS_N > 0

View File

@ -67,7 +67,12 @@ module.exports = function (data, api, outputCommon, outputData) {
ports: [],
ttlURI: function (uri) {
return uri.charAt(0) == "@" ? uri.substring(1) : "<" + uri + ">";
if (uri.charAt(0) == "@") {
if (uri.indexOf("#") == -1)
return uri.substring(1);
uri = data.tibia.lv2.expandURI(uri);
}
return "<" + uri + ">";
},
expandURI: function (uri) {
@ -82,6 +87,8 @@ module.exports = function (data, api, outputCommon, outputData) {
for (var id in data.lv2.prefixes)
data.tibia.lv2.prefixes.push({ id: id, uri: data.lv2.prefixes[id] });
data.tibia.lv2.prefixes.push({ id: "plugin", uri: data.tibia.lv2.expandURI(data.lv2.uri) + "#" });
var buses = data.product.buses;
var audioPorts = [];
var midiPorts = [];

View File

@ -6,9 +6,6 @@
"uri": "@example:tibia_test",
"project": "@example:project",
"types": [ "@lv2:AmplifierPlugin" ],
"version": "1.0",
"ui": {
"uri": "@example:tibia_test_ui"
}
"version": "1.0"
}
}