diff options
-rw-r--r-- | .travis.yml | 36 | ||||
-rw-r--r-- | CMakeLists.txt | 90 | ||||
-rw-r--r-- | README.md | 54 | ||||
-rw-r--r-- | atom_inspector.c | 172 | ||||
-rw-r--r-- | atom_inspector_eo.c | 768 | ||||
-rw-r--r-- | common.h | 36 | ||||
-rw-r--r-- | encoder.l | 298 | ||||
-rw-r--r-- | manifest.ttl.in | 104 | ||||
-rw-r--r-- | midi_inspector.c | 190 | ||||
-rw-r--r-- | midi_inspector_eo.c | 867 | ||||
-rw-r--r-- | omk_logo_256x256.png | bin | 0 -> 6916 bytes | |||
-rw-r--r-- | osc.lv2/CMakeLists.txt | 16 | ||||
-rw-r--r-- | osc.lv2/COPYING | 201 | ||||
-rw-r--r-- | osc.lv2/README.md | 3 | ||||
-rw-r--r-- | osc.lv2/lv2-osc.doap.ttl (renamed from lv2-osc.doap.ttl) | 0 | ||||
-rw-r--r-- | osc.lv2/manifest.ttl (renamed from manifest.ttl) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.lv2/forge.h (renamed from osc.lv2/forge.h) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.lv2/osc.h (renamed from osc.lv2/osc.h) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.lv2/reader.h (renamed from osc.lv2/reader.h) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.lv2/util.h (renamed from osc.lv2/util.h) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.lv2/writer.h (renamed from osc.lv2/writer.h) | 0 | ||||
-rw-r--r-- | osc.lv2/osc.ttl (renamed from osc.ttl) | 0 | ||||
-rw-r--r-- | osc.lv2/osc_test.c (renamed from osc_test.c) | 0 | ||||
-rw-r--r-- | osc_inspector.c | 193 | ||||
-rw-r--r-- | osc_inspector_eo.c | 978 | ||||
-rw-r--r-- | sandbox_ui.lv2/COPYING | 201 | ||||
-rw-r--r-- | sandbox_ui.lv2/README.md | 18 | ||||
-rw-r--r-- | sandbox_ui.lv2/lv2_external_ui.h | 109 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_efl.c | 199 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_io.h | 555 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_master.c | 100 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_master.h | 65 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_slave.c | 561 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_slave.h | 73 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_ui.c | 395 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_ui.h | 47 | ||||
-rw-r--r-- | sherlock.c | 34 | ||||
-rw-r--r-- | sherlock.h | 75 | ||||
-rw-r--r-- | sherlock.ttl | 187 | ||||
-rw-r--r-- | sherlock_eo.c | 35 | ||||
-rw-r--r-- | sherlock_ui.c | 97 | ||||
-rw-r--r-- | sherlock_ui.ttl | 135 | ||||
-rw-r--r-- | symap/symap.c | 231 | ||||
-rw-r--r-- | symap/symap.h | 69 |
44 files changed, 7183 insertions, 9 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c161961 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +sudo: required +dist: trusty +language: + - c +os: + - linux +compiler: + - gcc + # - clang +before_install: + - wget http://lv2plug.in/spec/lv2-1.12.0.tar.bz2 + - wget http://download.drobilla.net/serd-0.22.0.tar.bz2 + - wget http://download.drobilla.net/sord-0.14.0.tar.bz2 + - wget http://download.drobilla.net/sratom-0.4.6.tar.bz2 + - wget http://download.drobilla.net/lilv-0.22.0.tar.bz2 + - wget https://github.com/nanomsg/nanomsg/releases/download/0.8-beta/nanomsg-0.8-beta.tar.gz + - tar xjf lv2-1.12.0.tar.bz2 + - tar xjf serd-0.22.0.tar.bz2 + - tar xjf sord-0.14.0.tar.bz2 + - tar xjf sratom-0.4.6.tar.bz2 + - tar xjf lilv-0.22.0.tar.bz2 + - tar xzf nanomsg-0.8-beta.tar.gz + - sudo add-apt-repository -y ppa:enlightenment-git/ppa + - sudo apt-get -q update +install: + - sudo apt-get install -y libefl-dev + - pushd lv2-1.12.0 && ./waf configure --no-plugins --prefix=/usr && ./waf build && sudo ./waf install && popd + - pushd serd-0.22.0 && ./waf configure --no-utils --prefix=/usr && ./waf build && sudo ./waf install && popd + - pushd sord-0.14.0 && ./waf configure --no-utils --prefix=/usr && ./waf build && sudo ./waf install && popd + - pushd sratom-0.4.6 && ./waf configure --prefix=/usr && ./waf build && sudo ./waf install && popd + - pushd lilv-0.22.0 && ./waf configure --no-utils --prefix=/usr && ./waf build && sudo ./waf install && popd + - pushd nanomsg-0.8-beta && ./configure --prefix=/usr && make && sudo make install && popd +before_script: + - mkdir build && pushd build && cmake .. && popd +script: + - pushd build && make && sudo make install && popd diff --git a/CMakeLists.txt b/CMakeLists.txt index 64ce73b..b8e848e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,92 @@ cmake_minimum_required(VERSION 2.8) -project(osc.lv2) +project(sherlock.lv2) include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/libosc) +include_directories(${PROJECT_SOURCE_DIR}/osc.lv2) +include_directories(${PROJECT_SOURCE_DIR}/sandbox_ui.lv2) +include_directories(${PROJECT_SOURCE_DIR}/symap) set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}") -set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}") +set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}") -include(CTest) +set(SHERLOCK_MAJOR_VERSION 0) +set(SHERLOCK_MINOR_VERSION 1) +set(SHERLOCK_MICRO_VERSION 3) +set(SHERLOCK_VERSION "${SHERLOCK_MAJOR_VERSION}.${SHERLOCK_MINOR_VERSION}.${SHERLOCK_MICRO_VERSION}") +add_definitions("-DSHERLOCK_VERSION=\"${SHERLOCK_VERSION}\"") +add_definitions("-D_GNU_SOURCE=1") # asprintf -if(${BUILD_TESTING}) - add_executable(osc_test - osc_test.c) - add_test(NAME API-Test COMMAND osc_test) +set(DEST lib/lv2/sherlock.lv2) + +find_package(PkgConfig) # ${PKG_CONFIG_FOUND} + +pkg_search_module(LV2 REQUIRED lv2>=1.10) +include_directories(${LV2_INCLUDE_DIRS}) + +pkg_search_module(ELM REQUIRED elementary>=1.8) +include_directories(${ELM_INCLUDE_DIRS}) + +pkg_search_module(NANOMSG REQUIRED libnanomsg>=2.0) +include_directories(${NANOMSG_INCLUDE_DIRS}) + +pkg_search_module(SRATOM REQUIRED sratom-0>=0.4.0) +include_directories(${SRATOM_INCLUDE_DIRS}) + +pkg_search_module(LILV REQUIRED lilv-0>=0.20.0) +include_directories(${LILV_INCLUDE_DIRS}) +if((${LILV_VERSION} VERSION_EQUAL "0.22.0") OR (${LILV_VERSION} VERSION_GREATER "0.22.0")) + add_definitions("-DLILV_0_22") endif() + +add_library(sherlock MODULE + sherlock.c + atom_inspector.c + midi_inspector.c + osc_inspector.c) +set_target_properties(sherlock PROPERTIES PREFIX "") +install(TARGETS sherlock DESTINATION ${DEST}) + +add_library(sherlock_ui MODULE + ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_ui.c + ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_master.c + sherlock_ui.c) +target_link_libraries(sherlock_ui + ${NANOMSG_LDFLAGS} + ${SRATOM_LDFLAGS}) +set_target_properties(sherlock_ui PROPERTIES PREFIX "") +install(TARGETS sherlock_ui DESTINATION ${DEST}) + +find_package(FLEX) +flex_target(encoder encoder.l ${PROJECT_BINARY_DIR}/encoder.c + COMPILE_FLAGS "--header-file=${PROJECT_BINARY_DIR}/encoder.h --prefix=enc") + +add_library(sherlock_eo MODULE + sherlock_eo.c + atom_inspector_eo.c + midi_inspector_eo.c + osc_inspector_eo.c + ${FLEX_encoder_OUTPUTS}) +target_link_libraries(sherlock_eo + ${ELM_LDFLAGS} + ${SRATOM_LDFLAGS}) +set_target_properties(sherlock_eo PROPERTIES PREFIX "") +install(TARGETS sherlock_eo DESTINATION ${DEST}) + +add_executable(sandbox_efl + ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_slave.c + ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_efl.c + ${PROJECT_SOURCE_DIR}/symap/symap.c) +target_link_libraries(sandbox_efl + ${ELM_LDFLAGS} + ${NANOMSG_LDFLAGS} + ${LILV_LDFLAGS}) +install(TARGETS sandbox_efl DESTINATION ${DEST}) + +configure_file(${PROJECT_SOURCE_DIR}/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl) +install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${DEST}) +install(FILES ${PROJECT_SOURCE_DIR}/sherlock.ttl DESTINATION ${DEST}) +install(FILES ${PROJECT_SOURCE_DIR}/sherlock_ui.ttl DESTINATION ${DEST}) +install(FILES ${PROJECT_SOURCE_DIR}/omk_logo_256x256.png DESTINATION ${DEST}) @@ -1,3 +1,53 @@ -# osc.lv2 +# Sherlock -## Open Sound Control Extension for the LV2 Plugin Specification +## An investigative LV2 plugin bundle + +### Webpage + +Get more information at: [http://open-music-kontrollers.ch/lv2/sherlock](http://open-music-kontrollers.ch/lv2/sherlock) + +### Build status + +[](https://travis-ci.org/OpenMusicKontrollers/sherlock.lv2) + +### Plugins + +#### Atom Inspector + +##### Screenshot + + + +### Dependencies + +* [LV2](http://lv2plug.in) (LV2 Plugin Standard) +* [EFL](http://docs.enlightenment.org/stable/elementary/) (Enlightenment Foundation Libraries) +* [Elementary](http://docs.enlightenment.org/stable/efl/) (Lightweight GUI Toolkit) + +### Build / install + + git clone https://github.com/OpenMusicKontrollers/sherlock.lv2.git + cd sherlock.lv2 + git submodule update --init + mkdir build + cd build + cmake -DCMAKE_C_FLAGS="-std=gnu99" .. + make + sudo make install + +### License + +Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source 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 +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/atom_inspector.c b/atom_inspector.c new file mode 100644 index 0000000..2d733b4 --- /dev/null +++ b/atom_inspector.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <sherlock.h> + +typedef struct _handle_t handle_t; + +struct _handle_t { + LV2_URID_Map *map; + const LV2_Atom_Sequence *control_in; + LV2_Atom_Sequence *control_out; + LV2_Atom_Sequence *notify; + LV2_Atom_Forge forge; + + LV2_URID time_position; + LV2_URID time_frame; + + int64_t frame; +}; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path, const LV2_Feature *const *features) +{ + int i; + handle_t *handle = calloc(1, sizeof(handle_t)); + if(!handle) + return NULL; + + for(i=0; features[i]; i++) + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = (LV2_URID_Map *)features[i]->data; + + if(!handle->map) + { + fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position); + handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame); + + lv2_atom_forge_init(&handle->forge, handle->map); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + handle_t *handle = (handle_t *)instance; + + switch(port) + { + case 0: + handle->control_in = (const LV2_Atom_Sequence *)data; + break; + case 1: + handle->control_out = (LV2_Atom_Sequence *)data; + break; + case 2: + handle->notify = (LV2_Atom_Sequence *)data; + break; + default: + break; + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + handle_t *handle = (handle_t *)instance; + uint32_t capacity; + LV2_Atom_Forge *forge = &handle->forge; + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + + if(lv2_atom_forge_is_object_type(forge, obj->atom.type)) + { + if(obj->body.otype == handle->time_position) + { + const LV2_Atom_Long *time_frame = NULL; + lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL); + if(time_frame) + handle->frame = time_frame->body - ev->time.frames; + } + } + } + + // size of input sequence + size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size; + + // copy whole input sequence to through port + capacity = handle->control_out->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity); + ref = lv2_atom_forge_raw(forge, handle->control_in, size); + if(!ref) + lv2_atom_sequence_clear(handle->control_out); + + // forge whole sequence as single event + capacity = handle->notify->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity); + ref = lv2_atom_forge_sequence_head(forge, &frame, 0); + + // only serialize sequence to UI if there were actually any events + if(handle->control_in->atom.size > sizeof(LV2_Atom_Sequence_Body)) + { + LV2_Atom_Forge_Frame tup_frame; + + if(ref) + ref = lv2_atom_forge_frame_time(forge, 0); + if(ref) + ref = lv2_atom_forge_tuple(forge, &tup_frame); + if(ref) + ref = lv2_atom_forge_long(forge, handle->frame); + if(ref) + ref = lv2_atom_forge_int(forge, nsamples); + if(ref) + ref = lv2_atom_forge_write(forge, handle->control_in, size); + if(ref) + lv2_atom_forge_pop(forge, &tup_frame); + + } + + if(ref) + lv2_atom_forge_pop(forge, &frame); + else + lv2_atom_sequence_clear(handle->notify); + + handle->frame += nsamples; +} + +static void +cleanup(LV2_Handle instance) +{ + handle_t *handle = (handle_t *)instance; + + free(handle); +} + +const LV2_Descriptor atom_inspector = { + .URI = SHERLOCK_ATOM_INSPECTOR_URI, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = NULL +}; diff --git a/atom_inspector_eo.c b/atom_inspector_eo.c new file mode 100644 index 0000000..41ba32a --- /dev/null +++ b/atom_inspector_eo.c @@ -0,0 +1,768 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <inttypes.h> +#include <stdio.h> + +#include <sherlock.h> +#include <common.h> + +#include <Elementary.h> + +#include <sratom/sratom.h> + +#define COUNT_MAX 2048 // maximal amount of events shown +#define STRING_BUF_SIZE 2048 +#define STRING_MAX 256 +#define STRING_OFF (STRING_MAX - 4) + +#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#" +#define NS_OSC (const uint8_t*)"http://open-music-kontrollers.ch/lv2/osc#" +#define NS_XPRESS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/xpress#" +#define NS_SPOD (const uint8_t*)"http://open-music-kontrollers.ch/lv2/synthpod#" + +typedef struct _UI UI; + +struct _UI { + LV2UI_Write_Function write_function; + LV2UI_Controller controller; + + LV2_URID_Map *map; + LV2_URID_Unmap *unmap; + struct { + LV2_URID event_transfer; + } uris; + + LV2_Atom_Forge forge; + + Evas_Object *widget; + Evas_Object *table; + Evas_Object *list; + Evas_Object *info; + Evas_Object *clear; + Evas_Object *autoclear; + Evas_Object *autoblock; + Evas_Object *popup; + + Elm_Genlist_Item_Class *itc_list; + Elm_Genlist_Item_Class *itc_group; + + char string_buf [STRING_BUF_SIZE]; + char *logo_path; + + Eina_Hash *urids; + + Sratom *sratom; + const char *base_uri; + + char *chunk; +}; + +#define CODE_PRE "<style=shadow,bottom>" +#define CODE_POST "</style>" + +#define URI(VAL,TYP) ("<color=#bbb font=Mono><b>"VAL"</b></color> <color=#fff font=Default>"TYP"</color>") + +static void +_encoder_begin(void *data) +{ + UI *ui = data; + + ui->chunk = strdup(""); +} + +static void +_encoder_append(const char *str, void *data) +{ + UI *ui = data; + + size_t size = 0; + size += ui->chunk ? strlen(ui->chunk) : 0; + size += str ? strlen(str) + 1 : 0; + + if(size) + ui->chunk = realloc(ui->chunk, size); + + if(ui->chunk && str) + strcat(ui->chunk, str); +} + +static void +_encoder_end(void *data) +{ + UI *ui = data; + + elm_entry_entry_set(ui->info, ui->chunk); + free(ui->chunk); +} + +static moony_encoder_t enc = { + .begin = _encoder_begin, + .append = _encoder_append, + .end = _encoder_end, + .data = NULL +}; +moony_encoder_t *encoder = &enc; + +static void +_hash_del(void *data) +{ + char *uri = data; + + if(uri) + free(uri); +} + +static inline const char * +_hash_set(UI *ui, LV2_URID urid) +{ + const char *uri = ui->unmap->unmap(ui->unmap->handle, urid); + if(uri) + eina_hash_add(ui->urids, &urid, strdup(uri)); + + //printf("prefill: %s (%"PRIu32")\n", uri, urid); + + return uri; +} + +static inline const char * +_hash_get(UI *ui, LV2_URID urid) +{ + const char *uri = eina_hash_find(ui->urids, &urid); + + if(!uri) + uri = _hash_set(ui, urid); + + return uri; +} + +static char * +_list_item_label_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + const LV2_Atom *atom = &ev->body; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.text")) + { + char *buf = ui->string_buf; + char *ptr = buf; + char *end = buf + STRING_BUF_SIZE; + + sprintf(ptr, CODE_PRE); + ptr += strlen(ptr); + + const char *type = _hash_get(ui, atom->type); + sprintf(ptr, URI(" ", "%s (%"PRIu32")"), type, atom->type); + + return ptr + ? strdup(buf) + : NULL; + } + + return NULL; +} + +static Evas_Object * +_seq_item_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + const LV2_Atom *atom = &ev->body; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#bb0 font=Mono>%04"PRIi64"</color>", ev->time.frames); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%4"PRIu32"</color>", atom->size); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static Evas_Object * +_group_item_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const position_t *pos = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static void +_del(void *data, Evas_Object *obj) +{ + free(data); +} + +// copyied and adapted from libsratom +static inline char * +_sratom_to_turtle(Sratom* sratom, + LV2_URID_Unmap* unmap, + const char* base_uri, + const SerdNode* subject, + const SerdNode* predicate, + uint32_t type, + uint32_t size, + const void* body) +{ + SerdURI buri = SERD_URI_NULL; + SerdNode base = serd_node_new_uri_from_string((uint8_t *)(base_uri), NULL, &buri); + SerdEnv* env = serd_env_new(&base); + SerdChunk str = { NULL, 0 }; + + serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdf", NS_RDF); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"xsd", NS_XSD); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"lv2", (const uint8_t *)LV2_CORE_PREFIX); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"midi", (const uint8_t *)LV2_MIDI_PREFIX); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"atom", (const uint8_t *)LV2_ATOM_PREFIX); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"units", (const uint8_t *)LV2_UNITS_PREFIX); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"ui", (const uint8_t *)LV2_UI_PREFIX); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"time", (const uint8_t *)LV2_TIME_URI"#"); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"patch", (const uint8_t *)LV2_PATCH_PREFIX); + + serd_env_set_prefix_from_strings(env, (const uint8_t *)"osc", NS_OSC); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"xpress", NS_XPRESS); + serd_env_set_prefix_from_strings(env, (const uint8_t *)"spod", NS_SPOD); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_ABBREVIATED | + SERD_STYLE_RESOLVED | + SERD_STYLE_CURIED), + env, &buri, serd_chunk_sink, &str); + + // Write @prefix directives + serd_env_foreach(env, + (SerdPrefixSink)serd_writer_set_prefix, + writer); + + sratom_set_sink(sratom, base_uri, + (SerdStatementSink)serd_writer_write_statement, + (SerdEndSink)serd_writer_end_anon, + writer); + sratom_write(sratom, unmap, SERD_EMPTY_S, + subject, predicate, type, size, body); + serd_writer_finish(writer); + + serd_writer_free(writer); + serd_env_free(env); + serd_node_free(&base); + return (char*)serd_chunk_sink_finish(&str); +} + +static void +_item_selected(void *data, Evas_Object *obj, void *event_info) +{ + Elm_Object_Item *itm = event_info; + UI *ui = data; + + const LV2_Atom_Event *ev = elm_object_item_data_get(itm); + const LV2_Atom *atom = &ev->body; + + char *ttl = _sratom_to_turtle(ui->sratom, ui->unmap, + ui->base_uri, NULL, NULL, + atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); + if(ttl) + { + enc.data = ui; + ttl_to_markup(ttl, NULL); + + free(ttl); + } +} + +static void +_clear_update(UI *ui, int count) +{ + if(!ui->clear) + return; + + char *buf = ui->string_buf; + sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX); + elm_object_text_set(ui->clear, buf); +} + +static void +_clear_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + if(ui->list) + elm_genlist_clear(ui->list); + if(ui->info) + elm_entry_entry_set(ui->info, ""); + + _clear_update(ui, 0); +} + +static void +_info_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + // toggle popup + if(ui->popup) + { + if(evas_object_visible_get(ui->popup)) + evas_object_hide(ui->popup); + else + evas_object_show(ui->popup); + } +} + +static void +_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + ui->widget = NULL; +} + +static void +_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + evas_object_del(ui->widget); +} + +static Evas_Object * +_content_get(UI *ui, Evas_Object *parent) +{ + ui->table = elm_table_add(parent); + if(ui->table) + { + elm_table_homogeneous_set(ui->table, EINA_FALSE); + elm_table_padding_set(ui->table, 0, 0); + evas_object_size_hint_min_set(ui->table, 1280, 720); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui); + + + Evas_Object *panes = elm_panes_add(ui->table); + if(panes) + { + elm_panes_horizontal_set(panes, EINA_FALSE); + elm_panes_content_left_size_set(panes, 0.3); + evas_object_size_hint_weight_set(panes, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(panes, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(panes); + elm_table_pack(ui->table, panes, 0, 0, 4, 1); + + ui->list = elm_genlist_add(panes); + if(ui->list) + { + elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading + elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT); + elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading + elm_genlist_reorder_mode_set(ui->list, EINA_FALSE); + elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT); + evas_object_data_set(ui->list, "ui", ui); + evas_object_smart_callback_add(ui->list, "selected", _item_selected, ui); + evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->list); + elm_object_part_content_set(panes, "left", ui->list); + } + + ui->info = elm_entry_add(ui->table); + if(ui->info) + { + elm_entry_autosave_set(ui->info, EINA_FALSE); + elm_entry_entry_set(ui->info, ""); + elm_entry_single_line_set(ui->info, EINA_FALSE); + elm_entry_scrollable_set(ui->info, EINA_TRUE); + elm_entry_editable_set(ui->info, EINA_FALSE); + elm_entry_cnp_mode_set(ui->info, ELM_CNP_MODE_PLAINTEXT); + evas_object_size_hint_weight_set(ui->info, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(ui->info, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->info); + elm_object_part_content_set(panes, "right", ui->info); + } + } + + ui->clear = elm_button_add(ui->table); + if(ui->clear) + { + _clear_update(ui, 0); + evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui); + evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f); + evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->clear); + elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1); + } + + ui->autoclear = elm_check_add(ui->table); + if(ui->autoclear) + { + elm_object_text_set(ui->autoclear, "overwrite"); + evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoclear); + elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1); + } + + ui->autoblock = elm_check_add(ui->table); + if(ui->autoblock) + { + elm_object_text_set(ui->autoblock, "block"); + evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoblock); + elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1); + } + + Evas_Object *info = elm_button_add(ui->table); + if(info) + { + evas_object_smart_callback_add(info, "clicked", _info_clicked, ui); + evas_object_size_hint_weight_set(info, 0.f, 0.f); + evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL); + evas_object_show(info); + elm_table_pack(ui->table, info, 3, 1, 1, 1); + + Evas_Object *icon = elm_icon_add(info); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 20, 20); + evas_object_size_hint_max_set(icon, 32, 32); + //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_object_part_content_set(info, "icon", icon); + } + } + + ui->popup = elm_popup_add(ui->table); + if(ui->popup) + { + elm_popup_allow_events_set(ui->popup, EINA_TRUE); + + Evas_Object *hbox = elm_box_add(ui->popup); + if(hbox) + { + elm_box_horizontal_set(hbox, EINA_TRUE); + elm_box_homogeneous_set(hbox, EINA_FALSE); + elm_box_padding_set(hbox, 10, 0); + evas_object_show(hbox); + elm_object_content_set(ui->popup, hbox); + + Evas_Object *icon = elm_icon_add(hbox); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 128, 128); + evas_object_size_hint_max_set(icon, 256, 256); + evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_box_pack_end(hbox, icon); + } + + Evas_Object *label = elm_label_add(hbox); + if(label) + { + elm_object_text_set(label, + "<color=#b00 shadow_color=#fff font_size=20>" + "Sherlock - Atom Inspector" + "</color></br><align=left>" + "Version "SHERLOCK_VERSION"</br></br>" + "Copyright (c) 2015 Hanspeter Portner</br></br>" + "This is free and libre software</br>" + "Released under Artistic License 2.0</br>" + "By Open Music Kontrollers</br></br>" + "<color=#bbb>" + "http://open-music-kontrollers.ch/lv2/sherlock</br>" + "dev@open-music-kontrollers.ch" + "</color></align>"); + + evas_object_show(label); + elm_box_pack_end(hbox, label); + } + } + } + } + + ui->sratom = sratom_new(ui->map); //FIXME check + sratom_set_pretty_numbers(ui->sratom, false); + ui->base_uri = "file:///tmp/base"; + + return ui->table; +} + +static LV2UI_Handle +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) +{ + if(strcmp(plugin_uri, SHERLOCK_ATOM_INSPECTOR_URI)) + return NULL; + + UI *ui = calloc(1, sizeof(UI)); + if(!ui) + return NULL; + + ui->write_function = write_function; + ui->controller = controller; + + Evas_Object *parent = NULL; + for(int i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + ui->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_URID__unmap)) + ui->unmap = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_UI__parent)) + parent = features[i]->data; + } + + if(!ui->map || !ui->unmap) + { + fprintf(stderr, "LV2 URID extension not supported\n"); + free(ui); + return NULL; + } + if(!parent) + { + free(ui); + return NULL; + } + + lv2_atom_forge_init(&ui->forge, ui->map); + + ui->itc_list = elm_genlist_item_class_new(); + if(ui->itc_list) + { + ui->itc_list->item_style = "default_style"; + ui->itc_list->func.text_get = _list_item_label_get; + ui->itc_list->func.content_get = _seq_item_content_get; + ui->itc_list->func.state_get = NULL; + ui->itc_list->func.del = _del; + } + + ui->itc_group = elm_genlist_item_class_new(); + if(ui->itc_group) + { + ui->itc_group->item_style = "default_style"; + ui->itc_group->func.text_get = NULL; + ui->itc_group->func.content_get = _group_item_content_get; + ui->itc_group->func.state_get = NULL; + ui->itc_group->func.del = _del; + } + + sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path); + ui->logo_path = strdup(ui->string_buf); + + ui->uris.event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer); + + ui->urids = eina_hash_int32_new(_hash_del); + + // prepopulate hash table + _hash_set(ui, ui->forge.Bool); + _hash_set(ui, ui->forge.Chunk); + _hash_set(ui, ui->forge.Double); + _hash_set(ui, ui->forge.Float); + _hash_set(ui, ui->forge.Int); + _hash_set(ui, ui->forge.Long); + _hash_set(ui, ui->forge.Literal); + _hash_set(ui, ui->forge.Object); + _hash_set(ui, ui->forge.Path); + _hash_set(ui, ui->forge.Property); + _hash_set(ui, ui->forge.Sequence); + _hash_set(ui, ui->forge.String); + _hash_set(ui, ui->forge.Tuple); + _hash_set(ui, ui->forge.URI); + _hash_set(ui, ui->forge.URID); + _hash_set(ui, ui->forge.Vector); + + ui->widget = _content_get(ui, parent); + if(!ui->widget) + { + free(ui); + return NULL; + } + *(Evas_Object **)widget = ui->widget; + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + UI *ui = handle; + + if(ui->widget) + evas_object_del(ui->widget); + if(ui->logo_path) + free(ui->logo_path); + + eina_hash_free(ui->urids); + + if(ui->itc_list) + elm_genlist_item_class_free(ui->itc_list); + if(ui->itc_group) + elm_genlist_item_class_free(ui->itc_group); + + if(ui->sratom) + sratom_free(ui->sratom); + + free(ui); +} + +static void +port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid, + const void *buf) +{ + UI *ui = handle; + + if( (i == 2) && (urid == ui->uris.event_transfer) && ui->list) + { + const LV2_Atom_Tuple *tup = buf; + const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup); + const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom); + const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom); + int n = elm_genlist_items_count(ui->list); + + Elm_Object_Item *itm = NULL; + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events + { + position_t *pos = malloc(sizeof(position_t)); + if(!pos) + return; + + pos->offset = offset->body; + pos->nsamples = nsamples->body; + + // check item count + if(n + 1 > COUNT_MAX) + { + if(elm_check_state_get(ui->autoclear)) + { + elm_genlist_clear(ui->list); + n = 0; + } + else + { + return; + } + } + else if(elm_check_state_get(ui->autoblock)) + { + return; + } + + itm = elm_genlist_item_append(ui->list, ui->itc_group, + pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL); + elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE); + + LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt) + { + size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size; + LV2_Atom_Event *ev = malloc(len); + if(!ev) + continue; + + memcpy(ev, elmnt, len); + + Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_list, + ev, itm, ELM_GENLIST_ITEM_NONE, NULL, NULL); + elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT); + n++; + + // scroll to last item + //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE); + } + } + + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) + _clear_update(ui, n); // only update if there where any events + } +} + +const LV2UI_Descriptor atom_inspector_eo = { + .URI = SHERLOCK_ATOM_INSPECTOR_EO_URI, + .instantiate = instantiate, + .cleanup = cleanup, + .port_event = port_event, + .extension_data = NULL +}; diff --git a/common.h b/common.h new file mode 100644 index 0000000..35dc3e9 --- /dev/null +++ b/common.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +typedef void (*encoder_begin_t)(void *data); +typedef void (*encoder_append_t)(const char *str, void *data); +typedef void (*encoder_end_t)(void *data); +typedef struct _moony_encoder_t moony_encoder_t; + +struct _moony_encoder_t { + encoder_begin_t begin; + encoder_append_t append; + encoder_end_t end; + void *data; +}; +extern moony_encoder_t *encoder; + +void ttl_to_markup(const char *utf8, FILE *f); + +#endif diff --git a/encoder.l b/encoder.l new file mode 100644 index 0000000..0fc55a0 --- /dev/null +++ b/encoder.l @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +%{ +#include <stdio.h> +#include <string.h> + +#include <common.h> + +typedef enum _markup_type_t markup_type_t; +typedef struct _markup_item_t markup_item_t; + +enum _markup_type_t { + MARKUP_CODE, + MARKUP_PREFIX, + MARKUP_SUBJECT, + MARKUP_PREDICATE, + MARKUP_NUMBER, + MARKUP_STRING, + MARKUP_URI +}; + +struct _markup_item_t { + const char *begin; + const char *end; +}; + +static const markup_item_t markup_items [] = { + [MARKUP_CODE] = {"font=Mono style=Plain color=#ffffff", "font"}, + [MARKUP_PREFIX] = {"color=#cc00cc", "color"}, + [MARKUP_SUBJECT] = {"color=#00cccc", "color"}, + [MARKUP_PREDICATE] = {"color=#00cc00", "color"}, + [MARKUP_NUMBER] = {"color=#0000cc", "color"}, + [MARKUP_STRING] = {"color=#cc0000", "color"}, + [MARKUP_URI] = {"color=#cccc00", "color"} +}; + +static void +_add_plain(const char *content) +{ + encoder->append(content, encoder->data); +} + +static void +_add_singleton(const char *key) +{ + char buf [64]; + sprintf(buf, "<%s/>", key); + encoder->append(buf, encoder->data); +} + +static void +_add_markup_begin(markup_type_t type) +{ + char buf [64]; + sprintf(buf, "<%s>", markup_items[type].begin); + encoder->append(buf, encoder->data); +} + +static void +_add_markup_end(markup_type_t type) +{ + char buf [64]; + sprintf(buf, "</%s>", markup_items[type].end); + encoder->append(buf, encoder->data); +} + +static void +_add_markup(markup_type_t type, const char *content) +{ + char buf [64]; + sprintf(buf, "<%s>", markup_items[type].begin); + encoder->append(buf, encoder->data); + encoder->append(content, encoder->data); + sprintf(buf, "</%s>", markup_items[type].end); + encoder->append(buf, encoder->data); +} + +enum { + TK_NONE, + TK_PREFIX, + TK_SUBJECT, + TK_PREDICATE, + TK_NUMBER, + TK_URI_IN, + TK_URI_OUT, + TK_URI_ERR, + TK_STRING_IN, + TK_STRING_OUT, + TK_STRING_ERR, + TK_LONG_STRING_IN, + TK_LONG_STRING_OUT, + TK_WHITESPACE, + TK_RAW, + TK_TAB, + TK_NEWLINE, + TK_LT, + TK_GT, + TK_AMP, + TK_NAME, + TK_BADCHAR +}; + +%} + +%option reentrant noyywrap + +w [ \v\a]+ +name [_a-zA-Z@][_a-zA-Z0-9\.]* +n [0-9]+ +exp [Ee][+-]?{n} +number ({n}|{n}[.]{n}){exp}? +eol [\n\r] + +%x XSTRING +%x XLONG_STRING +%x XURI + +%% + +{w} return TK_WHITESPACE; +"\t" return TK_TAB; +{eol} return TK_NEWLINE; +"<" BEGIN(XURI); return TK_URI_IN; +\"\"\" BEGIN(XLONG_STRING); return TK_LONG_STRING_IN; +\" BEGIN(XSTRING); return TK_STRING_IN; +{name}: return TK_SUBJECT; +"@prefix" return TK_PREFIX; +"a" return TK_PREFIX; +{name} return TK_PREDICATE; +{number} return TK_NUMBER; +. return TK_RAW; + +<XURI> +{ + ">" BEGIN(0); return TK_URI_OUT; + {eol} BEGIN(0); return TK_URI_ERR; + . return TK_RAW; +} + +<XLONG_STRING> +{ + \\\" return TK_RAW; + \"\"\" BEGIN(0); return TK_LONG_STRING_OUT; + {w} return TK_WHITESPACE; + "\t" return TK_TAB; + {eol} return TK_NEWLINE; + "<" return TK_LT; + ">" return TK_GT; + "&" return TK_AMP; + . return TK_RAW; +} + +<XSTRING> +{ + \\\" return TK_RAW; + \" BEGIN(0); return TK_STRING_OUT; + {eol} BEGIN(0); return TK_STRING_ERR; + {w} return TK_WHITESPACE; + "\t" return TK_TAB; + {eol} return TK_NEWLINE; + "<" return TK_LT; + ">" return TK_GT; + "&" return TK_AMP; + . return TK_RAW; +} + +%% + +void +ttl_to_markup(const char *utf8, FILE *f) +{ + yyscan_t scanner; + YY_BUFFER_STATE buf; + + enclex_init(&scanner); + if(utf8) + { + buf = enc_scan_string(utf8, scanner); + } + else if(f) + { + encset_in(f, scanner); + buf = enc_create_buffer(NULL, YY_BUF_SIZE, scanner); + } + else + { + enclex_destroy(scanner); + return; + } + + encoder->begin(encoder->data); + _add_markup_begin(MARKUP_CODE); + + for(int tok=enclex(scanner); tok; tok=enclex(scanner)) + { + const char *txt = encget_text(scanner); + switch(tok) + { + case TK_PREFIX: + _add_markup(MARKUP_PREFIX, txt); + break; + + case TK_NUMBER: + _add_markup(MARKUP_NUMBER, txt); + break; + + case TK_URI_IN: + _add_markup_begin(MARKUP_URI); + _add_plain("<"); + break; + case TK_URI_OUT: + _add_plain(">"); + _add_markup_end(MARKUP_URI); + break; + case TK_URI_ERR: + _add_markup_end(MARKUP_URI); + _add_singleton("br"); + break; + + case TK_STRING_IN: + _add_markup_begin(MARKUP_STRING); + _add_plain("\""); + break; + case TK_STRING_OUT: + _add_plain("\""); + _add_markup_end(MARKUP_STRING); + break; + case TK_STRING_ERR: + _add_markup_end(MARKUP_STRING); + _add_singleton("br"); + break; + + case TK_LONG_STRING_IN: + _add_markup_begin(MARKUP_STRING); + _add_plain("\"\"\""); + break; + case TK_LONG_STRING_OUT: + _add_plain("\"\"\""); + _add_markup_end(MARKUP_STRING); + break; + + case TK_NEWLINE: + _add_singleton("br"); + break; + case TK_LT: + _add_plain("<"); + break; + case TK_GT: + _add_plain(">"); + break; + case TK_AMP: + _add_plain("&"); + break; + + case TK_BADCHAR: + break; + + case TK_TAB: + _add_plain(" "); + break; + + case TK_SUBJECT: + _add_markup(MARKUP_SUBJECT, txt); + break; + case TK_PREDICATE: + _add_markup(MARKUP_PREDICATE, txt); + break; + + case TK_NAME: + case TK_WHITESPACE: + case TK_RAW: + default: + _add_plain(txt); + break; + } + } + + _add_markup_end(MARKUP_CODE); + encoder->end(encoder->data); + + enc_delete_buffer(buf, scanner); + enclex_destroy(scanner); +} diff --git a/manifest.ttl.in b/manifest.ttl.in new file mode 100644 index 0000000..c483930 --- /dev/null +++ b/manifest.ttl.in @@ -0,0 +1,104 @@ +# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix kx: <http://kxstudio.sf.net/ns/lv2ext/external-ui#> . + +@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> . + +# to please sord_validate +ui:EoUI + a rdfs:Class, owl:Class ; + rdfs:subClassOf ui:UI . +kx:Widget + a rdfs:Class, owl:Class ; + rdfs:subClassOf ui:UI . +kx:Host + a lv2:Feature . + +# Atom Inspector Plugin +sherlock:atom_inspector + a lv2:Plugin ; + lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ; + lv2:microVersion @SHERLOCK_MICRO_VERSION@ ; + lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ; + ui:ui sherlock:atom_inspector_1_ui ; + ui:ui sherlock:atom_inspector_2_kx ; + ui:ui sherlock:atom_inspector_3_eo ; + rdfs:seeAlso <sherlock.ttl> . + +sherlock:atom_inspector_1_ui + a ui:UI ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:atom_inspector_2_kx + a kx:Widget ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:atom_inspector_3_eo + a ui:EoUI ; + ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . + +# MIDI Inspector Plugin +sherlock:midi_inspector + a lv2:Plugin ; + lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ; + lv2:microVersion @SHERLOCK_MICRO_VERSION@ ; + lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ; + ui:ui sherlock:midi_inspector_1_ui ; + ui:ui sherlock:midi_inspector_2_kx ; + ui:ui sherlock:midi_inspector_3_eo ; + rdfs:seeAlso <sherlock.ttl> . + +sherlock:midi_inspector_1_ui + a ui:UI ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:midi_inspector_2_kx + a kx:Widget ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:midi_inspector_3_eo + a ui:EoUI ; + ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . + +# OSC Inspector Plugin +sherlock:osc_inspector + a lv2:Plugin ; + lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ; + lv2:microVersion @SHERLOCK_MICRO_VERSION@ ; + lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ; + ui:ui sherlock:osc_inspector_1_ui ; + ui:ui sherlock:osc_inspector_2_kx ; + ui:ui sherlock:osc_inspector_3_eo ; + rdfs:seeAlso <sherlock.ttl> . + +sherlock:osc_inspector_1_ui + a ui:UI ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:osc_inspector_2_kx + a kx:Widget ; + ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . +sherlock:osc_inspector_3_eo + a ui:EoUI ; + ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ; + rdfs:seeAlso <sherlock_ui.ttl> . diff --git a/midi_inspector.c b/midi_inspector.c new file mode 100644 index 0000000..ae60b5e --- /dev/null +++ b/midi_inspector.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <sherlock.h> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +typedef struct _handle_t handle_t; + +struct _handle_t { + LV2_URID_Map *map; + const LV2_Atom_Sequence *control_in; + LV2_Atom_Sequence *control_out; + LV2_Atom_Sequence *notify; + LV2_Atom_Forge forge; + + LV2_URID time_position; + LV2_URID time_frame; + LV2_URID midi_event; + + int64_t frame; +}; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path, const LV2_Feature *const *features) +{ + int i; + handle_t *handle = calloc(1, sizeof(handle_t)); + if(!handle) + return NULL; + + for(i=0; features[i]; i++) + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = (LV2_URID_Map *)features[i]->data; + + if(!handle->map) + { + fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position); + handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame); + + handle->midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent); + + lv2_atom_forge_init(&handle->forge, handle->map); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + handle_t *handle = (handle_t *)instance; + + switch(port) + { + case 0: + handle->control_in = (const LV2_Atom_Sequence *)data; + break; + case 1: + handle->control_out = (LV2_Atom_Sequence *)data; + break; + case 2: + handle->notify = (LV2_Atom_Sequence *)data; + break; + default: + break; + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + handle_t *handle = (handle_t *)instance; + uint32_t capacity; + LV2_Atom_Forge *forge = &handle->forge; + LV2_Atom_Forge_Frame frame [3]; + LV2_Atom_Forge_Ref ref; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + + if(lv2_atom_forge_is_object_type(forge, obj->atom.type)) + { + if(obj->body.otype == handle->time_position) + { + const LV2_Atom_Long *time_frame = NULL; + lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL); + if(time_frame) + handle->frame = time_frame->body - ev->time.frames; + } + } + } + + // size of input sequence + size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size; + + // copy whole input sequence to through port + capacity = handle->control_out->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity); + ref = lv2_atom_forge_raw(forge, handle->control_in, size); + if(!ref) + lv2_atom_sequence_clear(handle->control_out); + + // forge whole sequence as single event + capacity = handle->notify->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity); + + bool has_midi = false; + + ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0); + if(ref) + ref = lv2_atom_forge_frame_time(forge, 0); + if(ref) + ref = lv2_atom_forge_tuple(forge, &frame[1]); + if(ref) + ref = lv2_atom_forge_long(forge, handle->frame); + if(ref) + ref = lv2_atom_forge_int(forge, nsamples); + if(ref) + ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0); + + // only serialize MIDI events to UI + LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev) + { + if(ev->body.type == handle->midi_event) + { + has_midi = true; + if(ref) + ref = lv2_atom_forge_frame_time(forge, ev->time.frames); + if(ref) + ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size); + } + } + + if(ref) + lv2_atom_forge_pop(forge, &frame[2]); + if(ref) + lv2_atom_forge_pop(forge, &frame[1]); + if(ref) + lv2_atom_forge_pop(forge, &frame[0]); + else + lv2_atom_sequence_clear(handle->notify); + + if(!has_midi) // don't send anything + lv2_atom_sequence_clear(handle->notify); + + handle->frame += nsamples; +} + +static void +cleanup(LV2_Handle instance) +{ + handle_t *handle = (handle_t *)instance; + + free(handle); +} + +const LV2_Descriptor midi_inspector = { + .URI = SHERLOCK_MIDI_INSPECTOR_URI, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = NULL +}; diff --git a/midi_inspector_eo.c b/midi_inspector_eo.c new file mode 100644 index 0000000..0234d68 --- /dev/null +++ b/midi_inspector_eo.c @@ -0,0 +1,867 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <sherlock.h> + +#include <Elementary.h> + +#define COUNT_MAX 2048 // maximal amount of events shown +#define STRING_BUF_SIZE 2048 +#define STRING_MAX 256 +#define STRING_OFF (STRING_MAX - 4) + +typedef struct _UI UI; + +struct _UI { + LV2UI_Write_Function write_function; + LV2UI_Controller controller; + + LV2_URID_Map *map; + LV2_Atom_Forge forge; + LV2_URID event_transfer; + + Evas_Object *widget; + Evas_Object *table; + Evas_Object *list; + Evas_Object *clear; + Evas_Object *autoclear; + Evas_Object *autoblock; + Evas_Object *popup; + + Elm_Genlist_Item_Class *itc_midi; + Elm_Genlist_Item_Class *itc_group; + + char string_buf [STRING_BUF_SIZE]; + char *logo_path; +}; + +typedef struct _midi_msg_t midi_msg_t; + +struct _midi_msg_t { + uint8_t type; + const char *key; +}; + +#define COMMANDS_NUM 18 +static const midi_msg_t commands [COMMANDS_NUM] = { + { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" }, + { LV2_MIDI_MSG_NOTE_ON , "NoteOn" }, + { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" }, + { LV2_MIDI_MSG_CONTROLLER , "Controller" }, + { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" }, + { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" }, + { LV2_MIDI_MSG_BENDER , "Bender" }, + { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" }, + { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" }, + { LV2_MIDI_MSG_SONG_POS , "SongPosition" }, + { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" }, + { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" }, + { LV2_MIDI_MSG_CLOCK , "Clock" }, + { LV2_MIDI_MSG_START , "Start" }, + { LV2_MIDI_MSG_CONTINUE , "Continue" }, + { LV2_MIDI_MSG_STOP , "Stop" }, + { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" }, + { LV2_MIDI_MSG_RESET , "Reset" }, +}; + +#define CONTROLLERS_NUM 72 +static const midi_msg_t controllers [CONTROLLERS_NUM] = { + { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" }, + { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" }, + { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" }, + { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" }, + { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" }, + { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" }, + { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" }, + { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" }, + { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" }, + { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" }, + { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" }, + { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" }, + { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" }, + { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" }, + { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" }, + { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" }, + + { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" }, + { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" }, + { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" }, + { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" }, + { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" }, + { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" }, + { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" }, + { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" }, + { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" }, + { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" }, + { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" }, + { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" }, + { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" }, + { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" }, + { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" }, + { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" }, + + { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" }, + { LV2_MIDI_CTL_PORTAMENTO , "Portamento" }, + { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" }, + { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" }, + { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" }, + { LV2_MIDI_CTL_HOLD2 , "Hold2" }, + + { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" }, + { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" }, + { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" }, + { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" }, + { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" }, + { LV2_MIDI_CTL_SC6 , "SC6" }, + { LV2_MIDI_CTL_SC7 , "SC7" }, + { LV2_MIDI_CTL_SC8 , "SC8" }, + { LV2_MIDI_CTL_SC9 , "SC9" }, + { LV2_MIDI_CTL_SC10 , "SC10" }, + + { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" }, + { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" }, + { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" }, + { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" }, + { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" }, + + { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" }, + { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" }, + { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" }, + { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" }, + { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" }, + + { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" }, + { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" }, + + { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" }, + { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" }, + + { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" }, + { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" }, + + { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" }, + { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" }, + { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" }, + { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" }, + { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" }, + { LV2_MIDI_CTL_OMNI_ON , "OmniOn" }, + { LV2_MIDI_CTL_MONO1 , "Mono1" }, + { LV2_MIDI_CTL_MONO2 , "Mono2" }, +}; + +static int +_cmp_search(const void *itm1, const void *itm2) +{ + const midi_msg_t *msg1 = itm1; + const midi_msg_t *msg2 = itm2; + + if(msg1->type < msg2->type) + return -1; + else if(msg1->type > msg2->type) + return 1; + + return 0; +} + +static inline const midi_msg_t * +_search_command(uint8_t type) +{ + return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search); +} + +static inline const midi_msg_t * +_search_controller(uint8_t type) +{ + return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search); +} + +#define CODE_PRE "<font=Mono style=shadow,bottom>" +#define CODE_POST "</font>" + +#define RAW_PRE "<color=#b0b><b>" +#define RAW_POST "</b></color>" + +#define SYSTEM(TYP) "<color=#888><b>"TYP"</b></color>" +#define CHANNEL(TYP, VAL) "<color=#888><b>"TYP"</b></color><color=#fff>"VAL"</color>" +#define COMMAND(VAL) "<color=#0b0>"VAL"</color>" +#define CONTROLLER(VAL) "<color=#b00>"VAL"</color>" +#define PUNKT(VAL) "<color=#00b>"VAL"</color>" + +static const char *keys [12] = { + "C", "#C", + "D", "#D", + "E", + "F", "#F", + "G", "#G", + "A", "#A", + "H" +}; + +static inline const char * +_note(uint8_t val, uint8_t *octave) +{ + *octave = val / 12; + + return keys[val % 12]; +} + +static inline char * +_atom_stringify(UI *ui, char *ptr, char *end, const LV2_Atom *atom) +{ + //FIXME check for buffer overflows!!! + + const uint8_t *midi = LV2_ATOM_BODY_CONST(atom); + + sprintf(ptr, CODE_PRE RAW_PRE); + ptr += strlen(ptr); + + char *barrier = ptr + STRING_OFF; + unsigned i; + for(i=0; (i<atom->size) && (ptr<barrier); i++, ptr += 3) + sprintf(ptr, " %02"PRIX8, midi[i]); + + for( ; (i<3) && (ptr<barrier); i++, ptr += 3) + sprintf(ptr, " "); + + sprintf(ptr, RAW_POST); + ptr += strlen(ptr); + + if( (midi[0] & 0xf0) == 0xf0) // system messages + { + const midi_msg_t *command_msg = _search_command(midi[0]); + const char *command_str = command_msg + ? command_msg->key + : "Unknown"; + + if(midi[0] == LV2_MIDI_MSG_SYSTEM_EXCLUSIVE) // sysex message + { + for( ; (i<atom->size) && ptr<barrier; i++, ptr += 3) + sprintf(ptr, " %02"PRIX8, midi[i]); + } + else if(midi[0] == LV2_MIDI_MSG_SONG_POS) + { + uint16_t pos = (((uint16_t)midi[2] << 7) | midi[1]); + sprintf(ptr, + PUNKT(" [") SYSTEM("Syst.") + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIu16 + PUNKT("]"), + command_str, pos); + } + else // other system message + { + if(atom->size == 2) + { + sprintf(ptr, + PUNKT(" [") SYSTEM("Syst.") + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + command_str, midi[1]); + } + else if(atom->size == 3) + { + sprintf(ptr, + PUNKT(" [") SYSTEM("Syst.") + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIi8 + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + command_str, midi[1], midi[2]); + } + else // assume atom->size == 1, aka no data + { + sprintf(ptr, + PUNKT(" [") SYSTEM("Syst.") + PUNKT(", ") COMMAND("%s") + PUNKT("]"), + command_str); + } + } + } + else // channel messages + { + const uint8_t command = midi[0] & 0xf0; + const uint8_t channel = midi[0] & 0x0f; + + const midi_msg_t *command_msg = _search_command(command); + const char *command_str = command_msg + ? command_msg->key + : "Unknown"; + + if( (command == LV2_MIDI_MSG_NOTE_ON) + || (command == LV2_MIDI_MSG_NOTE_OFF) + || (command == LV2_MIDI_MSG_NOTE_PRESSURE)) + { + uint8_t octave; + const char *note = _note(midi[1], &octave); + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%s-%"PRIu8 + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + channel, command_str, note, octave, midi[2]); + } + else if(command == LV2_MIDI_MSG_CONTROLLER) + { + const midi_msg_t *controller_msg = _search_controller(midi[1]); + const char *controller_str = controller_msg + ? controller_msg->key + : "Unknown"; + + if(atom->size == 3) + { + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") CONTROLLER("%s") + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + channel, command_str, controller_str, midi[2]); + } + else if(atom->size == 2) + { + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") CONTROLLER("%s") + PUNKT("]"), + channel, command_str, controller_str); + } + } + else if(command == LV2_MIDI_MSG_BENDER) + { + int16_t bender = (((int16_t)midi[2] << 7) | midi[1]) - 0x2000; + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIi16 + PUNKT("]"), + channel, command_str, bender); + } + else if(atom->size == 3) + { + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIi8 + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + channel, command_str, midi[1], midi[2]); + } + else if(atom->size == 2) + { + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT(", ") "%"PRIi8 + PUNKT("]"), + channel, command_str, midi[1]); + } + else // fall-back + { + sprintf(ptr, + PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8) + PUNKT(", ") COMMAND("%s") + PUNKT("]"), + channel, command_str); + } + } + ptr += strlen(ptr); + + if(ptr >= barrier) // there would be more to print + { + ptr = barrier; + sprintf(ptr, "..."); + ptr += 4; + } + + sprintf(ptr, CODE_POST); + + return ptr + strlen(ptr); +} + +static char * +_midi_label_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.text")) + { + char *buf = ui->string_buf; + char *ptr = buf; + char *end = buf + STRING_BUF_SIZE; + + ptr = _atom_stringify(ui, ptr, end, &ev->body); + + return ptr + ? strdup(buf) + : NULL; + } + + return NULL; +} + +static Evas_Object * +_midi_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#bb0 font=Mono>%04ld</color>", ev->time.frames); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%4u</color>", ev->body.size); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static Evas_Object * +_group_item_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const position_t *pos = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static void +_del(void *data, Evas_Object *obj) +{ + free(data); +} + +static void +_clear_update(UI *ui, int count) +{ + if(!ui->clear) + return; + + char *buf = ui->string_buf; + sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX); + elm_object_text_set(ui->clear, buf); +} + +static void +_clear_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + if(ui->list) + elm_genlist_clear(ui->list); + + _clear_update(ui, 0); +} + +static void +_info_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + // toggle popup + if(ui->popup) + { + if(evas_object_visible_get(ui->popup)) + evas_object_hide(ui->popup); + else + evas_object_show(ui->popup); + } +} + +static void +_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + ui->widget = NULL; +} + +static void +_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + evas_object_del(ui->widget); +} + +static Evas_Object * +_content_get(UI *ui, Evas_Object *parent) +{ + ui->table = elm_table_add(parent); + if(ui->table) + { + elm_table_homogeneous_set(ui->table, EINA_FALSE); + elm_table_padding_set(ui->table, 0, 0); + evas_object_size_hint_min_set(ui->table, 600, 800); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui); + + ui->list = elm_genlist_add(ui->table); + if(ui->list) + { + elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading + elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT); + elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading + elm_genlist_reorder_mode_set(ui->list, EINA_FALSE); + elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT); + evas_object_data_set(ui->list, "ui", ui); + evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->list); + elm_table_pack(ui->table, ui->list, 0, 0, 4, 1); + } + + ui->clear = elm_button_add(ui->table); + if(ui->clear) + { + _clear_update(ui, 0); + evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui); + evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f); + evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->clear); + elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1); + } + + ui->autoclear = elm_check_add(ui->table); + if(ui->autoclear) + { + elm_object_text_set(ui->autoclear, "overwrite"); + evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoclear); + elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1); + } + + ui->autoblock = elm_check_add(ui->table); + if(ui->autoblock) + { + elm_object_text_set(ui->autoblock, "block"); + evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoblock); + elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1); + } + + Evas_Object *info = elm_button_add(ui->table); + if(info) + { + evas_object_smart_callback_add(info, "clicked", _info_clicked, ui); + evas_object_size_hint_weight_set(info, 0.f, 0.f); + evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL); + evas_object_show(info); + elm_table_pack(ui->table, info, 3, 1, 1, 1); + + Evas_Object *icon = elm_icon_add(info); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 20, 20); + evas_object_size_hint_max_set(icon, 32, 32); + //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_object_part_content_set(info, "icon", icon); + } + } + + ui->popup = elm_popup_add(ui->table); + if(ui->popup) + { + elm_popup_allow_events_set(ui->popup, EINA_TRUE); + + Evas_Object *hbox = elm_box_add(ui->popup); + if(hbox) + { + elm_box_horizontal_set(hbox, EINA_TRUE); + elm_box_homogeneous_set(hbox, EINA_FALSE); + elm_box_padding_set(hbox, 10, 0); + evas_object_show(hbox); + elm_object_content_set(ui->popup, hbox); + + Evas_Object *icon = elm_icon_add(hbox); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 128, 128); + evas_object_size_hint_max_set(icon, 256, 256); + evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_box_pack_end(hbox, icon); + } + + Evas_Object *label = elm_label_add(hbox); + if(label) + { + elm_object_text_set(label, + "<color=#b00 shadow_color=#fff font_size=20>" + "Sherlock - MIDI Inspector" + "</color></br><align=left>" + "Version "SHERLOCK_VERSION"</br></br>" + "Copyright (c) 2015 Hanspeter Portner</br></br>" + "This is free and libre software</br>" + "Released under Artistic License 2.0</br>" + "By Open Music Kontrollers</br></br>" + "<color=#bbb>" + "http://open-music-kontrollers.ch/lv2/sherlock</br>" + "dev@open-music-kontrollers.ch" + "</color></align>"); + + evas_object_show(label); + elm_box_pack_end(hbox, label); + } + } + } + } + + return ui->table; +} + +static LV2UI_Handle +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) +{ + if(strcmp(plugin_uri, SHERLOCK_MIDI_INSPECTOR_URI)) + return NULL; + + UI *ui = calloc(1, sizeof(UI)); + if(!ui) + return NULL; + + ui->write_function = write_function; + ui->controller = controller; + + Evas_Object *parent = NULL; + for(int i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + ui->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_UI__parent)) + parent = features[i]->data; + } + + if(!ui->map) + { + fprintf(stderr, "LV2 URID extension not supported\n"); + free(ui); + return NULL; + } + if(!parent) + { + free(ui); + return NULL; + } + + ui->event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer); + lv2_atom_forge_init(&ui->forge, ui->map); + + ui->itc_midi = elm_genlist_item_class_new(); + if(ui->itc_midi) + { + ui->itc_midi->item_style = "default_style"; + ui->itc_midi->func.text_get = _midi_label_get; + ui->itc_midi->func.content_get = _midi_content_get; + ui->itc_midi->func.state_get = NULL; + ui->itc_midi->func.del = _del; + } + + ui->itc_group = elm_genlist_item_class_new(); + if(ui->itc_group) + { + ui->itc_group->item_style = "default_style"; + ui->itc_group->func.text_get = NULL; + ui->itc_group->func.content_get = _group_item_content_get; + ui->itc_group->func.state_get = NULL; + ui->itc_group->func.del = _del; + } + + sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path); + ui->logo_path = strdup(ui->string_buf); + + ui->widget = _content_get(ui, parent); + if(!ui->widget) + { + free(ui); + return NULL; + } + *(Evas_Object **)widget = ui->widget; + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + UI *ui = handle; + + if(ui->widget) + evas_object_del(ui->widget); + + if(ui->logo_path) + free(ui->logo_path); + + if(ui->itc_midi) + elm_genlist_item_class_free(ui->itc_midi); + + free(ui); +} + +static void +port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid, + const void *buf) +{ + UI *ui = handle; + + if( (i == 2) && (urid == ui->event_transfer) && ui->list) + { + const LV2_Atom_Tuple *tup = buf; + const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup); + const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom); + const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom); + int n = elm_genlist_items_count(ui->list); + + Elm_Object_Item *itm = NULL; + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events + { + position_t *pos = malloc(sizeof(position_t)); + if(!pos) + return; + + pos->offset = offset->body; + pos->nsamples = nsamples->body; + + // check item count + if(n + 1 > COUNT_MAX) + { + if(elm_check_state_get(ui->autoclear)) + { + elm_genlist_clear(ui->list); + n = 0; + } + else + { + return; + } + } + else if(elm_check_state_get(ui->autoblock)) + { + return; + } + + itm = elm_genlist_item_append(ui->list, ui->itc_group, + pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL); + elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE); + + LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt) + { + size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size; + LV2_Atom_Event *ev = malloc(len); + if(!ev) + continue; + + memcpy(ev, elmnt, len); + + Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_midi, + ev, itm, ELM_GENLIST_ITEM_NONE, NULL, NULL); + elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT); + n++; + + // scroll to last item + //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE); + } + } + + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) + _clear_update(ui, n); // only update if there where any events + } +} + +const LV2UI_Descriptor midi_inspector_eo = { + .URI = SHERLOCK_MIDI_INSPECTOR_EO_URI, + .instantiate = instantiate, + .cleanup = cleanup, + .port_event = port_event, + .extension_data = NULL +}; diff --git a/omk_logo_256x256.png b/omk_logo_256x256.png Binary files differnew file mode 100644 index 0000000..5f3ff48 --- /dev/null +++ b/omk_logo_256x256.png diff --git a/osc.lv2/CMakeLists.txt b/osc.lv2/CMakeLists.txt new file mode 100644 index 0000000..64ce73b --- /dev/null +++ b/osc.lv2/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8) + +project(osc.lv2) + +include_directories(${PROJECT_SOURCE_DIR}) + +set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}") + +include(CTest) + +if(${BUILD_TESTING}) + add_executable(osc_test + osc_test.c) + add_test(NAME API-Test COMMAND osc_test) +endif() diff --git a/osc.lv2/COPYING b/osc.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/osc.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/osc.lv2/README.md b/osc.lv2/README.md new file mode 100644 index 0000000..b48021f --- /dev/null +++ b/osc.lv2/README.md @@ -0,0 +1,3 @@ +# osc.lv2 + +## Open Sound Control Extension for the LV2 Plugin Specification diff --git a/lv2-osc.doap.ttl b/osc.lv2/lv2-osc.doap.ttl index ef74f92..ef74f92 100644 --- a/lv2-osc.doap.ttl +++ b/osc.lv2/lv2-osc.doap.ttl diff --git a/manifest.ttl b/osc.lv2/manifest.ttl index a2bbaf8..a2bbaf8 100644 --- a/manifest.ttl +++ b/osc.lv2/manifest.ttl diff --git a/osc.lv2/forge.h b/osc.lv2/osc.lv2/forge.h index d46121f..d46121f 100644 --- a/osc.lv2/forge.h +++ b/osc.lv2/osc.lv2/forge.h diff --git a/osc.lv2/osc.h b/osc.lv2/osc.lv2/osc.h index 3b36a19..3b36a19 100644 --- a/osc.lv2/osc.h +++ b/osc.lv2/osc.lv2/osc.h diff --git a/osc.lv2/reader.h b/osc.lv2/osc.lv2/reader.h index 2f5def9..2f5def9 100644 --- a/osc.lv2/reader.h +++ b/osc.lv2/osc.lv2/reader.h diff --git a/osc.lv2/util.h b/osc.lv2/osc.lv2/util.h index d1cb762..d1cb762 100644 --- a/osc.lv2/util.h +++ b/osc.lv2/osc.lv2/util.h diff --git a/osc.lv2/writer.h b/osc.lv2/osc.lv2/writer.h index 2cc89bb..2cc89bb 100644 --- a/osc.lv2/writer.h +++ b/osc.lv2/osc.lv2/writer.h diff --git a/osc.ttl b/osc.lv2/osc.ttl index db4a048..db4a048 100644 --- a/osc.ttl +++ b/osc.lv2/osc.ttl diff --git a/osc_test.c b/osc.lv2/osc_test.c index 1280fc0..1280fc0 100644 --- a/osc_test.c +++ b/osc.lv2/osc_test.c diff --git a/osc_inspector.c b/osc_inspector.c new file mode 100644 index 0000000..fc9f9a5 --- /dev/null +++ b/osc_inspector.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <sherlock.h> + +#include <lv2_osc.h> + +typedef struct _handle_t handle_t; + +struct _handle_t { + LV2_URID_Map *map; + const LV2_Atom_Sequence *control_in; + LV2_Atom_Sequence *control_out; + LV2_Atom_Sequence *notify; + LV2_Atom_Forge forge; + + LV2_URID time_position; + LV2_URID time_frame; + + osc_forge_t oforge; + + int64_t frame; +}; + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, double rate, + const char *bundle_path, const LV2_Feature *const *features) +{ + int i; + handle_t *handle = calloc(1, sizeof(handle_t)); + if(!handle) + return NULL; + + for(i=0; features[i]; i++) + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->map = (LV2_URID_Map *)features[i]->data; + + if(!handle->map) + { + fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI); + free(handle); + return NULL; + } + + handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position); + handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame); + + osc_forge_init(&handle->oforge, handle->map); + lv2_atom_forge_init(&handle->forge, handle->map); + + return handle; +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void *data) +{ + handle_t *handle = (handle_t *)instance; + + switch(port) + { + case 0: + handle->control_in = (const LV2_Atom_Sequence *)data; + break; + case 1: + handle->control_out = (LV2_Atom_Sequence *)data; + break; + case 2: + handle->notify = (LV2_Atom_Sequence *)data; + break; + default: + break; + } +} + +static void +run(LV2_Handle instance, uint32_t nsamples) +{ + handle_t *handle = (handle_t *)instance; + uint32_t capacity; + LV2_Atom_Forge *forge = &handle->forge; + LV2_Atom_Forge_Frame frame [3]; + LV2_Atom_Forge_Ref ref; + + LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + + if(lv2_atom_forge_is_object_type(forge, obj->atom.type)) + { + if(obj->body.otype == handle->time_position) + { + const LV2_Atom_Long *time_frame = NULL; + lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL); + if(time_frame) + handle->frame = time_frame->body - ev->time.frames; + } + } + } + + // size of input sequence + size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size; + + // copy whole input sequence to through port + capacity = handle->control_out->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity); + ref = lv2_atom_forge_raw(forge, handle->control_in, size); + if(!ref) + lv2_atom_sequence_clear(handle->control_out); + + // forge whole sequence as single event + capacity = handle->notify->atom.size; + lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity); + + bool has_osc = false; + + ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0); + if(ref) + ref = lv2_atom_forge_frame_time(forge, 0); + if(ref) + ref = lv2_atom_forge_tuple(forge, &frame[1]); + if(ref) + ref = lv2_atom_forge_long(forge, handle->frame); + if(ref) + ref = lv2_atom_forge_int(forge, nsamples); + if(ref) + ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0); + + // only serialize OSC events to UI + LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + + if( osc_atom_is_bundle(&handle->oforge, obj) + || osc_atom_is_message(&handle->oforge, obj) ) + { + has_osc = true; + if(ref) + ref = lv2_atom_forge_frame_time(forge, ev->time.frames); + if(ref) + ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size); + } + } + + if(ref) + lv2_atom_forge_pop(forge, &frame[2]); + if(ref) + lv2_atom_forge_pop(forge, &frame[1]); + if(ref) + lv2_atom_forge_pop(forge, &frame[0]); + else + lv2_atom_sequence_clear(handle->notify); + + if(!has_osc) + lv2_atom_sequence_clear(handle->notify); + + handle->frame += nsamples; +} + +static void +cleanup(LV2_Handle instance) +{ + handle_t *handle = (handle_t *)instance; + + free(handle); +} + +const LV2_Descriptor osc_inspector = { + .URI = SHERLOCK_OSC_INSPECTOR_URI, + .instantiate = instantiate, + .connect_port = connect_port, + .activate = NULL, + .run = run, + .deactivate = NULL, + .cleanup = cleanup, + .extension_data = NULL +}; diff --git a/osc_inspector_eo.c b/osc_inspector_eo.c new file mode 100644 index 0000000..bd3ec01 --- /dev/null +++ b/osc_inspector_eo.c @@ -0,0 +1,978 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <inttypes.h> + +#include <sherlock.h> + +#include <lv2_osc.h> + +#include <Elementary.h> + +#define COUNT_MAX 2048 // maximal amount of events shown +#define STRING_BUF_SIZE 2048 +#define STRING_MAX 256 +#define STRING_OFF (STRING_MAX - 4) + +typedef struct _UI UI; + +struct _UI { + LV2UI_Write_Function write_function; + LV2UI_Controller controller; + + LV2_URID_Map *map; + LV2_Atom_Forge forge; + LV2_URID event_transfer; + LV2_URID midi_event; + osc_forge_t oforge; + + Evas_Object *widget; + Evas_Object *table; + Evas_Object *list; + Evas_Object *clear; + Evas_Object *autoclear; + Evas_Object *autoblock; + Evas_Object *popup; + + Elm_Genlist_Item_Class *itc_group; + Elm_Genlist_Item_Class *itc_packet; + Elm_Genlist_Item_Class *itc_item; + + char string_buf [STRING_BUF_SIZE]; + char *logo_path; +}; + +// there is a bug in LV2 <= 0.10 +#if defined(LV2_ATOM_TUPLE_FOREACH) +# undef LV2_ATOM_TUPLE_FOREACH +# define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \ + for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \ + !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \ + (iter) = lv2_atom_tuple_next(iter)) +#endif + +#define CODE_PRE ("<font=Mono style=shadow,bottom>") +#define CODE_POST ("</font>") + +#define PATH(VAL) ("<color=#b0b><b>"VAL"</b></color>") +#define BUNDLE(VAL) ("<color=#b0b><b>"VAL"</b></color>") +#define TYPE(TYP, VAL) ("<color=#0b0><b>"TYP"</b></color><color=#fff>"VAL"</color>") + +#define TYPE_PRE(TYP, VAL) ("<color=#0b0><b>"TYP"</b></color><color=#fff>"VAL) +#define TYPE_POST(VAL) (VAL"</color>") + +#define PUNKT(VAL) "<color=#00b>"VAL"</color>" + +static inline char * +_timestamp_stringify(UI *ui, char *ptr, char *end, const LV2_Atom_Long *atom) +{ + const uint32_t sec = (uint64_t)atom->body >> 32; + const uint32_t frac = (uint64_t)atom->body & 0xffffffff; + const double part = frac * 0x1p-32; + + if(sec <= 1UL) + { + sprintf(ptr, TYPE(" t:", "immediate")); + } + else + { + const time_t ttime = sec - 0x83aa7e80; + const struct tm *ltime = localtime(&ttime); + + char tmp [32]; + strftime(tmp, 32, "%d-%b-%Y %T", ltime); + + sprintf(ptr, TYPE(" t:", "%s.%06"PRIu32), tmp, (uint32_t)(part*1e6)); + } + + return ptr + strlen(ptr); +} + +static inline char * +_atom_stringify(UI *ui, char *ptr, char *end, const LV2_Atom *atom) +{ + //FIXME check for buffer overflows!!! + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom; + + if(osc_atom_is_message(&ui->oforge, obj)) + { + const LV2_Atom_String *path = NULL; + const LV2_Atom_String *fmt = NULL; + const LV2_Atom_Tuple *tup = NULL; + osc_atom_message_unpack(&ui->oforge, obj, &path, &fmt, &tup); + + sprintf(ptr, CODE_PRE); + ptr += strlen(ptr); + + if(path && fmt) + { + sprintf(ptr, PATH(" %s"), LV2_ATOM_BODY_CONST(path)); + } + else + { + sprintf(ptr, " unknown path and/or format string"); + } + ptr += strlen(ptr); + + const LV2_Atom *itm = lv2_atom_tuple_begin(tup); + for(const char *type = LV2_ATOM_BODY_CONST(fmt); *type; type++) + { + bool advance = true; + + switch(*type) + { + case 'i': + { + if(itm->type == ui->forge.Int) + { + sprintf(ptr, TYPE(" i:", "%"PRIi32), ((const LV2_Atom_Int *)itm)->body); + ptr += strlen(ptr); + } + break; + } + case 'f': + { + if(itm->type == ui->forge.Float) + { + sprintf(ptr, TYPE(" f:", "%f"), ((const LV2_Atom_Float *)itm)->body); + ptr += strlen(ptr); + } + break; + } + case 's': // fall-through + case 'S': + { + if(itm->type == ui->forge.String) + { + const char *str = LV2_ATOM_BODY_CONST(itm); + if(itm->size == 0) + str = ""; + sprintf(ptr, TYPE(" s:", "")); + ptr += strlen(ptr); + + for(unsigned i=0; i<strlen(str) + 1; i++) + { + switch(str[i]) + { + case '<': + strncpy(ptr, "<", 4); + ptr += 4; + break; + case '>': + strncpy(ptr, ">", 4); + ptr += 4; + break; + case '&': + strncpy(ptr, "&", 5); + ptr += 5; + break; + case '\n': + strncpy(ptr, "\\n", 2); + ptr += 2; + break; + case '\r': + strncpy(ptr, "\\r", 2); + ptr += 2; + break; + default: + *ptr++ = str[i]; + break; + } + } + } + break; + } + case 'b': + { + if(itm->type == ui->forge.Chunk) + { + const uint8_t *chunk = LV2_ATOM_BODY_CONST(itm); + sprintf(ptr, TYPE_PRE(" b:", PUNKT("["))); + ptr += strlen(ptr); + if(itm->size) + { + sprintf(ptr, "%02"PRIX8, chunk[0]); + ptr += strlen(ptr); + + for(unsigned i=1; i<itm->size; i++) + { + sprintf(ptr, " %02"PRIX8, chunk[i]); + ptr += strlen(ptr); + } + } + sprintf(ptr, TYPE_POST(PUNKT("]"))); + ptr += strlen(ptr); + } + break; + } + + case 'h': + { + if(itm->type == ui->forge.Long) + { + sprintf(ptr, TYPE(" h:", "%"PRIi64), ((const LV2_Atom_Long *)itm)->body); + ptr += strlen(ptr); + } + break; + } + case 'd': + { + if(itm->type == ui->forge.Double) + { + sprintf(ptr, TYPE(" d:", "%lf"), ((const LV2_Atom_Double *)itm)->body); + ptr += strlen(ptr); + } + break; + } + case 't': + { + if(itm->type == ui->forge.Long) + { + ptr = _timestamp_stringify(ui, ptr, end, (const LV2_Atom_Long *)itm); + } + break; + } + + case 'c': + { + //if(itm->type == ui->forge.Char) + { + sprintf(ptr, TYPE(" c:", "%c"), ((const LV2_Atom_Int *)itm)->body); + ptr += strlen(ptr); + } + break; + } + case 'm': + { + if(itm->type == ui->midi_event) + { + const uint8_t *chunk = LV2_ATOM_BODY_CONST(itm); + sprintf(ptr, TYPE_PRE(" m:", PUNKT("["))); + ptr += strlen(ptr); + if(itm->size) + { + sprintf(ptr, "%02"PRIX8, chunk[0]); + ptr += strlen(ptr); + + for(unsigned i=1; i<itm->size; i++) + { + sprintf(ptr, " %02"PRIX8, chunk[i]); + ptr += strlen(ptr); + } + } + sprintf(ptr, TYPE_POST(PUNKT("]"))); + ptr += strlen(ptr); + } + break; + } + + case 'T': + { + //if(itm->type == ui->forge.Bool) + { + sprintf(ptr, TYPE(" T:", "true")); + ptr += strlen(ptr); + } + advance = false; + break; + } + case 'F': + { + //if(itm->type == ui->forge.Bool) + { + sprintf(ptr, TYPE(" F:", "false")); + ptr += strlen(ptr); + } + advance = false; + break; + } + case 'N': + { + //if(itm->type == 0) + { + sprintf(ptr, TYPE(" N:", "nil")); + ptr += strlen(ptr); + } + advance = false; + break; + } + case 'I': + { + //if(itm->type == ui->forge.Impulse) + { + sprintf(ptr, TYPE(" I:", "impulse")); + ptr += strlen(ptr); + } + advance = false; + break; + } + + default: + { + { + sprintf(ptr, TYPE(" %c:", "unknown"), *type); + ptr += strlen(ptr); + } + break; + } + } + + if(advance && !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tup), tup->atom.size, itm)) + itm = lv2_atom_tuple_next(itm); + } + + sprintf(ptr, CODE_POST); + + return ptr + strlen(ptr); + } + else if(osc_atom_is_bundle(&ui->oforge, obj)) + { + const LV2_Atom_Long *timestamp = NULL; + const LV2_Atom_Tuple *tup = NULL; + osc_atom_bundle_unpack(&ui->oforge, obj, ×tamp, &tup); + + sprintf(ptr, CODE_PRE); + ptr += strlen(ptr); + + sprintf(ptr, BUNDLE(" #bundle")); + ptr += strlen(ptr); + + ptr = _timestamp_stringify(ui, ptr, end, timestamp); + + sprintf(ptr, CODE_POST); + + return ptr + strlen(ptr); + } + + return NULL; +} + +static char * +_packet_label_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.text")) + { + char *buf = ui->string_buf; + char *ptr = buf; + char *end = buf + STRING_BUF_SIZE; + + ptr = _atom_stringify(ui, ptr, end, &ev->body); + + return ptr + ? strdup(buf) + : NULL; + } + + return NULL; +} + +static Evas_Object * +_packet_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom_Event *ev = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#bb0 font=Mono>%04ld</color>", ev->time.frames); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%4u</color>", ev->body.size); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static void +_del(void *data, Evas_Object *obj) +{ + free(data); +} + +static char * +_item_label_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom *atom = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.text")) + { + char *buf = ui->string_buf; + char *ptr = buf; + char *end = buf + STRING_BUF_SIZE; + + ptr = _atom_stringify(ui, ptr, end, atom); + + return ptr + ? strdup(buf) + : NULL; + } + + return NULL; +} + +static Evas_Object * +_item_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const LV2_Atom *atom = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%4u</color>", atom->size); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static void +_item_expand_request(void *data, Evas_Object *obj, void *event_info) +{ + Elm_Object_Item *itm = event_info; + UI *ui = data; + + elm_genlist_item_expanded_set(itm, EINA_TRUE); +} + +static void +_item_contract_request(void *data, Evas_Object *obj, void *event_info) +{ + Elm_Object_Item *itm = event_info; + UI *ui = data; + + elm_genlist_item_expanded_set(itm, EINA_FALSE); +} + +static void +_item_expanded(void *data, Evas_Object *obj, void *event_info) +{ + Elm_Object_Item *itm = event_info; + UI *ui = data; + + const Elm_Genlist_Item_Class *class = elm_genlist_item_item_class_get(itm); + const void *udata = elm_object_item_data_get(itm); + + if(!udata) + return; + + const LV2_Atom_Object *_obj = NULL; + + if(class == ui->itc_packet) + { + const LV2_Atom_Event *ev = udata; + _obj = (const LV2_Atom_Object *)&ev->body; + } + else if(class == ui->itc_item) + { + _obj = udata; + } + + if(_obj && osc_atom_is_bundle(&ui->oforge, _obj)) + { + const LV2_Atom_Long *timestamp = NULL; + const LV2_Atom_Tuple *tup = NULL; + osc_atom_bundle_unpack(&ui->oforge, _obj, ×tamp, &tup); + + if(tup) + { + LV2_ATOM_TUPLE_FOREACH(tup, atom) + { + const LV2_Atom_Object *_obj2 = (const LV2_Atom_Object *)atom; + + Elm_Object_Item *itm2 = elm_genlist_item_append(obj, ui->itc_item, + _obj2, itm, ELM_GENLIST_ITEM_TREE, NULL, NULL); + elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT); + if(osc_atom_is_bundle(&ui->oforge, _obj2)) + elm_genlist_item_expanded_set(itm2, EINA_TRUE); + } + } + } +} + +static void +_item_contracted(void *data, Evas_Object *obj, void *event_info) +{ + Elm_Object_Item *itm = event_info; + UI *ui = data; + + elm_genlist_item_subitems_clear(itm); +} + +static Evas_Object * +_group_item_content_get(void *data, Evas_Object *obj, const char *part) +{ + UI *ui = evas_object_data_get(obj, "ui"); + const position_t *pos = data; + + if(!ui) + return NULL; + + if(!strcmp(part, "elm.swallow.icon")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + else if(!strcmp(part, "elm.swallow.end")) + { + char *buf = ui->string_buf; + + sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples); + + Evas_Object *label = elm_label_add(obj); + if(label) + { + elm_object_part_text_set(label, "default", buf); + evas_object_show(label); + } + + return label; + } + + return NULL; +} + +static void +_clear_update(UI *ui, int count) +{ + if(!ui->clear) + return; + + char *buf = ui->string_buf; + sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX); + elm_object_text_set(ui->clear, buf); +} + +static void +_clear_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + if(ui->list) + elm_genlist_clear(ui->list); + + _clear_update(ui, 0); +} + +static void +_info_clicked(void *data, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + // toggle popup + if(ui->popup) + { + if(evas_object_visible_get(ui->popup)) + evas_object_hide(ui->popup); + else + evas_object_show(ui->popup); + } +} + +static void +_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + ui->widget = NULL; +} + +static void +_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + UI *ui = data; + + evas_object_del(ui->widget); +} + +static Evas_Object * +_content_get(UI *ui, Evas_Object *parent) +{ + ui->table = elm_table_add(parent); + if(ui->table) + { + elm_table_homogeneous_set(ui->table, EINA_FALSE); + elm_table_padding_set(ui->table, 0, 0); + evas_object_size_hint_min_set(ui->table, 600, 800); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui); + evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui); + + ui->list = elm_genlist_add(ui->table); + if(ui->list) + { + elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading + elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT); + elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading + elm_genlist_reorder_mode_set(ui->list, EINA_FALSE); + elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT); + evas_object_data_set(ui->list, "ui", ui); + evas_object_smart_callback_add(ui->list, "expand,request", + _item_expand_request, ui); + evas_object_smart_callback_add(ui->list, "contract,request", + _item_contract_request, ui); + evas_object_smart_callback_add(ui->list, "expanded", _item_expanded, ui); + evas_object_smart_callback_add(ui->list, "contracted", _item_contracted, ui); + evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->list); + elm_table_pack(ui->table, ui->list, 0, 0, 4, 1); + } + + ui->clear = elm_button_add(ui->table); + if(ui->clear) + { + _clear_update(ui, 0); + evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui); + evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f); + evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->clear); + elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1); + } + + ui->autoclear = elm_check_add(ui->table); + if(ui->autoclear) + { + elm_object_text_set(ui->autoclear, "overwrite"); + evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoclear); + elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1); + } + + ui->autoblock = elm_check_add(ui->table); + if(ui->autoblock) + { + elm_object_text_set(ui->autoblock, "block"); + evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f); + evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(ui->autoblock); + elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1); + } + + Evas_Object *info = elm_button_add(ui->table); + if(info) + { + evas_object_smart_callback_add(info, "clicked", _info_clicked, ui); + evas_object_size_hint_weight_set(info, 0.f, 0.f); + evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL); + evas_object_show(info); + elm_table_pack(ui->table, info, 3, 1, 1, 1); + + Evas_Object *icon = elm_icon_add(info); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 20, 20); + evas_object_size_hint_max_set(icon, 32, 32); + //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_object_part_content_set(info, "icon", icon); + } + } + + ui->popup = elm_popup_add(ui->table); + if(ui->popup) + { + elm_popup_allow_events_set(ui->popup, EINA_TRUE); + + Evas_Object *hbox = elm_box_add(ui->popup); + if(hbox) + { + elm_box_horizontal_set(hbox, EINA_TRUE); + elm_box_homogeneous_set(hbox, EINA_FALSE); + elm_box_padding_set(hbox, 10, 0); + evas_object_show(hbox); + elm_object_content_set(ui->popup, hbox); + + Evas_Object *icon = elm_icon_add(hbox); + if(icon) + { + elm_image_file_set(icon, ui->logo_path, NULL); + evas_object_size_hint_min_set(icon, 128, 128); + evas_object_size_hint_max_set(icon, 256, 256); + evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + evas_object_show(icon); + elm_box_pack_end(hbox, icon); + } + + Evas_Object *label = elm_label_add(hbox); + if(label) + { + elm_object_text_set(label, + "<color=#b00 shadow_color=#fff font_size=20>" + "Sherlock - OSC Inspector" + "</color></br><align=left>" + "Version "SHERLOCK_VERSION"</br></br>" + "Copyright (c) 2015 Hanspeter Portner</br></br>" + "This is free and libre software</br>" + "Released under Artistic License 2.0</br>" + "By Open Music Kontrollers</br></br>" + "<color=#bbb>" + "http://open-music-kontrollers.ch/lv2/sherlock</br>" + "dev@open-music-kontrollers.ch" + "</color></align>"); + + evas_object_show(label); + elm_box_pack_end(hbox, label); + } + } + } + } + + return ui->table; +} + +static LV2UI_Handle +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) +{ + if(strcmp(plugin_uri, SHERLOCK_OSC_INSPECTOR_URI)) + return NULL; + + UI *ui = calloc(1, sizeof(UI)); + if(!ui) + return NULL; + + ui->write_function = write_function; + ui->controller = controller; + + Evas_Object *parent = NULL; + for(int i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + ui->map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_UI__parent)) + parent = features[i]->data; + } + + if(!ui->map) + { + fprintf(stderr, "LV2 URID extension not supported\n"); + free(ui); + return NULL; + } + if(!parent) + { + free(ui); + return NULL; + } + + ui->event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer); + ui->midi_event = ui->map->map(ui->map->handle, LV2_MIDI__MidiEvent); + lv2_atom_forge_init(&ui->forge, ui->map); + osc_forge_init(&ui->oforge, ui->map); + + ui->itc_packet = elm_genlist_item_class_new(); + if(ui->itc_packet) + { + ui->itc_packet->item_style = "default_style"; + ui->itc_packet->func.text_get = _packet_label_get; + ui->itc_packet->func.content_get = _packet_content_get; + ui->itc_packet->func.state_get = NULL; + ui->itc_packet->func.del = _del; + } + + ui->itc_item = elm_genlist_item_class_new(); + if(ui->itc_item) + { + ui->itc_item->item_style = "default_style"; + ui->itc_item->func.text_get = _item_label_get; + ui->itc_item->func.content_get = _item_content_get; + ui->itc_item->func.state_get = NULL; + ui->itc_item->func.del = NULL; + } + + ui->itc_group = elm_genlist_item_class_new(); + if(ui->itc_group) + { + ui->itc_group->item_style = "default_style"; + ui->itc_group->func.text_get = NULL; + ui->itc_group->func.content_get = _group_item_content_get; + ui->itc_group->func.state_get = NULL; + ui->itc_group->func.del = _del; + } + + sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path); + ui->logo_path = strdup(ui->string_buf); + + ui->widget = _content_get(ui, parent); + if(!ui->widget) + { + free(ui); + return NULL; + } + *(Evas_Object **)widget = ui->widget; + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + UI *ui = handle; + + if(ui->widget) + evas_object_del(ui->widget); + if(ui->logo_path) + free(ui->logo_path); + + if(ui->itc_packet) + elm_genlist_item_class_free(ui->itc_packet); + if(ui->itc_item) + elm_genlist_item_class_free(ui->itc_item); + + free(ui); +} + +static void +port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid, + const void *buf) +{ + UI *ui = handle; + + if( (i == 2) && (urid == ui->event_transfer) && ui->list) + { + const LV2_Atom_Tuple *tup = buf; + const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup); + const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom); + const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom); + int n = elm_genlist_items_count(ui->list); + + Elm_Object_Item *itm = NULL; + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events + { + position_t *pos = malloc(sizeof(position_t)); + if(!pos) + return; + + pos->offset = offset->body; + pos->nsamples = nsamples->body; + + // check item count + if(n + 1 > COUNT_MAX) + { + if(elm_check_state_get(ui->autoclear)) + { + elm_genlist_clear(ui->list); + n = 0; + } + else + { + return; + } + } + else if(elm_check_state_get(ui->autoblock)) + { + return; + } + + itm = elm_genlist_item_append(ui->list, ui->itc_group, + pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL); + elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE); + + LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt) + { + size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size; + LV2_Atom_Event *ev = malloc(len); + if(!ev) + continue; + + memcpy(ev, elmnt, len); + + Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_packet, + ev, itm, ELM_GENLIST_ITEM_TREE, NULL, NULL); + elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT); + elm_genlist_item_expanded_set(itm2, EINA_FALSE); + n++; + + // scroll to last item + //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE); + } + } + + if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) + _clear_update(ui, n); // only update if there where any events + } +} + +const LV2UI_Descriptor osc_inspector_eo = { + .URI = SHERLOCK_OSC_INSPECTOR_EO_URI, + .instantiate = instantiate, + .cleanup = cleanup, + .port_event = port_event, + .extension_data = NULL +}; diff --git a/sandbox_ui.lv2/COPYING b/sandbox_ui.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/sandbox_ui.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/sandbox_ui.lv2/README.md b/sandbox_ui.lv2/README.md new file mode 100644 index 0000000..d9f1d9f --- /dev/null +++ b/sandbox_ui.lv2/README.md @@ -0,0 +1,18 @@ +# LV2 sandboxed UI + +### License + +Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source 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 +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/sandbox_ui.lv2/lv2_external_ui.h b/sandbox_ui.lv2/lv2_external_ui.h new file mode 100644 index 0000000..2c9e6ee --- /dev/null +++ b/sandbox_ui.lv2/lv2_external_ui.h @@ -0,0 +1,109 @@ +/* + LV2 External UI extension + This work is in public domain. + + This file 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. + + If you have questions, contact Filipe Coelho (aka falkTX) <falktx@falktx.com> + or ask in #lad channel, FreeNode IRC network. +*/ + +/** + @file lv2_external_ui.h + C header for the LV2 External UI extension <http://kxstudio.sf.net/ns/lv2ext/external-ui>. +*/ + +#ifndef LV2_EXTERNAL_UI_H +#define LV2_EXTERNAL_UI_H + +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#define LV2_EXTERNAL_UI_URI "http://kxstudio.sf.net/ns/lv2ext/external-ui" +#define LV2_EXTERNAL_UI_PREFIX LV2_EXTERNAL_UI_URI "#" + +#define LV2_EXTERNAL_UI__Host LV2_EXTERNAL_UI_PREFIX "Host" +#define LV2_EXTERNAL_UI__Widget LV2_EXTERNAL_UI_PREFIX "Widget" + +/** This extension used to be defined by a lv2plug.in URI */ +#define LV2_EXTERNAL_UI_DEPRECATED_URI "http://lv2plug.in/ns/extensions/ui#external" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * When LV2_EXTERNAL_UI__Widget UI is instantiated, the returned + * LV2UI_Widget handle must be cast to pointer to LV2_External_UI_Widget. + * UI is created in invisible state. + */ +typedef struct _LV2_External_UI_Widget { + /** + * Host calls this function regulary. UI library implementing the + * callback may do IPC or redraw the UI. + * + * @param _this_ the UI context + */ + void (*run)(struct _LV2_External_UI_Widget * _this_); + + /** + * Host calls this function to make the plugin UI visible. + * + * @param _this_ the UI context + */ + void (*show)(struct _LV2_External_UI_Widget * _this_); + + /** + * Host calls this function to make the plugin UI invisible again. + * + * @param _this_ the UI context + */ + void (*hide)(struct _LV2_External_UI_Widget * _this_); + +} LV2_External_UI_Widget; + +#define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run(ptr) +#define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show(ptr) +#define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide(ptr) + +/** + * On UI instantiation, host must supply LV2_EXTERNAL_UI__Host feature. + * LV2_Feature::data must be pointer to LV2_External_UI_Host. + */ +typedef struct _LV2_External_UI_Host { + /** + * Callback that plugin UI will call when UI (GUI window) is closed by user. + * This callback will be called during execution of LV2_External_UI_Widget::run() + * (i.e. not from background thread). + * + * After this callback is called, UI is defunct. Host must call LV2UI_Descriptor::cleanup(). + * If host wants to make the UI visible again, the UI must be reinstantiated. + * + * @note When using the depreated URI LV2_EXTERNAL_UI_DEPRECATED_URI, + * some hosts will not call LV2UI_Descriptor::cleanup() as they should, + * and may call show() again without re-initialization. + * + * @param controller Host context associated with plugin UI, as + * supplied to LV2UI_Descriptor::instantiate(). + */ + void (*ui_closed)(LV2UI_Controller controller); + + /** + * Optional (may be NULL) "user friendly" identifier which the UI + * may display to allow a user to easily associate this particular + * UI instance with the correct plugin instance as it is represented + * by the host (e.g. "track 1" or "channel 4"). + * + * If supplied by host, the string will be referenced only during + * LV2UI_Descriptor::instantiate() + */ + const char * plugin_human_id; + +} LV2_External_UI_Host; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV2_EXTERNAL_UI_H */ diff --git a/sandbox_ui.lv2/sandbox_efl.c b/sandbox_ui.lv2/sandbox_efl.c new file mode 100644 index 0000000..59344a0 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_efl.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sandbox_slave.h> + +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#include <Elementary.h> + +typedef struct _app_t app_t; + +struct _app_t { + sandbox_slave_t *sb; + + Evas_Object *win; + Evas_Object *bg; + Evas_Object *widget; + Ecore_Fd_Handler *fd; +}; + +static Eina_Bool +_recv(void *data, Ecore_Fd_Handler *fd_handler) +{ + sandbox_slave_t *sb = data; + + sandbox_slave_recv(sb); + + return ECORE_CALLBACK_RENEW; +} + +static void +_del_request(void *data, Evas_Object *obj, void *event_info) +{ + app_t *app = data; + + elm_exit(); + app->bg = NULL; + app->win = NULL; +} + +static inline int +_init(sandbox_slave_t *sb, void *data) +{ + app_t *app= data; + + int w = 640; + int h = 360; + + const char *title = sandbox_slave_title_get(sb); + app->win = elm_win_add(NULL, title, ELM_WIN_BASIC); + if(!app->win) + { + fprintf(stderr, "elm_win_add failed\n"); + goto fail; + } + elm_win_title_set(app->win, title); + elm_win_autodel_set(app->win, EINA_TRUE); + evas_object_smart_callback_add(app->win, "delete,request", _del_request, app); + + app->bg = elm_bg_add(app->win); + if(!app->bg) + { + fprintf(stderr, "elm_bg_add failed\n"); + goto fail; + } + elm_bg_color_set(app->bg, 64, 64, 64); + evas_object_size_hint_weight_set(app->bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(app->bg, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(app->bg); + elm_win_resize_object_add(app->win, app->bg); + + const LV2_Feature parent_feature = { + .URI = LV2_UI__parent, + .data = app->win + }; + + if( !sandbox_slave_instantiate(sb, &parent_feature, &app->widget) + || !app->widget) + { + fprintf(stderr, "sandbox_slave_instantiate failed\n"); + goto fail; + } + evas_object_size_hint_weight_set(app->widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(app->widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + + // get widget size hint + int W, H; + evas_object_size_hint_min_get(app->widget, &W, &H); + if(W != 0) + w = W; + if(H != 0) + h = H; + evas_object_show(app->widget); + elm_win_resize_object_add(app->win, app->widget); + + evas_object_resize(app->win, w, h); + evas_object_show(app->win); + + int fd; + sandbox_slave_fd_get(sb, &fd); + if(fd == -1) + { + fprintf(stderr, "sandbox_slave_instantiate failed\n"); + goto fail; + } + + app->fd= ecore_main_fd_handler_add(fd, ECORE_FD_READ, + _recv, sb, NULL, NULL); + if(!app->fd) + { + fprintf(stderr, "ecore_main_fd_handler_add failed\n"); + goto fail; + } + + return 0; + +fail: + return -1; +} + +static inline void +_run(sandbox_slave_t *sb, void *data) +{ + app_t *app = data; + + elm_run(); +} + +static inline void +_deinit(void *data) +{ + app_t *app = data; + + if(app->fd) + ecore_main_fd_handler_del(app->fd); + + if(app->bg) + { + elm_win_resize_object_del(app->win, app->bg); + evas_object_hide(app->bg); + evas_object_del(app->bg); + } + + if(app->win) + { + evas_object_hide(app->win); + evas_object_del(app->win); + } +} + +static const sandbox_slave_driver_t driver = { + .init_cb = _init, + .run_cb = _run, + .deinit_cb = _deinit, + .resize_cb = NULL +}; + +static int +elm_main(int argc, char **argv) +{ + static app_t app; + +#ifdef ELM_1_10 + elm_config_accel_preference_set("gl"); +#endif + + app.sb = sandbox_slave_new(argc, argv, &driver, &app); + if(app.sb) + { + sandbox_slave_run(app.sb); + sandbox_slave_free(app.sb); + printf("bye from %s\n", argv[0]); + return 0; + } + + printf("fail from %s\n", argv[0]); + return -1; +} + +ELM_MAIN(); diff --git a/sandbox_ui.lv2/sandbox_io.h b/sandbox_ui.lv2/sandbox_io.h new file mode 100644 index 0000000..aa11427 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_io.h @@ -0,0 +1,555 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_IO_H +#define _SANDBOX_IO_H + +#include <sratom/sratom.h> + +#include <nanomsg/nn.h> +#include <nanomsg/pair.h> + +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define RDF_PREFIX "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0 +#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \ + for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \ + !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \ + (iter) = lv2_atom_tuple_next(iter)) + +typedef struct _atom_ser_t atom_ser_t; +typedef struct _sandbox_io_subscription_t sandbox_io_subscription_t; +typedef struct _sandbox_io_t sandbox_io_t; + +typedef void (*_sandbox_io_recv_cb_t)(void *data, uint32_t index, uint32_t size, + uint32_t format, const void *buf); +typedef void (*_sandbox_io_subscribe_cb_t)(void *data, uint32_t index, + uint32_t protocol, bool state); + +struct _atom_ser_t { + uint32_t size; + uint8_t *buf; + uint32_t offset; +}; + +struct _sandbox_io_subscription_t { + uint32_t protocol; + int32_t state; +}; + +struct _sandbox_io_t { + LV2_URID_Map *map; + LV2_URID_Unmap *unmap; + + int sock; + int id; + + Sratom *sratom; + atom_ser_t ser; + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge forge; + + const char *base_uri; + SerdNode subject; + SerdNode predicate; + + LV2_URID float_protocol; + LV2_URID peak_protocol; + LV2_URID event_transfer; + LV2_URID atom_transfer; + LV2_URID core_index; + LV2_URID rdf_value; + LV2_URID ui_protocol; + LV2_URID ui_period_start; + LV2_URID ui_period_size; + LV2_URID ui_peak; + LV2_URID ui_window_title; + LV2_URID ui_port_subscribe; + LV2_URID params_sample_rate; +}; + +static inline LV2_Atom_Forge_Ref +_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size) +{ + atom_ser_t *ser = handle; + + const LV2_Atom_Forge_Ref ref = ser->offset + 1; + + const uint32_t new_offset = ser->offset + size; + if(new_offset > ser->size) + { + uint32_t new_size = ser->size << 1; + while(new_offset > new_size) + new_size <<= 1; + + if(!(ser->buf = realloc(ser->buf, new_size))) + return 0; // realloc failed + + ser->size = new_size; + } + + memcpy(ser->buf + ser->offset, buf, size); + ser->offset = new_offset; + + return ref; +} + +static inline LV2_Atom * +_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) +{ + atom_ser_t *ser = handle; + + const uint32_t offset = ref - 1; + + return (LV2_Atom *)(ser->buf + offset); +} + +static inline void +_sandbox_io_reset(sandbox_io_t *io) +{ + atom_ser_t *ser = &io->ser; + ser->offset = 0; //TODO free? + + lv2_atom_forge_set_sink(&io->forge, _sink, _deref, ser); + lv2_atom_forge_tuple(&io->forge, &io->frame); +} + +static inline void +_sandbox_io_recv(sandbox_io_t *io, _sandbox_io_recv_cb_t recv_cb, + _sandbox_io_subscribe_cb_t subscribe_cb, void *data) +{ + char *ttl = NULL; + int res; + + while((res = nn_recv(io->sock, &ttl, NN_MSG, NN_DONTWAIT)) != -1) + { + //printf("%s\n\n", ttl); + LV2_Atom *atom = sratom_from_turtle(io->sratom, io->base_uri, + &io->subject, &io->predicate, ttl); + if(atom) + { + LV2_ATOM_TUPLE_FOREACH((LV2_Atom_Tuple *)atom, itm) + { + LV2_Atom_Object *obj = (LV2_Atom_Object *)itm; + + if(!lv2_atom_forge_is_object_type(&io->forge, obj->atom.type)) + continue; + + if(obj->body.otype == io->float_protocol) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_Float *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value && (value->atom.type == io->forge.Float) + && (value->atom.size == sizeof(float)) ) + { + recv_cb(data, index->body, + sizeof(float), io->float_protocol, &value->body); + recv_cb(data, index->body, + sizeof(float), 0, &value->body); + } + } + else if(obj->body.otype == io->peak_protocol) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_Int *period_start = NULL; + const LV2_Atom_Int *period_size = NULL; + const LV2_Atom_Float *peak= NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->ui_period_start, (const LV2_Atom **)&period_start}, + {io->ui_period_size, (const LV2_Atom **)&period_size}, + {io->ui_peak, (const LV2_Atom **)&peak}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && period_start && (period_start->atom.type == io->forge.Int) + && (period_start->atom.size == sizeof(int32_t)) + && period_size && (period_size->atom.type == io->forge.Int) + && (period_size->atom.size == sizeof(int32_t)) + && peak && (peak->atom.type == io->forge.Float) + && (peak->atom.size == sizeof(float)) ) + { + const LV2UI_Peak_Data peak_data = { + .period_start = period_start->body, + .period_size = period_size->body, + .peak = peak->body + }; + recv_cb(data, index->body, + sizeof(LV2UI_Peak_Data), io->peak_protocol, &peak_data); + } + } + else if(obj->body.otype == io->event_transfer) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value) + { + recv_cb(data, index->body, + lv2_atom_total_size(value), io->event_transfer, value); + } + } + else if(obj->body.otype == io->atom_transfer) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value) + { + recv_cb(data, index->body, + lv2_atom_total_size(value), io->atom_transfer, value); + } + } + else if(obj->body.otype == io->ui_port_subscribe) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_URID *protocol = NULL; + const LV2_Atom_Bool *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->ui_protocol, (const LV2_Atom **)&protocol}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value && (value->atom.type == io->forge.Bool) + && protocol && (protocol->atom.type == io->forge.URID)) + { + if(subscribe_cb) + subscribe_cb(data, index->body, protocol->body, value->body); + } + } + } + + free(atom); + } + + nn_freemsg(ttl); + } + + switch(nn_errno()) + { + case EAGAIN: + // do nothing + break; + case ETERM: + //FIXME done + // fall-through + default: + fprintf(stderr, "nn_recv: %s\n", nn_strerror(nn_errno())); + break; + } +} + +static inline bool +_sandbox_io_flush(sandbox_io_t *io) +{ + const LV2_Atom *atom = (const LV2_Atom *)io->ser.buf; + + bool more = false; + if( (io->ser.offset == 0) || (atom->size == 0) ) + return more; // empty tuple + + char *ttl = sratom_to_turtle(io->sratom, io->unmap, + io->base_uri, &io->subject, &io->predicate, + atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); + + if(ttl) + { + const size_t len = strlen(ttl) + 1; + //printf("sending: %zu\n\n%s\n\n", len, ttl); + + if(nn_send(io->sock, ttl, len, NN_DONTWAIT) == -1) + { + switch(nn_errno()) + { + case EAGAIN: + more = true; + break; + case ETERM: + //FIXME done + // fall-through + default: + fprintf(stderr, "nn_send: %s\n", nn_strerror(nn_errno())); + break; + } + } + else + { + _sandbox_io_reset(io); + } + + free(ttl); + } + + return more; +} + +static inline void +_sandbox_io_clean(LV2_Atom_Forge *forge, LV2_Atom *atom) +{ + if(atom->type == forge->Object) + { + LV2_Atom_Object *obj = (LV2_Atom_Object *)atom; + + if(obj->body.id != 0) + obj->body.id = 0; // if not, sratom will fail + + LV2_ATOM_OBJECT_FOREACH(obj, prop) + { + _sandbox_io_clean(forge, &prop->value); + } + } + else if(atom->type == forge->Tuple) + { + LV2_Atom_Tuple *tup = (LV2_Atom_Tuple *)atom; + + LV2_ATOM_TUPLE_FOREACH(tup, itm) + { + _sandbox_io_clean(forge, itm); + } + } + else if(atom->type == forge->Sequence) + { + LV2_Atom_Sequence *seq = (LV2_Atom_Sequence *)atom; + + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) + { + _sandbox_io_clean(forge, &ev->body); + } + } +} + +static inline bool +_sandbox_io_send(sandbox_io_t *io, uint32_t index, + uint32_t size, uint32_t protocol, const void *buf) +{ + LV2_Atom_Forge_Frame frame; + + if(protocol == 0) + protocol = io->float_protocol; + + lv2_atom_forge_object(&io->forge, &frame, 0, protocol); + + lv2_atom_forge_key(&io->forge, io->core_index); + lv2_atom_forge_int(&io->forge, index); + + if(protocol == io->float_protocol) + { + const float *value = buf; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + lv2_atom_forge_float(&io->forge, *value); + } + else if(protocol == io->peak_protocol) + { + const LV2UI_Peak_Data *peak_data = buf; + + lv2_atom_forge_key(&io->forge, io->ui_period_start); + lv2_atom_forge_int(&io->forge, peak_data->period_start); + + lv2_atom_forge_key(&io->forge, io->ui_period_size); + lv2_atom_forge_int(&io->forge, peak_data->period_size); + + lv2_atom_forge_key(&io->forge, io->ui_peak); + lv2_atom_forge_float(&io->forge, peak_data->peak); + } + else if(protocol == io->event_transfer) + { + const LV2_Atom *atom = buf; + LV2_Atom_Forge_Ref ref; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + ref = lv2_atom_forge_atom(&io->forge, atom->size, atom->type); + lv2_atom_forge_write(&io->forge, LV2_ATOM_BODY_CONST(atom), atom->size); + + LV2_Atom *src= lv2_atom_forge_deref(&io->forge, ref); + _sandbox_io_clean(&io->forge, src); + } + else if(protocol == io->atom_transfer) + { + const LV2_Atom *atom = buf; + LV2_Atom_Forge_Ref ref; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + ref = lv2_atom_forge_atom(&io->forge, atom->size, atom->type); + lv2_atom_forge_write(&io->forge, LV2_ATOM_BODY_CONST(atom), atom->size); + + LV2_Atom *src= lv2_atom_forge_deref(&io->forge, ref); + _sandbox_io_clean(&io->forge, src); + } + else if(protocol == io->ui_port_subscribe) + { + const sandbox_io_subscription_t *sub = buf; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + lv2_atom_forge_bool(&io->forge, sub->state); + + lv2_atom_forge_key(&io->forge, io->ui_protocol); + lv2_atom_forge_urid(&io->forge, protocol); + } + + lv2_atom_forge_pop(&io->forge, &frame); + + //lv2_atom_forge_pop(&io->forge, &io->frame); + + return _sandbox_io_flush(io); +} + +static inline int +_sandbox_io_fd_get(sandbox_io_t *io) +{ + int fd = -1; + size_t sz = sizeof(int); + + if(io->sock == -1) + return -1; + + if(nn_getsockopt(io->sock, NN_SOL_SOCKET, NN_RCVFD, &fd, &sz) < 0) + return -1; + + //printf("_sandbox_io_fd_get: %i\n", fd); + + return fd; +} + +static inline int +_sandbox_io_init(sandbox_io_t *io, LV2_URID_Map *map, LV2_URID_Unmap *unmap, + const char *socket_path, bool is_master) +{ + io->base_uri = "file:///tmp/base"; + io->subject = serd_node_from_string(SERD_URI, (const uint8_t *)("")); + io->predicate = serd_node_from_string(SERD_URI, (const uint8_t *)(LV2_ATOM__atomTransfer)); + + io->map = map; + io->unmap = unmap; + + io->ser.offset = 0; + io->ser.size = 1024; + io->ser.buf = malloc(io->ser.size); + if(!io->ser.buf) + return -1; + + if(!(io->sratom = sratom_new(map))) + return -1; + sratom_set_pretty_numbers(io->sratom, false); + sratom_set_object_mode(io->sratom, SRATOM_OBJECT_MODE_BLANK); + + if((io->sock = nn_socket(AF_SP, NN_PAIR)) == -1) + return -1; + + const int ms = 10000; + if(nn_setsockopt(io->sock, NN_SOL_SOCKET, NN_LINGER, &ms, sizeof(ms)) == -1) + return -1; + + if(is_master) + { + if((io->id = nn_bind(io->sock, socket_path)) == -1) + return -1; + } + else // is_slave + { + if((io->id = nn_connect(io->sock, socket_path)) == -1) + return -1; + } + + lv2_atom_forge_init(&io->forge, map); + + io->float_protocol = map->map(map->handle, LV2_UI_PREFIX"floatProtocol"); + io->peak_protocol = map->map(map->handle, LV2_UI_PREFIX"peakProtocol"); + io->event_transfer = map->map(map->handle, LV2_ATOM__eventTransfer); + io->atom_transfer = map->map(map->handle, LV2_ATOM__atomTransfer); + io->core_index = map->map(map->handle, LV2_CORE__index); + io->rdf_value = map->map(map->handle, RDF_PREFIX"value"); + io->ui_protocol = map->map(map->handle, LV2_UI_PREFIX"protocol"); + io->ui_period_start = map->map(map->handle, LV2_UI_PREFIX"periodStart"); + io->ui_period_size = map->map(map->handle, LV2_UI_PREFIX"periodSize"); + io->ui_peak = map->map(map->handle, LV2_UI_PREFIX"peak"); + io->ui_window_title = map->map(map->handle, LV2_UI__windowTitle); + io->ui_port_subscribe = map->map(map->handle, LV2_UI__portSubscribe); + io->params_sample_rate = map->map(map->handle, LV2_PARAMETERS__sampleRate); + + _sandbox_io_reset(io); + + return 0; +} + +static inline void +_sandbox_io_deinit(sandbox_io_t *io) +{ + if(io->id != -1) + { + int res = nn_shutdown(io->sock, io->id); + if(res < 0) + fprintf(stderr, "nn_shutdown failed: %s\n", nn_strerror(nn_errno())); + } + + if(io->sock != -1) + { + int res = nn_close(io->sock); + if(res < 0) + fprintf(stderr, "nn_close failed: %s\n", nn_strerror(nn_errno())); + } + + if(io->sratom) + sratom_free(io->sratom); + + if(io->ser.buf) + free(io->ser.buf); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_master.c b/sandbox_ui.lv2/sandbox_master.c new file mode 100644 index 0000000..c61e1d0 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_master.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sandbox_master.h> +#include <sandbox_io.h> + +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +struct _sandbox_master_t { + sandbox_io_t io; + + sandbox_master_driver_t *driver; + void *data; +}; + +sandbox_master_t * +sandbox_master_new(sandbox_master_driver_t *driver, void *data) +{ + sandbox_master_t *sb = calloc(1, sizeof(sandbox_master_t)); + if(!sb) + goto fail; + + sb->driver = driver; + sb->data = data; + + if(_sandbox_io_init(&sb->io, driver->map, driver->unmap, driver->socket_path, true)) + goto fail; + + return sb; + +fail: + sandbox_master_free(sb); + return NULL; +} + +void +sandbox_master_free(sandbox_master_t *sb) +{ + if(sb) + { + _sandbox_io_deinit(&sb->io); + free(sb); + } +} + +void +sandbox_master_recv(sandbox_master_t *sb) +{ + if(sb) + _sandbox_io_recv(&sb->io, sb->driver->recv_cb, sb->driver->subscribe_cb, sb->data); +} + +bool +sandbox_master_send(sandbox_master_t *sb, uint32_t index, uint32_t size, + uint32_t format, const void *buf) +{ + if(sb) + return _sandbox_io_send(&sb->io, index, size, format, buf); + + return false; +} + +bool +sandbox_master_flush(sandbox_master_t *sb) +{ + if(sb) + return _sandbox_io_flush(&sb->io); + + return false; +} + +void +sandbox_master_fd_get(sandbox_master_t *sb, int *fd) +{ + if(sb && fd) + *fd = _sandbox_io_fd_get(&sb->io); + else if(fd) + *fd = -1; +} diff --git a/sandbox_ui.lv2/sandbox_master.h b/sandbox_ui.lv2/sandbox_master.h new file mode 100644 index 0000000..287a319 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_master.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_MASTER_H +#define _SANDBOX_MASTER_H + +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _sandbox_master_t sandbox_master_t; +typedef struct _sandbox_master_driver_t sandbox_master_driver_t; +typedef void (*sandbox_master_recv_cb_t)(void *data, uint32_t index, uint32_t size, + uint32_t format, const void *buf); +typedef void (*sandbox_master_subscribe_cb_t)(void *data, uint32_t index, + uint32_t protocol, bool state); + +struct _sandbox_master_driver_t { + const char *socket_path; + LV2_URID_Map *map; + LV2_URID_Unmap *unmap; + sandbox_master_recv_cb_t recv_cb; + sandbox_master_subscribe_cb_t subscribe_cb; +}; + +sandbox_master_t * +sandbox_master_new(sandbox_master_driver_t *driver, void *data); + +void +sandbox_master_free(sandbox_master_t *sb); + +void +sandbox_master_recv(sandbox_master_t *sb); + +bool +sandbox_master_send(sandbox_master_t *sb, uint32_t index, uint32_t size, + uint32_t format, const void *buf); + +bool +sandbox_master_flush(sandbox_master_t *sb); + +void +sandbox_master_fd_get(sandbox_master_t *sb, int *fd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_slave.c b/sandbox_ui.lv2/sandbox_slave.c new file mode 100644 index 0000000..b0c3bec --- /dev/null +++ b/sandbox_ui.lv2/sandbox_slave.c @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dlfcn.h> +#include <string.h> +#include <ctype.h> + +#include <sandbox_slave.h> +#include <sandbox_io.h> + +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#include <lilv/lilv.h> +#include <symap.h> + +struct _sandbox_slave_t { + Symap *symap; + LV2_URID_Map map; + LV2_URID_Unmap unmap; + + LV2_Log_Log log; + + LV2UI_Port_Map port_map; + LV2UI_Port_Subscribe port_subscribe; + + LV2UI_Resize host_resize; + + LilvWorld *world; + LilvNode *bundle_node; + LilvNode *plugin_node; + LilvNode *ui_node; + + const LilvPlugin *plug; + const LilvUI *ui; + + void *lib; + const LV2UI_Descriptor *desc; + void *handle; + + sandbox_io_t io; + + const sandbox_slave_driver_t *driver; + void *data; + + const char *plugin_uri; + const char *bundle_path; + const char *ui_uri; + const char *socket_path; + const char *window_title; + float sample_rate; +}; + +static inline LV2_URID +_map(void *data, const char *uri) +{ + sandbox_slave_t *sb= data; + + return symap_map(sb->symap, uri); +} + +static inline const char * +_unmap(void *data, LV2_URID urid) +{ + sandbox_slave_t *sb= data; + + return symap_unmap(sb->symap, urid); +} + +static inline int +_log_vprintf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, va_list args) +{ + sandbox_slave_t *sb = handle; + + vprintf(fmt, args); + + return 0; +} + +static inline int +_log_printf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, ...) +{ + va_list args; + + va_start (args, fmt); + const int ret = _log_vprintf(handle, type, fmt, args); + va_end(args); + + return ret; +} + +static inline uint32_t +_port_index(LV2UI_Feature_Handle handle, const char *symbol) +{ + sandbox_slave_t *sb = handle; + uint32_t index = LV2UI_INVALID_PORT_INDEX; + + LilvNode *symbol_uri = lilv_new_string(sb->world, symbol); + if(symbol_uri) + { + const LilvPort *port = lilv_plugin_get_port_by_symbol(sb->plug, symbol_uri); + + if(port) + index = lilv_port_get_index(sb->plug, port); + + lilv_node_free(symbol_uri); + } + + return index; +} + +static inline void +_write_function(LV2UI_Controller controller, uint32_t index, + uint32_t size, uint32_t protocol, const void *buf) +{ + sandbox_slave_t *sb = controller; + + const bool more = _sandbox_io_send(&sb->io, index, size, protocol, buf); + (void)more; //TODO +} + +static inline uint32_t +_port_subscribe(LV2UI_Feature_Handle handle, uint32_t index, uint32_t protocol, + const LV2_Feature *const *features) +{ + sandbox_slave_t *sb = handle; + + const sandbox_io_subscription_t sub = { + .state = 1, + .protocol = protocol + }; + _write_function(handle, index, sizeof(sandbox_io_subscription_t), sb->io.ui_port_subscribe, &sub); + + return 0; +} + +static inline uint32_t +_port_unsubscribe(LV2UI_Feature_Handle handle, uint32_t index, uint32_t protocol, + const LV2_Feature *const *features) +{ + sandbox_slave_t *sb = handle; + + const sandbox_io_subscription_t sub = { + .state = 0, + .protocol = protocol + }; + _write_function(handle, index, sizeof(sandbox_io_subscription_t), sb->io.ui_port_subscribe, &sub); + + return 0; +} + +static inline void +_sandbox_recv_cb(LV2UI_Handle handle, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf) +{ + sandbox_slave_t *sb = handle; + + if(sb->desc && sb->desc->port_event) + sb->desc->port_event(sb->handle, index, size, protocol, buf); +} + +sandbox_slave_t * +sandbox_slave_new(int argc, char **argv, const sandbox_slave_driver_t *driver, void *data) +{ + sandbox_slave_t *sb = calloc(1, sizeof(sandbox_slave_t)); + if(!sb) + { + fprintf(stderr, "allocation failed\n"); + goto fail; + } + + sb->sample_rate = 44100; // fall-back + + int c; + while((c = getopt(argc, argv, "p:b:u:s:w:r:")) != -1) + { + switch(c) + { + case 'p': + sb->plugin_uri = optarg; + break; + case 'b': + sb->bundle_path = optarg; + break; + case 'u': + sb->ui_uri = optarg; + break; + case 's': + sb->socket_path = optarg; + break; + case 'w': + sb->window_title = optarg; + break; + case 'r': + sb->sample_rate = atof(optarg); + break; + case '?': + if( (optopt == 'p') || (optopt == 'b') || (optopt == 'u') || (optopt == 's') || (optopt == 'w') || (optopt == 'r') ) + fprintf(stderr, "Option `-%c' requires an argument.\n", optopt); + else if(isprint(optopt)) + fprintf(stderr, "Unknown option `-%c'.\n", optopt); + else + fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); + goto fail; + default: + goto fail; + } + } + + if( !sb->plugin_uri + || !sb->bundle_path + || !sb->ui_uri + || !sb->socket_path + || !sb->window_title) + { + fprintf(stderr, "invalid arguments\n"); + goto fail; + } + + sb->driver = driver; + sb->data = data; + + sb->map.handle = sb; + sb->map.map = _map; + + sb->unmap.handle = sb; + sb->unmap.unmap = _unmap; + + sb->log.handle = sb; + sb->log.printf = _log_printf; + sb->log.vprintf = _log_vprintf; + + sb->port_map.handle = sb; + sb->port_map.port_index = _port_index; + + sb->port_subscribe.handle = sb; + sb->port_subscribe.subscribe = _port_subscribe; + sb->port_subscribe.unsubscribe = _port_unsubscribe; + + sb->host_resize.handle = data; + sb->host_resize.ui_resize = driver->resize_cb; + + if(!(sb->symap = symap_new())) + { + fprintf(stderr, "symap_new failed\n"); + goto fail; + } + + if(!(sb->world = lilv_world_new())) + { + fprintf(stderr, "lilv_world_new failed\n"); + goto fail; + } + + sb->bundle_node = lilv_new_file_uri(sb->world, NULL, sb->bundle_path); + sb->plugin_node = lilv_new_uri(sb->world, sb->plugin_uri); + sb->ui_node = lilv_new_uri(sb->world, sb->ui_uri); + + if(!sb->bundle_node || !sb->plugin_node || !sb->ui_node) + { + fprintf(stderr, "lilv_new_uri failes\n"); + goto fail; + } + + lilv_world_load_bundle(sb->world, sb->bundle_node); + lilv_world_load_resource(sb->world, sb->ui_node); + + const LilvPlugins *plugins = lilv_world_get_all_plugins(sb->world); + if(!plugins) + { + fprintf(stderr, "lilv_world_get_all_plugins failed\n"); + goto fail; + } + + if(!(sb->plug = lilv_plugins_get_by_uri(plugins, sb->plugin_node))) + { + fprintf(stderr, "lilv_plugins_get_by_uri failed\n"); + goto fail; + } + + LilvUIs *uis = lilv_plugin_get_uis(sb->plug); + if(!uis) + { + fprintf(stderr, "lilv_plugin_get_uis failed\n"); + goto fail; + } + + if(!(sb->ui = lilv_uis_get_by_uri(uis, sb->ui_node))) + { + fprintf(stderr, "lilv_uis_get_by_uri failed\n"); + goto fail; + } + + const LilvNode *ui_path = lilv_ui_get_binary_uri(sb->ui); + if(!ui_path) + { + fprintf(stderr, "lilv_ui_get_binary_uri failed\n"); + goto fail; + } + +#if defined(LILV_0_22) + char *binary_path = lilv_file_uri_parse(lilv_node_as_string(ui_path), NULL); +#else + const char *binary_path = lilv_uri_to_path(lilv_node_as_string(ui_path)); +#endif + if(!(sb->lib = dlopen(binary_path, RTLD_LAZY))) + { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + goto fail; + } + +#if defined(LILV_0_22) + lilv_free(binary_path); +#endif + + LV2UI_DescriptorFunction desc_func = dlsym(sb->lib, "lv2ui_descriptor"); + if(!desc_func) + { + fprintf(stderr, "dlsym failed\n"); + goto fail; + } + + for(int i=0; true; i++) + { + const LV2UI_Descriptor *desc = desc_func(i); + if(!desc) // sentinel + break; + + if(!strcmp(desc->URI, sb->ui_uri)) + { + sb->desc = desc; + break; + } + } + + if(!sb->desc) + { + fprintf(stderr, "LV2UI_Descriptor lookup failed\n"); + goto fail; + } + + if(_sandbox_io_init(&sb->io, &sb->map, &sb->unmap, sb->socket_path, false)) + { + fprintf(stderr, "_sandbox_io_init failed\n"); + goto fail; + } + + if(driver->init_cb && (driver->init_cb(sb, data) != 0) ) + { + fprintf(stderr, "driver->init_cb failed\n"); + goto fail; + } + + return sb; // success + +fail: + sandbox_slave_free(sb); + return NULL; +} + +void +sandbox_slave_free(sandbox_slave_t *sb) +{ + if(!sb) + return; + + if(sb->desc && sb->desc->cleanup) + sb->desc->cleanup(sb->handle); + + if(sb->driver && sb->driver->deinit_cb) + sb->driver->deinit_cb(sb->data); + + _sandbox_io_deinit(&sb->io); + + if(sb->lib) + dlclose(sb->lib); + + if(sb->world) + { + if(sb->ui_node) + { + lilv_world_unload_resource(sb->world, sb->ui_node); + lilv_node_free(sb->ui_node); + } + + if(sb->bundle_node) + { + lilv_world_unload_bundle(sb->world, sb->bundle_node); + lilv_node_free(sb->bundle_node); + } + + if(sb->plugin_node) + lilv_node_free(sb->plugin_node); + + lilv_world_free(sb->world); + } + + if(sb->symap) + symap_free(sb->symap); + + free(sb); +} + +void * +sandbox_slave_instantiate(sandbox_slave_t *sb, const LV2_Feature *parent_feature, void *widget) +{ + LV2_Options_Option options [] = { + [0] = { + .context = LV2_OPTIONS_INSTANCE, + .subject = 0, + .key = sb->io.ui_window_title, + .size = strlen(sb->plugin_uri) + 1, + .type = sb->io.forge.String, + .value = sb->plugin_uri + }, + [1] = { + .context = LV2_OPTIONS_INSTANCE, + .subject = 0, + .key = sb->io.params_sample_rate, + .size = sizeof(float), + .type = sb->io.forge.Float, + .value = &sb->sample_rate + }, + [2] = { + .key = 0, + .value = NULL + } + }; + + const LV2_Feature map_feature = { + .URI = LV2_URID__map, + .data = &sb->map + }; + const LV2_Feature unmap_feature = { + .URI = LV2_URID__unmap, + .data = &sb->unmap + }; + const LV2_Feature log_feature = { + .URI = LV2_LOG__log, + .data = &sb->log + }; + const LV2_Feature port_map_feature = { + .URI = LV2_UI__portMap, + .data = &sb->port_map + }; + const LV2_Feature port_subscribe_feature = { + .URI = LV2_UI__portSubscribe, + .data = &sb->port_subscribe + }; + const LV2_Feature options_feature = { + .URI = LV2_OPTIONS__options, + .data = options + }; + const LV2_Feature resize_feature = { + .URI = LV2_UI__resize, + .data = &sb->host_resize + }; + + const LV2_Feature *const features [] = { + &map_feature, + &unmap_feature, + &log_feature, + &port_map_feature, + &port_subscribe_feature, + &options_feature, + sb->host_resize.ui_resize ? &resize_feature : parent_feature, + sb->host_resize.ui_resize && parent_feature ? parent_feature : NULL, + NULL + }; + + const LilvNode *ui_bundle_uri = lilv_ui_get_bundle_uri(sb->ui); +#if defined(LILV_0_22) + char *ui_bundle_path = lilv_file_uri_parse(lilv_node_as_string(ui_bundle_uri), NULL); +#else + const char *ui_bundle_path = lilv_uri_to_path(lilv_node_as_string(ui_bundle_uri)); +#endif + + if(sb->desc && sb->desc->instantiate) + { + sb->handle = sb->desc->instantiate(sb->desc, sb->plugin_uri, + ui_bundle_path, _write_function, sb, widget, features); + } + +#if defined(LILV_0_22) + lilv_free(ui_bundle_path); +#endif + + if(sb->handle) + return sb->handle; // success + + return NULL; +} + +void +sandbox_slave_recv(sandbox_slave_t *sb) +{ + if(sb) + _sandbox_io_recv(&sb->io, _sandbox_recv_cb, NULL, sb); +} + +bool +sandbox_slave_flush(sandbox_slave_t *sb) +{ + if(sb) + return _sandbox_io_flush(&sb->io); + + return false; +} + +const void * +sandbox_slave_extension_data(sandbox_slave_t *sb, const char *URI) +{ + if(sb && sb->desc && sb->desc->extension_data) + return sb->desc->extension_data(URI); + + return NULL; +} + +void +sandbox_slave_run(sandbox_slave_t *sb) +{ + if(sb && sb->driver && sb->driver->run_cb) + sb->driver->run_cb(sb, sb->data); +} + +void +sandbox_slave_fd_get(sandbox_slave_t *sb, int *fd) +{ + if(sb && fd) + *fd = _sandbox_io_fd_get(&sb->io); + else if(fd) + *fd = -1; +} + +const char * +sandbox_slave_title_get(sandbox_slave_t *sb) +{ + if(sb) + return sb->window_title; + + return NULL; +} diff --git a/sandbox_ui.lv2/sandbox_slave.h b/sandbox_ui.lv2/sandbox_slave.h new file mode 100644 index 0000000..dcd78bb --- /dev/null +++ b/sandbox_ui.lv2/sandbox_slave.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_SLAVE_H +#define _SANDBOX_SLAVE_H + +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _sandbox_slave_t sandbox_slave_t; +typedef struct _sandbox_slave_driver_t sandbox_slave_driver_t; + +typedef int (*sandbox_slave_driver_init_t)(sandbox_slave_t *sb, void *data); +typedef void (*sandbox_slave_driver_run_t)(sandbox_slave_t *sb, void *data); +typedef void (*sandbox_slave_driver_deinit_t)(void *data); +typedef int (*sandbox_slave_driver_resize_t)(void *data, int width, int height); + +struct _sandbox_slave_driver_t { + sandbox_slave_driver_init_t init_cb; + sandbox_slave_driver_run_t run_cb; + sandbox_slave_driver_deinit_t deinit_cb; + sandbox_slave_driver_resize_t resize_cb; +}; + +sandbox_slave_t * +sandbox_slave_new(int argc, char **argv, const sandbox_slave_driver_t *driver, void *data); + +void +sandbox_slave_free(sandbox_slave_t *sb); + +void * +sandbox_slave_instantiate(sandbox_slave_t *sb, const LV2_Feature *parent_feature, void *widget); + +void +sandbox_slave_recv(sandbox_slave_t *sb); + +bool +sandbox_slave_flush(sandbox_slave_t *sb); + +const void * +sandbox_slave_extension_data(sandbox_slave_t *sb, const char *URI); + +void +sandbox_slave_run(sandbox_slave_t *sb); + +void +sandbox_slave_fd_get(sandbox_slave_t *sb, int *fd); + +const char * +sandbox_slave_title_get(sandbox_slave_t *sb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_ui.c b/sandbox_ui.lv2/sandbox_ui.c new file mode 100644 index 0000000..9acadea --- /dev/null +++ b/sandbox_ui.lv2/sandbox_ui.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <sys/wait.h> +#include <errno.h> +#include <signal.h> +#include <string.h> + +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h> + +#include <sandbox_ui.h> +#include <sandbox_master.h> +#include <lv2_external_ui.h> // kxstudio external-ui extension + +#define SOCKET_PATH_LEN 32 + +typedef struct _plughandle_t plughandle_t; + +struct _plughandle_t { + int done; + + LV2UI_Write_Function write_function; + LV2UI_Controller controller; + LV2UI_Port_Subscribe *subscribe; + + sandbox_master_driver_t driver; + sandbox_master_t *sb; + + char *plugin_uri; + char *bundle_path; + char *executable; + char *ui_uri; + char *window_title; + char *sample_rate; + char socket_path [SOCKET_PATH_LEN]; + + LV2_URID ui_window_title; + LV2_URID params_sample_rate; + LV2_URID atom_string; + LV2_URID atom_float; + + pid_t pid; + + struct { + LV2_External_UI_Widget widget; + const LV2_External_UI_Host *host; + } kx; +}; + +static void +_recv(LV2UI_Controller controller, uint32_t port, + uint32_t size, uint32_t protocol, const void *buf) +{ + plughandle_t *handle = controller; + + handle->write_function(handle->controller, port, size, protocol, buf); +} + +static void +_subscribe(LV2UI_Controller controller, uint32_t port, + uint32_t protocol, bool state) +{ + plughandle_t *handle = controller; + + if(handle->subscribe) + { + if(state) + handle->subscribe->subscribe(handle->subscribe->handle, + port, protocol, NULL); + else + handle->subscribe->unsubscribe(handle->subscribe->handle, + port, protocol, NULL); + } +} + +// Show Interface +static inline int +_show_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(!handle->done) + return 0; // already showing + + strncpy(handle->socket_path, "ipc:///tmp/sandbox_ui_XXXXXX", SOCKET_PATH_LEN); + int fd = mkstemp(&handle->socket_path[6]); + if(!fd) + return -1; + close(fd); + + handle->sb = sandbox_master_new(&handle->driver, handle); + if(!handle->sb) + return -1; + + handle->pid = fork(); + if(handle->pid == 0) // child + { + char *const argv [] = { + handle->executable, + "-p", handle->plugin_uri, + "-b", handle->bundle_path, + "-u", handle->ui_uri, + "-s", handle->socket_path, + "-w", handle->window_title, + "-r", handle->sample_rate, + NULL + }; + execv(handle->executable, argv); // p = search PATH for executable + + printf("fork child failed\n"); + exit(-1); + } + else if(handle->pid < 0) + { + printf("fork failed\n"); + return -1; + } + + handle->done = 0; + + return 0; +} + +static inline int +_hide_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(handle->pid > 0) // has child + { + kill(handle->pid, SIGINT); + + int status; + waitpid(handle->pid, &status, 0); + + handle->pid = -1; // invalidate + } + + if(handle->sb) + { + sandbox_master_free(handle->sb); + handle->sb = NULL; + } + + /* FIXME + remove(&handle->socket_path[6]); + */ + + if(handle->kx.host && handle->kx.host->ui_closed) + handle->kx.host->ui_closed(handle->controller); + + handle->done = 1; + + return 0; +} + +static const LV2UI_Show_Interface show_ext = { + .show = _show_cb, + .hide = _hide_cb +}; + +// Idle interface +static inline int +_idle_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(handle->pid > 0) + { + int status; + int res; + if((res = waitpid(handle->pid, &status, WNOHANG)) < 0) + { + if(errno == ECHILD) + { + handle->pid = -1; // invalidate + //_hide_cb(ui); + handle->done = 1; + } + } + else if( (res > 0) && WIFEXITED(status) ) + { + handle->pid = -1; // invalidate + //_hide_cb(ui); + handle->done = 1; + } + } + + if(!handle->done && handle->sb) + { + sandbox_master_recv(handle->sb); + sandbox_master_flush(handle->sb); + } + + return handle->done; +} + +static const LV2UI_Idle_Interface idle_ext = { + .idle = _idle_cb +}; + +// External-UI Interface +static inline void +_kx_run(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + if(_idle_cb(handle)) + _hide_cb(handle); +} + +static inline void +_kx_hide(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + _hide_cb(handle); +} + +static inline void +_kx_show(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + _show_cb(handle); +} + +static inline void +_free_strdups(plughandle_t *handle) +{ + if(handle->plugin_uri) + free(handle->plugin_uri); + if(handle->bundle_path) + free(handle->bundle_path); + if(handle->executable) + free(handle->executable); + if(handle->ui_uri) + free(handle->ui_uri); + if(handle->window_title) + free(handle->window_title); + if(handle->sample_rate) + free(handle->sample_rate); +}; + +LV2UI_Handle +sandbox_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) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + + handle->write_function = write_function; + handle->controller = controller; + + LV2_Options_Option *opts = NULL; // optional + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->driver.map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_URID__unmap)) + handle->driver.unmap = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_UI__portSubscribe)) + handle->subscribe = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_EXTERNAL_UI__Host)) + handle->kx.host = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_OPTIONS__options)) + opts = features[i]->data; + } + + if(!handle->driver.map || !handle->driver.unmap) + { + free(handle); + return NULL; + } + + handle->ui_window_title = handle->driver.map->map(handle->driver.map->handle, + LV2_UI__windowTitle); + handle->params_sample_rate= handle->driver.map->map(handle->driver.map->handle, + LV2_PARAMETERS__sampleRate); + handle->atom_string = handle->driver.map->map(handle->driver.map->handle, + LV2_ATOM__String); + handle->atom_float = handle->driver.map->map(handle->driver.map->handle, + LV2_ATOM__Float); + + handle->plugin_uri = strdup(plugin_uri); + handle->bundle_path = strdup(bundle_path); + if(asprintf(&handle->executable, "%ssandbox_efl", bundle_path) == -1) + handle->executable = NULL; // failed + handle->ui_uri = strdup(descriptor->URI); + sprintf(&handle->ui_uri[strlen(handle->ui_uri) - 4], "%s", "3_eo"); //TODO more elegant way? + + if(opts) + { + for(LV2_Options_Option *opt = opts; + (opt->key != 0) && (opt->value != NULL); + opt++) + { + if( (opt->key == handle->ui_window_title) && (opt->type == handle->atom_string) ) + { + handle->window_title = strdup(opt->value); + } + else if( (opt->key == handle->params_sample_rate) && (opt->type == handle->atom_float) ) + { + if(asprintf(&handle->sample_rate, "%f", *(const float *)opt->value) == -1) + handle->sample_rate = NULL; + } + } + } + if(!handle->window_title && handle->kx.host && handle->kx.host->plugin_human_id) + handle->window_title = strdup(handle->kx.host->plugin_human_id); + if(!handle->window_title) + handle->window_title = strdup(descriptor->URI); + if(!handle->sample_rate) + handle->sample_rate = strdup("44100"); + + if(!handle->plugin_uri || !handle->bundle_path || !handle->executable || !handle->ui_uri || !handle->window_title || !handle->sample_rate) + { + _free_strdups(handle); + free(handle); + return NULL; + } + + handle->driver.socket_path = handle->socket_path; + handle->driver.recv_cb = _recv; + handle->driver.subscribe_cb = _subscribe; + + handle->pid = -1; // invalidate + + handle->kx.widget.run = _kx_run; + handle->kx.widget.show = _kx_show; + handle->kx.widget.hide = _kx_hide; + + if(strstr(descriptor->URI, "_kx")) + *(LV2_External_UI_Widget **)widget = &handle->kx.widget; + else + *widget = NULL; + + handle->done = 1; + + return handle; +} + +void +sandbox_ui_cleanup(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + _free_strdups(handle); + free(handle); +} + +void +sandbox_ui_port_event(LV2UI_Handle instance, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf) +{ + plughandle_t *handle = instance; + + if(handle->sb) + sandbox_master_send(handle->sb, index, size, protocol, buf); +} + +// extension data callback for show interface UI +const void * +sandbox_ui_extension_data(const char *uri) +{ + if(!strcmp(uri, LV2_UI__idleInterface)) + return &idle_ext; + else if(!strcmp(uri, LV2_UI__showInterface)) + return &show_ext; + + return NULL; +} diff --git a/sandbox_ui.lv2/sandbox_ui.h b/sandbox_ui.lv2/sandbox_ui.h new file mode 100644 index 0000000..6dee0e3 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_ui.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_UI_H +#define _SANDBOX_UI_H + +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#ifdef __cplusplus +extern "C" { +#endif + +LV2UI_Handle +sandbox_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 +sandbox_ui_cleanup(LV2UI_Handle instance); + +void +sandbox_ui_port_event(LV2UI_Handle instance, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf); + +const void * +sandbox_ui_extension_data(const char *uri); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sherlock.c b/sherlock.c new file mode 100644 index 0000000..340176c --- /dev/null +++ b/sherlock.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <sherlock.h> + +LV2_SYMBOL_EXPORT const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch(index) + { + case 0: + return &atom_inspector; + case 1: + return &midi_inspector; + case 2: + return &osc_inspector; + default: + return NULL; + } +} diff --git a/sherlock.h b/sherlock.h new file mode 100644 index 0000000..00e36e9 --- /dev/null +++ b/sherlock.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SHERLOCK_LV2_H +#define _SHERLOCK_LV2_H + +#include <stdint.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/time/time.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/ext/time/time.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "lv2/lv2plug.in/ns/extensions/units/units.h" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define LV2_OSC__OscEvent "http://opensoundcontrol.org#OscEvent" + +#define SHERLOCK_URI "http://open-music-kontrollers.ch/lv2/sherlock" + +#define SHERLOCK_ATOM_INSPECTOR_URI SHERLOCK_URI"#atom_inspector" +#define SHERLOCK_ATOM_INSPECTOR_UI_URI SHERLOCK_URI"#atom_inspector_1_ui" +#define SHERLOCK_ATOM_INSPECTOR_KX_URI SHERLOCK_URI"#atom_inspector_2_kx" +#define SHERLOCK_ATOM_INSPECTOR_EO_URI SHERLOCK_URI"#atom_inspector_3_eo" + +#define SHERLOCK_MIDI_INSPECTOR_URI SHERLOCK_URI"#midi_inspector" +#define SHERLOCK_MIDI_INSPECTOR_UI_URI SHERLOCK_URI"#midi_inspector_1_ui" +#define SHERLOCK_MIDI_INSPECTOR_KX_URI SHERLOCK_URI"#midi_inspector_2_kx" +#define SHERLOCK_MIDI_INSPECTOR_EO_URI SHERLOCK_URI"#midi_inspector_3_eo" + +#define SHERLOCK_OSC_INSPECTOR_URI SHERLOCK_URI"#osc_inspector" +#define SHERLOCK_OSC_INSPECTOR_UI_URI SHERLOCK_URI"#osc_inspector_1_ui" +#define SHERLOCK_OSC_INSPECTOR_KX_URI SHERLOCK_URI"#osc_inspector_2_kx" +#define SHERLOCK_OSC_INSPECTOR_EO_URI SHERLOCK_URI"#osc_inspector_3_eo" + +extern const LV2_Descriptor atom_inspector; +extern const LV2UI_Descriptor atom_inspector_ui; +extern const LV2UI_Descriptor atom_inspector_kx; +extern const LV2UI_Descriptor atom_inspector_eo; + +extern const LV2_Descriptor midi_inspector; +extern const LV2UI_Descriptor midi_inspector_ui; +extern const LV2UI_Descriptor midi_inspector_kx; +extern const LV2UI_Descriptor midi_inspector_eo; + +extern const LV2_Descriptor osc_inspector; +extern const LV2UI_Descriptor osc_inspector_ui; +extern const LV2UI_Descriptor osc_inspector_kx; +extern const LV2UI_Descriptor osc_inspector_eo; + +typedef struct _position_t position_t; + +struct _position_t { + uint64_t offset; + uint32_t nsamples; +}; + +#endif // _SHERLOCK_LV2_H diff --git a/sherlock.ttl b/sherlock.ttl new file mode 100644 index 0000000..8d01e23 --- /dev/null +++ b/sherlock.ttl @@ -0,0 +1,187 @@ +# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix midi: <http://lv2plug.in/ns/ext/midi#> . +@prefix time: <http://lv2plug.in/ns/ext/time#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix patch: <http://lv2plug.in/ns/ext/patch#> . + +@prefix xpress: <http://open-music-kontrollers.ch/lv2/xpress#> . +@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> . +@prefix lic: <http://opensource.org/licenses/> . +@prefix omk: <http://open-music-kontrollers.ch/ventosus#> . +@prefix proj: <http://open-music-kontrollers.ch/lv2/> . +@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> . + +osc:Event + a rdfs:Class ; + rdfs:subClassOf atom:Object ; + rdfs:label "OSC Event (Bundle or Message)" . + +xpress:Message + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Atom . + +# Maintainer +omk:me + a foaf:Person ; + foaf:name "Hanspeter Portner" ; + foaf:mbox <mailto:dev@open-music-kontrollers.ch> ; + foaf:homepage <http://open-music-kontrollers.ch> . + +# Project +proj:sherlock + a doap:Project ; + doap:maintainer omk:me ; + doap:name "Sherlock Bundle" . + +# Atom Inspector Plugin +sherlock:atom_inspector + a lv2:Plugin, + lv2:AnalyserPlugin ; + doap:name "Sherlock Atom Inspector" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:sherlock ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ; + lv2:requiredFeature urid:map ; + + lv2:port [ + # input event port + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent , + time:Position , + patch:Message , + osc:Event , + xpress:Message ; + lv2:index 0 ; + lv2:symbol "control_in" ; + lv2:name "Control In" ; + lv2:designation lv2:control ; + ] , [ + # output event port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent , + time:Position , + patch:Message , + osc:Event , + xpress:Message ; + lv2:index 1 ; + lv2:symbol "control_out" ; + lv2:name "Control Out" ; + lv2:designation lv2:control ; + ] , [ + # output notify port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports atom:Sequence ; + lv2:index 2 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + ] . + +# MIDI Inspector Plugin +sherlock:midi_inspector + a lv2:Plugin, + lv2:AnalyserPlugin ; + doap:name "Sherlock MIDI Inspector" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:sherlock ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ; + lv2:requiredFeature urid:map ; + + lv2:port [ + # input event port + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent , + time:Position ; + lv2:index 0 ; + lv2:symbol "control_in" ; + lv2:name "Control In" ; + lv2:designation lv2:control ; + ] , [ + # output event port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + lv2:index 1 ; + lv2:symbol "control_out" ; + lv2:name "Control Out" ; + lv2:designation lv2:control ; + ] , [ + # output notify port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports atom:Sequence ; + lv2:index 2 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + ] . + +# OSC Inspector Plugin +sherlock:osc_inspector + a lv2:Plugin, + lv2:AnalyserPlugin ; + doap:name "Sherlock OSC Inspector" ; + doap:license lic:Artistic-2.0 ; + lv2:project proj:sherlock ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ; + lv2:requiredFeature urid:map ; + + lv2:port [ + # input event port + a lv2:InputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports osc:Event , + time:Position ; + lv2:index 0 ; + lv2:symbol "control_in" ; + lv2:name "Control In" ; + lv2:designation lv2:control ; + ] , [ + # output event port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports osc:Event ; + lv2:index 1 ; + lv2:symbol "control_out" ; + lv2:name "Control Out" ; + lv2:designation lv2:control ; + ] , [ + # output notify port + a lv2:OutputPort , + atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports atom:Sequence ; + lv2:index 2 ; + lv2:symbol "notify" ; + lv2:name "Notify" ; + ] . diff --git a/sherlock_eo.c b/sherlock_eo.c new file mode 100644 index 0000000..260f859 --- /dev/null +++ b/sherlock_eo.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <sherlock.h> + +LV2_SYMBOL_EXPORT const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + switch(index) + { + case 0: + return &atom_inspector_eo; + case 1: + return &midi_inspector_eo; + case 2: + return &osc_inspector_eo; + + default: + return NULL; + } +} diff --git a/sherlock_ui.c b/sherlock_ui.c new file mode 100644 index 0000000..f28f57d --- /dev/null +++ b/sherlock_ui.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source 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 + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <sherlock.h> +#include <sandbox_ui.h> + +const LV2UI_Descriptor atom_inspector_ui= { + .URI = SHERLOCK_ATOM_INSPECTOR_UI_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = sandbox_ui_extension_data +}; + +const LV2UI_Descriptor atom_inspector_kx= { + .URI = SHERLOCK_ATOM_INSPECTOR_KX_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = NULL +}; + +const LV2UI_Descriptor midi_inspector_ui= { + .URI = SHERLOCK_MIDI_INSPECTOR_UI_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = sandbox_ui_extension_data +}; + +const LV2UI_Descriptor midi_inspector_kx= { + .URI = SHERLOCK_MIDI_INSPECTOR_KX_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = NULL +}; + +const LV2UI_Descriptor osc_inspector_ui= { + .URI = SHERLOCK_OSC_INSPECTOR_UI_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = sandbox_ui_extension_data +}; + +const LV2UI_Descriptor osc_inspector_kx= { + .URI = SHERLOCK_OSC_INSPECTOR_KX_URI, + .instantiate = sandbox_ui_instantiate, + .cleanup = sandbox_ui_cleanup, + .port_event = sandbox_ui_port_event, + .extension_data = NULL +}; + +#ifdef _WIN32 +__declspec(dllexport) +#else +__attribute__((visibility("default"))) +#endif +const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + switch(index) + { + case 0: + return &atom_inspector_ui; + case 1: + return &atom_inspector_kx; + + case 2: + return &midi_inspector_ui; + case 3: + return &midi_inspector_kx; + + case 4: + return &osc_inspector_ui; + case 5: + return &osc_inspector_kx; + + default: + return NULL; + } +} diff --git a/sherlock_ui.ttl b/sherlock_ui.ttl new file mode 100644 index 0000000..2885ab6 --- /dev/null +++ b/sherlock_ui.ttl @@ -0,0 +1,135 @@ +# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the Artistic License 2.0 as published by +# The Perl Foundation. +# +# This source 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 +# Artistic License 2.0 for more details. +# +# You should have received a copy of the Artistic License 2.0 +# along the source as a COPYING file. If not, obtain it from +# http://www.perlfoundation.org/artistic_license_2_0. + +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix midi: <http://lv2plug.in/ns/ext/midi#> . +@prefix time: <http://lv2plug.in/ns/ext/time#> . +@prefix urid: <http://lv2plug.in/ns/ext/urid#> . +@prefix patch: <http://lv2plug.in/ns/ext/patch#> . +@prefix kx: <http://kxstudio.sf.net/ns/lv2ext/external-ui#> . + +@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> . +@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> . + +# Atom Inspector UI +sherlock:atom_inspector_1_ui + a ui:UI ; + ui:portNotification [ + ui:plugin sherlock:atom_inspector ; + lv2:symbol "notify" ; + ui:notifyType atom:Object ; + ui:notifyType osc:Event ; + ui:notifyType midi:MidiEvent ; + ui:notifyType time:Position ; + ui:notifyType patch:Message ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap ; + lv2:extensionData ui:idleInterface, ui:showInterface . + +sherlock:atom_inspector_2_kx + a kx:Widget ; + ui:portNotification [ + ui:plugin sherlock:atom_inspector ; + lv2:symbol "notify" ; + ui:notifyType atom:Object ; + ui:notifyType osc:Event ; + ui:notifyType midi:MidiEvent ; + ui:notifyType time:Position ; + ui:notifyType patch:Message ; + ui:protocol atom:eventTransfer ; + ] ; + lv2:requiredFeature kx:Host, urid:map, urid:unmap . + +sherlock:atom_inspector_3_eo + a ui:EoUI ; + ui:portNotification [ + ui:plugin sherlock:atom_inspector ; + lv2:symbol "notify" ; + ui:notifyType atom:Object ; + ui:notifyType osc:Event ; + ui:notifyType midi:MidiEvent ; + ui:notifyType time:Position ; + ui:notifyType patch:Message ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature urid:map, urid:unmap . + +# MIDI Inspector UI +sherlock:midi_inspector_1_ui + a ui:UI ; + ui:portNotification [ + ui:plugin sherlock:midi_inspector ; + lv2:symbol "notify" ; + ui:notifyType midi:MidiEvent ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature ui:idleInterface, urid:map ; + lv2:extensionData ui:idleInterface, ui:showInterface . + +sherlock:midi_inspector_2_kx + a kx:Widget ; + ui:portNotification [ + ui:plugin sherlock:midi_inspector ; + lv2:symbol "notify" ; + ui:notifyType midi:MidiEvent ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature kx:Host, urid:map . + +sherlock:midi_inspector_3_eo + a ui:EoUI ; + ui:portNotification [ + ui:plugin sherlock:midi_inspector ; + lv2:symbol "notify" ; + ui:notifyType midi:MidiEvent ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature urid:map . + +# OSC Inspector UI +sherlock:osc_inspector_1_ui + a ui:UI ; + ui:portNotification [ + ui:plugin sherlock:osc_inspector ; + lv2:symbol "notify" ; + ui:notifyType osc:Event ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature ui:idleInterface, urid:map ; + lv2:extensionData ui:idleInterface, ui:showInterface . + +sherlock:osc_inspector_2_kx + a kx:Widget ; + ui:portNotification [ + ui:plugin sherlock:osc_inspector ; + lv2:symbol "notify" ; + ui:notifyType osc:Event ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature kx:Host, urid:map . + +sherlock:osc_inspector_3_eo + a ui:EoUI ; + ui:portNotification [ + ui:plugin sherlock:osc_inspector ; + lv2:symbol "notify" ; + ui:notifyType osc:Event ; + ui:protocol atom:eventTransfer + ] ; + lv2:requiredFeature urid:map . diff --git a/symap/symap.c b/symap/symap.c new file mode 100644 index 0000000..40c8980 --- /dev/null +++ b/symap/symap.c @@ -0,0 +1,231 @@ +/* + Copyright 2011-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "symap.h" + +/** + @file symap.c Implementation of Symap, a basic symbol map (string interner). + + This implementation is primitive, but has some desirable qualities: good + (O(lg(n)) lookup performance for already-mapped symbols, minimal space + overhead, extremely fast (O(1)) reverse mapping (ID to string), simple code, + no dependencies. + + The tradeoff is that mapping new symbols may be quite slow. In other words, + this implementation is ideal for use cases with a relatively limited set of + symbols, or where most symbols are mapped early. It will not fare so well + with very dynamic sets of symbols. For that, you're better off with a + tree-based implementation (and the associated space cost, especially if you + need reverse mapping). +*/ + +struct SymapImpl { + /** + Unsorted array of strings, such that the symbol for ID i is found + at symbols[i - 1]. + */ + char** symbols; + + /** + Array of IDs, sorted by corresponding string in `symbols`. + */ + uint32_t* index; + + /** + Number of symbols (number of items in `symbols` and `index`). + */ + uint32_t size; +}; + +Symap* +symap_new(void) +{ + Symap* map = (Symap*)malloc(sizeof(Symap)); + map->symbols = NULL; + map->index = NULL; + map->size = 0; + return map; +} + +void +symap_free(Symap* map) +{ + for (uint32_t i = 0; i < map->size; ++i) { + free(map->symbols[i]); + } + + free(map->symbols); + free(map->index); + free(map); +} + +static char* +symap_strdup(const char* str) +{ + const size_t len = strlen(str); + char* copy = (char*)malloc(len + 1); + memcpy(copy, str, len + 1); + return copy; +} + +/** + Return the index into map->index (not the ID) corresponding to `sym`, + or the index where a new entry for `sym` should be inserted. +*/ +static uint32_t +symap_search(const Symap* map, const char* sym, bool* exact) +{ + *exact = false; + if (map->size == 0) { + return 0; // Empty map, insert at 0 + } else if (strcmp(map->symbols[map->index[map->size - 1] - 1], sym) < 0) { + return map->size; // Greater than last element, append + } + + uint32_t lower = 0; + uint32_t upper = map->size - 1; + uint32_t i = upper; + int cmp; + + while (upper >= lower) { + i = lower + ((upper - lower) / 2); + cmp = strcmp(map->symbols[map->index[i] - 1], sym); + + if (cmp == 0) { + *exact = true; + return i; + } else if (cmp > 0) { + if (i == 0) { + break; // Avoid underflow + } + upper = i - 1; + } else { + lower = ++i; + } + } + + assert(!*exact || strcmp(map->symbols[map->index[i] - 1], sym) > 0); + return i; +} + +uint32_t +symap_try_map(Symap* map, const char* sym) +{ + bool exact; + const uint32_t index = symap_search(map, sym, &exact); + if (exact) { + assert(!strcmp(map->symbols[map->index[index]], sym)); + return map->index[index]; + } + + return 0; +} + +uint32_t +symap_map(Symap* map, const char* sym) +{ + bool exact; + const uint32_t index = symap_search(map, sym, &exact); + if (exact) { + assert(!strcmp(map->symbols[map->index[index] - 1], sym)); + return map->index[index]; + } + + const uint32_t id = ++map->size; + char* const str = symap_strdup(sym); + + /* Append new symbol to symbols array */ + map->symbols = (char**)realloc(map->symbols, map->size * sizeof(str)); + map->symbols[id - 1] = str; + + /* Insert new index element into sorted index */ + map->index = (uint32_t*)realloc(map->index, map->size * sizeof(uint32_t)); + if (index < map->size - 1) { + memmove(map->index + index + 1, + map->index + index, + (map->size - index - 1) * sizeof(uint32_t)); + } + + map->index[index] = id; + + return id; +} + +const char* +symap_unmap(Symap* map, uint32_t id) +{ + if (id == 0) { + return NULL; + } else if (id <= map->size) { + return map->symbols[id - 1]; + } + return NULL; +} + +#ifdef STANDALONE + +#include <stdio.h> + +static void +symap_dump(Symap* map) +{ + fprintf(stderr, "{\n"); + for (uint32_t i = 0; i < map->size; ++i) { + fprintf(stderr, "\t%u = %s\n", + map->index[i], map->symbols[map->index[i] - 1]); + } + fprintf(stderr, "}\n"); +} + +int +main() +{ + #define N_SYMS 5 + char* syms[N_SYMS] = { + "hello", "bonjour", "goodbye", "aloha", "salut" + }; + + Symap* map = symap_new(); + for (int i = 0; i < N_SYMS; ++i) { + if (symap_try_map(map, syms[i])) { + fprintf(stderr, "error: Symbol already mapped\n"); + return 1; + } + + const uint32_t id = symap_map(map, syms[i]); + if (strcmp(map->symbols[id - 1], syms[i])) { + fprintf(stderr, "error: Corrupt symbol table\n"); + return 1; + } + + if (symap_map(map, syms[i]) != id) { + fprintf(stderr, "error: Remapped symbol to a different ID\n"); + return 1; + } + + symap_dump(map); + } + + symap_free(map); + return 0; +} + +#endif /* STANDALONE */ diff --git a/symap/symap.h b/symap/symap.h new file mode 100644 index 0000000..79de8ff --- /dev/null +++ b/symap/symap.h @@ -0,0 +1,69 @@ +/* + Copyright 2011-2012 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file symap.h API for Symap, a basic symbol map (string interner). + + Particularly useful for implementing LV2 URI mapping. + + @see <a href="http://lv2plug.in/ns/ext/urid">LV2 URID</a> + @see <a href="http://lv2plug.in/ns/ext/uri-map">LV2 URI Map</a> +*/ + +#ifndef SYMAP_H +#define SYMAP_H + +#include <stdint.h> + +struct SymapImpl; + +typedef struct SymapImpl Symap; + +/** + Create a new symbol map. +*/ +Symap* +symap_new(void); + +/** + Free a symbol map. +*/ +void +symap_free(Symap* map); + +/** + Map a string to a symbol ID if it is already mapped, otherwise return 0. +*/ +uint32_t +symap_try_map(Symap* map, const char* sym); + +/** + Map a string to a symbol ID. + + Note that 0 is never a valid symbol ID. +*/ +uint32_t +symap_map(Symap* map, const char* sym); + +/** + Unmap a symbol ID back to a symbol, or NULL if no such ID exists. + + Note that 0 is never a valid symbol ID. +*/ +const char* +symap_unmap(Symap* map, uint32_t id); + +#endif /* SYMAP_H */ |