aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml36
-rw-r--r--CMakeLists.txt90
-rw-r--r--README.md54
-rw-r--r--atom_inspector.c172
-rw-r--r--atom_inspector_eo.c768
-rw-r--r--common.h36
-rw-r--r--encoder.l298
-rw-r--r--manifest.ttl.in104
-rw-r--r--midi_inspector.c190
-rw-r--r--midi_inspector_eo.c867
-rw-r--r--omk_logo_256x256.pngbin0 -> 6916 bytes
-rw-r--r--osc.lv2/CMakeLists.txt16
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md3
-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.c193
-rw-r--r--osc_inspector_eo.c978
-rw-r--r--sandbox_ui.lv2/COPYING201
-rw-r--r--sandbox_ui.lv2/README.md18
-rw-r--r--sandbox_ui.lv2/lv2_external_ui.h109
-rw-r--r--sandbox_ui.lv2/sandbox_efl.c199
-rw-r--r--sandbox_ui.lv2/sandbox_io.h555
-rw-r--r--sandbox_ui.lv2/sandbox_master.c100
-rw-r--r--sandbox_ui.lv2/sandbox_master.h65
-rw-r--r--sandbox_ui.lv2/sandbox_slave.c561
-rw-r--r--sandbox_ui.lv2/sandbox_slave.h73
-rw-r--r--sandbox_ui.lv2/sandbox_ui.c395
-rw-r--r--sandbox_ui.lv2/sandbox_ui.h47
-rw-r--r--sherlock.c34
-rw-r--r--sherlock.h75
-rw-r--r--sherlock.ttl187
-rw-r--r--sherlock_eo.c35
-rw-r--r--sherlock_ui.c97
-rw-r--r--sherlock_ui.ttl135
-rw-r--r--symap/symap.c231
-rw-r--r--symap/symap.h69
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})
diff --git a/README.md b/README.md
index b48021f..0d60fa1 100644
--- a/README.md
+++ b/README.md
@@ -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
+
+[![Build Status](https://travis-ci.org/OpenMusicKontrollers/sherlock.lv2.svg)](https://travis-ci.org/OpenMusicKontrollers/sherlock.lv2)
+
+### Plugins
+
+#### Atom Inspector
+
+##### Screenshot
+
+![Screeny](http://open-music-kontrollers.ch/lv2/sherlock/sherlock_atom_inspector.png "")
+
+### 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("&lt;");
+ break;
+ case TK_URI_OUT:
+ _add_plain("&gt;");
+ _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("&lt;");
+ break;
+ case TK_GT:
+ _add_plain("&gt;");
+ break;
+ case TK_AMP:
+ _add_plain("&amp;");
+ 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
new file mode 100644
index 0000000..5f3ff48
--- /dev/null
+++ b/omk_logo_256x256.png
Binary files differ
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, "&lt;", 4);
+ ptr += 4;
+ break;
+ case '>':
+ strncpy(ptr, "&gt;", 4);
+ ptr += 4;
+ break;
+ case '&':
+ strncpy(ptr, "&amp;", 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, &timestamp, &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, &timestamp, &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 */