From f7b8b434c84a6120df9163568e7203fefe983ba2 Mon Sep 17 00:00:00 2001 From: Paolo Marrone Date: Tue, 10 Jun 2025 18:18:18 +0200 Subject: [PATCH] lv2: messaging wip (actually broken) --- templates/lv2/data/manifest.ttl.in | 17 ++- templates/lv2/src/data.h | 7 ++ templates/lv2/src/lv2.c | 190 ++++++++++++++++++++++++++++- templates/lv2/tibia-index.js | 24 +++- 4 files changed, 232 insertions(+), 6 deletions(-) diff --git a/templates/lv2/data/manifest.ttl.in b/templates/lv2/data/manifest.ttl.in index 4f9086f..0d0a575 100644 --- a/templates/lv2/data/manifest.ttl.in +++ b/templates/lv2/data/manifest.ttl.in @@ -69,7 +69,7 @@ lv2:portProperty lv2:integer ; lv2:portProperty lv2:reportsLatency ; {{??}} - a {{?p.type == "control"}}lv2:ControlPort{{??p.type == "midi"}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} , + a {{?p.type == "control"}}lv2:ControlPort{{??p.type == "midi"}}atom:AtomPort{{??p.type == "message"}}atom:AtomPort{{??}}{{?p.cv}}lv2:CVPort{{??}}lv2:AudioPort{{?}}{{?}} , {{?p.direction == "input"}}lv2:InputPort{{??}}lv2:OutputPort{{?}} ; lv2:name "{{=p.name}}" ; {{?"shortName" in p}} @@ -83,6 +83,11 @@ {{??p.type == "midi"}} atom:bufferType atom:Sequence ; atom:supports midi:MidiEvent ; +{{??p.type == "message"}} + atom:bufferType atom:Sequence ; +{{?}} +{{?p.maxSize}} + rsz:minimumSize {{=p.maxSize}}; {{?}} {{?p.sidechain}} lv2:portProperty lv2:isSideChain ; @@ -140,5 +145,13 @@ {{?}} {{?}} lv2:requiredFeature ui:idleInterface ; - lv2:extensionData ui:idleInterface . + lv2:extensionData ui:idleInterface ; {{?}} +{{?it.product.messaging}} + ui:portNotification [ + ui:ui {{=it.tibia.lv2.ttlURI(it.lv2.uri)}} ; + lv2:symbol "notify" ; + ui:notifyType atom:Blank + ] +{{?}} + . \ No newline at end of file diff --git a/templates/lv2/src/data.h b/templates/lv2/src/data.h index 4d304e2..1a08bd8 100644 --- a/templates/lv2/src/data.h +++ b/templates/lv2/src/data.h @@ -86,3 +86,10 @@ static uint32_t index_to_param[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONT {{?it.product.state && it.product.state.dspCustom}} #define DATA_STATE_DSP_CUSTOM {{?}} + +{{?it.product.messaging}} +#define DATA_MESSAGING 1 +#define DATA_MESSAGING_MAX {{=it.product.messaging.maxSize}} +#define DATA_MESSAGING_PORT_IN {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.id == 'message_in'))}} +#define DATA_MESSAGING_PORT_OUT {{=it.tibia.lv2.ports.indexOf(it.tibia.lv2.ports.find(p => p.id == 'message_out'))}} +{{?}} diff --git a/templates/lv2/src/lv2.c b/templates/lv2/src/lv2.c index ba3ce82..f573ecd 100644 --- a/templates/lv2/src/lv2.c +++ b/templates/lv2/src/lv2.c @@ -49,7 +49,9 @@ #ifdef DATA_STATE_DSP_CUSTOM # include #endif - +#ifdef DATA_MESSAGING +#include +#endif #include #if defined(__i386__) || defined(__x86_64__) @@ -91,6 +93,25 @@ static float adjust_param(size_t index, float value) { } #endif +#ifdef DATA_MESSAGING +typedef struct { + LV2_URID prop_customMessage; + LV2_URID atom_String; + LV2_URID atom_eventTransfer; + LV2_URID ui_On; + LV2_URID length; +} communication_URIs; + +static inline void map_comm_uris(LV2_URID_Map* map, communication_URIs* uris) { + uris->prop_customMessage = map->map(map->handle, "http://example.org#message"); + uris->atom_String = map->map(map->handle, LV2_ATOM__String); + uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); + uris->ui_On = map->map(map->handle, DATA_LV2_URI "#UIOn"); + uris->length = map->map(map->handle, LV2_ATOM__Int); +} + +#endif + typedef struct { plugin p; float sample_rate; @@ -132,6 +153,15 @@ typedef struct { LV2_State_Store_Function state_store; LV2_State_Handle state_handle; #endif + +#ifdef DATA_MESSAGING + LV2_Atom_Forge forge; + LV2_Atom_Forge_Frame frame; + + communication_URIs c_uris; + const LV2_Atom_Sequence* control; + LV2_Atom_Sequence* notify; +#endif } plugin_instance; static const char * get_bundle_path_cb(void *handle) { @@ -168,6 +198,36 @@ static void state_set_parameter_cb(void *handle, size_t index, float value) { # endif #endif +#ifdef DATA_MESSAGING +static char send_to_ui (void *handle, const void *data, size_t bytes) { + + plugin_instance * i = (plugin_instance *) handle; + + if (!data || bytes <= 0 || bytes > DATA_MESSAGING_MAX) { + return 1; + } + + const uint32_t space = i->notify->atom.size; + lv2_atom_forge_set_buffer(&i->forge, (uint8_t*)i->notify, space); + lv2_atom_forge_sequence_head(&i->forge, &i->frame, 0); + + LV2_Atom_Forge_Frame frame; + + lv2_atom_forge_frame_time(&i->forge, 0); + lv2_atom_forge_object(&i->forge, &frame, 0, i->c_uris.prop_customMessage); + + lv2_atom_forge_key(&i->forge, i->c_uris.length); + lv2_atom_forge_int(&i->forge, (int32_t)bytes); + + lv2_atom_forge_key(&i->forge, i->c_uris.prop_customMessage); + lv2_atom_forge_write(&i->forge, data, bytes); + + lv2_atom_forge_pop(&i->forge, &frame); + + return 0; +} +#endif + 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; @@ -209,7 +269,10 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s /* .handle = */ (void *)instance, /* .format = */ "lv2", /* .get_bindir = */ get_bundle_path_cb, - /* .get_datadir = */ get_bundle_path_cb + /* .get_datadir = */ get_bundle_path_cb, +#ifdef DATA_MESSAGING + /* .send_to_ui = */ send_to_ui +#endif }; plugin_init(&instance->p, &cbs); @@ -246,6 +309,10 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s for (uint32_t i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++) instance->c[i] = NULL; #endif +#ifdef DATA_MESSAGING + lv2_atom_forge_init(&instance->forge, instance->map); + map_comm_uris(instance->map, &instance->c_uris); +#endif return instance; @@ -263,6 +330,18 @@ err_instance: static void connect_port(LV2_Handle instance, uint32_t port, void * data_location) { plugin_instance * i = (plugin_instance *)instance; + printf("connect_port %u \n", port); +#ifdef DATA_MESSAGING + // Well, these should stay last... + if (port == DATA_MESSAGING_PORT_IN) { + i->control = (const LV2_Atom_Sequence*)data_location; + return; + } + if (port == DATA_MESSAGING_PORT_OUT) { + i->notify = (LV2_Atom_Sequence*)data_location; + return; + } +#endif #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 if (port < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N) { i->x[port] = data_location; @@ -319,6 +398,34 @@ static void activate(LV2_Handle instance) { static void run(LV2_Handle instance, uint32_t sample_count) { plugin_instance * i = (plugin_instance *)instance; + printf("run A \n"); fflush(stdout); + + if (0 && i->control) { + const LV2_Atom_Event* ev = lv2_atom_sequence_begin(&(i->control)->body); + while (!lv2_atom_sequence_is_end(&i->control->body, i->control->atom.size, ev)) { + if (lv2_atom_forge_is_object_type(&i->forge, ev->body.type)) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; + + printf("audio - received object, otype: %d\n", obj->body.otype); + + const LV2_Atom* msg_atom = NULL; + + lv2_atom_object_get(obj, i->c_uris.prop_customMessage, &msg_atom, 0); + + printf("audio - custom msg %d \n", i->c_uris.prop_customMessage); + printf("audio - %p \n", msg_atom); + printf("audio - %d %d \n", msg_atom->type, i->c_uris.atom_String); + + if (msg_atom && msg_atom->type == i->c_uris.atom_String) { + const char* msg_str = (const char*)(msg_atom + 1); + printf("audio - received string from UI: %s\n", msg_str); + } + } + ev = lv2_atom_sequence_next(ev); + } + } + + #if defined(__aarch64__) uint64_t fpcr; __asm__ __volatile__ ("mrs %0, fpcr" : "=r"(fpcr)); @@ -521,6 +628,11 @@ typedef struct { char has_touch; LV2UI_Touch touch; # endif +# ifdef DATA_MESSAGING + LV2_URID_Map* map; + LV2_Atom_Forge forge; + communication_URIs c_uris; +# endif } ui_instance; static const char * ui_get_bundle_path_cb(void *handle) { @@ -557,6 +669,38 @@ static void ui_set_parameter_end_cb(void *handle, size_t index, float value) { } # endif +# ifdef DATA_MESSAGING +static char send_to_dsp (void *handle, const void *data, size_t bytes) { + ui_instance* instance = (ui_instance*) handle; + + printf("send_to_dsp A \n"); fflush(stdout); + + if (!data || bytes <= 0 || bytes > DATA_MESSAGING_MAX) { + return 1; + } + + uint8_t obj_buf[256]; + lv2_atom_forge_set_buffer(&instance->forge, obj_buf, sizeof(obj_buf)); + LV2_Atom_Forge_Frame frame; + + // Start forging an object + LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&instance->forge, &frame, 0, instance->c_uris.ui_On); + assert(msg); + + // Add a string property to the object + lv2_atom_forge_key(&instance->forge, instance->c_uris.prop_customMessage); + lv2_atom_forge_string(&instance->forge, data, bytes); + + // Finish the object + lv2_atom_forge_pop(&instance->forge, &frame); + + // Send the forged atom to the host + instance->write(instance->controller, 8, lv2_atom_total_size(msg), instance->c_uris.atom_eventTransfer, msg); + + return 0; +} +# 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; @@ -596,6 +740,9 @@ static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const ch /* .set_parameter_begin = */ ui_set_parameter_begin_cb, /* .set_parameter = */ ui_set_parameter_cb, /* .set_parameter_end = */ ui_set_parameter_end_cb, +# endif +# ifdef DATA_MESSAGING + /* .send_to_dsp = */ send_to_dsp # endif }; # if DATA_PRODUCT_CONTROL_INPUTS_N > 0 @@ -610,6 +757,15 @@ static LV2UI_Handle ui_instantiate(const LV2UI_Descriptor * descriptor, const ch goto err_create; *widget = instance->ui->widget; + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + instance->map = (LV2_URID_Map*)features[i]->data; + } + } + lv2_atom_forge_init(&instance->forge, instance->map); + map_comm_uris(instance->map, &instance->c_uris); + return instance; err_create: @@ -631,7 +787,6 @@ static void ui_cleanup(LV2UI_Handle handle) { # 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 @@ -646,6 +801,35 @@ static void ui_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buf # endif plugin_ui_set_parameter(instance->ui, param_out_index[port_index - CONTROL_OUTPUT_INDEX_OFFSET], *((float *)buffer)); # endif + + const LV2_Atom* atom = (const LV2_Atom*)buffer; + + /* Check type of data received + * - format == 0: Control port event (float) + * - format > 0: Message (atom) + */ + if (format == instance->c_uris.atom_eventTransfer && lv2_atom_forge_is_object_type(&instance->forge, atom->type)) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + if (obj->body.otype == instance->c_uris.prop_customMessage) { + printf("received something from audio INCREDIBLE \n"); + const LV2_Atom* len_a = NULL; + const LV2_Atom* data_a = NULL; + const int n_props = lv2_atom_object_get(obj, instance->c_uris.length, &len_a, instance->c_uris.prop_customMessage, &data_a, NULL); + const int32_t len = ((const LV2_Atom_Int*) len_a)->body; + printf("and n_props is %d \n", n_props); + printf("and len is %d \n", len); + printf("and data_a is %p \n", (void*)data_a); + //printf("and data_a type: %u, size: %u \n", data_a->type, data_a->size); + const uint8_t* data = (uint8_t*) data_a; + printf("data... \n"); + for (int x = 0; x < len; x++) { + printf("%u ", data[x]); + } + printf("\n"); + } + } + + } # endif diff --git a/templates/lv2/tibia-index.js b/templates/lv2/tibia-index.js index 0a52f1d..1f0e112 100644 --- a/templates/lv2/tibia-index.js +++ b/templates/lv2/tibia-index.js @@ -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: "rsz", uri: "http://lv2plug.in/ns/ext/resize-port#" } ], units: { "bar": "@units:bar", @@ -123,6 +124,27 @@ module.exports = function (data, api, outputCommon, outputData) { } 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); + + if (data.product.messaging) { + const ps = [ + { + type: "message", + direction: "input", + control: true, + id: "message_in", + name: "message_in" + }, + { + type: "message", + direction: "output", + control: true, + id: "message_out", + name: "message_out", + maxSize: data.product.messaging.maxSize + } + ]; + data.tibia.lv2.ports.push.apply(data.tibia.lv2.ports, ps); + } } api.generateFileFromTemplateFile(`data${sep}manifest.ttl.in`, `data${sep}manifest.ttl.in`, data);