aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml36
-rw-r--r--CMakeLists.txt92
-rw-r--r--README.md37
-rw-r--r--atom_inspector.c172
-rw-r--r--atom_inspector_eo.c768
-rw-r--r--common.h36
-rw-r--r--encoder.l298
-rw-r--r--manifest.ttl.in104
-rw-r--r--midi_inspector.c190
-rw-r--r--midi_inspector_eo.c867
-rw-r--r--omk_logo_256x256.pngbin0 -> 6916 bytes
-rw-r--r--osc.lv2/CMakeLists.txt16
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md3
-rw-r--r--osc.lv2/lv2-osc.doap.ttl40
-rw-r--r--osc.lv2/manifest.ttl23
-rw-r--r--osc.lv2/osc.lv2/endian.h118
-rw-r--r--osc.lv2/osc.lv2/forge.h474
-rw-r--r--osc.lv2/osc.lv2/osc.h192
-rw-r--r--osc.lv2/osc.lv2/reader.h570
-rw-r--r--osc.lv2/osc.lv2/util.h475
-rw-r--r--osc.lv2/osc.lv2/writer.h551
-rw-r--r--osc.lv2/osc.ttl42
-rw-r--r--osc.lv2/osc_test.c423
-rw-r--r--osc_inspector.c192
-rw-r--r--osc_inspector_eo.c951
-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.h75
-rw-r--r--sherlock.ttl187
-rw-r--r--sherlock_eo.c35
-rw-r--r--sherlock_ui.c97
-rw-r--r--sherlock_ui.ttl135
-rw-r--r--symap/symap.c231
-rw-r--r--symap/symap.h69
45 files changed, 7952 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..b8e848e
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,92 @@
+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 3)
+set(SHERLOCK_VERSION "${SHERLOCK_MAJOR_VERSION}.${SHERLOCK_MINOR_VERSION}.${SHERLOCK_MICRO_VERSION}")
+add_definitions("-DSHERLOCK_VERSION=\"${SHERLOCK_VERSION}\"")
+add_definitions("-D_GNU_SOURCE=1") # asprintf
+
+set(DEST lib/lv2/sherlock.lv2)
+
+find_package(PkgConfig) # ${PKG_CONFIG_FOUND}
+
+pkg_search_module(LV2 REQUIRED lv2>=1.10)
+include_directories(${LV2_INCLUDE_DIRS})
+
+pkg_search_module(ELM REQUIRED elementary>=1.8)
+include_directories(${ELM_INCLUDE_DIRS})
+
+pkg_search_module(NANOMSG REQUIRED libnanomsg>=2.0)
+include_directories(${NANOMSG_INCLUDE_DIRS})
+
+pkg_search_module(SRATOM REQUIRED sratom-0>=0.4.0)
+include_directories(${SRATOM_INCLUDE_DIRS})
+
+pkg_search_module(LILV REQUIRED lilv-0>=0.20.0)
+include_directories(${LILV_INCLUDE_DIRS})
+if((${LILV_VERSION} VERSION_EQUAL "0.22.0") OR (${LILV_VERSION} VERSION_GREATER "0.22.0"))
+ add_definitions("-DLILV_0_22")
+endif()
+
+add_library(sherlock MODULE
+ sherlock.c
+ atom_inspector.c
+ midi_inspector.c
+ osc_inspector.c)
+set_target_properties(sherlock PROPERTIES PREFIX "")
+install(TARGETS sherlock DESTINATION ${DEST})
+
+add_library(sherlock_ui MODULE
+ ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_ui.c
+ ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_master.c
+ sherlock_ui.c)
+target_link_libraries(sherlock_ui
+ ${NANOMSG_LDFLAGS}
+ ${SRATOM_LDFLAGS})
+set_target_properties(sherlock_ui PROPERTIES PREFIX "")
+install(TARGETS sherlock_ui DESTINATION ${DEST})
+
+find_package(FLEX)
+flex_target(encoder encoder.l ${PROJECT_BINARY_DIR}/encoder.c
+ COMPILE_FLAGS "--header-file=${PROJECT_BINARY_DIR}/encoder.h --prefix=enc")
+
+add_library(sherlock_eo MODULE
+ sherlock_eo.c
+ atom_inspector_eo.c
+ midi_inspector_eo.c
+ osc_inspector_eo.c
+ ${FLEX_encoder_OUTPUTS})
+target_link_libraries(sherlock_eo
+ ${ELM_LDFLAGS}
+ ${SRATOM_LDFLAGS})
+set_target_properties(sherlock_eo PROPERTIES PREFIX "")
+install(TARGETS sherlock_eo DESTINATION ${DEST})
+
+add_executable(sandbox_efl
+ ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_slave.c
+ ${PROJECT_SOURCE_DIR}/sandbox_ui.lv2/sandbox_efl.c
+ ${PROJECT_SOURCE_DIR}/symap/symap.c)
+target_link_libraries(sandbox_efl
+ ${ELM_LDFLAGS}
+ ${NANOMSG_LDFLAGS}
+ ${LILV_LDFLAGS})
+install(TARGETS sandbox_efl DESTINATION ${DEST})
+
+configure_file(${PROJECT_SOURCE_DIR}/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
+install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/sherlock.ttl DESTINATION ${DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/sherlock_ui.ttl DESTINATION ${DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/omk_logo_256x256.png DESTINATION ${DEST})
diff --git a/README.md b/README.md
index 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..2d733b4
--- /dev/null
+++ b/atom_inspector.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sherlock.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+
+ int64_t frame;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ {
+ if(obj->body.otype == handle->time_position)
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - ev->time.frames;
+ }
+ }
+ }
+
+ // size of input sequence
+ size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size;
+
+ // copy whole input sequence to through port
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_raw(forge, handle->control_in, size);
+ if(!ref)
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+ ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ // only serialize sequence to UI if there were actually any events
+ if(handle->control_in->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ {
+ LV2_Atom_Forge_Frame tup_frame;
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &tup_frame);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, handle->control_in, size);
+ if(ref)
+ lv2_atom_forge_pop(forge, &tup_frame);
+
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+const LV2_Descriptor atom_inspector = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
diff --git a/atom_inspector_eo.c b/atom_inspector_eo.c
new file mode 100644
index 0000000..41ba32a
--- /dev/null
+++ b/atom_inspector_eo.c
@@ -0,0 +1,768 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <sherlock.h>
+#include <common.h>
+
+#include <Elementary.h>
+
+#include <sratom/sratom.h>
+
+#define COUNT_MAX 2048 // maximal amount of events shown
+#define STRING_BUF_SIZE 2048
+#define STRING_MAX 256
+#define STRING_OFF (STRING_MAX - 4)
+
+#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#"
+#define NS_OSC (const uint8_t*)"http://open-music-kontrollers.ch/lv2/osc#"
+#define NS_XPRESS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/xpress#"
+#define NS_SPOD (const uint8_t*)"http://open-music-kontrollers.ch/lv2/synthpod#"
+
+typedef struct _UI UI;
+
+struct _UI {
+ LV2UI_Write_Function write_function;
+ LV2UI_Controller controller;
+
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ struct {
+ LV2_URID event_transfer;
+ } uris;
+
+ LV2_Atom_Forge forge;
+
+ Evas_Object *widget;
+ Evas_Object *table;
+ Evas_Object *list;
+ Evas_Object *info;
+ Evas_Object *clear;
+ Evas_Object *autoclear;
+ Evas_Object *autoblock;
+ Evas_Object *popup;
+
+ Elm_Genlist_Item_Class *itc_list;
+ Elm_Genlist_Item_Class *itc_group;
+
+ char string_buf [STRING_BUF_SIZE];
+ char *logo_path;
+
+ Eina_Hash *urids;
+
+ Sratom *sratom;
+ const char *base_uri;
+
+ char *chunk;
+};
+
+#define CODE_PRE "<style=shadow,bottom>"
+#define CODE_POST "</style>"
+
+#define URI(VAL,TYP) ("<color=#bbb font=Mono><b>"VAL"</b></color> <color=#fff font=Default>"TYP"</color>")
+
+static void
+_encoder_begin(void *data)
+{
+ UI *ui = data;
+
+ ui->chunk = strdup("");
+}
+
+static void
+_encoder_append(const char *str, void *data)
+{
+ UI *ui = data;
+
+ size_t size = 0;
+ size += ui->chunk ? strlen(ui->chunk) : 0;
+ size += str ? strlen(str) + 1 : 0;
+
+ if(size)
+ ui->chunk = realloc(ui->chunk, size);
+
+ if(ui->chunk && str)
+ strcat(ui->chunk, str);
+}
+
+static void
+_encoder_end(void *data)
+{
+ UI *ui = data;
+
+ elm_entry_entry_set(ui->info, ui->chunk);
+ free(ui->chunk);
+}
+
+static moony_encoder_t enc = {
+ .begin = _encoder_begin,
+ .append = _encoder_append,
+ .end = _encoder_end,
+ .data = NULL
+};
+moony_encoder_t *encoder = &enc;
+
+static void
+_hash_del(void *data)
+{
+ char *uri = data;
+
+ if(uri)
+ free(uri);
+}
+
+static inline const char *
+_hash_set(UI *ui, LV2_URID urid)
+{
+ const char *uri = ui->unmap->unmap(ui->unmap->handle, urid);
+ if(uri)
+ eina_hash_add(ui->urids, &urid, strdup(uri));
+
+ //printf("prefill: %s (%"PRIu32")\n", uri, urid);
+
+ return uri;
+}
+
+static inline const char *
+_hash_get(UI *ui, LV2_URID urid)
+{
+ const char *uri = eina_hash_find(ui->urids, &urid);
+
+ if(!uri)
+ uri = _hash_set(ui, urid);
+
+ return uri;
+}
+
+static char *
+_list_item_label_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+ const LV2_Atom *atom = &ev->body;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.text"))
+ {
+ char *buf = ui->string_buf;
+ char *ptr = buf;
+ char *end = buf + STRING_BUF_SIZE;
+
+ sprintf(ptr, CODE_PRE);
+ ptr += strlen(ptr);
+
+ const char *type = _hash_get(ui, atom->type);
+ sprintf(ptr, URI(" ", "%s (%"PRIu32")"), type, atom->type);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_seq_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+ const LV2_Atom *atom = &ev->body;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#bb0 font=Mono>%04"PRIi64"</color>", ev->time.frames);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%4"PRIu32"</color>", atom->size);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_group_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const position_t *pos = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static void
+_del(void *data, Evas_Object *obj)
+{
+ free(data);
+}
+
+// copyied and adapted from libsratom
+static inline char *
+_sratom_to_turtle(Sratom* sratom,
+ LV2_URID_Unmap* unmap,
+ const char* base_uri,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ uint32_t type,
+ uint32_t size,
+ const void* body)
+{
+ SerdURI buri = SERD_URI_NULL;
+ SerdNode base = serd_node_new_uri_from_string((uint8_t *)(base_uri), NULL, &buri);
+ SerdEnv* env = serd_env_new(&base);
+ SerdChunk str = { NULL, 0 };
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdf", NS_RDF);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xsd", NS_XSD);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"lv2", (const uint8_t *)LV2_CORE_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"midi", (const uint8_t *)LV2_MIDI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"atom", (const uint8_t *)LV2_ATOM_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"units", (const uint8_t *)LV2_UNITS_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"ui", (const uint8_t *)LV2_UI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"time", (const uint8_t *)LV2_TIME_URI"#");
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"patch", (const uint8_t *)LV2_PATCH_PREFIX);
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"osc", NS_OSC);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xpress", NS_XPRESS);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"spod", NS_SPOD);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_ABBREVIATED |
+ SERD_STYLE_RESOLVED |
+ SERD_STYLE_CURIED),
+ env, &buri, serd_chunk_sink, &str);
+
+ // Write @prefix directives
+ serd_env_foreach(env,
+ (SerdPrefixSink)serd_writer_set_prefix,
+ writer);
+
+ sratom_set_sink(sratom, base_uri,
+ (SerdStatementSink)serd_writer_write_statement,
+ (SerdEndSink)serd_writer_end_anon,
+ writer);
+ sratom_write(sratom, unmap, SERD_EMPTY_S,
+ subject, predicate, type, size, body);
+ serd_writer_finish(writer);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+ serd_node_free(&base);
+ return (char*)serd_chunk_sink_finish(&str);
+}
+
+static void
+_item_selected(void *data, Evas_Object *obj, void *event_info)
+{
+ Elm_Object_Item *itm = event_info;
+ UI *ui = data;
+
+ const LV2_Atom_Event *ev = elm_object_item_data_get(itm);
+ const LV2_Atom *atom = &ev->body;
+
+ char *ttl = _sratom_to_turtle(ui->sratom, ui->unmap,
+ ui->base_uri, NULL, NULL,
+ atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+ if(ttl)
+ {
+ enc.data = ui;
+ ttl_to_markup(ttl, NULL);
+
+ free(ttl);
+ }
+}
+
+static void
+_clear_update(UI *ui, int count)
+{
+ if(!ui->clear)
+ return;
+
+ char *buf = ui->string_buf;
+ sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX);
+ elm_object_text_set(ui->clear, buf);
+}
+
+static void
+_clear_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ if(ui->list)
+ elm_genlist_clear(ui->list);
+ if(ui->info)
+ elm_entry_entry_set(ui->info, "");
+
+ _clear_update(ui, 0);
+}
+
+static void
+_info_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ // toggle popup
+ if(ui->popup)
+ {
+ if(evas_object_visible_get(ui->popup))
+ evas_object_hide(ui->popup);
+ else
+ evas_object_show(ui->popup);
+ }
+}
+
+static void
+_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ ui->widget = NULL;
+}
+
+static void
+_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ evas_object_del(ui->widget);
+}
+
+static Evas_Object *
+_content_get(UI *ui, Evas_Object *parent)
+{
+ ui->table = elm_table_add(parent);
+ if(ui->table)
+ {
+ elm_table_homogeneous_set(ui->table, EINA_FALSE);
+ elm_table_padding_set(ui->table, 0, 0);
+ evas_object_size_hint_min_set(ui->table, 1280, 720);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui);
+
+
+ Evas_Object *panes = elm_panes_add(ui->table);
+ if(panes)
+ {
+ elm_panes_horizontal_set(panes, EINA_FALSE);
+ elm_panes_content_left_size_set(panes, 0.3);
+ evas_object_size_hint_weight_set(panes, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(panes, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(panes);
+ elm_table_pack(ui->table, panes, 0, 0, 4, 1);
+
+ ui->list = elm_genlist_add(panes);
+ if(ui->list)
+ {
+ elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT);
+ elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading
+ elm_genlist_reorder_mode_set(ui->list, EINA_FALSE);
+ elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ evas_object_data_set(ui->list, "ui", ui);
+ evas_object_smart_callback_add(ui->list, "selected", _item_selected, ui);
+ evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->list);
+ elm_object_part_content_set(panes, "left", ui->list);
+ }
+
+ ui->info = elm_entry_add(ui->table);
+ if(ui->info)
+ {
+ elm_entry_autosave_set(ui->info, EINA_FALSE);
+ elm_entry_entry_set(ui->info, "");
+ elm_entry_single_line_set(ui->info, EINA_FALSE);
+ elm_entry_scrollable_set(ui->info, EINA_TRUE);
+ elm_entry_editable_set(ui->info, EINA_FALSE);
+ elm_entry_cnp_mode_set(ui->info, ELM_CNP_MODE_PLAINTEXT);
+ evas_object_size_hint_weight_set(ui->info, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ui->info, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->info);
+ elm_object_part_content_set(panes, "right", ui->info);
+ }
+ }
+
+ ui->clear = elm_button_add(ui->table);
+ if(ui->clear)
+ {
+ _clear_update(ui, 0);
+ evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui);
+ evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f);
+ evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->clear);
+ elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1);
+ }
+
+ ui->autoclear = elm_check_add(ui->table);
+ if(ui->autoclear)
+ {
+ elm_object_text_set(ui->autoclear, "overwrite");
+ evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoclear);
+ elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1);
+ }
+
+ ui->autoblock = elm_check_add(ui->table);
+ if(ui->autoblock)
+ {
+ elm_object_text_set(ui->autoblock, "block");
+ evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoblock);
+ elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1);
+ }
+
+ Evas_Object *info = elm_button_add(ui->table);
+ if(info)
+ {
+ evas_object_smart_callback_add(info, "clicked", _info_clicked, ui);
+ evas_object_size_hint_weight_set(info, 0.f, 0.f);
+ evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL);
+ evas_object_show(info);
+ elm_table_pack(ui->table, info, 3, 1, 1, 1);
+
+ Evas_Object *icon = elm_icon_add(info);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 20, 20);
+ evas_object_size_hint_max_set(icon, 32, 32);
+ //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_object_part_content_set(info, "icon", icon);
+ }
+ }
+
+ ui->popup = elm_popup_add(ui->table);
+ if(ui->popup)
+ {
+ elm_popup_allow_events_set(ui->popup, EINA_TRUE);
+
+ Evas_Object *hbox = elm_box_add(ui->popup);
+ if(hbox)
+ {
+ elm_box_horizontal_set(hbox, EINA_TRUE);
+ elm_box_homogeneous_set(hbox, EINA_FALSE);
+ elm_box_padding_set(hbox, 10, 0);
+ evas_object_show(hbox);
+ elm_object_content_set(ui->popup, hbox);
+
+ Evas_Object *icon = elm_icon_add(hbox);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 128, 128);
+ evas_object_size_hint_max_set(icon, 256, 256);
+ evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_box_pack_end(hbox, icon);
+ }
+
+ Evas_Object *label = elm_label_add(hbox);
+ if(label)
+ {
+ elm_object_text_set(label,
+ "<color=#b00 shadow_color=#fff font_size=20>"
+ "Sherlock - Atom Inspector"
+ "</color></br><align=left>"
+ "Version "SHERLOCK_VERSION"</br></br>"
+ "Copyright (c) 2015 Hanspeter Portner</br></br>"
+ "This is free and libre software</br>"
+ "Released under Artistic License 2.0</br>"
+ "By Open Music Kontrollers</br></br>"
+ "<color=#bbb>"
+ "http://open-music-kontrollers.ch/lv2/sherlock</br>"
+ "dev@open-music-kontrollers.ch"
+ "</color></align>");
+
+ evas_object_show(label);
+ elm_box_pack_end(hbox, label);
+ }
+ }
+ }
+ }
+
+ ui->sratom = sratom_new(ui->map); //FIXME check
+ sratom_set_pretty_numbers(ui->sratom, false);
+ ui->base_uri = "file:///tmp/base";
+
+ return ui->table;
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ if(strcmp(plugin_uri, SHERLOCK_ATOM_INSPECTOR_URI))
+ return NULL;
+
+ UI *ui = calloc(1, sizeof(UI));
+ if(!ui)
+ return NULL;
+
+ ui->write_function = write_function;
+ ui->controller = controller;
+
+ Evas_Object *parent = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ ui->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ ui->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ }
+
+ if(!ui->map || !ui->unmap)
+ {
+ fprintf(stderr, "LV2 URID extension not supported\n");
+ free(ui);
+ return NULL;
+ }
+ if(!parent)
+ {
+ free(ui);
+ return NULL;
+ }
+
+ lv2_atom_forge_init(&ui->forge, ui->map);
+
+ ui->itc_list = elm_genlist_item_class_new();
+ if(ui->itc_list)
+ {
+ ui->itc_list->item_style = "default_style";
+ ui->itc_list->func.text_get = _list_item_label_get;
+ ui->itc_list->func.content_get = _seq_item_content_get;
+ ui->itc_list->func.state_get = NULL;
+ ui->itc_list->func.del = _del;
+ }
+
+ ui->itc_group = elm_genlist_item_class_new();
+ if(ui->itc_group)
+ {
+ ui->itc_group->item_style = "default_style";
+ ui->itc_group->func.text_get = NULL;
+ ui->itc_group->func.content_get = _group_item_content_get;
+ ui->itc_group->func.state_get = NULL;
+ ui->itc_group->func.del = _del;
+ }
+
+ sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path);
+ ui->logo_path = strdup(ui->string_buf);
+
+ ui->uris.event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer);
+
+ ui->urids = eina_hash_int32_new(_hash_del);
+
+ // prepopulate hash table
+ _hash_set(ui, ui->forge.Bool);
+ _hash_set(ui, ui->forge.Chunk);
+ _hash_set(ui, ui->forge.Double);
+ _hash_set(ui, ui->forge.Float);
+ _hash_set(ui, ui->forge.Int);
+ _hash_set(ui, ui->forge.Long);
+ _hash_set(ui, ui->forge.Literal);
+ _hash_set(ui, ui->forge.Object);
+ _hash_set(ui, ui->forge.Path);
+ _hash_set(ui, ui->forge.Property);
+ _hash_set(ui, ui->forge.Sequence);
+ _hash_set(ui, ui->forge.String);
+ _hash_set(ui, ui->forge.Tuple);
+ _hash_set(ui, ui->forge.URI);
+ _hash_set(ui, ui->forge.URID);
+ _hash_set(ui, ui->forge.Vector);
+
+ ui->widget = _content_get(ui, parent);
+ if(!ui->widget)
+ {
+ free(ui);
+ return NULL;
+ }
+ *(Evas_Object **)widget = ui->widget;
+
+ return ui;
+}
+
+static void
+cleanup(LV2UI_Handle handle)
+{
+ UI *ui = handle;
+
+ if(ui->widget)
+ evas_object_del(ui->widget);
+ if(ui->logo_path)
+ free(ui->logo_path);
+
+ eina_hash_free(ui->urids);
+
+ if(ui->itc_list)
+ elm_genlist_item_class_free(ui->itc_list);
+ if(ui->itc_group)
+ elm_genlist_item_class_free(ui->itc_group);
+
+ if(ui->sratom)
+ sratom_free(ui->sratom);
+
+ free(ui);
+}
+
+static void
+port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid,
+ const void *buf)
+{
+ UI *ui = handle;
+
+ if( (i == 2) && (urid == ui->uris.event_transfer) && ui->list)
+ {
+ const LV2_Atom_Tuple *tup = buf;
+ const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom);
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom);
+ int n = elm_genlist_items_count(ui->list);
+
+ Elm_Object_Item *itm = NULL;
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events
+ {
+ position_t *pos = malloc(sizeof(position_t));
+ if(!pos)
+ return;
+
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ return;
+ }
+
+ itm = elm_genlist_item_append(ui->list, ui->itc_group,
+ pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE);
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt)
+ {
+ size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size;
+ LV2_Atom_Event *ev = malloc(len);
+ if(!ev)
+ continue;
+
+ memcpy(ev, elmnt, len);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_list,
+ ev, itm, ELM_GENLIST_ITEM_NONE, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ n++;
+
+ // scroll to last item
+ //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE);
+ }
+ }
+
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ _clear_update(ui, n); // only update if there where any events
+ }
+}
+
+const LV2UI_Descriptor atom_inspector_eo = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_EO_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = NULL
+};
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..35dc3e9
--- /dev/null
+++ b/common.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+typedef void (*encoder_begin_t)(void *data);
+typedef void (*encoder_append_t)(const char *str, void *data);
+typedef void (*encoder_end_t)(void *data);
+typedef struct _moony_encoder_t moony_encoder_t;
+
+struct _moony_encoder_t {
+ encoder_begin_t begin;
+ encoder_append_t append;
+ encoder_end_t end;
+ void *data;
+};
+extern moony_encoder_t *encoder;
+
+void ttl_to_markup(const char *utf8, FILE *f);
+
+#endif
diff --git a/encoder.l b/encoder.l
new file mode 100644
index 0000000..0fc55a0
--- /dev/null
+++ b/encoder.l
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+%{
+#include <stdio.h>
+#include <string.h>
+
+#include <common.h>
+
+typedef enum _markup_type_t markup_type_t;
+typedef struct _markup_item_t markup_item_t;
+
+enum _markup_type_t {
+ MARKUP_CODE,
+ MARKUP_PREFIX,
+ MARKUP_SUBJECT,
+ MARKUP_PREDICATE,
+ MARKUP_NUMBER,
+ MARKUP_STRING,
+ MARKUP_URI
+};
+
+struct _markup_item_t {
+ const char *begin;
+ const char *end;
+};
+
+static const markup_item_t markup_items [] = {
+ [MARKUP_CODE] = {"font=Mono style=Plain color=#ffffff", "font"},
+ [MARKUP_PREFIX] = {"color=#cc00cc", "color"},
+ [MARKUP_SUBJECT] = {"color=#00cccc", "color"},
+ [MARKUP_PREDICATE] = {"color=#00cc00", "color"},
+ [MARKUP_NUMBER] = {"color=#0000cc", "color"},
+ [MARKUP_STRING] = {"color=#cc0000", "color"},
+ [MARKUP_URI] = {"color=#cccc00", "color"}
+};
+
+static void
+_add_plain(const char *content)
+{
+ encoder->append(content, encoder->data);
+}
+
+static void
+_add_singleton(const char *key)
+{
+ char buf [64];
+ sprintf(buf, "<%s/>", key);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup_begin(markup_type_t type)
+{
+ char buf [64];
+ sprintf(buf, "<%s>", markup_items[type].begin);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup_end(markup_type_t type)
+{
+ char buf [64];
+ sprintf(buf, "</%s>", markup_items[type].end);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup(markup_type_t type, const char *content)
+{
+ char buf [64];
+ sprintf(buf, "<%s>", markup_items[type].begin);
+ encoder->append(buf, encoder->data);
+ encoder->append(content, encoder->data);
+ sprintf(buf, "</%s>", markup_items[type].end);
+ encoder->append(buf, encoder->data);
+}
+
+enum {
+ TK_NONE,
+ TK_PREFIX,
+ TK_SUBJECT,
+ TK_PREDICATE,
+ TK_NUMBER,
+ TK_URI_IN,
+ TK_URI_OUT,
+ TK_URI_ERR,
+ TK_STRING_IN,
+ TK_STRING_OUT,
+ TK_STRING_ERR,
+ TK_LONG_STRING_IN,
+ TK_LONG_STRING_OUT,
+ TK_WHITESPACE,
+ TK_RAW,
+ TK_TAB,
+ TK_NEWLINE,
+ TK_LT,
+ TK_GT,
+ TK_AMP,
+ TK_NAME,
+ TK_BADCHAR
+};
+
+%}
+
+%option reentrant noyywrap
+
+w [ \v\a]+
+name [_a-zA-Z@][_a-zA-Z0-9\.]*
+n [0-9]+
+exp [Ee][+-]?{n}
+number ({n}|{n}[.]{n}){exp}?
+eol [\n\r]
+
+%x XSTRING
+%x XLONG_STRING
+%x XURI
+
+%%
+
+{w} return TK_WHITESPACE;
+"\t" return TK_TAB;
+{eol} return TK_NEWLINE;
+"<" BEGIN(XURI); return TK_URI_IN;
+\"\"\" BEGIN(XLONG_STRING); return TK_LONG_STRING_IN;
+\" BEGIN(XSTRING); return TK_STRING_IN;
+{name}: return TK_SUBJECT;
+"@prefix" return TK_PREFIX;
+"a" return TK_PREFIX;
+{name} return TK_PREDICATE;
+{number} return TK_NUMBER;
+. return TK_RAW;
+
+<XURI>
+{
+ ">" BEGIN(0); return TK_URI_OUT;
+ {eol} BEGIN(0); return TK_URI_ERR;
+ . return TK_RAW;
+}
+
+<XLONG_STRING>
+{
+ \\\" return TK_RAW;
+ \"\"\" BEGIN(0); return TK_LONG_STRING_OUT;
+ {w} return TK_WHITESPACE;
+ "\t" return TK_TAB;
+ {eol} return TK_NEWLINE;
+ "<" return TK_LT;
+ ">" return TK_GT;
+ "&" return TK_AMP;
+ . return TK_RAW;
+}
+
+<XSTRING>
+{
+ \\\" return TK_RAW;
+ \" BEGIN(0); return TK_STRING_OUT;
+ {eol} BEGIN(0); return TK_STRING_ERR;
+ {w} return TK_WHITESPACE;
+ "\t" return TK_TAB;
+ {eol} return TK_NEWLINE;
+ "<" return TK_LT;
+ ">" return TK_GT;
+ "&" return TK_AMP;
+ . return TK_RAW;
+}
+
+%%
+
+void
+ttl_to_markup(const char *utf8, FILE *f)
+{
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+
+ enclex_init(&scanner);
+ if(utf8)
+ {
+ buf = enc_scan_string(utf8, scanner);
+ }
+ else if(f)
+ {
+ encset_in(f, scanner);
+ buf = enc_create_buffer(NULL, YY_BUF_SIZE, scanner);
+ }
+ else
+ {
+ enclex_destroy(scanner);
+ return;
+ }
+
+ encoder->begin(encoder->data);
+ _add_markup_begin(MARKUP_CODE);
+
+ for(int tok=enclex(scanner); tok; tok=enclex(scanner))
+ {
+ const char *txt = encget_text(scanner);
+ switch(tok)
+ {
+ case TK_PREFIX:
+ _add_markup(MARKUP_PREFIX, txt);
+ break;
+
+ case TK_NUMBER:
+ _add_markup(MARKUP_NUMBER, txt);
+ break;
+
+ case TK_URI_IN:
+ _add_markup_begin(MARKUP_URI);
+ _add_plain("&lt;");
+ break;
+ case TK_URI_OUT:
+ _add_plain("&gt;");
+ _add_markup_end(MARKUP_URI);
+ break;
+ case TK_URI_ERR:
+ _add_markup_end(MARKUP_URI);
+ _add_singleton("br");
+ break;
+
+ case TK_STRING_IN:
+ _add_markup_begin(MARKUP_STRING);
+ _add_plain("\"");
+ break;
+ case TK_STRING_OUT:
+ _add_plain("\"");
+ _add_markup_end(MARKUP_STRING);
+ break;
+ case TK_STRING_ERR:
+ _add_markup_end(MARKUP_STRING);
+ _add_singleton("br");
+ break;
+
+ case TK_LONG_STRING_IN:
+ _add_markup_begin(MARKUP_STRING);
+ _add_plain("\"\"\"");
+ break;
+ case TK_LONG_STRING_OUT:
+ _add_plain("\"\"\"");
+ _add_markup_end(MARKUP_STRING);
+ break;
+
+ case TK_NEWLINE:
+ _add_singleton("br");
+ break;
+ case TK_LT:
+ _add_plain("&lt;");
+ break;
+ case TK_GT:
+ _add_plain("&gt;");
+ break;
+ case TK_AMP:
+ _add_plain("&amp;");
+ break;
+
+ case TK_BADCHAR:
+ break;
+
+ case TK_TAB:
+ _add_plain(" ");
+ break;
+
+ case TK_SUBJECT:
+ _add_markup(MARKUP_SUBJECT, txt);
+ break;
+ case TK_PREDICATE:
+ _add_markup(MARKUP_PREDICATE, txt);
+ break;
+
+ case TK_NAME:
+ case TK_WHITESPACE:
+ case TK_RAW:
+ default:
+ _add_plain(txt);
+ break;
+ }
+ }
+
+ _add_markup_end(MARKUP_CODE);
+ encoder->end(encoder->data);
+
+ enc_delete_buffer(buf, scanner);
+ enclex_destroy(scanner);
+}
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..c483930
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,104 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix kx: <http://kxstudio.sf.net/ns/lv2ext/external-ui#> .
+
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+# to please sord_validate
+ui:EoUI
+ a rdfs:Class, owl:Class ;
+ rdfs:subClassOf ui:UI .
+kx:Widget
+ a rdfs:Class, owl:Class ;
+ rdfs:subClassOf ui:UI .
+kx:Host
+ a lv2:Feature .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:atom_inspector_1_ui ;
+ ui:ui sherlock:atom_inspector_2_kx ;
+ ui:ui sherlock:atom_inspector_3_eo ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:atom_inspector_1_ui
+ a ui:UI ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:atom_inspector_2_kx
+ a kx:Widget ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:atom_inspector_3_eo
+ a ui:EoUI ;
+ ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:midi_inspector_1_ui ;
+ ui:ui sherlock:midi_inspector_2_kx ;
+ ui:ui sherlock:midi_inspector_3_eo ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:midi_inspector_1_ui
+ a ui:UI ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:midi_inspector_2_kx
+ a kx:Widget ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:midi_inspector_3_eo
+ a ui:EoUI ;
+ ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:osc_inspector_1_ui ;
+ ui:ui sherlock:osc_inspector_2_kx ;
+ ui:ui sherlock:osc_inspector_3_eo ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:osc_inspector_1_ui
+ a ui:UI ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:osc_inspector_2_kx
+ a kx:Widget ;
+ ui:binary <sherlock_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+sherlock:osc_inspector_3_eo
+ a ui:EoUI ;
+ ui:binary <sherlock_eo@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
diff --git a/midi_inspector.c b/midi_inspector.c
new file mode 100644
index 0000000..ae60b5e
--- /dev/null
+++ b/midi_inspector.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sherlock.h>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+ LV2_URID midi_event;
+
+ int64_t frame;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ handle->midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ {
+ if(obj->body.otype == handle->time_position)
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - ev->time.frames;
+ }
+ }
+ }
+
+ // size of input sequence
+ size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size;
+
+ // copy whole input sequence to through port
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_raw(forge, handle->control_in, size);
+ if(!ref)
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+
+ bool has_midi = false;
+
+ ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0);
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &frame[1]);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0);
+
+ // only serialize MIDI events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ if(ev->body.type == handle->midi_event)
+ {
+ has_midi = true;
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[2]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[0]);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ if(!has_midi) // don't send anything
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+const LV2_Descriptor midi_inspector = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
diff --git a/midi_inspector_eo.c b/midi_inspector_eo.c
new file mode 100644
index 0000000..0234d68
--- /dev/null
+++ b/midi_inspector_eo.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <sherlock.h>
+
+#include <Elementary.h>
+
+#define COUNT_MAX 2048 // maximal amount of events shown
+#define STRING_BUF_SIZE 2048
+#define STRING_MAX 256
+#define STRING_OFF (STRING_MAX - 4)
+
+typedef struct _UI UI;
+
+struct _UI {
+ LV2UI_Write_Function write_function;
+ LV2UI_Controller controller;
+
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2_URID event_transfer;
+
+ Evas_Object *widget;
+ Evas_Object *table;
+ Evas_Object *list;
+ Evas_Object *clear;
+ Evas_Object *autoclear;
+ Evas_Object *autoblock;
+ Evas_Object *popup;
+
+ Elm_Genlist_Item_Class *itc_midi;
+ Elm_Genlist_Item_Class *itc_group;
+
+ char string_buf [STRING_BUF_SIZE];
+ char *logo_path;
+};
+
+typedef struct _midi_msg_t midi_msg_t;
+
+struct _midi_msg_t {
+ uint8_t type;
+ const char *key;
+};
+
+#define COMMANDS_NUM 18
+static const midi_msg_t commands [COMMANDS_NUM] = {
+ { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" },
+ { LV2_MIDI_MSG_NOTE_ON , "NoteOn" },
+ { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" },
+ { LV2_MIDI_MSG_CONTROLLER , "Controller" },
+ { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" },
+ { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" },
+ { LV2_MIDI_MSG_BENDER , "Bender" },
+ { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" },
+ { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" },
+ { LV2_MIDI_MSG_SONG_POS , "SongPosition" },
+ { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" },
+ { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" },
+ { LV2_MIDI_MSG_CLOCK , "Clock" },
+ { LV2_MIDI_MSG_START , "Start" },
+ { LV2_MIDI_MSG_CONTINUE , "Continue" },
+ { LV2_MIDI_MSG_STOP , "Stop" },
+ { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" },
+ { LV2_MIDI_MSG_RESET , "Reset" },
+};
+
+#define CONTROLLERS_NUM 72
+static const midi_msg_t controllers [CONTROLLERS_NUM] = {
+ { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" },
+ { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" },
+ { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" },
+ { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" },
+ { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" },
+ { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" },
+ { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" },
+ { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" },
+ { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" },
+ { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" },
+
+ { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" },
+ { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" },
+ { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" },
+ { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" },
+ { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" },
+ { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" },
+ { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" },
+ { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" },
+ { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" },
+ { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" },
+
+ { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" },
+ { LV2_MIDI_CTL_PORTAMENTO , "Portamento" },
+ { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" },
+ { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" },
+ { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" },
+ { LV2_MIDI_CTL_HOLD2 , "Hold2" },
+
+ { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" },
+ { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" },
+ { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" },
+ { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" },
+ { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" },
+ { LV2_MIDI_CTL_SC6 , "SC6" },
+ { LV2_MIDI_CTL_SC7 , "SC7" },
+ { LV2_MIDI_CTL_SC8 , "SC8" },
+ { LV2_MIDI_CTL_SC9 , "SC9" },
+ { LV2_MIDI_CTL_SC10 , "SC10" },
+
+ { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" },
+ { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" },
+
+ { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" },
+ { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" },
+ { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" },
+ { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" },
+ { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" },
+
+ { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" },
+ { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" },
+
+ { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" },
+ { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" },
+
+ { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" },
+ { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" },
+
+ { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" },
+ { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" },
+ { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" },
+ { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" },
+ { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" },
+ { LV2_MIDI_CTL_OMNI_ON , "OmniOn" },
+ { LV2_MIDI_CTL_MONO1 , "Mono1" },
+ { LV2_MIDI_CTL_MONO2 , "Mono2" },
+};
+
+static int
+_cmp_search(const void *itm1, const void *itm2)
+{
+ const midi_msg_t *msg1 = itm1;
+ const midi_msg_t *msg2 = itm2;
+
+ if(msg1->type < msg2->type)
+ return -1;
+ else if(msg1->type > msg2->type)
+ return 1;
+
+ return 0;
+}
+
+static inline const midi_msg_t *
+_search_command(uint8_t type)
+{
+ return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_controller(uint8_t type)
+{
+ return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+#define CODE_PRE "<font=Mono style=shadow,bottom>"
+#define CODE_POST "</font>"
+
+#define RAW_PRE "<color=#b0b><b>"
+#define RAW_POST "</b></color>"
+
+#define SYSTEM(TYP) "<color=#888><b>"TYP"</b></color>"
+#define CHANNEL(TYP, VAL) "<color=#888><b>"TYP"</b></color><color=#fff>"VAL"</color>"
+#define COMMAND(VAL) "<color=#0b0>"VAL"</color>"
+#define CONTROLLER(VAL) "<color=#b00>"VAL"</color>"
+#define PUNKT(VAL) "<color=#00b>"VAL"</color>"
+
+static const char *keys [12] = {
+ "C", "#C",
+ "D", "#D",
+ "E",
+ "F", "#F",
+ "G", "#G",
+ "A", "#A",
+ "H"
+};
+
+static inline const char *
+_note(uint8_t val, uint8_t *octave)
+{
+ *octave = val / 12;
+
+ return keys[val % 12];
+}
+
+static inline char *
+_atom_stringify(UI *ui, char *ptr, char *end, const LV2_Atom *atom)
+{
+ //FIXME check for buffer overflows!!!
+
+ const uint8_t *midi = LV2_ATOM_BODY_CONST(atom);
+
+ sprintf(ptr, CODE_PRE RAW_PRE);
+ ptr += strlen(ptr);
+
+ char *barrier = ptr + STRING_OFF;
+ unsigned i;
+ for(i=0; (i<atom->size) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, " %02"PRIX8, midi[i]);
+
+ for( ; (i<3) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, " ");
+
+ sprintf(ptr, RAW_POST);
+ ptr += strlen(ptr);
+
+ if( (midi[0] & 0xf0) == 0xf0) // system messages
+ {
+ const midi_msg_t *command_msg = _search_command(midi[0]);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ if(midi[0] == LV2_MIDI_MSG_SYSTEM_EXCLUSIVE) // sysex message
+ {
+ for( ; (i<atom->size) && ptr<barrier; i++, ptr += 3)
+ sprintf(ptr, " %02"PRIX8, midi[i]);
+ }
+ else if(midi[0] == LV2_MIDI_MSG_SONG_POS)
+ {
+ uint16_t pos = (((uint16_t)midi[2] << 7) | midi[1]);
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIu16
+ PUNKT("]"),
+ command_str, pos);
+ }
+ else // other system message
+ {
+ if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ command_str, midi[1]);
+ }
+ else if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ command_str, midi[1], midi[2]);
+ }
+ else // assume atom->size == 1, aka no data
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT("]"),
+ command_str);
+ }
+ }
+ }
+ else // channel messages
+ {
+ const uint8_t command = midi[0] & 0xf0;
+ const uint8_t channel = midi[0] & 0x0f;
+
+ const midi_msg_t *command_msg = _search_command(command);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ if( (command == LV2_MIDI_MSG_NOTE_ON)
+ || (command == LV2_MIDI_MSG_NOTE_OFF)
+ || (command == LV2_MIDI_MSG_NOTE_PRESSURE))
+ {
+ uint8_t octave;
+ const char *note = _note(midi[1], &octave);
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%s-%"PRIu8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, note, octave, midi[2]);
+ }
+ else if(command == LV2_MIDI_MSG_CONTROLLER)
+ {
+ const midi_msg_t *controller_msg = _search_controller(midi[1]);
+ const char *controller_str = controller_msg
+ ? controller_msg->key
+ : "Unknown";
+
+ if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") CONTROLLER("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, controller_str, midi[2]);
+ }
+ else if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") CONTROLLER("%s")
+ PUNKT("]"),
+ channel, command_str, controller_str);
+ }
+ }
+ else if(command == LV2_MIDI_MSG_BENDER)
+ {
+ int16_t bender = (((int16_t)midi[2] << 7) | midi[1]) - 0x2000;
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi16
+ PUNKT("]"),
+ channel, command_str, bender);
+ }
+ else if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, midi[1], midi[2]);
+ }
+ else if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, midi[1]);
+ }
+ else // fall-back
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT("]"),
+ channel, command_str);
+ }
+ }
+ ptr += strlen(ptr);
+
+ if(ptr >= barrier) // there would be more to print
+ {
+ ptr = barrier;
+ sprintf(ptr, "...");
+ ptr += 4;
+ }
+
+ sprintf(ptr, CODE_POST);
+
+ return ptr + strlen(ptr);
+}
+
+static char *
+_midi_label_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.text"))
+ {
+ char *buf = ui->string_buf;
+ char *ptr = buf;
+ char *end = buf + STRING_BUF_SIZE;
+
+ ptr = _atom_stringify(ui, ptr, end, &ev->body);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_midi_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#bb0 font=Mono>%04ld</color>", ev->time.frames);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%4u</color>", ev->body.size);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_group_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const position_t *pos = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static void
+_del(void *data, Evas_Object *obj)
+{
+ free(data);
+}
+
+static void
+_clear_update(UI *ui, int count)
+{
+ if(!ui->clear)
+ return;
+
+ char *buf = ui->string_buf;
+ sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX);
+ elm_object_text_set(ui->clear, buf);
+}
+
+static void
+_clear_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ if(ui->list)
+ elm_genlist_clear(ui->list);
+
+ _clear_update(ui, 0);
+}
+
+static void
+_info_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ // toggle popup
+ if(ui->popup)
+ {
+ if(evas_object_visible_get(ui->popup))
+ evas_object_hide(ui->popup);
+ else
+ evas_object_show(ui->popup);
+ }
+}
+
+static void
+_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ ui->widget = NULL;
+}
+
+static void
+_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ evas_object_del(ui->widget);
+}
+
+static Evas_Object *
+_content_get(UI *ui, Evas_Object *parent)
+{
+ ui->table = elm_table_add(parent);
+ if(ui->table)
+ {
+ elm_table_homogeneous_set(ui->table, EINA_FALSE);
+ elm_table_padding_set(ui->table, 0, 0);
+ evas_object_size_hint_min_set(ui->table, 600, 800);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui);
+
+ ui->list = elm_genlist_add(ui->table);
+ if(ui->list)
+ {
+ elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT);
+ elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading
+ elm_genlist_reorder_mode_set(ui->list, EINA_FALSE);
+ elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ evas_object_data_set(ui->list, "ui", ui);
+ evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->list);
+ elm_table_pack(ui->table, ui->list, 0, 0, 4, 1);
+ }
+
+ ui->clear = elm_button_add(ui->table);
+ if(ui->clear)
+ {
+ _clear_update(ui, 0);
+ evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui);
+ evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f);
+ evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->clear);
+ elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1);
+ }
+
+ ui->autoclear = elm_check_add(ui->table);
+ if(ui->autoclear)
+ {
+ elm_object_text_set(ui->autoclear, "overwrite");
+ evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoclear);
+ elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1);
+ }
+
+ ui->autoblock = elm_check_add(ui->table);
+ if(ui->autoblock)
+ {
+ elm_object_text_set(ui->autoblock, "block");
+ evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoblock);
+ elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1);
+ }
+
+ Evas_Object *info = elm_button_add(ui->table);
+ if(info)
+ {
+ evas_object_smart_callback_add(info, "clicked", _info_clicked, ui);
+ evas_object_size_hint_weight_set(info, 0.f, 0.f);
+ evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL);
+ evas_object_show(info);
+ elm_table_pack(ui->table, info, 3, 1, 1, 1);
+
+ Evas_Object *icon = elm_icon_add(info);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 20, 20);
+ evas_object_size_hint_max_set(icon, 32, 32);
+ //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_object_part_content_set(info, "icon", icon);
+ }
+ }
+
+ ui->popup = elm_popup_add(ui->table);
+ if(ui->popup)
+ {
+ elm_popup_allow_events_set(ui->popup, EINA_TRUE);
+
+ Evas_Object *hbox = elm_box_add(ui->popup);
+ if(hbox)
+ {
+ elm_box_horizontal_set(hbox, EINA_TRUE);
+ elm_box_homogeneous_set(hbox, EINA_FALSE);
+ elm_box_padding_set(hbox, 10, 0);
+ evas_object_show(hbox);
+ elm_object_content_set(ui->popup, hbox);
+
+ Evas_Object *icon = elm_icon_add(hbox);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 128, 128);
+ evas_object_size_hint_max_set(icon, 256, 256);
+ evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_box_pack_end(hbox, icon);
+ }
+
+ Evas_Object *label = elm_label_add(hbox);
+ if(label)
+ {
+ elm_object_text_set(label,
+ "<color=#b00 shadow_color=#fff font_size=20>"
+ "Sherlock - MIDI Inspector"
+ "</color></br><align=left>"
+ "Version "SHERLOCK_VERSION"</br></br>"
+ "Copyright (c) 2015 Hanspeter Portner</br></br>"
+ "This is free and libre software</br>"
+ "Released under Artistic License 2.0</br>"
+ "By Open Music Kontrollers</br></br>"
+ "<color=#bbb>"
+ "http://open-music-kontrollers.ch/lv2/sherlock</br>"
+ "dev@open-music-kontrollers.ch"
+ "</color></align>");
+
+ evas_object_show(label);
+ elm_box_pack_end(hbox, label);
+ }
+ }
+ }
+ }
+
+ return ui->table;
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ if(strcmp(plugin_uri, SHERLOCK_MIDI_INSPECTOR_URI))
+ return NULL;
+
+ UI *ui = calloc(1, sizeof(UI));
+ if(!ui)
+ return NULL;
+
+ ui->write_function = write_function;
+ ui->controller = controller;
+
+ Evas_Object *parent = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ ui->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ }
+
+ if(!ui->map)
+ {
+ fprintf(stderr, "LV2 URID extension not supported\n");
+ free(ui);
+ return NULL;
+ }
+ if(!parent)
+ {
+ free(ui);
+ return NULL;
+ }
+
+ ui->event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer);
+ lv2_atom_forge_init(&ui->forge, ui->map);
+
+ ui->itc_midi = elm_genlist_item_class_new();
+ if(ui->itc_midi)
+ {
+ ui->itc_midi->item_style = "default_style";
+ ui->itc_midi->func.text_get = _midi_label_get;
+ ui->itc_midi->func.content_get = _midi_content_get;
+ ui->itc_midi->func.state_get = NULL;
+ ui->itc_midi->func.del = _del;
+ }
+
+ ui->itc_group = elm_genlist_item_class_new();
+ if(ui->itc_group)
+ {
+ ui->itc_group->item_style = "default_style";
+ ui->itc_group->func.text_get = NULL;
+ ui->itc_group->func.content_get = _group_item_content_get;
+ ui->itc_group->func.state_get = NULL;
+ ui->itc_group->func.del = _del;
+ }
+
+ sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path);
+ ui->logo_path = strdup(ui->string_buf);
+
+ ui->widget = _content_get(ui, parent);
+ if(!ui->widget)
+ {
+ free(ui);
+ return NULL;
+ }
+ *(Evas_Object **)widget = ui->widget;
+
+ return ui;
+}
+
+static void
+cleanup(LV2UI_Handle handle)
+{
+ UI *ui = handle;
+
+ if(ui->widget)
+ evas_object_del(ui->widget);
+
+ if(ui->logo_path)
+ free(ui->logo_path);
+
+ if(ui->itc_midi)
+ elm_genlist_item_class_free(ui->itc_midi);
+
+ free(ui);
+}
+
+static void
+port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid,
+ const void *buf)
+{
+ UI *ui = handle;
+
+ if( (i == 2) && (urid == ui->event_transfer) && ui->list)
+ {
+ const LV2_Atom_Tuple *tup = buf;
+ const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom);
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom);
+ int n = elm_genlist_items_count(ui->list);
+
+ Elm_Object_Item *itm = NULL;
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events
+ {
+ position_t *pos = malloc(sizeof(position_t));
+ if(!pos)
+ return;
+
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ return;
+ }
+
+ itm = elm_genlist_item_append(ui->list, ui->itc_group,
+ pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE);
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt)
+ {
+ size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size;
+ LV2_Atom_Event *ev = malloc(len);
+ if(!ev)
+ continue;
+
+ memcpy(ev, elmnt, len);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_midi,
+ ev, itm, ELM_GENLIST_ITEM_NONE, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ n++;
+
+ // scroll to last item
+ //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE);
+ }
+ }
+
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ _clear_update(ui, n); // only update if there where any events
+ }
+}
+
+const LV2UI_Descriptor midi_inspector_eo = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_EO_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = NULL
+};
diff --git a/omk_logo_256x256.png b/omk_logo_256x256.png
new file mode 100644
index 0000000..5f3ff48
--- /dev/null
+++ b/omk_logo_256x256.png
Binary files differ
diff --git a/osc.lv2/CMakeLists.txt b/osc.lv2/CMakeLists.txt
new file mode 100644
index 0000000..64ce73b
--- /dev/null
+++ b/osc.lv2/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(osc.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+
+set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+
+include(CTest)
+
+if(${BUILD_TESTING})
+ add_executable(osc_test
+ osc_test.c)
+ add_test(NAME API-Test COMMAND osc_test)
+endif()
diff --git a/osc.lv2/COPYING b/osc.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/osc.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/osc.lv2/README.md b/osc.lv2/README.md
new file mode 100644
index 0000000..b48021f
--- /dev/null
+++ b/osc.lv2/README.md
@@ -0,0 +1,3 @@
+# osc.lv2
+
+## Open Sound Control Extension for the LV2 Plugin Specification
diff --git a/osc.lv2/lv2-osc.doap.ttl b/osc.lv2/lv2-osc.doap.ttl
new file mode 100644
index 0000000..ef74f92
--- /dev/null
+++ b/osc.lv2/lv2-osc.doap.ttl
@@ -0,0 +1,40 @@
+# 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.
+
+@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/manifest.ttl b/osc.lv2/manifest.ttl
new file mode 100644
index 0000000..a2bbaf8
--- /dev/null
+++ b/osc.lv2/manifest.ttl
@@ -0,0 +1,23 @@
+# 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.
+
+@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.lv2/endian.h b/osc.lv2/osc.lv2/endian.h
new file mode 100644
index 0000000..99845d0
--- /dev/null
+++ b/osc.lv2/osc.lv2/endian.h
@@ -0,0 +1,118 @@
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef PORTABLE_ENDIAN_H__
+#define PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+# define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+# include <endian.h>
+
+#elif defined(__APPLE__)
+
+# include <libkern/OSByteOrder.h>
+
+# define htobe16(x) OSSwapHostToBigInt16(x)
+# define htole16(x) OSSwapHostToLittleInt16(x)
+# define be16toh(x) OSSwapBigToHostInt16(x)
+# define le16toh(x) OSSwapLittleToHostInt16(x)
+
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define htole32(x) OSSwapHostToLittleInt32(x)
+# define be32toh(x) OSSwapBigToHostInt32(x)
+# define le32toh(x) OSSwapLittleToHostInt32(x)
+
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define htole64(x) OSSwapHostToLittleInt64(x)
+# define be64toh(x) OSSwapBigToHostInt64(x)
+# define le64toh(x) OSSwapLittleToHostInt64(x)
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+# include <sys/endian.h>
+
+# define be16toh(x) betoh16(x)
+# define le16toh(x) letoh16(x)
+
+# define be32toh(x) betoh32(x)
+# define le32toh(x) letoh32(x)
+
+# define be64toh(x) betoh64(x)
+# define le64toh(x) letoh64(x)
+
+#elif defined(__WINDOWS__)
+
+# include <winsock2.h>
+# include <sys/param.h>
+
+# if BYTE_ORDER == LITTLE_ENDIAN
+
+# define htobe16(x) htons(x)
+# define htole16(x) (x)
+# define be16toh(x) ntohs(x)
+# define le16toh(x) (x)
+
+# define htobe32(x) htonl(x)
+# define htole32(x) (x)
+# define be32toh(x) ntohl(x)
+# define le32toh(x) (x)
+
+# define htobe64(x) htonll(x)
+# define htole64(x) (x)
+# define be64toh(x) ntohll(x)
+# define le64toh(x) (x)
+
+# elif BYTE_ORDER == BIG_ENDIAN
+
+ /* that would be xbox 360 */
+# define htobe16(x) (x)
+# define htole16(x) __builtin_bswap16(x)
+# define be16toh(x) (x)
+# define le16toh(x) __builtin_bswap16(x)
+
+# define htobe32(x) (x)
+# define htole32(x) __builtin_bswap32(x)
+# define be32toh(x) (x)
+# define le32toh(x) __builtin_bswap32(x)
+
+# define htobe64(x) (x)
+# define htole64(x) __builtin_bswap64(x)
+# define be64toh(x) (x)
+# define le64toh(x) __builtin_bswap64(x)
+
+# else
+
+# error byte order not supported
+
+# endif
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#else
+
+# error platform not supported
+
+#endif
+
+#endif \ No newline at end of file
diff --git a/osc.lv2/osc.lv2/forge.h b/osc.lv2/osc.lv2/forge.h
new file mode 100644
index 0000000..6dc5fe7
--- /dev/null
+++ b/osc.lv2/osc.lv2/forge.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_FORGE_H
+#define LV2_OSC_FORGE_H
+
+#include <inttypes.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/reader.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define lv2_osc_forge_int(forge, osc_urid, val) \
+ lv2_atom_forge_int((forge), (val))
+
+#define lv2_osc_forge_float(forge, osc_urid, val) \
+ lv2_atom_forge_float((forge), (val))
+
+#define lv2_osc_forge_string(forge, osc_urid, val, len) \
+ lv2_atom_forge_string((forge), (val), (len))
+
+#define lv2_osc_forge_long(forge, osc_urid, val) \
+ lv2_atom_forge_long((forge), (val))
+
+#define lv2_osc_forge_double(forge, osc_urid, val) \
+ lv2_atom_forge_double((forge), (val))
+
+#define lv2_osc_forge_true(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 1)
+
+#define lv2_osc_forge_false(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 0)
+
+#define lv2_osc_forge_nil(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Nil, 0)
+
+#define lv2_osc_forge_impulse(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Impulse, 0)
+
+#define lv2_osc_forge_symbol(forge, osc_urid, val) \
+ lv2_atom_forge_urid((forge), (val))
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_chunk(LV2_Atom_Forge *forge, LV2_URID type,
+ const uint8_t *buf, uint32_t size)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_atom(forge, size, type))
+ && (ref = lv2_atom_forge_raw(forge, buf, size)) )
+ {
+ lv2_atom_forge_pad(forge, size);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_midi(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ assert(size <= 3);
+ return lv2_osc_forge_chunk(forge, osc_urid->MIDI_MidiEvent, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_blob(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ return lv2_osc_forge_chunk(forge, osc_urid->ATOM_Chunk, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_char(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ char val)
+{
+ return lv2_atom_forge_literal(forge, &val, 1, osc_urid->OSC_Char, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_rgba(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ char val [9];
+ sprintf(val, "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, r, g, b, a);
+ return lv2_atom_forge_literal(forge, val, 8, osc_urid->OSC_RGBA, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_timetag(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame, 0, osc_urid->OSC_Timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagIntegral))
+ && (ref = lv2_atom_forge_long(forge, timetag->integral))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagFraction))
+ && (ref = lv2_atom_forge_long(forge, timetag->fraction)) )
+ {
+ lv2_atom_forge_pop(forge, &frame);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_bundle_head(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Bundle))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleTimetag))
+ && (ref = lv2_osc_forge_timetag(forge, osc_urid, timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleItems))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_head(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const char *path)
+{
+ assert(path);
+
+ LV2_Atom_Forge_Ref ref;
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Message))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messagePath))
+ && (ref = lv2_atom_forge_string(forge, path, strlen(path)))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messageArguments))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline void
+lv2_osc_forge_pop(LV2_Atom_Forge *forge, LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(forge, &frame[1]); // a LV2_Atom_Tuple
+ lv2_atom_forge_pop(forge, &frame[0]); // a LV2_Atom_Object
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_varlist(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, va_list args)
+{
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ if(!lv2_osc_check_path(path) || !lv2_osc_check_fmt(fmt, 0))
+ return 0;
+ if(!(ref = lv2_osc_forge_message_head(forge, osc_urid, frame, path)))
+ return 0;
+
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, va_arg(args, int32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, (float)va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ const char *s = va_arg(args, const char *);
+ if(!s || !(ref = lv2_osc_forge_string(forge, osc_urid, s, strlen(s))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *b = va_arg(args, const uint8_t *);
+ if(!b || !(ref = lv2_osc_forge_blob(forge, osc_urid, b, size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, va_arg(args, int64_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ const LV2_OSC_Timetag timetag = {
+ .integral = va_arg(args, uint32_t),
+ .fraction = va_arg(args, uint32_t)
+ };
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, &timetag)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid, va_arg(args, uint32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *m = va_arg(args, const uint8_t *);
+ if(!m || !(ref = lv2_osc_forge_midi(forge, osc_urid, m, size)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, (char)va_arg(args, int))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid,
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned))))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_vararg(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, ...)
+{
+ LV2_Atom_Forge_Ref ref;
+ va_list args;
+
+ va_start(args, fmt);
+
+ ref = lv2_osc_forge_message_varlist(forge, osc_urid, path, fmt, args);
+
+ va_end(args);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_packet(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_URID_Map *map, const uint8_t *buf, size_t size)
+{
+ LV2_OSC_Reader reader;
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ lv2_osc_reader_initialize(&reader, buf, size);
+
+ if(lv2_osc_reader_is_bundle(&reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(&reader, size);
+
+ if(itm && (ref = lv2_osc_forge_bundle_head(forge, osc_urid, frame,
+ LV2_OSC_TIMETAG_CREATE(itm->timetag))))
+ {
+ OSC_READER_BUNDLE_ITERATE(&reader, itm)
+ {
+ if(!(ref = lv2_osc_forge_packet(forge, osc_urid, map, itm->body, itm->size)))
+ return 0;
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+ else if(lv2_osc_reader_is_message(&reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(&reader, size);
+
+ if(arg && (ref = lv2_osc_forge_message_head(forge, osc_urid, frame, arg->path)))
+ {
+ OSC_READER_MESSAGE_ITERATE(&reader, arg)
+ {
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, arg->i)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, arg->f)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!(ref = lv2_osc_forge_string(forge, osc_urid, arg->s, arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!(ref = lv2_osc_forge_blob(forge, osc_urid, arg->b, arg->size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, arg->h)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, arg->d)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, LV2_OSC_TIMETAG_CREATE(arg->t))))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid,
+ map->map(map->handle, arg->S))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ if(!(ref = lv2_osc_forge_midi(forge, osc_urid, &arg->b[1], arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, arg->c)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid, arg->R, arg->G, arg->B, arg->A)))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_FORGE_H
diff --git a/osc.lv2/osc.lv2/osc.h b/osc.lv2/osc.lv2/osc.h
new file mode 100644
index 0000000..1ada68c
--- /dev/null
+++ b/osc.lv2/osc.lv2/osc.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_H
+#define LV2_OSC_H
+
+#include <stdint.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/midi/midi.h>
+
+#define LV2_OSC_URI "http://open-music-kontrollers.ch/lv2/osc"
+#define LV2_OSC_PREFIX LV2_OSC_URI "#"
+
+#define LV2_OSC__Event LV2_OSC_PREFIX "Event" // atom message type
+#define LV2_OSC__schedule LV2_OSC_PREFIX "schedule" // feature
+
+#define LV2_OSC__Packet LV2_OSC_PREFIX "Packet" // atom object type
+
+#define LV2_OSC__Bundle LV2_OSC_PREFIX "Bundle" // atom object type
+#define LV2_OSC__bundleTimetag LV2_OSC_PREFIX "bundleTimetag" // atom object property
+#define LV2_OSC__bundleItems LV2_OSC_PREFIX "bundleItems"
+
+#define LV2_OSC__Message LV2_OSC_PREFIX "Message" // atom object type
+#define LV2_OSC__messagePath LV2_OSC_PREFIX "messagePath" // atom object property
+#define LV2_OSC__messageArguments LV2_OSC_PREFIX "messageArguments" // atom object property
+
+#define LV2_OSC__Timetag LV2_OSC_PREFIX "Timetag" // atom object type
+#define LV2_OSC__timetagIntegral LV2_OSC_PREFIX "timetagIntegral" // atom object property
+#define LV2_OSC__timetagFraction LV2_OSC_PREFIX "timetagFraction" // atom object property
+
+#define LV2_OSC__Nil LV2_OSC_PREFIX "Nil" // atom literal type
+#define LV2_OSC__Impulse LV2_OSC_PREFIX "Impulse" // atom literal type
+#define LV2_OSC__Char LV2_OSC_PREFIX "Char" // atom literal type
+#define LV2_OSC__RGBA LV2_OSC_PREFIX "RGBA" // atom literal type
+
+#define LV2_OSC_PADDED_SIZE(size) ( ( (size_t)(size) + 3 ) & ( ~3 ) )
+#define LV2_OSC_IMMEDIATE 1ULL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *LV2_OSC_Schedule_Handle;
+
+typedef double (*LV2_OSC_Schedule_OSC2Frames)(
+ LV2_OSC_Schedule_Handle handle,
+ uint64_t timetag);
+
+typedef uint64_t (*LV2_OSC_Schedule_Frames2OSC)(
+ LV2_OSC_Schedule_Handle handle,
+ double frames);
+
+typedef struct _LV2_OSC_Schedule {
+ LV2_OSC_Schedule_Handle handle;
+ LV2_OSC_Schedule_OSC2Frames osc2frames;
+ LV2_OSC_Schedule_Frames2OSC frames2osc;
+} LV2_OSC_Schedule;
+
+typedef enum LV2_OSC_Type {
+ LV2_OSC_INT32 = 'i',
+ LV2_OSC_FLOAT = 'f',
+ LV2_OSC_STRING = 's',
+ LV2_OSC_BLOB = 'b',
+
+ LV2_OSC_TRUE = 'T',
+ LV2_OSC_FALSE = 'F',
+ LV2_OSC_NIL = 'N',
+ LV2_OSC_IMPULSE = 'I',
+
+ LV2_OSC_INT64 = 'h',
+ LV2_OSC_DOUBLE = 'd',
+ LV2_OSC_TIMETAG = 't',
+
+ LV2_OSC_SYMBOL = 'S',
+ LV2_OSC_CHAR = 'c',
+ LV2_OSC_MIDI = 'm',
+ LV2_OSC_RGBA = 'r'
+} LV2_OSC_Type;
+
+union swap32_t {
+ uint32_t u;
+
+ int32_t i;
+ float f;
+};
+
+union swap64_t {
+ uint64_t u;
+
+ int64_t h;
+ uint64_t t;
+ double d;
+};
+
+typedef struct _LV2_OSC_Timetag {
+ uint32_t integral;
+ uint32_t fraction;
+} LV2_OSC_Timetag;
+
+typedef struct _LV2_OSC_URID {
+ LV2_URID OSC_Packet;
+
+ LV2_URID OSC_Bundle;
+ LV2_URID OSC_bundleTimetag;
+ LV2_URID OSC_bundleItems;
+
+ LV2_URID OSC_Message;
+ LV2_URID OSC_messagePath;
+ LV2_URID OSC_messageArguments;
+
+ LV2_URID OSC_Timetag;
+ LV2_URID OSC_timetagIntegral;
+ LV2_URID OSC_timetagFraction;
+
+ LV2_URID OSC_Nil;
+ LV2_URID OSC_Impulse;
+ LV2_URID OSC_Char;
+ LV2_URID OSC_RGBA;
+
+ LV2_URID MIDI_MidiEvent;
+
+ LV2_URID ATOM_Int;
+ LV2_URID ATOM_Long;
+ LV2_URID ATOM_String;
+ LV2_URID ATOM_Literal;
+ LV2_URID ATOM_Float;
+ LV2_URID ATOM_Double;
+ LV2_URID ATOM_URID;
+ LV2_URID ATOM_Bool;
+ LV2_URID ATOM_Tuple;
+ LV2_URID ATOM_Object;
+ LV2_URID ATOM_Chunk;
+} LV2_OSC_URID;
+
+static inline void
+lv2_osc_urid_init(LV2_OSC_URID *osc_urid, LV2_URID_Map *map)
+{
+ osc_urid->OSC_Packet = map->map(map->handle, LV2_OSC__Packet);
+
+ osc_urid->OSC_Bundle = map->map(map->handle, LV2_OSC__Bundle);
+ osc_urid->OSC_bundleTimetag = map->map(map->handle, LV2_OSC__bundleTimetag);
+ osc_urid->OSC_bundleItems = map->map(map->handle, LV2_OSC__bundleItems);
+
+ osc_urid->OSC_Message = map->map(map->handle, LV2_OSC__Message);
+ osc_urid->OSC_messagePath = map->map(map->handle, LV2_OSC__messagePath);
+ osc_urid->OSC_messageArguments = map->map(map->handle, LV2_OSC__messageArguments);
+
+ osc_urid->OSC_Timetag = map->map(map->handle, LV2_OSC__Timetag);
+ osc_urid->OSC_timetagIntegral = map->map(map->handle, LV2_OSC__timetagIntegral);
+ osc_urid->OSC_timetagFraction = map->map(map->handle, LV2_OSC__timetagFraction);
+
+ osc_urid->OSC_Nil = map->map(map->handle, LV2_OSC__Nil);
+ osc_urid->OSC_Impulse = map->map(map->handle, LV2_OSC__Impulse);
+ osc_urid->OSC_Char = map->map(map->handle, LV2_OSC__Char);
+ osc_urid->OSC_RGBA = map->map(map->handle, LV2_OSC__RGBA);
+
+ osc_urid->MIDI_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ osc_urid->ATOM_Int = map->map(map->handle, LV2_ATOM__Int);
+ osc_urid->ATOM_Long = map->map(map->handle, LV2_ATOM__Long);
+ osc_urid->ATOM_String = map->map(map->handle, LV2_ATOM__String);
+ osc_urid->ATOM_Literal = map->map(map->handle, LV2_ATOM__Literal);
+ osc_urid->ATOM_Float = map->map(map->handle, LV2_ATOM__Float);
+ osc_urid->ATOM_Double = map->map(map->handle, LV2_ATOM__Double);
+ osc_urid->ATOM_URID = map->map(map->handle, LV2_ATOM__URID);
+ osc_urid->ATOM_Bool = map->map(map->handle, LV2_ATOM__Bool);
+ osc_urid->ATOM_Tuple = map->map(map->handle, LV2_ATOM__Tuple);
+ osc_urid->ATOM_Object = map->map(map->handle, LV2_ATOM__Object);
+ osc_urid->ATOM_Chunk = map->map(map->handle, LV2_ATOM__Chunk);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_H
diff --git a/osc.lv2/osc.lv2/reader.h b/osc.lv2/osc.lv2/reader.h
new file mode 100644
index 0000000..9dda227
--- /dev/null
+++ b/osc.lv2/osc.lv2/reader.h
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_READER_H
+#define LV2_OSC_READER_H
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/endian.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _LV2_OSC_Reader LV2_OSC_Reader;
+typedef struct _LV2_OSC_Item LV2_OSC_Item;
+typedef struct _LV2_OSC_Arg LV2_OSC_Arg;
+
+struct _LV2_OSC_Reader {
+ const uint8_t *buf;
+ const uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Item {
+ int32_t size;
+ const uint8_t *body;
+
+ uint64_t timetag;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Arg {
+ const char *type;
+ int32_t size;
+ union {
+ int32_t i;
+ float f;
+ const char *s;
+ const uint8_t *b;
+
+ int64_t h;
+ double d;
+ uint64_t t;
+
+ const uint8_t *m;
+ const char *S;
+ char c;
+ struct {
+ uint8_t R;
+ uint8_t G;
+ uint8_t B;
+ uint8_t A;
+ }; // anonymous RGBA struct
+ };
+
+ const char *path;
+ const uint8_t *end;
+};
+
+static inline void
+lv2_osc_reader_initialize(LV2_OSC_Reader *reader, const uint8_t *buf, size_t size)
+{
+ reader->buf = buf;
+ reader->ptr = buf;
+ reader->end = buf + size;
+}
+
+static inline bool
+lv2_osc_reader_overflow(LV2_OSC_Reader *reader, size_t size)
+{
+ return reader->ptr + size > reader->end;
+}
+
+static inline bool
+lv2_osc_reader_be32toh(LV2_OSC_Reader *reader, union swap32_t *s32)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ s32->u = *(const uint32_t *)reader->ptr;
+ s32->u = be32toh(s32->u);
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_be64toh(LV2_OSC_Reader *reader, union swap64_t *s64)
+{
+ if(lv2_osc_reader_overflow(reader, 8))
+ return false;
+
+ s64->u = *(const uint64_t *)reader->ptr;
+ s64->u = be64toh(s64->u);
+ reader->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int32(LV2_OSC_Reader *reader, int32_t *i)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *i = s32.i;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_float(LV2_OSC_Reader *reader, float *f)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *f = s32.f;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int64(LV2_OSC_Reader *reader, int64_t *h)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *h = s64.h;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_timetag(LV2_OSC_Reader *reader, uint64_t *t)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *t = s64.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_double(LV2_OSC_Reader *reader, double *d)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *d = s64.d;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_string(LV2_OSC_Reader *reader, const char **s)
+{
+ const char *str = (const char *)reader->ptr;
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(str) + 1);
+ if(lv2_osc_reader_overflow(reader, padded ))
+ return false;
+
+ *s = str;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_symbol(LV2_OSC_Reader *reader, const char **S)
+{
+ return lv2_osc_reader_get_string(reader, S);
+}
+
+static inline bool
+lv2_osc_reader_get_midi(LV2_OSC_Reader *reader, const uint8_t **m)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *m = reader->ptr;
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_blob(LV2_OSC_Reader *reader, int32_t *len, const uint8_t **body)
+{
+ if(!lv2_osc_reader_get_int32(reader, len))
+ return false;
+
+ const size_t padded = LV2_OSC_PADDED_SIZE(*len);
+ if(lv2_osc_reader_overflow(reader, padded))
+ return false;
+
+ *body = reader->ptr;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_rgba(LV2_OSC_Reader *reader, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *r = reader->ptr[0];
+ *g = reader->ptr[1];
+ *b = reader->ptr[2];
+ *a = reader->ptr[3];
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_char(LV2_OSC_Reader *reader, char *c)
+{
+ int32_t i;
+ if(!lv2_osc_reader_get_int32(reader, &i))
+ return false;
+
+ *c = i;
+
+ return true;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_raw(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ if(!lv2_osc_reader_get_int32(reader, &itm->size))
+ return NULL;
+
+ if(lv2_osc_reader_overflow(reader, itm->size))
+ return NULL;
+
+ itm->body = reader->ptr;
+
+ return itm;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_begin(LV2_OSC_Reader *reader, LV2_OSC_Item *itm, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ itm->end = reader->ptr + len;
+
+ if(lv2_osc_reader_overflow(reader, 16))
+ return NULL;
+
+ if(strncmp((const char *)reader->ptr, "#bundle", 8))
+ return NULL;
+ reader->ptr += 8;
+
+ if(!lv2_osc_reader_get_timetag(reader, &itm->timetag))
+ return NULL;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+static inline bool
+lv2_osc_reader_item_is_end(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ return reader->ptr > itm->end;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_next(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ reader->ptr += itm->size;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+#define OSC_READER_BUNDLE_BEGIN(reader, len) \
+ lv2_osc_reader_item_begin( \
+ (reader), \
+ &(LV2_OSC_Item){ .size = 0, .body = NULL, .timetag = 1ULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_BUNDLE_ITERATE(reader, itm) \
+ for(itm = itm; \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+#define OSC_READER_BUNDLE_FOREACH(reader, itm, len) \
+ for(LV2_OSC_Item *(itm) = OSC_READER_BUNDLE_BEGIN((reader), (len)); \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_raw(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!lv2_osc_reader_get_int32(reader, &arg->i))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!lv2_osc_reader_get_float(reader, &arg->f))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!lv2_osc_reader_get_string(reader, &arg->s))
+ return NULL;
+ arg->size = strlen(arg->s) + 1;
+
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!lv2_osc_reader_get_blob(reader, &arg->size, &arg->b))
+ return NULL;
+ //arg->size = arg->size;
+
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ {
+ if(!lv2_osc_reader_get_int64(reader, &arg->h))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!lv2_osc_reader_get_double(reader, &arg->d))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!lv2_osc_reader_get_timetag(reader, &arg->t))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+
+ case LV2_OSC_MIDI:
+ {
+ if(!lv2_osc_reader_get_midi(reader, &arg->m))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_SYMBOL:
+ {
+ if(!lv2_osc_reader_get_symbol(reader, &arg->S))
+ return NULL;
+ arg->size = strlen(arg->S) + 1;
+
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!lv2_osc_reader_get_char(reader, &arg->c))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!lv2_osc_reader_get_rgba(reader, &arg->R, &arg->G, &arg->B, &arg->A))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ }
+
+ return arg;
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_begin(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ arg->end = reader->ptr + len;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->path)) //TODO check for validity
+ return NULL;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->type)) //TODO check for validity
+ return NULL;
+
+ if(*arg->type != ',')
+ return NULL;
+
+ arg->type++; // skip ','
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+static inline bool
+lv2_osc_reader_arg_is_end(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ return (*arg->type == '\0') || (reader->ptr > arg->end);
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_next(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ arg->type++;
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+#define OSC_READER_MESSAGE_BEGIN(reader, len) \
+ lv2_osc_reader_arg_begin( \
+ (reader), \
+ &(LV2_OSC_Arg){ .type = NULL, .size = 0, .path = NULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_MESSAGE_ITERATE(reader, arg) \
+ for(arg = arg; \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+#define OSC_READER_MESSAGE_FOREACH(reader, arg, len) \
+ for(LV2_OSC_Arg *(arg) = OSC_READER_MESSAGE_BEGIN((reader), (len)); \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+static inline bool
+lv2_osc_reader_arg_varlist(LV2_OSC_Reader *reader, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_reader_get_int32(reader, va_arg(args, int32_t *)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_reader_get_float(reader, va_arg(args, float *)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_reader_get_string(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!lv2_osc_reader_get_blob(reader, va_arg(args, int32_t *), va_arg(args, const uint8_t **)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_reader_get_int64(reader, va_arg(args, int64_t *)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_reader_get_double(reader, va_arg(args, double *)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_reader_get_timetag(reader, va_arg(args, uint64_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!lv2_osc_reader_get_midi(reader, va_arg(args, const uint8_t **)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_reader_get_symbol(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_reader_get_char(reader, va_arg(args, char *)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!lv2_osc_reader_get_rgba(reader, va_arg(args, uint8_t *), va_arg(args, uint8_t *),
+ va_arg(args, uint8_t *), va_arg(args, uint8_t *)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_arg_vararg(LV2_OSC_Reader *reader, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_reader_arg_varlist(reader, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_reader_is_bundle(LV2_OSC_Reader *reader)
+{
+ return strncmp((const char *)reader->ptr, "#bundle", 8) == 0;
+}
+
+static inline bool
+lv2_osc_reader_is_message(LV2_OSC_Reader *reader)
+{
+ return reader->ptr[0] == '/'; //FIXME check path
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_READER_H
diff --git a/osc.lv2/osc.lv2/util.h b/osc.lv2/osc.lv2/util.h
new file mode 100644
index 0000000..c7c5d66
--- /dev/null
+++ b/osc.lv2/osc.lv2/util.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_UTIL_H
+#define LV2_OSC_UTIL_H
+
+#include <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <osc.lv2/osc.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*LV2_OSC_Method)(const char *path,
+ const LV2_Atom_Tuple *arguments, void *data);
+
+// characters not allowed in OSC path string
+static const char invalid_path_chars [] = {
+ ' ', '#',
+ '\0'
+};
+
+// allowed characters in OSC format string
+static const char valid_format_chars [] = {
+ LV2_OSC_INT32, LV2_OSC_FLOAT, LV2_OSC_STRING, LV2_OSC_BLOB,
+ LV2_OSC_TRUE, LV2_OSC_FALSE, LV2_OSC_NIL, LV2_OSC_IMPULSE,
+ LV2_OSC_INT64, LV2_OSC_DOUBLE, LV2_OSC_TIMETAG,
+ LV2_OSC_SYMBOL, LV2_OSC_MIDI,
+ '\0'
+};
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_path(const char *path)
+{
+ assert(path);
+
+ if(path[0] != '/')
+ return false;
+
+ for(const char *ptr=path+1; *ptr!='\0'; ptr++)
+ if( (isprint(*ptr) == 0) || (strchr(invalid_path_chars, *ptr) != NULL) )
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_fmt(const char *format, int offset)
+{
+ assert(format);
+
+ if(offset && (format[0] != ',') )
+ return false;
+
+ for(const char *ptr=format+offset; *ptr!='\0'; ptr++)
+ if(strchr(valid_format_chars, *ptr) == NULL)
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline uint64_t
+lv2_osc_timetag_parse(const LV2_OSC_Timetag *timetag)
+{
+ return ((uint64_t)timetag->integral << 32) | timetag->fraction;
+}
+
+/**
+ TODO
+*/
+static inline LV2_OSC_Timetag *
+lv2_osc_timetag_create(LV2_OSC_Timetag *timetag, uint64_t tt)
+{
+ timetag->integral = tt >> 32;
+ timetag->fraction = tt & 0xffffffff;
+
+ return timetag;
+}
+
+#define LV2_OSC_TIMETAG_CREATE(tt) \
+ lv2_osc_timetag_create(&(LV2_OSC_Timetag){.integral = 0, .fraction = 0}, (tt))
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_packet_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Packet;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Bundle;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Message;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_or_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return lv2_osc_is_message_type(osc_urid, type)
+ || lv2_osc_is_bundle_type(osc_urid, type);
+}
+
+static inline LV2_OSC_Type
+lv2_osc_argument_type(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ return LV2_OSC_INT32;
+ else if(atom->type == osc_urid->ATOM_Float)
+ return LV2_OSC_FLOAT;
+ else if(atom->type == osc_urid->ATOM_String)
+ return LV2_OSC_STRING;
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ return LV2_OSC_BLOB;
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ return LV2_OSC_INT64;
+ else if(atom->type == osc_urid->ATOM_Double)
+ return LV2_OSC_DOUBLE;
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ return LV2_OSC_TIMETAG;
+
+ else if(atom->type == osc_urid->ATOM_Bool)
+ {
+ if(((const LV2_Atom_Bool *)atom)->body)
+ return LV2_OSC_TRUE;
+ else
+ return LV2_OSC_FALSE;
+ }
+ else if(atom->type == osc_urid->ATOM_Literal)
+ {
+ const LV2_Atom_Literal *lit = (const LV2_Atom_Literal *)atom;
+ if(lit->body.datatype == osc_urid->OSC_Nil)
+ return LV2_OSC_NIL;
+ else if(lit->body.datatype == osc_urid->OSC_Impulse)
+ return LV2_OSC_IMPULSE;
+ else if(lit->body.datatype == osc_urid->OSC_Char)
+ return LV2_OSC_CHAR;
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ return LV2_OSC_RGBA;
+ }
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ return LV2_OSC_SYMBOL;
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ return LV2_OSC_MIDI;
+
+ return '\0';
+}
+
+static inline const LV2_Atom *
+lv2_osc_int32_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, int32_t *i)
+{
+ assert(i);
+ *i = ((const LV2_Atom_Int *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_float_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, float *f)
+{
+ assert(f);
+ *f = ((const LV2_Atom_Float *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_string_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, const char **s)
+{
+ assert(s);
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_blob_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, uint32_t *size,
+ const uint8_t **b)
+{
+ assert(size && b);
+ *size = atom->size;
+ *b = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_int64_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, int64_t *h)
+{
+ assert(h);
+ *h = ((const LV2_Atom_Long *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_double_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, double *d)
+{
+ assert(d);
+ *d = ((const LV2_Atom_Double *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_timetag_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
+ LV2_OSC_Timetag *timetag)
+{
+ assert(timetag);
+
+ const LV2_Atom_Long *integral = NULL;
+ const LV2_Atom_Long *fraction = NULL;
+
+ lv2_atom_object_get((const LV2_Atom_Object *)atom,
+ osc_urid->OSC_timetagIntegral, &integral,
+ osc_urid->OSC_timetagFraction, &fraction,
+ 0);
+
+ if( integral && (integral->atom.type == osc_urid->ATOM_Long)
+ && fraction && (fraction->atom.type == osc_urid->ATOM_Long) )
+ {
+ timetag->integral = integral->body;
+ timetag->fraction = fraction->body;
+ }
+ else
+ {
+ // set to immediate
+ timetag->integral = 0;
+ timetag->fraction = 1;
+ }
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_true_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_false_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_nil_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_impulse_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_symbol_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, const char **s)
+{
+ assert(s);
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_midi_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, uint32_t *size,
+ const uint8_t **m)
+{
+ assert(size && m);
+ *size = atom->size;
+ *m = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_char_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, char *c)
+{
+ assert(c);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ *c = str[0];
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_rgba_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
+ uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ assert(r && g && b && a);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ sscanf(str, "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, r, g, b, a);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ assert(timetag && items);
+
+ *timetag = NULL;
+ *items = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_bundleTimetag, timetag,
+ osc_urid->OSC_bundleItems, items,
+ 0);
+
+ if(!*timetag || ((*timetag)->atom.type != osc_urid->ATOM_Object) || ((*timetag)->body.otype != osc_urid->OSC_Timetag))
+ return false;
+ if(!*items || ((*items)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ return lv2_osc_bundle_body_get(osc_urid, obj->atom.size, &obj->body,
+ timetag, items);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ assert(path && arguments);
+
+ *path = NULL;
+ *arguments = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_messagePath, path,
+ osc_urid->OSC_messageArguments, arguments,
+ 0);
+
+ if(!*path || ((*path)->atom.type != osc_urid->ATOM_String))
+ return false;
+ // message without arguments is valid
+ if( *arguments && ((*arguments)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ return lv2_osc_message_body_get(osc_urid, obj->atom.size, &obj->body,
+ path, arguments);
+}
+
+static inline bool
+lv2_osc_body_unroll(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ LV2_OSC_Method method, void *data)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(!lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data))
+ return false;
+ }
+
+ return true;
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(!lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ return false;
+
+ if(method)
+ method(LV2_ATOM_BODY_CONST(path), arguments, data);
+
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool
+lv2_osc_unroll(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ LV2_OSC_Method method, void *data)
+{
+ return lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_UTIL_H
diff --git a/osc.lv2/osc.lv2/writer.h b/osc.lv2/osc.lv2/writer.h
new file mode 100644
index 0000000..d11cfb6
--- /dev/null
+++ b/osc.lv2/osc.lv2/writer.h
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_WRITER_H
+#define LV2_OSC_WRITER_H
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/endian.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _LV2_OSC_Writer LV2_OSC_Writer;
+typedef struct _LV2_OSC_Writer_Frame LV2_OSC_Writer_Frame;
+
+struct _LV2_OSC_Writer {
+ uint8_t *buf;
+ uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Writer_Frame {
+ uint8_t *ref;
+};
+
+static inline void
+lv2_osc_writer_initialize(LV2_OSC_Writer *writer, uint8_t *buf, size_t size)
+{
+ writer->buf = buf;
+ writer->ptr = buf;
+ writer->end = buf + size;
+}
+
+static inline size_t
+lv2_osc_writer_get_size(LV2_OSC_Writer *writer)
+{
+ if(writer->ptr > writer->buf)
+ return writer->ptr - writer->buf;
+
+ return 0;
+}
+
+static inline uint8_t *
+lv2_osc_writer_finalize(LV2_OSC_Writer *writer, size_t *size)
+{
+ *size = lv2_osc_writer_get_size(writer);
+
+ if(*size)
+ return writer->buf;
+
+ return NULL;
+}
+
+static inline bool
+lv2_osc_writer_overflow(LV2_OSC_Writer *writer, size_t size)
+{
+ return writer->ptr + size >= writer->end;
+}
+
+static inline bool
+lv2_osc_writer_htobe32(LV2_OSC_Writer *writer, union swap32_t *s32)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ s32->u = htobe32(s32->u);
+ *(uint32_t *)writer->ptr = s32->u;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_htobe64(LV2_OSC_Writer *writer, union swap64_t *s64)
+{
+ if(lv2_osc_writer_overflow(writer, 8))
+ return false;
+
+ s64->u = htobe64(s64->u);
+ *(uint64_t *)writer->ptr = s64->u;
+ writer->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_int32(LV2_OSC_Writer *writer, int32_t i)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .i = i });
+}
+
+static inline bool
+lv2_osc_writer_add_float(LV2_OSC_Writer *writer, float f)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .f = f });
+}
+
+static inline bool
+lv2_osc_writer_add_string(LV2_OSC_Writer *writer, const char *s)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(s) + 1);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ strncpy((char *)writer->ptr, s, padded);
+ writer->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_symbol(LV2_OSC_Writer *writer, const char *S)
+{
+ return lv2_osc_writer_add_string(writer, S);
+}
+
+static inline bool
+lv2_osc_writer_add_int64(LV2_OSC_Writer *writer, int64_t h)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .h = h });
+}
+
+static inline bool
+lv2_osc_writer_add_double(LV2_OSC_Writer *writer, double d)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .d = d });
+}
+
+static inline bool
+lv2_osc_writer_add_timetag(LV2_OSC_Writer *writer, uint64_t u)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .u = u });
+}
+
+static inline bool
+lv2_osc_writer_add_blob_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **body)
+{
+ const size_t len_padded = LV2_OSC_PADDED_SIZE(len);
+ const size_t size = 4 + len_padded;
+ if(lv2_osc_writer_overflow(writer, size))
+ return false;
+
+ if(!lv2_osc_writer_add_int32(writer, len))
+ return false;
+
+ *body = writer->ptr;
+ memset(&writer->ptr[len], 0x0, len_padded - len);
+ writer->ptr += len_padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_blob(LV2_OSC_Writer *writer, int32_t len, const uint8_t *body)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_blob_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, body, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **m)
+{
+ if( (len > 4) || lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ *m = writer->ptr;
+ memset(&writer->ptr[len], 0x0, 4 - len);
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi(LV2_OSC_Writer *writer, int32_t len, const uint8_t *m)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_midi_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, m, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_rgba(LV2_OSC_Writer *writer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ writer->ptr[0] = r;
+ writer->ptr[1] = g;
+ writer->ptr[2] = b;
+ writer->ptr[3] = a;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_char(LV2_OSC_Writer *writer, char c)
+{
+ return lv2_osc_writer_add_int32(writer, (int32_t)c);
+}
+
+static inline bool
+lv2_osc_writer_push_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame, uint64_t t)
+{
+ if(lv2_osc_writer_overflow(writer, 16))
+ return false;
+
+ frame->ref = writer->ptr;
+
+ strncpy((char *)writer->ptr, "#bundle", 8);
+ writer->ptr += 8;
+
+ return lv2_osc_writer_add_timetag(writer, t);
+}
+
+static inline bool
+lv2_osc_writer_pop_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 16};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_push_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ frame->ref = writer->ptr;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_pop_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 4};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ s32.u = htobe32(s32.u);
+ *(uint32_t *)frame->ref = s32.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_path(LV2_OSC_Writer *writer, const char *path)
+{
+ return lv2_osc_writer_add_string(writer, path);
+}
+
+static inline bool
+lv2_osc_writer_add_format(LV2_OSC_Writer *writer, const char *fmt)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(fmt) + 2);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ *writer->ptr++ = ',';
+ strncpy((char *)writer->ptr, fmt, padded - 1);
+ writer->ptr += padded - 1;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_writer_add_int32(writer, va_arg(args, int32_t)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_writer_add_float(writer, (float)va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_writer_add_string(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!lv2_osc_writer_add_blob(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_writer_add_int64(writer, va_arg(args, int64_t)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_writer_add_double(writer, va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_writer_add_timetag(writer, va_arg(args, uint64_t)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!lv2_osc_writer_add_midi(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_writer_add_symbol(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_writer_add_char(writer, va_arg(args, int)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!lv2_osc_writer_add_rgba(writer, va_arg(args, unsigned), va_arg(args, unsigned),
+ va_arg(args, unsigned), va_arg(args, unsigned)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_vararg(LV2_OSC_Writer *writer, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_arg_varlist(writer, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_message_varlist(LV2_OSC_Writer *writer, const char *path, const char *fmt, va_list args)
+{
+ if(!lv2_osc_writer_add_path(writer, path))
+ return false;
+
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ return lv2_osc_writer_arg_varlist(writer, fmt, args);
+}
+
+static inline bool
+lv2_osc_writer_message_vararg(LV2_OSC_Writer *writer, const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_message_varlist(writer, path, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_packet(LV2_OSC_Writer *writer, LV2_OSC_URID *osc_urid,
+ LV2_URID_Unmap *unmap, uint32_t size, const LV2_Atom_Object_Body *body)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ LV2_OSC_Writer_Frame bndl;
+
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+ if(!lv2_osc_writer_push_bundle(writer, &bndl, lv2_osc_timetag_parse(&tt)))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+ LV2_OSC_Writer_Frame itm;
+
+ if( !lv2_osc_writer_push_item(writer, &itm)
+ || !lv2_osc_writer_packet(writer, osc_urid, unmap, obj->atom.size, &obj->body)
+ || !lv2_osc_writer_pop_item(writer, &itm) )
+ {
+ return false;
+ }
+ }
+
+ return lv2_osc_writer_pop_bundle(writer, &bndl);
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ {
+ if(!lv2_osc_writer_add_path(writer, LV2_ATOM_BODY_CONST(path)))
+ return false;
+
+ char fmt [128]; //TODO how big?
+ char *ptr = fmt;
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ *ptr++ = lv2_osc_argument_type(osc_urid, atom);
+ }
+ *ptr = '\0';
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ {
+ if(!lv2_osc_writer_add_int32(writer, ((const LV2_Atom_Int *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Float)
+ {
+ if(!lv2_osc_writer_add_float(writer, ((const LV2_Atom_Float *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_String)
+ {
+ if(!lv2_osc_writer_add_string(writer, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ {
+ if(!lv2_osc_writer_add_blob(writer, atom->size, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ {
+ if(!lv2_osc_writer_add_int64(writer, ((const LV2_Atom_Long *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Double)
+ {
+ if(!lv2_osc_writer_add_double(writer, ((const LV2_Atom_Double *)atom)->body))
+ return false;
+ }
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &obj->atom, &tt);
+ if(!lv2_osc_writer_add_timetag(writer, lv2_osc_timetag_parse(&tt)))
+ return false;
+ }
+
+ // there is nothing to do for: true, false, nil, impulse
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ {
+ const char *symbol = unmap->unmap(unmap->handle, ((const LV2_Atom_URID *)atom)->body);
+ if(!symbol || !lv2_osc_writer_add_symbol(writer, symbol))
+ return false;
+ }
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ {
+ uint8_t *m = NULL;
+ if(!lv2_osc_writer_add_midi_inline(writer, atom->size + 1, &m))
+ return false;
+ m[0] = 0x0; // port
+ memcpy(&m[1], LV2_ATOM_BODY_CONST(atom), atom->size);
+ }
+ else if(atom->type == osc_urid->OSC_Char)
+ {
+ const char c = *(const char *)LV2_ATOM_BODY_CONST(atom);
+ if(!lv2_osc_writer_add_char(writer, c))
+ return false;
+ }
+ else if(atom->type == osc_urid->OSC_RGBA)
+ {
+ const uint8_t *rgba = LV2_ATOM_BODY_CONST(atom);
+ if(!lv2_osc_writer_add_rgba(writer, rgba[0], rgba[1], rgba[2], rgba[3]))
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_WRITER_H
diff --git a/osc.lv2/osc.ttl b/osc.lv2/osc.ttl
new file mode 100644
index 0000000..db4a048
--- /dev/null
+++ b/osc.lv2/osc.ttl
@@ -0,0 +1,42 @@
+# 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.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@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:Datatype ;
+ rdfs:subClassOf atom:Atom ;
+ owl:onDatatype xsd:hexBinary ;
+ rdfs:label "OSC Event (Bundle or Message)" .
diff --git a/osc.lv2/osc_test.c b/osc.lv2/osc_test.c
new file mode 100644
index 0000000..1280fc0
--- /dev/null
+++ b/osc.lv2/osc_test.c
@@ -0,0 +1,423 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/reader.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+
+#define BUF_SIZE 8192
+#define MAX_URIDS 512
+
+typedef void (*test_t)(LV2_OSC_Writer *writer);
+typedef struct _urid_t urid_t;
+typedef struct _handle_t handle_t;
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+struct _handle_t {
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+static handle_t __handle;
+static uint8_t buf0 [BUF_SIZE];
+static uint8_t buf1 [BUF_SIZE];
+static uint8_t buf2 [BUF_SIZE];
+static const LV2_Atom_Object *obj2= (const LV2_Atom_Object *)buf2;
+
+const uint8_t raw_0 [] = {
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_1 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'i', 'f', 's',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x59, 0x99, 0x9a,
+ 'w', 'o', 'r', 'l',
+ 'd', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_2 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'h', 'd', 'S',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x0b, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33,
+ 'h', 't', 't', 'p',
+ ':', '/', '/', 'e',
+ 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c',
+ 'o', 'm', 0x0, 0x0
+};
+
+const uint8_t raw_3 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'T', 'F', 'N',
+ 'I', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_4 [] = {
+ '/', 'm', 'i', 'd',
+ 'i', 0x0, 0x0, 0x0,
+ ',', 'm', 0x0, 0x0,
+ 0x0, 0x90, 24, 0x7f
+};
+
+const uint8_t raw_5 [] = {
+ '/', 'b', 'l', 'o',
+ 'b', 0x0, 0x0, 0x0,
+ ',', 'b', 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x6,
+ 0x1, 0x2, 0x3, 0x4,
+ 0x5, 0x6, 0x0, 0x0
+};
+
+const uint8_t raw_6 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_7 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x1c,
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(handle->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++handle->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const char *
+_unmap(LV2_URID_Unmap_Handle instance, LV2_URID urid)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(itm->urid == urid)
+ return itm->uri;
+ }
+
+ // not found
+ return NULL;
+}
+
+static LV2_URID_Map map = {
+ .handle = &__handle,
+ .map = _map
+};
+
+static LV2_URID_Unmap unmap = {
+ .handle = &__handle,
+ .unmap = _unmap
+};
+
+static void
+_dump(const uint8_t *src, const uint8_t *dst, size_t size)
+{
+ for(size_t i = 0; i < size; i++)
+ printf("%zu %02x %02x\n", i, src[i], dst[i]);
+ printf("\n");
+}
+
+static void
+_clone(LV2_OSC_Reader *reader, LV2_OSC_Writer *writer, size_t size)
+{
+ if(lv2_osc_reader_is_bundle(reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(reader, size);
+ assert(itm);
+
+ LV2_OSC_Writer_Frame frame_bndl;
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, itm->timetag));
+
+ OSC_READER_BUNDLE_ITERATE(reader, itm)
+ {
+ LV2_OSC_Reader reader2;
+ lv2_osc_reader_initialize(&reader2, itm->body, itm->size);
+
+ LV2_OSC_Writer_Frame frame_itm;
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ _clone(&reader2, writer, itm->size);
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+ }
+ else if(lv2_osc_reader_is_message(reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, size);
+ assert(arg);
+
+ assert(lv2_osc_writer_add_path(writer, arg->path));
+ assert(lv2_osc_writer_add_format(writer, arg->type));
+
+ OSC_READER_MESSAGE_ITERATE(reader, arg)
+ {
+ switch((LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ assert(lv2_osc_writer_add_int32(writer, arg->i));
+ break;
+ case LV2_OSC_FLOAT:
+ assert(lv2_osc_writer_add_float(writer, arg->f));
+ break;
+ case LV2_OSC_STRING:
+ assert(lv2_osc_writer_add_string(writer, arg->s));
+ break;
+ case LV2_OSC_BLOB:
+ assert(lv2_osc_writer_add_blob(writer, arg->size, arg->b));
+ break;
+
+ case LV2_OSC_INT64:
+ assert(lv2_osc_writer_add_int64(writer, arg->h));
+ break;
+ case LV2_OSC_DOUBLE:
+ assert(lv2_osc_writer_add_double(writer, arg->d));
+ break;
+ case LV2_OSC_TIMETAG:
+ assert(lv2_osc_writer_add_timetag(writer, arg->t));
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_MIDI:
+ assert(lv2_osc_writer_add_midi(writer, arg->size, arg->m));
+ break;
+ case LV2_OSC_SYMBOL:
+ assert(lv2_osc_writer_add_symbol(writer, arg->S));
+ break;
+ case LV2_OSC_CHAR:
+ assert(lv2_osc_writer_add_char(writer, arg->c));
+ break;
+ case LV2_OSC_RGBA:
+ assert(lv2_osc_writer_add_rgba(writer, arg->R, arg->G, arg->B, arg->A));
+ break;
+ }
+ }
+ }
+}
+
+static void
+_test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
+{
+ LV2_OSC_URID osc_urid;
+ lv2_osc_urid_init(&osc_urid, &map);
+
+ // check writer against raw bytes
+ size_t len;
+ assert(lv2_osc_writer_finalize(writer, &len) == buf0);
+ assert(len == size);
+ assert(memcmp(raw, buf0, size) == 0);
+
+ // check reader & writer
+ LV2_OSC_Reader reader;
+ lv2_osc_reader_initialize(&reader, buf0, size);
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ _clone(&reader, writer, size);
+
+ // check cloned against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+ assert(memcmp(raw, buf1, size) == 0);
+
+ // check forge
+ LV2_Atom_Forge forge;
+ lv2_atom_forge_init(&forge, &map);
+ lv2_atom_forge_set_buffer(&forge, buf2, BUF_SIZE);
+ assert(lv2_osc_forge_packet(&forge, &osc_urid, &map, buf0, size));
+
+ // check deforge
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ assert(lv2_osc_writer_packet(writer, &osc_urid, &unmap, obj2->atom.size, &obj2->body));
+
+ // check deforged against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+ assert(memcmp(raw, buf1, size) == 0);
+}
+
+static void
+test_0_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ _test_a(writer, raw_0, sizeof(raw_0));
+}
+
+static void
+test_1_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "ifs",
+ 12, 3.4f, "world"));
+ _test_a(writer, raw_1, sizeof(raw_1));
+}
+
+static void
+test_2_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "hdS",
+ 12, 3.4, "http://example.com"));
+ _test_a(writer, raw_2, sizeof(raw_2));
+}
+
+static void
+test_3_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "TFNI"));
+ _test_a(writer, raw_3, sizeof(raw_3));
+}
+
+static void
+test_4_a(LV2_OSC_Writer *writer)
+{
+ uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", 4, m));
+ _test_a(writer, raw_4, sizeof(raw_4));
+}
+
+static void
+test_5_a(LV2_OSC_Writer *writer)
+{
+ uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", 6, b));
+ _test_a(writer, raw_5, sizeof(raw_5));
+}
+
+static void
+test_6_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl, frame_itm;
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+
+ _test_a(writer, raw_6, sizeof(raw_6));
+}
+
+static void
+test_7_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl[2], frame_itm[2];
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[0], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[1], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[1]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[1]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[1]));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[0]));
+
+ _test_a(writer, raw_7, sizeof(raw_7));
+}
+
+static test_t tests [] = {
+ test_0_a,
+ test_1_a,
+ test_2_a,
+ test_3_a,
+ test_4_a,
+ test_5_a,
+ test_6_a,
+ test_7_a,
+
+ NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ LV2_OSC_Writer writer;
+
+ for(test_t *test=tests; *test; test++)
+ {
+ test_t cb = *test;
+
+ memset(buf0, 0x0, BUF_SIZE);
+ memset(buf1, 0x0, BUF_SIZE);
+
+ lv2_osc_writer_initialize(&writer, buf0, BUF_SIZE);
+
+ cb(&writer);
+ }
+
+ return 0;
+}
diff --git a/osc_inspector.c b/osc_inspector.c
new file mode 100644
index 0000000..45b7a73
--- /dev/null
+++ b/osc_inspector.c
@@ -0,0 +1,192 @@
+/*
+ * 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 <osc.lv2/util.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+
+ LV2_OSC_URID osc_urid;
+
+ int64_t frame;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ {
+ if(obj->body.otype == handle->time_position)
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - ev->time.frames;
+ }
+ }
+ }
+
+ // size of input sequence
+ size_t size = sizeof(LV2_Atom) + handle->control_in->atom.size;
+
+ // copy whole input sequence to through port
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_raw(forge, handle->control_in, size);
+ if(!ref)
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+
+ bool has_osc = false;
+
+ ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0);
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &frame[1]);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0);
+
+ // only serialize OSC events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ has_osc = true;
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[2]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[0]);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ if(!has_osc)
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+const LV2_Descriptor osc_inspector = {
+ .URI = SHERLOCK_OSC_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
diff --git a/osc_inspector_eo.c b/osc_inspector_eo.c
new file mode 100644
index 0000000..3738d3a
--- /dev/null
+++ b/osc_inspector_eo.c
@@ -0,0 +1,951 @@
+/*
+ * 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 <osc.lv2/util.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_URID_Unmap *unmap;
+ LV2_Atom_Forge forge;
+ LV2_URID event_transfer;
+ LV2_URID midi_event;
+ LV2_OSC_URID osc_urid;
+
+ 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 *
+_timetag_stringify(UI *ui, char *ptr, char *end, uint64_t body)
+{
+ const uint32_t sec = body >> 32;
+ const uint32_t frac = 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(lv2_osc_is_message_type(&ui->osc_urid, obj->body.otype))
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *tup = NULL;
+ lv2_osc_message_get(&ui->osc_urid, obj, &path, &tup);
+
+ sprintf(ptr, CODE_PRE);
+ ptr += strlen(ptr);
+
+ if(path)
+ {
+ sprintf(ptr, PATH(" %s"), LV2_ATOM_BODY_CONST(path));
+ }
+ else
+ {
+ sprintf(ptr, " unknown path and/or format string");
+ }
+ ptr += strlen(ptr);
+
+ LV2_ATOM_TUPLE_FOREACH(tup, itm)
+ {
+ switch(lv2_osc_argument_type(&ui->osc_urid, itm))
+ {
+ case LV2_OSC_INT32:
+ {
+ sprintf(ptr, TYPE(" i:", "%"PRIi32), ((const LV2_Atom_Int *)itm)->body);
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ sprintf(ptr, TYPE(" f:", "%f"), ((const LV2_Atom_Float *)itm)->body);
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_STRING: // fall-through
+ {
+ const char *str = LV2_ATOM_BODY_CONST(itm);
+ if(itm->size == 0)
+ str = "";
+ sprintf(ptr, TYPE(" s:", ""));
+ ptr += strlen(ptr);
+
+ for(unsigned i=0; i<strlen(str) + 1; i++)
+ {
+ switch(str[i])
+ {
+ case '<':
+ strncpy(ptr, "&lt;", 4);
+ ptr += 4;
+ break;
+ case '>':
+ strncpy(ptr, "&gt;", 4);
+ ptr += 4;
+ break;
+ case '&':
+ strncpy(ptr, "&amp;", 5);
+ ptr += 5;
+ break;
+ case '\n':
+ strncpy(ptr, "\\n", 2);
+ ptr += 2;
+ break;
+ case '\r':
+ strncpy(ptr, "\\r", 2);
+ ptr += 2;
+ break;
+ default:
+ *ptr++ = str[i];
+ break;
+ }
+ }
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ 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 LV2_OSC_INT64:
+ {
+ sprintf(ptr, TYPE(" h:", "%"PRIi64), ((const LV2_Atom_Long *)itm)->body);
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ sprintf(ptr, TYPE(" d:", "%lf"), ((const LV2_Atom_Double *)itm)->body);
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&ui->osc_urid, itm, &tt);
+ ptr = _timetag_stringify(ui, ptr, end, lv2_osc_timetag_parse(&tt));
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ sprintf(ptr, TYPE(" S:", "%s"), ui->unmap->unmap(ui->unmap->handle, ((const LV2_Atom_URID *)itm)->body));
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ 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 LV2_OSC_CHAR:
+ {
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, itm);
+ sprintf(ptr, TYPE(" c:", "%c"), str[0]);
+ ptr += strlen(ptr);
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, itm);
+ sprintf(ptr, TYPE(" r:", "%s"), str);
+ ptr += strlen(ptr);
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ //if(itm->type == ui->forge.Bool)
+ {
+ sprintf(ptr, TYPE(" T:", "true"));
+ ptr += strlen(ptr);
+ }
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ //if(itm->type == ui->forge.Bool)
+ {
+ sprintf(ptr, TYPE(" F:", "false"));
+ ptr += strlen(ptr);
+ }
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ //if(itm->type == 0)
+ {
+ sprintf(ptr, TYPE(" N:", "nil"));
+ ptr += strlen(ptr);
+ }
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ //if(itm->type == ui->forge.Impulse)
+ {
+ sprintf(ptr, TYPE(" I:", "impulse"));
+ ptr += strlen(ptr);
+ }
+ break;
+ }
+ }
+ }
+
+ sprintf(ptr, CODE_POST);
+
+ return ptr + strlen(ptr);
+ }
+ else if(lv2_osc_is_bundle_type(&ui->osc_urid, obj->body.otype))
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *tup = NULL;
+ lv2_osc_bundle_get(&ui->osc_urid, obj, &timetag, &tup);
+
+ sprintf(ptr, CODE_PRE);
+ ptr += strlen(ptr);
+
+ sprintf(ptr, BUNDLE(" #bundle"));
+ ptr += strlen(ptr);
+
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&ui->osc_urid, &timetag->atom, &tt);
+ ptr = _timetag_stringify(ui, ptr, end, lv2_osc_timetag_parse(&tt));
+
+ 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 && lv2_osc_is_bundle_type(&ui->osc_urid, _obj->body.otype))
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *tup = NULL;
+ lv2_osc_bundle_get(&ui->osc_urid, _obj, &timetag, &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(lv2_osc_is_bundle_type(&ui->osc_urid, _obj2->body.otype))
+ elm_genlist_item_expanded_set(itm2, EINA_TRUE);
+ }
+ }
+ }
+}
+
+static void
+_item_contracted(void *data, Evas_Object *obj, void *event_info)
+{
+ Elm_Object_Item *itm = event_info;
+ UI *ui = data;
+
+ elm_genlist_item_subitems_clear(itm);
+}
+
+static Evas_Object *
+_group_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const position_t *pos = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static void
+_clear_update(UI *ui, int count)
+{
+ if(!ui->clear)
+ return;
+
+ char *buf = ui->string_buf;
+ sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX);
+ elm_object_text_set(ui->clear, buf);
+}
+
+static void
+_clear_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ if(ui->list)
+ elm_genlist_clear(ui->list);
+
+ _clear_update(ui, 0);
+}
+
+static void
+_info_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ // toggle popup
+ if(ui->popup)
+ {
+ if(evas_object_visible_get(ui->popup))
+ evas_object_hide(ui->popup);
+ else
+ evas_object_show(ui->popup);
+ }
+}
+
+static void
+_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ ui->widget = NULL;
+}
+
+static void
+_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ evas_object_del(ui->widget);
+}
+
+static Evas_Object *
+_content_get(UI *ui, Evas_Object *parent)
+{
+ ui->table = elm_table_add(parent);
+ if(ui->table)
+ {
+ elm_table_homogeneous_set(ui->table, EINA_FALSE);
+ elm_table_padding_set(ui->table, 0, 0);
+ evas_object_size_hint_min_set(ui->table, 600, 800);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui);
+
+ ui->list = elm_genlist_add(ui->table);
+ if(ui->list)
+ {
+ elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT);
+ elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading
+ elm_genlist_reorder_mode_set(ui->list, EINA_FALSE);
+ elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ evas_object_data_set(ui->list, "ui", ui);
+ evas_object_smart_callback_add(ui->list, "expand,request",
+ _item_expand_request, ui);
+ evas_object_smart_callback_add(ui->list, "contract,request",
+ _item_contract_request, ui);
+ evas_object_smart_callback_add(ui->list, "expanded", _item_expanded, ui);
+ evas_object_smart_callback_add(ui->list, "contracted", _item_contracted, ui);
+ evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->list);
+ elm_table_pack(ui->table, ui->list, 0, 0, 4, 1);
+ }
+
+ ui->clear = elm_button_add(ui->table);
+ if(ui->clear)
+ {
+ _clear_update(ui, 0);
+ evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui);
+ evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f);
+ evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->clear);
+ elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1);
+ }
+
+ ui->autoclear = elm_check_add(ui->table);
+ if(ui->autoclear)
+ {
+ elm_object_text_set(ui->autoclear, "overwrite");
+ evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoclear);
+ elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1);
+ }
+
+ ui->autoblock = elm_check_add(ui->table);
+ if(ui->autoblock)
+ {
+ elm_object_text_set(ui->autoblock, "block");
+ evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoblock);
+ elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1);
+ }
+
+ Evas_Object *info = elm_button_add(ui->table);
+ if(info)
+ {
+ evas_object_smart_callback_add(info, "clicked", _info_clicked, ui);
+ evas_object_size_hint_weight_set(info, 0.f, 0.f);
+ evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL);
+ evas_object_show(info);
+ elm_table_pack(ui->table, info, 3, 1, 1, 1);
+
+ Evas_Object *icon = elm_icon_add(info);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 20, 20);
+ evas_object_size_hint_max_set(icon, 32, 32);
+ //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_object_part_content_set(info, "icon", icon);
+ }
+ }
+
+ ui->popup = elm_popup_add(ui->table);
+ if(ui->popup)
+ {
+ elm_popup_allow_events_set(ui->popup, EINA_TRUE);
+
+ Evas_Object *hbox = elm_box_add(ui->popup);
+ if(hbox)
+ {
+ elm_box_horizontal_set(hbox, EINA_TRUE);
+ elm_box_homogeneous_set(hbox, EINA_FALSE);
+ elm_box_padding_set(hbox, 10, 0);
+ evas_object_show(hbox);
+ elm_object_content_set(ui->popup, hbox);
+
+ Evas_Object *icon = elm_icon_add(hbox);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 128, 128);
+ evas_object_size_hint_max_set(icon, 256, 256);
+ evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_box_pack_end(hbox, icon);
+ }
+
+ Evas_Object *label = elm_label_add(hbox);
+ if(label)
+ {
+ elm_object_text_set(label,
+ "<color=#b00 shadow_color=#fff font_size=20>"
+ "Sherlock - OSC Inspector"
+ "</color></br><align=left>"
+ "Version "SHERLOCK_VERSION"</br></br>"
+ "Copyright (c) 2015 Hanspeter Portner</br></br>"
+ "This is free and libre software</br>"
+ "Released under Artistic License 2.0</br>"
+ "By Open Music Kontrollers</br></br>"
+ "<color=#bbb>"
+ "http://open-music-kontrollers.ch/lv2/sherlock</br>"
+ "dev@open-music-kontrollers.ch"
+ "</color></align>");
+
+ evas_object_show(label);
+ elm_box_pack_end(hbox, label);
+ }
+ }
+ }
+ }
+
+ return ui->table;
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ if(strcmp(plugin_uri, SHERLOCK_OSC_INSPECTOR_URI))
+ return NULL;
+
+ UI *ui = calloc(1, sizeof(UI));
+ if(!ui)
+ return NULL;
+
+ ui->write_function = write_function;
+ ui->controller = controller;
+
+ Evas_Object *parent = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ ui->map = features[i]->data;
+ 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;
+ }
+
+ 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);
+ lv2_osc_urid_init(&ui->osc_urid, ui->map);
+
+ ui->itc_packet = elm_genlist_item_class_new();
+ if(ui->itc_packet)
+ {
+ ui->itc_packet->item_style = "default_style";
+ ui->itc_packet->func.text_get = _packet_label_get;
+ ui->itc_packet->func.content_get = _packet_content_get;
+ ui->itc_packet->func.state_get = NULL;
+ ui->itc_packet->func.del = _del;
+ }
+
+ ui->itc_item = elm_genlist_item_class_new();
+ if(ui->itc_item)
+ {
+ ui->itc_item->item_style = "default_style";
+ ui->itc_item->func.text_get = _item_label_get;
+ ui->itc_item->func.content_get = _item_content_get;
+ ui->itc_item->func.state_get = NULL;
+ ui->itc_item->func.del = NULL;
+ }
+
+ ui->itc_group = elm_genlist_item_class_new();
+ if(ui->itc_group)
+ {
+ ui->itc_group->item_style = "default_style";
+ ui->itc_group->func.text_get = NULL;
+ ui->itc_group->func.content_get = _group_item_content_get;
+ ui->itc_group->func.state_get = NULL;
+ ui->itc_group->func.del = _del;
+ }
+
+ sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path);
+ ui->logo_path = strdup(ui->string_buf);
+
+ ui->widget = _content_get(ui, parent);
+ if(!ui->widget)
+ {
+ free(ui);
+ return NULL;
+ }
+ *(Evas_Object **)widget = ui->widget;
+
+ return ui;
+}
+
+static void
+cleanup(LV2UI_Handle handle)
+{
+ UI *ui = handle;
+
+ if(ui->widget)
+ evas_object_del(ui->widget);
+ if(ui->logo_path)
+ free(ui->logo_path);
+
+ if(ui->itc_packet)
+ elm_genlist_item_class_free(ui->itc_packet);
+ if(ui->itc_item)
+ elm_genlist_item_class_free(ui->itc_item);
+
+ free(ui);
+}
+
+static void
+port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid,
+ const void *buf)
+{
+ UI *ui = handle;
+
+ if( (i == 2) && (urid == ui->event_transfer) && ui->list)
+ {
+ const LV2_Atom_Tuple *tup = buf;
+ const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom);
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom);
+ int n = elm_genlist_items_count(ui->list);
+
+ Elm_Object_Item *itm = NULL;
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events
+ {
+ position_t *pos = malloc(sizeof(position_t));
+ if(!pos)
+ return;
+
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ return;
+ }
+
+ itm = elm_genlist_item_append(ui->list, ui->itc_group,
+ pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE);
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt)
+ {
+ size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size;
+ LV2_Atom_Event *ev = malloc(len);
+ if(!ev)
+ continue;
+
+ memcpy(ev, elmnt, len);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_packet,
+ ev, itm, ELM_GENLIST_ITEM_TREE, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ elm_genlist_item_expanded_set(itm2, EINA_FALSE);
+ n++;
+
+ // scroll to last item
+ //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE);
+ }
+ }
+
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ _clear_update(ui, n); // only update if there where any events
+ }
+}
+
+const LV2UI_Descriptor osc_inspector_eo = {
+ .URI = SHERLOCK_OSC_INSPECTOR_EO_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = NULL
+};
diff --git a/sandbox_ui.lv2/COPYING b/sandbox_ui.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/sandbox_ui.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/sandbox_ui.lv2/README.md b/sandbox_ui.lv2/README.md
new file mode 100644
index 0000000..d9f1d9f
--- /dev/null
+++ b/sandbox_ui.lv2/README.md
@@ -0,0 +1,18 @@
+# LV2 sandboxed UI
+
+### License
+
+Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/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 59344a0..59344a0 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 aa11427..aa11427 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 b0c3bec..b0c3bec 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 dcd78bb..dcd78bb 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 559ebc9..559ebc9 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..00e36e9
--- /dev/null
+++ b/sherlock.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _SHERLOCK_LV2_H
+#define _SHERLOCK_LV2_H
+
+#include <stdint.h>
+
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/extensions/units/units.h"
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define LV2_OSC__OscEvent "http://opensoundcontrol.org#OscEvent"
+
+#define SHERLOCK_URI "http://open-music-kontrollers.ch/lv2/sherlock"
+
+#define SHERLOCK_ATOM_INSPECTOR_URI SHERLOCK_URI"#atom_inspector"
+#define SHERLOCK_ATOM_INSPECTOR_UI_URI SHERLOCK_URI"#atom_inspector_1_ui"
+#define SHERLOCK_ATOM_INSPECTOR_KX_URI SHERLOCK_URI"#atom_inspector_2_kx"
+#define SHERLOCK_ATOM_INSPECTOR_EO_URI SHERLOCK_URI"#atom_inspector_3_eo"
+
+#define SHERLOCK_MIDI_INSPECTOR_URI SHERLOCK_URI"#midi_inspector"
+#define SHERLOCK_MIDI_INSPECTOR_UI_URI SHERLOCK_URI"#midi_inspector_1_ui"
+#define SHERLOCK_MIDI_INSPECTOR_KX_URI SHERLOCK_URI"#midi_inspector_2_kx"
+#define SHERLOCK_MIDI_INSPECTOR_EO_URI SHERLOCK_URI"#midi_inspector_3_eo"
+
+#define SHERLOCK_OSC_INSPECTOR_URI SHERLOCK_URI"#osc_inspector"
+#define SHERLOCK_OSC_INSPECTOR_UI_URI SHERLOCK_URI"#osc_inspector_1_ui"
+#define SHERLOCK_OSC_INSPECTOR_KX_URI SHERLOCK_URI"#osc_inspector_2_kx"
+#define SHERLOCK_OSC_INSPECTOR_EO_URI SHERLOCK_URI"#osc_inspector_3_eo"
+
+extern const LV2_Descriptor atom_inspector;
+extern const LV2UI_Descriptor atom_inspector_ui;
+extern const LV2UI_Descriptor atom_inspector_kx;
+extern const LV2UI_Descriptor atom_inspector_eo;
+
+extern const LV2_Descriptor midi_inspector;
+extern const LV2UI_Descriptor midi_inspector_ui;
+extern const LV2UI_Descriptor midi_inspector_kx;
+extern const LV2UI_Descriptor midi_inspector_eo;
+
+extern const LV2_Descriptor osc_inspector;
+extern const LV2UI_Descriptor osc_inspector_ui;
+extern const LV2UI_Descriptor osc_inspector_kx;
+extern const LV2UI_Descriptor osc_inspector_eo;
+
+typedef struct _position_t position_t;
+
+struct _position_t {
+ uint64_t offset;
+ uint32_t nsamples;
+};
+
+#endif // _SHERLOCK_LV2_H
diff --git a/sherlock.ttl b/sherlock.ttl
new file mode 100644
index 0000000..8d01e23
--- /dev/null
+++ b/sherlock.ttl
@@ -0,0 +1,187 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+
+@prefix xpress: <http://open-music-kontrollers.ch/lv2/xpress#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+xpress:Message
+ a rdfs:Class ,
+ rdfs:Datatype ;
+ rdfs:subClassOf atom:Atom .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:sherlock
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Sherlock Bundle" .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock Atom Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock MIDI Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock OSC Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ time:Position ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] .
diff --git a/sherlock_eo.c b/sherlock_eo.c
new file mode 100644
index 0000000..260f859
--- /dev/null
+++ b/sherlock_eo.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <sherlock.h>
+
+LV2_SYMBOL_EXPORT const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &atom_inspector_eo;
+ case 1:
+ return &midi_inspector_eo;
+ case 2:
+ return &osc_inspector_eo;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock_ui.c b/sherlock_ui.c
new file mode 100644
index 0000000..f28f57d
--- /dev/null
+++ b/sherlock_ui.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <sherlock.h>
+#include <sandbox_ui.h>
+
+const LV2UI_Descriptor atom_inspector_ui= {
+ .URI = SHERLOCK_ATOM_INSPECTOR_UI_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = sandbox_ui_extension_data
+};
+
+const LV2UI_Descriptor atom_inspector_kx= {
+ .URI = SHERLOCK_ATOM_INSPECTOR_KX_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = NULL
+};
+
+const LV2UI_Descriptor midi_inspector_ui= {
+ .URI = SHERLOCK_MIDI_INSPECTOR_UI_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = sandbox_ui_extension_data
+};
+
+const LV2UI_Descriptor midi_inspector_kx= {
+ .URI = SHERLOCK_MIDI_INSPECTOR_KX_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = NULL
+};
+
+const LV2UI_Descriptor osc_inspector_ui= {
+ .URI = SHERLOCK_OSC_INSPECTOR_UI_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = sandbox_ui_extension_data
+};
+
+const LV2UI_Descriptor osc_inspector_kx= {
+ .URI = SHERLOCK_OSC_INSPECTOR_KX_URI,
+ .instantiate = sandbox_ui_instantiate,
+ .cleanup = sandbox_ui_cleanup,
+ .port_event = sandbox_ui_port_event,
+ .extension_data = NULL
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &atom_inspector_ui;
+ case 1:
+ return &atom_inspector_kx;
+
+ case 2:
+ return &midi_inspector_ui;
+ case 3:
+ return &midi_inspector_kx;
+
+ case 4:
+ return &osc_inspector_ui;
+ case 5:
+ return &osc_inspector_kx;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock_ui.ttl b/sherlock_ui.ttl
new file mode 100644
index 0000000..8ac5d5b
--- /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, urid:unmap ;
+ 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, urid:unmap .
+
+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, urid:unmap .
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 */