aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml36
-rw-r--r--CMakeLists.txt86
-rw-r--r--README.md37
-rw-r--r--atom_inspector.c154
-rw-r--r--atom_inspector_eo.c1325
-rw-r--r--manifest.ttl.in104
-rw-r--r--midi_inspector.c170
-rw-r--r--midi_inspector_eo.c866
-rw-r--r--omk_logo_256x256.pngbin0 -> 6916 bytes
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md3
-rw-r--r--osc.lv2/lv2-osc.doap.ttl40
-rw-r--r--osc.lv2/lv2_osc.h704
-rw-r--r--osc.lv2/manifest.ttl23
-rw-r--r--osc.lv2/osc.ttl101
-rw-r--r--osc_inspector.c172
-rw-r--r--osc_inspector_eo.c947
-rw-r--r--sandbox_ui.lv2/COPYING201
-rw-r--r--sandbox_ui.lv2/README.md18
-rw-r--r--sandbox_ui.lv2/lv2_external_ui.h (renamed from lv2_external_ui.h)0
-rw-r--r--sandbox_ui.lv2/sandbox_efl.c (renamed from sandbox_efl.c)0
-rw-r--r--sandbox_ui.lv2/sandbox_io.h (renamed from sandbox_io.h)0
-rw-r--r--sandbox_ui.lv2/sandbox_master.c (renamed from sandbox_master.c)0
-rw-r--r--sandbox_ui.lv2/sandbox_master.h (renamed from sandbox_master.h)0
-rw-r--r--sandbox_ui.lv2/sandbox_slave.c (renamed from sandbox_slave.c)0
-rw-r--r--sandbox_ui.lv2/sandbox_slave.h (renamed from sandbox_slave.h)0
-rw-r--r--sandbox_ui.lv2/sandbox_ui.c (renamed from sandbox_ui.c)0
-rw-r--r--sandbox_ui.lv2/sandbox_ui.h (renamed from sandbox_ui.h)0
-rw-r--r--sherlock.c34
-rw-r--r--sherlock.h72
-rw-r--r--sherlock.ttl211
-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
36 files changed, 6071 insertions, 1 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
new file mode 100644
index 0000000..4c77c76
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,86 @@
+cmake_minimum_required(VERSION 2.8)
+
+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 -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}")
+
+set(SHERLOCK_MAJOR_VERSION 0)
+set(SHERLOCK_MINOR_VERSION 1)
+set(SHERLOCK_MICRO_VERSION 1)
+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
+
+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})
+
+add_library(sherlock_eo MODULE
+ sherlock_eo.c
+ atom_inspector_eo.c
+ midi_inspector_eo.c
+ osc_inspector_eo.c)
+target_link_libraries(sherlock_eo
+ ${ELM_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 d9f1d9f..0d60fa1 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,39 @@
-# LV2 sandboxed UI
+# Sherlock
+
+## 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
diff --git a/atom_inspector.c b/atom_inspector.c
new file mode 100644
index 0000000..0bfedba
--- /dev/null
+++ b/atom_inspector.c
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+ uint64_t offset;
+};
+
+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;
+ }
+
+ 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;
+
+ // 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_forge_pop(forge, &frame);
+ else
+ 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->offset);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, handle->control_in, size);
+ if(ref)
+ lv2_atom_forge_pad(forge, 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->offset += 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..a19de3c
--- /dev/null
+++ b/atom_inspector_eo.c
@@ -0,0 +1,1325 @@
+/*
+ * 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 <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)
+
+// Disable deprecation warnings for Blank and Resource
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+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 midi_MidiEvent;
+
+ LV2_URID event_transfer;
+
+ LV2_URID time_position;
+ LV2_URID time_barBeat;
+ LV2_URID time_bar;
+ LV2_URID time_beat;
+ LV2_URID time_beatUnit;
+ LV2_URID time_beatsPerBar;
+ LV2_URID time_beatsPerMinute;
+ LV2_URID time_frame;
+ LV2_URID time_framesPerSecond;
+ LV2_URID time_speed;
+ } 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_sherlock;
+ Elm_Genlist_Item_Class *itc_list;
+ Elm_Genlist_Item_Class *itc_group;
+ Elm_Genlist_Item_Class *itc_seq;
+ Elm_Genlist_Item_Class *itc_prop;
+ Elm_Genlist_Item_Class *itc_vec;
+ Elm_Genlist_Item_Class *itc_atom;
+
+ char string_buf [STRING_BUF_SIZE];
+ char *logo_path;
+
+ Eina_Hash *urids;
+};
+
+static inline int
+_is_expandable(UI *ui, const uint32_t type)
+{
+ return lv2_atom_forge_is_object_type(&ui->forge, type)
+ || (type == ui->forge.Tuple)
+ || (type == ui->forge.Vector)
+ || (type == ui->forge.Sequence);
+}
+
+#define CODE_PRE "<style=shadow,bottom>"
+#define CODE_POST "</style>"
+
+#define HIL_PRE(VAL) ("<color=#bbb font=Mono><b>"VAL"</b></color> <color=#b00 font=Mono>")
+#define HIL_POST ("</color>")
+
+#define URI(VAL,TYP) ("<color=#bbb font=Mono><b>"VAL"</b></color> <color=#fff font=Default>"TYP"</color>")
+#define HIL(VAL,TYP) ("<color=#bbb font=Mono><b>"VAL"</b></color> <color=#b00 font=Mono>"TYP"</color>")
+
+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 *
+_atom_stringify(UI *ui, char *ptr, char *end, int newline, const LV2_Atom *atom)
+{
+ //FIXME check for buffer overflows!!!
+ sprintf(ptr, CODE_PRE);
+ ptr += strlen(ptr);
+
+ const char *type = _hash_get(ui, atom->type);
+ sprintf(ptr, URI("type ", "%s (%"PRIu32")"), type, atom->type);
+ ptr += strlen(ptr);
+
+ if(lv2_atom_forge_is_object_type(&ui->forge, atom->type))
+ {
+ const LV2_Atom_Object *atom_object = (const LV2_Atom_Object *)atom;
+ const char *id = atom_object->body.id
+ ? _hash_get(ui, atom_object->body.id)
+ : NULL;
+ const char *otype = _hash_get(ui, atom_object->body.otype);
+
+ if(id)
+ {
+ sprintf(ptr, URI("</br>id ", "%s (%"PRIu32")"), id, atom_object->body.id);
+ ptr += strlen(ptr);
+
+ if(newline)
+ sprintf(ptr, "</br>");
+ else
+ sprintf(ptr, "</tab>");
+ }
+ else // !id
+ {
+ sprintf(ptr, "</br>");
+ ptr += strlen(ptr);
+
+ if(newline)
+ sprintf(ptr, "</br>");
+ }
+ ptr += strlen(ptr);
+
+ sprintf(ptr, URI("otype ", "%s (%"PRIu32")"), otype, atom_object->body.otype);
+ }
+ else if(atom->type == ui->forge.Tuple)
+ {
+ const LV2_Atom_Tuple *atom_tuple = (const LV2_Atom_Tuple *)atom;
+
+ // do nothing
+ sprintf(ptr, "</br>");
+ }
+ else if(atom->type == ui->forge.Vector)
+ {
+ const LV2_Atom_Vector *atom_vector = (const LV2_Atom_Vector *)atom;
+ const char *ctype = _hash_get(ui, atom_vector->body.child_type);
+
+ sprintf(ptr, URI("</br>ctype ", "%s (%"PRIu32")"), ctype, atom_vector->body.child_type);
+ ptr += strlen(ptr);
+
+ if(newline)
+ sprintf(ptr, "</br>");
+ else
+ sprintf(ptr, "</tab>");
+ ptr += strlen(ptr);
+
+ sprintf(ptr, URI("csize ", "%"PRIu32), atom_vector->body.child_size);
+ }
+ else if(atom->type == ui->forge.Sequence)
+ {
+ const LV2_Atom_Sequence *atom_seq = (const LV2_Atom_Sequence *)atom;
+
+ // do nothing
+ sprintf(ptr, "</br>");
+ }
+ else if(atom->type == ui->forge.Int)
+ {
+ const LV2_Atom_Int *atom_int = (const LV2_Atom_Int *)atom;
+
+ sprintf(ptr, HIL("</br>value ", "%"PRIi32), atom_int->body);
+ }
+ else if(atom->type == ui->forge.Long)
+ {
+ const LV2_Atom_Long *atom_long = (const LV2_Atom_Long *)atom;
+
+ sprintf(ptr, HIL("</br>value ", "%"PRIi64), atom_long->body);
+ }
+ else if(atom->type == ui->forge.Float)
+ {
+ const LV2_Atom_Float *atom_float = (const LV2_Atom_Float *)atom;
+
+ sprintf(ptr, HIL("</br>value ", "%f"), atom_float->body);
+ }
+ else if(atom->type == ui->forge.Double)
+ {
+ const LV2_Atom_Double *atom_double = (const LV2_Atom_Double *)atom;
+
+ sprintf(ptr, HIL("</br>value ", "%lf"), atom_double->body);
+ }
+ else if(atom->type == ui->forge.Bool)
+ {
+ const LV2_Atom_Int *atom_int = (const LV2_Atom_Int *)atom;
+
+ sprintf(ptr, HIL("</br>value ", "%s"), atom_int->body ? "true" : "false");
+ }
+ else if(atom->type == ui->forge.URID)
+ {
+ const LV2_Atom_URID *atom_urid = (const LV2_Atom_URID *)atom;
+ const char *uri = _hash_get(ui, atom_urid->body);
+
+ sprintf(ptr, HIL("</br>value ", "%"PRIu32" (%s)"), atom_urid->body, uri);
+ }
+ else if(atom->type == ui->forge.String)
+ {
+ char *str = LV2_ATOM_CONTENTS(LV2_Atom_String, atom);
+ if(atom->size == 0)
+ str = "";
+
+ // truncate
+ char tmp[4] = {'\0'};
+ if(atom->size > STRING_MAX)
+ {
+ memcpy(tmp, &str[STRING_OFF], 4);
+ strcpy(&str[STRING_OFF], "...");
+ }
+
+ sprintf(ptr, HIL("</br>value ", "%s"), str);
+
+ // restore
+ if(atom->size > STRING_MAX)
+ memcpy(&str[STRING_OFF], tmp, 4);
+ }
+ else if(atom->type == ui->forge.Path)
+ {
+ char *str = LV2_ATOM_CONTENTS(LV2_Atom_String, atom);
+ if(atom->size == 0)
+ str = "";
+
+ // truncate
+ char tmp[4] = {'\0'};
+ if(atom->size > STRING_MAX)
+ {
+ memcpy(tmp, &str[STRING_OFF], 4);
+ strcpy(&str[STRING_OFF], "...");
+ }
+
+ sprintf(ptr, HIL("</br>value ", "%s"), str);
+
+ // restore
+ if(atom->size > STRING_MAX)
+ memcpy(&str[STRING_OFF], tmp, 4);
+ }
+ else if(atom->type == ui->forge.Literal)
+ {
+ const LV2_Atom_Literal *atom_lit = (const LV2_Atom_Literal *)atom;
+
+ char *str = LV2_ATOM_CONTENTS(LV2_Atom_Literal, atom);
+ if(atom->size == sizeof(LV2_Atom_Literal_Body))
+ str = "";
+ const char *datatype = _hash_get(ui, atom_lit->body.datatype);
+ const char *lang = _hash_get(ui, atom_lit->body.lang);
+
+ // truncate
+ char tmp[4] = {'\0'};
+ if(atom->size > STRING_MAX)
+ {
+ memcpy(tmp, &str[STRING_OFF], 4);
+ strcpy(&str[STRING_OFF], "...");
+ }
+
+ sprintf(ptr, HIL("</br>value ", "%s"), str);
+ ptr += strlen(ptr);
+
+ // restore
+ if(atom->size > STRING_MAX)
+ memcpy(&str[STRING_OFF], tmp, 4);
+
+ if(newline)
+ sprintf(ptr, "</br>");
+ else
+ sprintf(ptr, "</tab>");
+ ptr += strlen(ptr);
+
+ sprintf(ptr, URI("datatype", "%s (%"PRIu32")"), datatype, atom_lit->body.datatype);
+ ptr += strlen(ptr);
+
+ sprintf(ptr, URI("</tab>lang ", "%s (%"PRIu32")"), lang, atom_lit->body.lang);
+ }
+ else if(atom->type == ui->forge.URI)
+ {
+ char *str = LV2_ATOM_CONTENTS(LV2_Atom_String, atom);
+ if(atom->size == 0)
+ str = "";
+ LV2_URID urid = ui->map->map(ui->map->handle, str); //TODO add hashing
+
+ // truncate
+ char tmp[4] = {'\0'};
+ if(atom->size > STRING_MAX)
+ {
+ memcpy(tmp, &str[STRING_OFF], 4);
+ strcpy(&str[STRING_OFF], "...");
+ }
+
+ sprintf(ptr, HIL("</br>value ", "%s (%"PRIu32")"), str, urid);
+
+ // restore
+ if(atom->size > STRING_MAX)
+ memcpy(&str[STRING_OFF], tmp, 4);
+ }
+ else if(atom->type == ui->uris.midi_MidiEvent)
+ {
+ const uint8_t *midi = LV2_ATOM_BODY_CONST(atom);
+
+ sprintf(ptr, HIL_PRE("</br>value "));
+ ptr += strlen(ptr);
+
+ char *barrier = ptr + STRING_OFF;
+ for(unsigned i=0; (i<atom->size) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, "%02X ", midi[i]);
+
+ if(ptr >= barrier) // there would be more to print
+ {
+ ptr = barrier;
+ sprintf(ptr, "...");
+ ptr += 4;
+ }
+
+ sprintf(ptr, HIL_POST);
+ }
+ else if(atom->type == ui->forge.Chunk)
+ {
+ const uint8_t *chunk = LV2_ATOM_BODY_CONST(atom);
+
+ sprintf(ptr, HIL_PRE("</br>value "));
+ ptr += strlen(ptr);
+
+ char *barrier = ptr + STRING_OFF;
+ for(unsigned i=0; (i<atom->size) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, "%02"PRIX8" ", chunk[i]);
+
+ if(ptr >= barrier) // there would be more to print
+ {
+ ptr = barrier;
+ sprintf(ptr, "...");
+ ptr += 4;
+ }
+
+ sprintf(ptr, HIL_POST);
+ }
+
+ if( newline
+ && !lv2_atom_forge_is_object_type(&ui->forge, atom->type)
+ && !(atom->type == ui->forge.Literal)
+ && !(atom->type == ui->forge.Vector) )
+ {
+ ptr += strlen(ptr);
+
+ sprintf(ptr, "</br>");
+ }
+ ptr += strlen(ptr);
+
+ sprintf(ptr, CODE_POST);
+
+ return ptr + strlen(ptr);
+}
+
+static char *
+_atom_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, 1, atom);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+static char *
+_prop_item_label_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Property_Body *prop = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.text"))
+ {
+ char *buf = ui->string_buf;
+ char *ptr = buf;
+ char *end = buf + STRING_BUF_SIZE;
+
+ const char *key = _hash_get(ui, prop->key);
+ const char *context = _hash_get(ui, prop->context);
+
+ sprintf(ptr, URI("key ", "%s (%"PRIu32")"), key, prop->key);
+ ptr += strlen(ptr);
+
+ if(context)
+ {
+ sprintf(ptr, URI("</tab>context ", "%s (%"PRIu32")"), context, prop->context);
+ ptr += strlen(ptr);
+ }
+
+ sprintf(ptr, "</br>");
+ ptr += strlen(ptr);
+
+ ptr = _atom_stringify(ui, ptr, end, 0, &prop->value);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+static char *
+_seq_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;
+
+ ptr = _atom_stringify(ui, ptr, end, 1, atom);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+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 *
+_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 Evas_Object *
+_atom_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>%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 *
+_prop_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Property_Body *prop = data;
+ const LV2_Atom *atom = &prop->value;
+
+ if(!ui)
+ return NULL;
+
+ 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 *
+_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 void
+_del(void *data, Evas_Object *obj)
+{
+ free(data);
+}
+
+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
+_atom_expand(UI *ui, const LV2_Atom *atom, Evas_Object *obj, Elm_Object_Item *itm)
+{
+ if(lv2_atom_forge_is_object_type(&ui->forge, atom->type))
+ {
+ const LV2_Atom_Object *atom_object = (const LV2_Atom_Object *)atom;
+
+ LV2_ATOM_OBJECT_FOREACH(atom_object, prop)
+ {
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->info, ui->itc_prop,
+ prop, 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_TRUE);
+ }
+ }
+ else if(atom->type == ui->forge.Tuple)
+ {
+ const LV2_Atom_Tuple *atom_tuple = (const LV2_Atom_Tuple *)atom;
+
+ for(LV2_Atom *iter = lv2_atom_tuple_begin(atom_tuple);
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(atom_tuple), atom_tuple->atom.size, iter);
+ iter = lv2_atom_tuple_next(iter))
+ {
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->info, ui->itc_atom,
+ iter, 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_TRUE);
+ }
+ }
+ else if(atom->type == ui->forge.Vector)
+ {
+ const LV2_Atom_Vector *atom_vector = (const LV2_Atom_Vector *)atom;
+
+ int num = (atom_vector->atom.size - sizeof(LV2_Atom_Vector_Body))
+ / atom_vector->body.child_size;
+ const uint8_t *body = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Vector, atom_vector);
+ for(int i=0; i<num; i++)
+ {
+ LV2_Atom *child = malloc(sizeof(LV2_Atom) + atom_vector->body.child_size);
+ if(child)
+ {
+ child->size = atom_vector->body.child_size;
+ child->type = atom_vector->body.child_type;
+ memcpy(LV2_ATOM_BODY(child), body + i*child->size, child->size);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->info, ui->itc_vec,
+ child, 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_TRUE);
+ }
+ }
+ }
+ else if(atom->type == ui->forge.Sequence)
+ {
+ const LV2_Atom_Sequence *atom_seq = (const LV2_Atom_Sequence *)atom;
+
+ LV2_ATOM_SEQUENCE_FOREACH(atom_seq, ev)
+ {
+ const LV2_Atom *child = &ev->body;
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->info, ui->itc_seq,
+ 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_TRUE);
+ }
+ }
+}
+
+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;
+
+ if( (class == ui->itc_sherlock) || (class == ui->itc_seq) )
+ {
+ const LV2_Atom_Event *ev = udata;
+ const LV2_Atom *atom = &ev->body;
+ _atom_expand(ui, atom, obj, itm);
+ }
+ else if(class == ui->itc_prop)
+ {
+ const LV2_Atom_Property_Body *prop = udata;
+ const LV2_Atom *atom = &prop->value;
+
+ _atom_expand(ui, atom, obj, itm);
+ }
+ else
+ {
+ const LV2_Atom *atom = udata;
+ _atom_expand(ui, atom, obj, itm);
+ }
+}
+
+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 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);
+ size_t len = sizeof(LV2_Atom_Event) + ev->body.size;
+ LV2_Atom_Event *ev2 = malloc(len);
+ if(!ev2)
+ return;
+
+ memcpy(ev2, ev, len);
+
+ elm_genlist_clear(ui->info);
+
+ const LV2_Atom *atom = &ev->body;
+ const bool is_expandable = _is_expandable(ui, atom->type);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->info, ui->itc_sherlock,
+ ev2, NULL, ELM_GENLIST_ITEM_TREE, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ if(is_expandable)
+ elm_genlist_item_expanded_set(itm2, EINA_TRUE);
+}
+
+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->info)
+ elm_genlist_clear(ui->info);
+ 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, 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_genlist_add(panes);
+ if(ui->info)
+ {
+ elm_genlist_homogeneous_set(ui->info, EINA_TRUE); // needef for lazy-loading
+ elm_genlist_mode_set(ui->info, ELM_LIST_SCROLL);
+ elm_genlist_block_count_set(ui->info, 64); // needef for lazy-loading
+ elm_genlist_reorder_mode_set(ui->info, EINA_FALSE);
+ elm_genlist_select_mode_set(ui->info, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ evas_object_data_set(ui->info, "ui", ui);
+ evas_object_smart_callback_add(ui->info, "expand,request",
+ _item_expand_request, ui);
+ evas_object_smart_callback_add(ui->info, "contract,request",
+ _item_contract_request, ui);
+ evas_object_smart_callback_add(ui->info, "expanded", _item_expanded, ui);
+ evas_object_smart_callback_add(ui->info, "contracted", _item_contracted, ui);
+ 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);
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+
+ ui->itc_sherlock = elm_genlist_item_class_new();
+ if(ui->itc_sherlock)
+ {
+ ui->itc_sherlock->item_style = "default_style";
+ ui->itc_sherlock->func.text_get = _seq_item_label_get;
+ ui->itc_sherlock->func.content_get = _seq_item_content_get;
+ ui->itc_sherlock->func.state_get = NULL;
+ ui->itc_sherlock->func.del = _del;
+ }
+
+ ui->itc_seq = elm_genlist_item_class_new();
+ if(ui->itc_seq)
+ {
+ ui->itc_seq->item_style = "default_style";
+ ui->itc_seq->func.text_get = _seq_item_label_get;
+ ui->itc_seq->func.content_get = _seq_item_content_get;
+ ui->itc_seq->func.state_get = NULL;
+ ui->itc_seq->func.del = NULL;
+ }
+
+ ui->itc_prop = elm_genlist_item_class_new();
+ if(ui->itc_prop)
+ {
+ ui->itc_prop->item_style = "default_style";
+ ui->itc_prop->func.text_get = _prop_item_label_get;
+ ui->itc_prop->func.content_get = _prop_item_content_get;
+ ui->itc_prop->func.state_get = NULL;
+ ui->itc_prop->func.del = NULL;
+ }
+
+ ui->itc_vec = elm_genlist_item_class_new();
+ if(ui->itc_vec)
+ {
+ ui->itc_vec->item_style = "default_style";
+ ui->itc_vec->func.text_get = _atom_item_label_get;
+ ui->itc_vec->func.content_get = _atom_item_content_get;
+ ui->itc_vec->func.state_get = NULL;
+ ui->itc_vec->func.del = _del;
+ }
+
+ ui->itc_atom = elm_genlist_item_class_new();
+ if(ui->itc_atom)
+ {
+ ui->itc_atom->item_style = "default_style";
+ ui->itc_atom->func.text_get = _atom_item_label_get;
+ ui->itc_atom->func.content_get = _atom_item_content_get;
+ ui->itc_atom->func.state_get = NULL;
+ ui->itc_atom->func.del = NULL;
+ }
+
+ sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path);
+ ui->logo_path = strdup(ui->string_buf);
+
+ ui->uris.midi_MidiEvent = ui->map->map(ui->map->handle, LV2_MIDI__MidiEvent);
+
+ ui->uris.event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer);
+
+ ui->uris.time_position = ui->map->map(ui->map->handle, LV2_TIME__Position);
+ ui->uris.time_barBeat = ui->map->map(ui->map->handle, LV2_TIME__barBeat);
+ ui->uris.time_bar = ui->map->map(ui->map->handle, LV2_TIME__bar);
+ ui->uris.time_beat = ui->map->map(ui->map->handle, LV2_TIME__beat);
+ ui->uris.time_beatUnit = ui->map->map(ui->map->handle, LV2_TIME__beatUnit);
+ ui->uris.time_beatsPerBar = ui->map->map(ui->map->handle, LV2_TIME__beatsPerBar);
+ ui->uris.time_beatsPerMinute = ui->map->map(ui->map->handle, LV2_TIME__beatsPerMinute);
+ ui->uris.time_frame = ui->map->map(ui->map->handle, LV2_TIME__frame);
+ ui->uris.time_framesPerSecond = ui->map->map(ui->map->handle, LV2_TIME__framesPerSecond);
+ ui->uris.time_speed = ui->map->map(ui->map->handle, LV2_TIME__speed);
+
+ ui->urids = eina_hash_int32_new(_hash_del);
+
+ // prepopulate hash table
+ _hash_set(ui, ui->forge.Blank);
+ _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.Resource);
+ _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);
+
+ _hash_set(ui, ui->uris.midi_MidiEvent);
+
+ _hash_set(ui, ui->uris.time_position);
+ _hash_set(ui, ui->uris.time_barBeat);
+ _hash_set(ui, ui->uris.time_bar);
+ _hash_set(ui, ui->uris.time_beat);
+ _hash_set(ui, ui->uris.time_beatUnit);
+ _hash_set(ui, ui->uris.time_beatsPerBar);
+ _hash_set(ui, ui->uris.time_beatsPerMinute);
+ _hash_set(ui, ui->uris.time_frame);
+ _hash_set(ui, ui->uris.time_framesPerSecond);
+ _hash_set(ui, ui->uris.time_speed);
+
+ 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_atom)
+ elm_genlist_item_class_free(ui->itc_atom);
+ if(ui->itc_vec)
+ elm_genlist_item_class_free(ui->itc_vec);
+ if(ui->itc_prop)
+ elm_genlist_item_class_free(ui->itc_prop);
+ if(ui->itc_seq)
+ elm_genlist_item_class_free(ui->itc_seq);
+ if(ui->itc_sherlock)
+ elm_genlist_item_class_free(ui->itc_sherlock);
+ if(ui->itc_list)
+ elm_genlist_item_class_free(ui->itc_list);
+ if(ui->itc_group)
+ elm_genlist_item_class_free(ui->itc_group);
+
+ 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)
+ {
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ 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);
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ break;
+ }
+
+ 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/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..48d4794
--- /dev/null
+++ b/midi_inspector.c
@@ -0,0 +1,170 @@
+/*
+ * 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 midi_event;
+ uint64_t offset;
+};
+
+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->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;
+
+ // 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->offset);
+ 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_raw(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ if(ref)
+ lv2_atom_forge_pad(forge, 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->offset += 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..0418192
--- /dev/null
+++ b/midi_inspector_eo.c
@@ -0,0 +1,866 @@
+/*
+ * 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_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ elm_genlist_homogeneous_set(ui->list, EINA_FALSE); // TRUE for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_SCROLL);
+ 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)
+ {
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ 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);
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ break;
+ }
+
+ 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);
+ 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 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/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/osc.lv2/lv2-osc.doap.ttl b/osc.lv2/lv2-osc.doap.ttl
new file mode 100644
index 0000000..15add6d
--- /dev/null
+++ b/osc.lv2/lv2-osc.doap.ttl
@@ -0,0 +1,40 @@
+# 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 dcs: <http://ontologi.es/doap-changeset#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a doap:Project ;
+ doap:license lic:Artistic-2.0 ;
+ doap:name "LV2 OSC" ;
+ doap:shortdesc "A definition of atomified OSC." ;
+ doap:maintainer omk:me ;
+ doap:created "2015-06-19" ;
+ doap:developer omk:me ;
+ doap:release [
+ doap:revision "1.0" ;
+ doap:created "2015-06-19" ;
+ dcs:blame omk:me ;
+ dcs:changeset [
+ dcs:item [
+ rdfs:label "Initial release."
+ ]
+ ]
+ ] .
diff --git a/osc.lv2/lv2_osc.h b/osc.lv2/lv2_osc.h
new file mode 100644
index 0000000..057f212
--- /dev/null
+++ b/osc.lv2/lv2_osc.h
@@ -0,0 +1,704 @@
+/*
+ * 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 _LV2_OSC_H_
+#define _LV2_OSC_H_
+
+#include <math.h> // INFINITY
+
+#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/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
+
+#define OSC_URI "http://open-music-kontrollers.ch/lv2/osc"
+#define OSC_PREFIX OSC_URI "#"
+
+#define OSC__Event OSC_PREFIX "Event" // event
+#define OSC__schedule OSC_PREFIX "schedule" // feature
+
+#define OSC__Bundle OSC_PREFIX "Bundle" // object otype
+#define OSC__Message OSC_PREFIX "Message" // object otype
+#define OSC__bundleTimestamp OSC_PREFIX "bundleTimestamp" // property key
+#define OSC__bundleItems OSC_PREFIX "bundleItems" // property key
+#define OSC__messagePath OSC_PREFIX "messagePath" // property key
+#define OSC__messageFormat OSC_PREFIX "messageFormat" // property key
+#define OSC__messageArguments OSC_PREFIX "messageArguments" // property key
+
+typedef void *osc_schedule_handle_t;
+typedef struct _osc_schedule_t osc_schedule_t;
+typedef struct _osc_forge_t osc_forge_t;
+
+typedef void (*osc_bundle_push_cb_t)(uint64_t timestamp, void *data);
+typedef void (*osc_bundle_pop_cb_t)(void *data);
+typedef void (*osc_message_cb_t)(const char *path, const char *fmt,
+ const LV2_Atom_Tuple *arguments, void *data);
+
+typedef double (*osc_schedule_osc2frames_t)(osc_schedule_handle_t handle,
+ uint64_t timestamp);
+typedef uint64_t (*osc_schedule_frames2osc_t)(osc_schedule_handle_t handle,
+ double frames);
+
+struct _osc_schedule_t {
+ osc_schedule_osc2frames_t osc2frames;
+ osc_schedule_frames2osc_t frames2osc;
+ osc_schedule_handle_t handle;
+};
+
+struct _osc_forge_t {
+ LV2_URID OSC_Event;
+
+ LV2_URID OSC_Bundle;
+ LV2_URID OSC_Message;
+
+ LV2_URID OSC_bundleTimestamp;
+ LV2_URID OSC_bundleItems;
+
+ LV2_URID OSC_messagePath;
+ LV2_URID OSC_messageFormat;
+ LV2_URID OSC_messageArguments;
+
+ LV2_URID MIDI_MidiEvent;
+
+ LV2_URID ATOM_Object;
+};
+
+static inline void
+osc_forge_init(osc_forge_t *oforge, LV2_URID_Map *map)
+{
+ oforge->OSC_Event = map->map(map->handle, OSC__Event);
+
+ oforge->OSC_Bundle = map->map(map->handle, OSC__Bundle);
+ oforge->OSC_Message = map->map(map->handle, OSC__Message);
+
+ oforge->OSC_bundleTimestamp = map->map(map->handle, OSC__bundleTimestamp);
+ oforge->OSC_bundleItems = map->map(map->handle, OSC__bundleItems);
+
+ oforge->OSC_messagePath = map->map(map->handle, OSC__messagePath);
+ oforge->OSC_messageFormat = map->map(map->handle, OSC__messageFormat);
+ oforge->OSC_messageArguments = map->map(map->handle, OSC__messageArguments);
+
+ oforge->MIDI_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ oforge->ATOM_Object = map->map(map->handle, LV2_ATOM__Object);
+}
+
+static inline int
+osc_atom_is_bundle(osc_forge_t *oforge, const LV2_Atom_Object *obj)
+{
+ return (obj->atom.type == oforge->ATOM_Object)
+ && (obj->body.otype == oforge->OSC_Bundle);
+}
+
+static inline void
+osc_atom_bundle_unpack(osc_forge_t *oforge, const LV2_Atom_Object *obj,
+ const LV2_Atom_Long **timestamp, const LV2_Atom_Tuple **items)
+{
+ *timestamp = NULL;
+ *items = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { oforge->OSC_bundleTimestamp, (const LV2_Atom **)timestamp },
+ { oforge->OSC_bundleItems, (const LV2_Atom **)items },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+
+ lv2_atom_object_query(obj, q);
+}
+
+static inline int
+osc_atom_is_message(osc_forge_t *oforge, const LV2_Atom_Object *obj)
+{
+ return (obj->atom.type == oforge->ATOM_Object)
+ && (obj->body.otype == oforge->OSC_Message);
+}
+
+static inline void
+osc_atom_message_unpack(osc_forge_t *oforge, const LV2_Atom_Object *obj,
+ const LV2_Atom_String **path, const LV2_Atom_String **format,
+ const LV2_Atom_Tuple **arguments)
+{
+ *path = NULL;
+ *format = NULL;
+ *arguments = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { oforge->OSC_messagePath, (const LV2_Atom **)path },
+ { oforge->OSC_messageFormat, (const LV2_Atom **)format },
+ { oforge->OSC_messageArguments, (const LV2_Atom **)arguments },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+
+ lv2_atom_object_query(obj, q);
+}
+
+static inline void osc_atom_event_unroll(osc_forge_t *oforge,
+ const LV2_Atom_Object *obj, osc_bundle_push_cb_t bundle_push_cb,
+ osc_bundle_pop_cb_t bundle_pop_cb, osc_message_cb_t message_cb, void *data);
+
+static inline void
+osc_atom_message_unroll(osc_forge_t *oforge, const LV2_Atom_Object *obj,
+ osc_message_cb_t message_cb, void *data)
+{
+ const LV2_Atom_String* path;
+ const LV2_Atom_String* fmt;
+ const LV2_Atom_Tuple* args;
+
+ osc_atom_message_unpack(oforge, obj, &path, &fmt, &args);
+
+ const char *path_str = path ? LV2_ATOM_BODY_CONST(path) : NULL;
+ const char *fmt_str = fmt ? LV2_ATOM_BODY_CONST(fmt) : NULL;
+
+ if(message_cb)
+ message_cb(path_str, fmt_str, args, data);
+}
+
+static inline void
+osc_atom_bundle_unroll(osc_forge_t *oforge, const LV2_Atom_Object *obj,
+ osc_bundle_push_cb_t bundle_push_cb, osc_bundle_pop_cb_t bundle_pop_cb,
+ osc_message_cb_t message_cb, void *data)
+{
+ const LV2_Atom_Long* timestamp;
+ const LV2_Atom_Tuple* items;
+
+ osc_atom_bundle_unpack(oforge, obj, &timestamp, &items);
+
+ uint64_t timestamp_body = timestamp ? (uint64_t)timestamp->body : 1ULL;
+
+ if(bundle_push_cb)
+ bundle_push_cb(timestamp_body, data);
+
+ // iterate over tuple body
+ if(items)
+ {
+ for(const LV2_Atom *itr = lv2_atom_tuple_begin(items);
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(items), items->atom.size, itr);
+ itr = lv2_atom_tuple_next(itr))
+ {
+ osc_atom_event_unroll(oforge, (const LV2_Atom_Object *)itr,
+ bundle_push_cb, bundle_pop_cb, message_cb, data);
+ }
+ }
+
+ if(bundle_pop_cb)
+ bundle_pop_cb(data);
+}
+
+static inline void
+osc_atom_event_unroll(osc_forge_t *oforge, const LV2_Atom_Object *obj,
+ osc_bundle_push_cb_t bundle_push_cb, osc_bundle_pop_cb_t bundle_pop_cb,
+ osc_message_cb_t message_cb, void *data)
+{
+ if(osc_atom_is_bundle(oforge, obj))
+ {
+ osc_atom_bundle_unroll(oforge, obj, bundle_push_cb, bundle_pop_cb,
+ message_cb, data);
+ }
+ else if(osc_atom_is_message(oforge, obj))
+ {
+ osc_atom_message_unroll(oforge, obj, message_cb, data);
+ }
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_bundle_push(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ LV2_Atom_Forge_Frame frame [2], uint64_t timestamp)
+{
+ if(!lv2_atom_forge_object(forge, &frame[0], 0, oforge->OSC_Bundle))
+ return 0;
+
+ if(!lv2_atom_forge_key(forge, oforge->OSC_bundleTimestamp))
+ return 0;
+ if(!lv2_atom_forge_long(forge, timestamp))
+ return 0;
+
+ if(!lv2_atom_forge_key(forge, oforge->OSC_bundleItems))
+ return 0;
+
+ return lv2_atom_forge_tuple(forge, &frame[1]);
+}
+
+static inline void
+osc_forge_bundle_pop(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(forge, &frame[1]);
+ lv2_atom_forge_pop(forge, &frame[0]);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_message_push(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ LV2_Atom_Forge_Frame frame [2], const char *path, const char *fmt)
+{
+ if(!lv2_atom_forge_object(forge, &frame[0], 0, oforge->OSC_Message))
+ return 0;
+
+ if(!lv2_atom_forge_key(forge, oforge->OSC_messagePath))
+ return 0;
+ if(!lv2_atom_forge_string(forge, path, strlen(path)))
+ return 0;
+
+ if(!lv2_atom_forge_key(forge, oforge->OSC_messageFormat))
+ return 0;
+ if(!lv2_atom_forge_string(forge, fmt, strlen(fmt)))
+ return 0;
+
+ if(!lv2_atom_forge_key(forge, oforge->OSC_messageArguments))
+ return 0;
+
+ return lv2_atom_forge_tuple(forge, &frame[1]);
+}
+
+static inline void
+osc_forge_message_pop(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(forge, &frame[1]);
+ lv2_atom_forge_pop(forge, &frame[0]);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_int32(osc_forge_t *oforge, LV2_Atom_Forge *forge, int32_t i)
+{
+ return lv2_atom_forge_int(forge, i);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_float(osc_forge_t *oforge, LV2_Atom_Forge *forge, float f)
+{
+ return lv2_atom_forge_float(forge, f);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_string(osc_forge_t *oforge, LV2_Atom_Forge *forge, const char *s)
+{
+ return lv2_atom_forge_string(forge, s, strlen(s));
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_symbol(osc_forge_t *oforge, LV2_Atom_Forge *forge, const char *s)
+{
+ return lv2_atom_forge_string(forge, s, strlen(s));
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_blob(osc_forge_t *oforge, LV2_Atom_Forge *forge, uint32_t size,
+ const uint8_t *b)
+{
+ LV2_Atom_Forge_Ref ref;
+ if(!(ref = lv2_atom_forge_atom(forge, size, forge->Chunk)))
+ return 0;
+ if(!(ref = lv2_atom_forge_raw(forge, b, size)))
+ return 0;
+ lv2_atom_forge_pad(forge, size);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_int64(osc_forge_t *oforge, LV2_Atom_Forge *forge, int64_t h)
+{
+ return lv2_atom_forge_long(forge, h);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_double(osc_forge_t *oforge, LV2_Atom_Forge *forge, double d)
+{
+ return lv2_atom_forge_double(forge, d);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_timestamp(osc_forge_t *oforge, LV2_Atom_Forge *forge, uint64_t t)
+{
+ return lv2_atom_forge_long(forge, t);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_char(osc_forge_t *oforge, LV2_Atom_Forge *forge, char c)
+{
+ return lv2_atom_forge_int(forge, c);
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_midi(osc_forge_t *oforge, LV2_Atom_Forge *forge, uint32_t size,
+ const uint8_t *m)
+{
+ LV2_Atom_Forge_Ref ref;
+ if(!(ref = lv2_atom_forge_atom(forge, size, oforge->MIDI_MidiEvent)))
+ return 0;
+ if(!(ref = lv2_atom_forge_raw(forge, m, size)))
+ return 0;
+ lv2_atom_forge_pad(forge, size);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_message_varlist(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const char *path, const char *fmt, va_list args)
+{
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ if(!(ref = osc_forge_message_push(oforge, forge, frame, path, fmt)))
+ return 0;
+
+ for(const char *type = fmt; *type; type++)
+ {
+ switch(*type)
+ {
+ case 'i':
+ {
+ if(!(ref =osc_forge_int32(oforge, forge, va_arg(args, int32_t))))
+ return 0;
+ break;
+ }
+ case 'f':
+ {
+ if(!(ref = osc_forge_float(oforge, forge, (float)va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case 's':
+ {
+ if(!(ref = osc_forge_string(oforge, forge, va_arg(args, const char *))))
+ return 0;
+ break;
+ }
+ case 'S':
+ {
+ if(!(ref = osc_forge_symbol(oforge, forge, va_arg(args, const char *))))
+ return 0;
+ break;
+ }
+ case 'b':
+ {
+ uint32_t size = va_arg(args, uint32_t);
+ const uint8_t *b = va_arg(args, const uint8_t *);
+ if(!(ref = osc_forge_blob(oforge, forge, size, b)))
+ return 0;
+ break;
+ }
+
+ case 'h':
+ {
+ if(!(ref = osc_forge_int64(oforge, forge, va_arg(args, int64_t))))
+ return 0;
+ break;
+ }
+ case 'd':
+ {
+ if(!(ref = osc_forge_double(oforge, forge, va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case 't':
+ {
+ if(!(ref = osc_forge_timestamp(oforge, forge, va_arg(args, uint64_t))))
+ return 0;
+ break;
+ }
+
+ case 'c':
+ {
+ if(!(ref = osc_forge_char(oforge, forge, (char)va_arg(args, unsigned int))))
+ return 0;
+ break;
+ }
+ case 'm':
+ {
+ int32_t size = va_arg(args, int32_t);
+ const uint8_t *m = va_arg(args, const uint8_t *);
+ if(!(ref = osc_forge_midi(oforge, forge, size, m)))
+ return 0;
+ break;
+ }
+
+ case 'T':
+ case 'F':
+ case 'N':
+ case 'I':
+ {
+ break;
+ }
+
+ default: // unknown argument type
+ {
+ return 0;
+ }
+ }
+ }
+
+ osc_forge_message_pop(oforge, forge, frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_message_vararg(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ LV2_Atom_Forge_Ref ref;
+ ref = osc_forge_message_varlist(oforge, forge, path, fmt, args);
+
+ va_end(args);
+
+ return ref;
+}
+
+static inline const LV2_Atom *
+osc_deforge_int32(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, int32_t *i)
+{
+ if(!atom || (atom->type != forge->Int) )
+ return NULL;
+
+ *i = ((const LV2_Atom_Int *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_float(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, float *f)
+{
+ if(!atom || (atom->type != forge->Float) )
+ return NULL;
+
+ *f = ((const LV2_Atom_Float *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_string(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, const char **s)
+{
+ if(!atom || (atom->type != forge->String) )
+ return NULL;
+
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_symbol(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, const char **s)
+{
+ if(!atom || (atom->type != forge->String) )
+ return NULL;
+
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_blob(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, uint32_t *size, const uint8_t **b)
+{
+ if(!atom || (atom->type != forge->Chunk) )
+ return NULL;
+
+ *size = atom->size;
+ *b = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_int64(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, int64_t *h)
+{
+ if(!atom || (atom->type != forge->Long) )
+ return NULL;
+
+ *h = ((const LV2_Atom_Long *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_double(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, double *d)
+{
+ if(!atom || (atom->type != forge->Double) )
+ return NULL;
+
+ *d = ((const LV2_Atom_Double *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_timestamp(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, uint64_t *t)
+{
+ if(!atom || (atom->type != forge->Long) )
+ return NULL;
+
+ *t = ((const LV2_Atom_Long *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_char(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, char *c)
+{
+ if(!atom || (atom->type != forge->Int) )
+ return NULL;
+
+ *c = ((const LV2_Atom_Int *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_midi(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, uint32_t *size, const uint8_t **m)
+{
+ if(!atom || (atom->type != oforge->MIDI_MidiEvent) )
+ return NULL;
+
+ *size = atom->size;
+ *m = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+osc_deforge_message_varlist(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch(*type)
+ {
+ case 'i':
+ {
+ int32_t *i = va_arg(args, int32_t *);
+ if(!(atom = osc_deforge_int32(oforge, forge, atom, i)))
+ return NULL;
+ break;
+ }
+ case 'f':
+ {
+ float *f = va_arg(args, float *);
+ if(!(atom = osc_deforge_float(oforge, forge, atom, f)))
+ return NULL;
+ break;
+ }
+ case 's':
+ {
+ const char **s = va_arg(args, const char **);
+ if(!(atom = osc_deforge_string(oforge, forge, atom, s)))
+ return NULL;
+ break;
+ }
+ case 'S':
+ {
+ const char **s = va_arg(args, const char **);
+ if(!(atom = osc_deforge_symbol(oforge, forge, atom, s)))
+ return NULL;
+ break;
+ }
+ case 'b':
+ {
+ uint32_t *size = va_arg(args, uint32_t *);
+ const uint8_t **b = va_arg(args, const uint8_t **);
+ if(!(atom = osc_deforge_blob(oforge, forge, atom, size, b)))
+ return NULL;
+ break;
+ }
+
+ case 'h':
+ {
+ int64_t *h = va_arg(args, int64_t *);
+ if(!(atom = osc_deforge_int64(oforge, forge, atom, h)))
+ return NULL;
+ break;
+ }
+ case 'd':
+ {
+ double *d = va_arg(args, double *);
+ if(!(atom = osc_deforge_double(oforge, forge, atom, d)))
+ return NULL;
+ break;
+ }
+ case 't':
+ {
+ uint64_t *t = va_arg(args, uint64_t *);
+ if(!(atom = osc_deforge_timestamp(oforge, forge, atom, t)))
+ return NULL;
+ break;
+ }
+
+ case 'c':
+ {
+ char *c = va_arg(args, char *);
+ if(!(atom = osc_deforge_char(oforge, forge, atom, c)))
+ return NULL;
+ break;
+ }
+ case 'm':
+ {
+ uint32_t *size = va_arg(args, uint32_t *);
+ const uint8_t **m = va_arg(args, const uint8_t **);
+ if(!(atom = osc_deforge_midi(oforge, forge, atom, size, m)))
+ return NULL;
+ break;
+ }
+
+ case 'T':
+ case 'F':
+ case 'N':
+ case 'I':
+ {
+ break;
+ }
+
+ default: // unknown argument type
+ {
+ return NULL;
+ }
+ }
+ }
+
+ return atom;
+}
+
+static inline const LV2_Atom *
+osc_deforge_message_vararg(osc_forge_t *oforge, LV2_Atom_Forge *forge,
+ const LV2_Atom *atom, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ atom = osc_deforge_message_varlist(oforge, forge, atom, fmt, args);
+
+ va_end(args);
+
+ return atom;
+}
+
+#endif // _LV2_OSC_H_
diff --git a/osc.lv2/manifest.ttl b/osc.lv2/manifest.ttl
new file mode 100644
index 0000000..9f75ab3
--- /dev/null
+++ b/osc.lv2/manifest.ttl
@@ -0,0 +1,23 @@
+# 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 rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a lv2:Specification ;
+ lv2:minorVersion 1 ;
+ lv2:microVersion 0 ;
+ rdfs:seeAlso <osc.ttl> .
diff --git a/osc.lv2/osc.ttl b/osc.lv2/osc.ttl
new file mode 100644
index 0000000..efa9e32
--- /dev/null
+++ b/osc.lv2/osc.ttl
@@ -0,0 +1,101 @@
+# 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 atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix midi: <http://lv2plug.in/ns/midi#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a owl:Ontology ;
+ rdfs:seeAlso <lv2_osc.h> ,
+ <lv2-osc.doap.ttl> ;
+ lv2:documentation """
+ <p>This specification defines event data types for OSC bundles and message.
+ To signal support for OSC events on an atom:AtomPort with an atom:bufferType
+ of atom:Sequence, plugin authors should add atom:supports osc:Event to
+ the plugin specification.</p>
+ """ .
+
+osc:schedule
+ a lv2:Feature .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+osc:Bundle
+ a rdfs:Class ;
+ rdfs:subClassOf osc:Event ;
+ rdfs:label "OSC Bundle" .
+
+osc:Message
+ a rdfs:Class ;
+ rdfs:subClassOf osc:Event ;
+ rdfs:label "OSC Message" .
+
+osc:bundleTimestamp
+ a rdf:Property ,
+ owl:ObjectProperty ,
+ owl:FunctionalProperty ;
+ rdfs:domain osc:Bundle ;
+ rdfs:range atom:Long ;
+ rdfs:label "OSC Bundle Timestamp" .
+
+osc:bundleItems
+ a rdf:Property ,
+ owl:ObjectProperty ,
+ owl:FunctionalProperty ;
+ rdfs:domain osc:Bundle ;
+ rdfs:range atom:Tuple ;
+ rdfs:label "OSC Bundle Items" ;
+ lv2:documentation """
+ <p>Tuple of OSC Bundle Items (e.g. nested osc:Bundle's and/or
+ osc:Message's).</p>
+ """ .
+
+osc:messagePath
+ a rdf:Property ,
+ owl:ObjectProperty ,
+ owl:FunctionalProperty ;
+ rdfs:domain osc:Message ;
+ rdfs:range atom:String ;
+ rdfs:label "OSC Message Path" .
+
+osc:messageFormat
+ a rdf:Property ,
+ owl:ObjectProperty ,
+ owl:FunctionalProperty ;
+ rdfs:domain osc:Message ;
+ rdfs:range atom:String ;
+ rdfs:label "OSC Message Format" .
+
+osc:messageArguments
+ a rdf:Property ,
+ owl:ObjectProperty ,
+ owl:FunctionalProperty ;
+ rdfs:domain osc:Message ;
+ rdfs:range atom:Tuple ;
+ rdfs:label "OSC Message Arguments" ;
+ lv2:documentation """
+ <p>Tuple of OSC Message Arguments (e.g. Atom:Int, Atom:Long, Atom:Float,
+ Atom:Double, Atom:String, Atom:Chunk, Atom:Bool, Atom:Blank,
+ MIDI:MidiEvent).</p>
+ """ .
diff --git a/osc_inspector.c b/osc_inspector.c
new file mode 100644
index 0000000..1655bc5
--- /dev/null
+++ b/osc_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>
+
+#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;
+
+ osc_forge_t oforge;
+ uint64_t offset;
+};
+
+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;
+ }
+
+ 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;
+
+ // 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->offset);
+ 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_raw(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ if(ref)
+ lv2_atom_forge_pad(forge, 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->offset += 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..1c6f669
--- /dev/null
+++ b/osc_inspector_eo.c
@@ -0,0 +1,947 @@
+/*
+ * 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:", "%s"), str);
+ ptr += strlen(ptr);
+ }
+ 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_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ elm_genlist_homogeneous_set(ui->list, EINA_FALSE); // TRUE for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_SCROLL);
+ 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)
+ {
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ 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);
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ break;
+ }
+
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ 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/lv2_external_ui.h b/sandbox_ui.lv2/lv2_external_ui.h
index 2c9e6ee..2c9e6ee 100644
--- a/lv2_external_ui.h
+++ b/sandbox_ui.lv2/lv2_external_ui.h
diff --git a/sandbox_efl.c b/sandbox_ui.lv2/sandbox_efl.c
index 90211de..90211de 100644
--- a/sandbox_efl.c
+++ b/sandbox_ui.lv2/sandbox_efl.c
diff --git a/sandbox_io.h b/sandbox_ui.lv2/sandbox_io.h
index 1fca98e..1fca98e 100644
--- a/sandbox_io.h
+++ b/sandbox_ui.lv2/sandbox_io.h
diff --git a/sandbox_master.c b/sandbox_ui.lv2/sandbox_master.c
index c61e1d0..c61e1d0 100644
--- a/sandbox_master.c
+++ b/sandbox_ui.lv2/sandbox_master.c
diff --git a/sandbox_master.h b/sandbox_ui.lv2/sandbox_master.h
index 287a319..287a319 100644
--- a/sandbox_master.h
+++ b/sandbox_ui.lv2/sandbox_master.h
diff --git a/sandbox_slave.c b/sandbox_ui.lv2/sandbox_slave.c
index 9e1e665..9e1e665 100644
--- a/sandbox_slave.c
+++ b/sandbox_ui.lv2/sandbox_slave.c
diff --git a/sandbox_slave.h b/sandbox_ui.lv2/sandbox_slave.h
index 321919a..321919a 100644
--- a/sandbox_slave.h
+++ b/sandbox_ui.lv2/sandbox_slave.h
diff --git a/sandbox_ui.c b/sandbox_ui.lv2/sandbox_ui.c
index 482ff41..482ff41 100644
--- a/sandbox_ui.c
+++ b/sandbox_ui.lv2/sandbox_ui.c
diff --git a/sandbox_ui.h b/sandbox_ui.lv2/sandbox_ui.h
index 6dee0e3..6dee0e3 100644
--- a/sandbox_ui.h
+++ b/sandbox_ui.lv2/sandbox_ui.h
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..62a730d
--- /dev/null
+++ b/sherlock.h
@@ -0,0 +1,72 @@
+/*
+ * 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/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..c582689
--- /dev/null
+++ b/sherlock.ttl
@@ -0,0 +1,211 @@
+# 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 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)" .
+
+# 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 ;
+ atom:supports time:Position ;
+ atom:supports patch:Message ;
+ atom:supports atom:Blank ;
+ atom:supports atom:Bool ;
+ atom:supports atom:Chunk ;
+ atom:supports atom:Double ;
+ atom:supports atom:Float ;
+ atom:supports atom:Int ;
+ atom:supports atom:Long ;
+ atom:supports atom:Literal ;
+ atom:supports atom:Object ;
+ atom:supports atom:Path ;
+ atom:supports atom:Property ;
+ atom:supports atom:Resource ;
+ atom:supports atom:Sequence ;
+ atom:supports atom:String ;
+ atom:supports atom:Tuple ;
+ atom:supports atom:URI ;
+ atom:supports atom:URID ;
+ atom:supports atom:Vector ;
+ 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 ;
+ atom:supports time:Position ;
+ atom:supports patch:Message ;
+ atom:supports atom:Blank ;
+ atom:supports atom:Bool ;
+ atom:supports atom:Chunk ;
+ atom:supports atom:Double ;
+ atom:supports atom:Float ;
+ atom:supports atom:Int ;
+ atom:supports atom:Long ;
+ atom:supports atom:Literal ;
+ atom:supports atom:Object ;
+ atom:supports atom:Path ;
+ atom:supports atom:Property ;
+ atom:supports atom:Resource ;
+ atom:supports atom:Sequence ;
+ atom:supports atom:String ;
+ atom:supports atom:Tuple ;
+ atom:supports atom:URI ;
+ atom:supports atom:URID ;
+ atom:supports atom:Vector ;
+ 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 ;
+ 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 ;
+ 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 */