aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml24
-rw-r--r--.travis.yml33
-rw-r--r--ChangeLog30
-rw-r--r--README.md90
-rw-r--r--VERSION2
-rw-r--r--eteroj.c38
-rw-r--r--eteroj.h82
-rw-r--r--eteroj.ttl324
-rw-r--r--eteroj_cloak.c248
-rw-r--r--eteroj_io.c783
-rw-r--r--eteroj_ninja.c508
-rw-r--r--eteroj_pack.c292
-rw-r--r--eteroj_query.c973
-rw-r--r--jsmn/LICENSE20
-rw-r--r--jsmn/jsmn.c311
-rw-r--r--jsmn/jsmn.h75
-rw-r--r--manifest.ttl.in60
-rw-r--r--meson.build101
-rw-r--r--netatom.lv2/.gitlab-ci.yml62
-rw-r--r--netatom.lv2/COPYING201
-rw-r--r--netatom.lv2/README.md33
-rw-r--r--netatom.lv2/VERSION1
-rw-r--r--netatom.lv2/meson.build38
-rw-r--r--netatom.lv2/netatom.lv2/endian.h120
-rw-r--r--netatom.lv2/netatom.lv2/netatom.h535
-rw-r--r--netatom.lv2/test/netatom_test.c289
-rw-r--r--osc.lv2/.gitlab-ci.yml62
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md33
-rw-r--r--osc.lv2/VERSION1
-rw-r--r--osc.lv2/lv2-osc.doap.ttl40
-rw-r--r--osc.lv2/manifest.ttl23
-rw-r--r--osc.lv2/meson.build36
-rw-r--r--osc.lv2/osc.lv2/endian.h120
-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.h571
-rw-r--r--osc.lv2/osc.lv2/stream.h1379
-rw-r--r--osc.lv2/osc.lv2/util.h505
-rw-r--r--osc.lv2/osc.lv2/writer.h579
-rw-r--r--osc.lv2/osc.ttl42
-rw-r--r--osc.lv2/test/osc_test.c968
-rw-r--r--props.lv2/.gitlab-ci.yml72
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/VERSION1
-rw-r--r--props.lv2/meson.build82
-rw-r--r--props.lv2/props.h (renamed from props.h)0
-rw-r--r--props.lv2/test/chunk.bin (renamed from test/chunk.bin)bin16 -> 16 bytes
-rw-r--r--props.lv2/test/manifest.ttl.in (renamed from test/manifest.ttl.in)0
-rw-r--r--props.lv2/test/props.c (renamed from test/props.c)0
-rw-r--r--props.lv2/test/props.ttl (renamed from test/props.ttl)0
-rw-r--r--props.lv2/test/props_test.c (renamed from test/props_test.c)0
-rw-r--r--varchunk/.gitlab-ci.yml63
-rw-r--r--varchunk/COPYING201
-rw-r--r--varchunk/README.md112
-rw-r--r--varchunk/VERSION1
-rw-r--r--varchunk/meson.build30
-rw-r--r--varchunk/test_varchunk.c254
-rw-r--r--varchunk/varchunk.h384
60 files changed, 11862 insertions, 58 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c027e92..f28a6d4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,8 +5,8 @@ stages:
.variables_template: &variables_definition
variables:
- BASE_NAME: "props.lv2"
- PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig"
+ BASE_NAME: "eteroj.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
.common_template: &common_definition
<<: *variables_definition
@@ -19,12 +19,12 @@ stages:
.build_template: &build_definition
<<: *common_definition
script:
- - mkdir build
- - pushd build
- - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} ..
- - cmake .. # needed for darwin
- - make
- - make install
+ - meson --prefix="/opt/${CI_BUILD_NAME}" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
+ - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' -e '/framework/s/-Wl,-soname,.*dylib//g' build/build.ninja
+ - ninja -C build
+ - ninja -C build install
+ - mkdir -p "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/${BASE_NAME}"
+ - cp -r "/opt/${CI_BUILD_NAME}/lib/lv2/${BASE_NAME}/" "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
.universal_linux_template: &universal_linux_definition
image: ventosus/universal-linux-gnu
@@ -52,11 +52,11 @@ i686-linux-gnu:
arm-linux-gnueabihf:
<<: *arm_linux_definition
-x86_64-w64-mingw32:
- <<: *universal_w64_definition
+ #x86_64-w64-mingw32:
+ # <<: *universal_w64_definition
-i686-w64-mingw32:
- <<: *universal_w64_definition
+ #i686-w64-mingw32:
+ # <<: *universal_w64_definition
universal-apple-darwin:
<<: *universal_apple_definition
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fe1ae36
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,33 @@
+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 https://github.com/libuv/libuv/archive/v1.6.1.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 xzf v1.6.1.tar.gz
+ - if [ "$CC" = "clang" ]; then sudo add-apt-repository -y ppa:h-rayflood/llvm-upper; fi
+ - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
+ - sudo apt-get update -qq
+install:
+ - if [ "$CC" = "clang" ]; then sudo apt-get install -y clang-3.6 libstdc++-5-dev; fi
+ - if [ "$CC" = "gcc" ]; then sudo apt-get install -y gcc-5 g++-5; fi
+ - pushd lv2-1.12.0 && ./waf configure --prefix=/usr && ./waf build && sudo ./waf install && popd
+ - pushd serd-0.22.0 && ./waf configure --prefix=/usr && ./waf build && sudo ./waf install && popd
+ - pushd sord-0.14.0 && ./waf configure --prefix=/usr && ./waf build && sudo ./waf install && popd
+ - pushd sratom-0.4.6 && ./waf configure --prefix=/usr && ./waf build && sudo ./waf install && popd
+ - pushd libuv-1.6.1 && sh autogen.sh && ./configure --prefix=/usr && make && sudo make install && popd
+before_script:
+ - if [ "$CC" = "clang" ]; then export CXX="clang++-3.6" CC="clang-3.6" CFLAGS="-ffreestanding"; fi
+ - if [ "$CC" = "gcc" ]; then export CXX="g++-5" CC="gcc-5"; fi
+ - mkdir build && pushd build && cmake .. && popd
+script:
+ - pushd build && make && sudo make install && popd
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..91a249d
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,30 @@
+# Changelog
+
+## [0.6.0] - 15 Apr 2019
+
+### Added
+
+* synchronous mode in eteroj:ninja (needs lock-free urid:map implementation)
+* connection status reporting in eteroj:io
+* connection errors reporting in eteroj:io
+
+### Changed
+
+* build system from CMake to meson
+* from libuv to pure POSIX network sockets
+* increase buffer sizes in eteroj:io
+* from lv2_atom_object_query to lv2_atom_object_get (GCC bug)
+* to fixed size scheduling array in eteroj:io
+* dispatching bundles immediately if no support for osc:sched (host feature)
+
+### Fixed
+
+* endian headers for BSDs
+* running forbidden custom worker thread in eteroj:io
+* broken automatic reconnections for lost UDP/TCP links in eteroj:io
+
+### Deprecated
+
+* support for mingw (POSIX sockets)
+* eteroj:control plugin
+* eteroj:disk plugin
diff --git a/README.md b/README.md
index 08466d2..58f77aa 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,92 @@
-# Props.lv2
+## Eteroj
-## Utility header for property based LV2 plugins
+### Open Sound Control inside LV2 plugin graphs
-### License
+#### Build status
-Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+[![build status](https://gitlab.com/OpenMusicKontrollers/eteroj.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/eteroj.lv2/commits/master)
+
+### Binaries
+
+For GNU/Linux (64-bit, 32-bit, armv7), Windows (64-bit, 32-bit) and MacOS
+(64/32-bit univeral).
+
+To install the plugin bundle on your system, simply copy the __eteroj.lv2__
+folder out of the platform folder of the downloaded package into your
+[LV2 path](http://lv2plug.in/pages/filesystem-hierarchy-standard.html).
+
+#### Stable release
+
+* [eteroj.lv2-0.4.0.zip](https://dl.open-music-kontrollers.ch/eteroj.lv2/stable/eteroj.lv2-0.4.0.zip) ([sig](https://dl.open-music-kontrollers.ch/eteroj.lv2/stable/eteroj.lv2-0.4.0.zip.sig))
+
+#### Unstable (nightly) release
+
+* [eteroj.lv2-latest-unstable.zip](https://dl.open-music-kontrollers.ch/eteroj.lv2/unstable/eteroj.lv2-latest-unstable.zip) ([sig](https://dl.open-music-kontrollers.ch/eteroj.lv2/unstable/eteroj.lv2-latest-unstable.zip.sig))
+
+### Sources
+
+#### Stable release
+
+* [eteroj.lv2-0.4.0.tar.xz](https://git.open-music-kontrollers.ch/lv2/eteroj.lv2/snapshot/eteroj.lv2-0.4.0.tar.xz)
+
+#### Git repository
+
+* <https://git.open-music-kontrollers.ch/lv2/eteroj.lv2>
+
+### Packages
+
+* [ArchLinux](https://www.archlinux.org/packages/community/x86_64/eteroj.lv2/)
+
+### Bugs and feature requests
+
+* [Gitlab](https://gitlab.com/OpenMusicKontrollers/eteroj.lv2)
+* [Github](https://github.com/OpenMusicKontrollers/eteroj.lv2)
+
+### Plugins
+
+#### (De)Cloak
+
+Embed OSC in MIDI Sysex messages. Use this to smuggle arbitrary OSC packets
+via MIDI to a given destination. It does also the opposite of course, e.g
+extract arbitrary OSC packets previously embedded in MIDI Sysex messages.
+
+#### IO
+
+A plugin able to inject/eject [OSC](http://opensoundcontrol.org)
+packets into/from the plugin graph to/from network and serial lines. The
+non-realtime network part of the plugin supports OSC via bidirectional UDP
+and TCP on top of IPv4/IPv6 and OSC via serial lines which is handy for
+interfacing to microcontrollers via U(S)ART and USB. Stream based connections
+(TCP, Serial) support both size-prefix and SLIP framing.
+
+Internally to the plugin graph, OSC packets are routed as first-class
+LV2 Atom objects, making the plugin compliant with any existing hosts.
+
+Timestamped OSC bundles are injected into the plugin graph with sample
+accuracy.
+
+### Ninja
+
+Embed Turtle RDF in OSC as string. Use this to smuggle arbitrary LV2 atom
+messages via OSC to a given destination. It does also the opposite of course,
+e.g. extract and deserialize Turtle RDF embedded in OSC messages to plain
+LV2 atoms.
+
+### (Un)Pack
+
+Embed arbitrary 1-3 byte MIDI commands (but Sysex) in OSC messages. Use this to
+send MIDI commands via OSC to a given destination. It does also the opposite
+of course, e.g. extract MIDI commands embedded in OSC messages to plain MIDI.
+
+### Query
+
+This plugin implements our [OSC Introspect](/osc/introspect/#)
+specification. It thus exports any methods and parameters of a given OSC
+device to transparently to LV2 properties.
+
+#### License
+
+Copyright (c) 2016-2019 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
diff --git a/VERSION b/VERSION
index 66c0104..5f63506 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.131
+0.5.211
diff --git a/eteroj.c b/eteroj.c
new file mode 100644
index 0000000..e446571
--- /dev/null
+++ b/eteroj.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 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 <eteroj.h>
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &eteroj_io;
+ case 1:
+ return &eteroj_query;
+ case 2:
+ return &eteroj_cloak;
+ case 3:
+ return &eteroj_pack;
+ case 4:
+ return &eteroj_ninja;
+ default:
+ return NULL;
+ }
+}
diff --git a/eteroj.h b/eteroj.h
new file mode 100644
index 0000000..efd1269
--- /dev/null
+++ b/eteroj.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 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 _ETEROJ_LV2_H
+#define _ETEROJ_LV2_H
+
+#include <stdint.h>
+#if !defined(_WIN32)
+# include <sys/mman.h>
+#else
+# define mlock(...)
+# define munlock(...)
+#endif
+
+#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/urid/urid.h"
+#include "lv2/lv2plug.in/ns/ext/worker/worker.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/log/logger.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define ETEROJ_URI "http://open-music-kontrollers.ch/lv2/eteroj"
+
+#define ETEROJ_DRAIN_URI ETEROJ_URI"#drain"
+
+#define ETEROJ_EVENT_URI ETEROJ_URI"#event"
+#define ETEROJ_URL_URI ETEROJ_URI"#url"
+#define ETEROJ_CONNECTED_URI ETEROJ_URI"#connected"
+#define ETEROJ_ERROR_URI ETEROJ_URI"#error"
+
+#define ETEROJ_DISK_RECORD_URI ETEROJ_URI"#disk_record"
+#define ETEROJ_DISK_PATH_URI ETEROJ_URI"#disk_path"
+#define ETEROJ_PACK_PATH_URI ETEROJ_URI"#pack_path"
+#define ETEROJ_PACK_FORMAT_URI ETEROJ_URI"#pack_format"
+#define ETEROJ_QUERY_REFRESH_URI ETEROJ_URI"#query_refresh"
+
+// plugin uris
+#define ETEROJ_IO_URI ETEROJ_URI"#io"
+#define ETEROJ_DISK_URI ETEROJ_URI"#disk"
+#define ETEROJ_QUERY_URI ETEROJ_URI"#query"
+#define ETEROJ_CLOAK_URI ETEROJ_URI"#cloak"
+#define ETEROJ_PACK_URI ETEROJ_URI"#pack"
+#define ETEROJ_NINJA_URI ETEROJ_URI"#ninja"
+#define ETEROJ_CONTROL_URI ETEROJ_URI"#control"
+
+extern const LV2_Descriptor eteroj_io;
+extern const LV2_Descriptor eteroj_disk;
+extern const LV2_Descriptor eteroj_query;
+extern const LV2_Descriptor eteroj_cloak;
+extern const LV2_Descriptor eteroj_pack;
+extern const LV2_Descriptor eteroj_ninja;
+extern const LV2_Descriptor eteroj_control;
+
+// 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
+
+#endif // _ETEROJ_LV2_H
diff --git a/eteroj.ttl b/eteroj.ttl
new file mode 100644
index 0000000..b5b8794
--- /dev/null
+++ b/eteroj.ttl
@@ -0,0 +1,324 @@
+# Copyright (c) 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 foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix work: <http://lv2plug.in/ns/ext/worker#> .
+@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 state: <http://lv2plug.in/ns/ext/state#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix eteroj: <http://open-music-kontrollers.ch/lv2/eteroj#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+
+osc:schedule
+ a lv2:Feature .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:eteroj
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Eteroj OSC Bundle" .
+
+eteroj:url
+ a lv2:Parameter ;
+ rdfs:label "Url" ;
+ rdfs:comment "eg. osc.udp://localhost:9090, osc.tcp://[::1]:4040" ;
+ rdfs:range atom:String .
+eteroj:connected
+ a lv2:Parameter ;
+ rdfs:label "Connected" ;
+ rdfs:comment "shows connection status" ;
+ rdfs:range atom:Bool .
+eteroj:error
+ a lv2:Parameter ;
+ rdfs:label "Error" ;
+ rdfs:comment "shows connection errors" ;
+ rdfs:range atom:String .
+
+# IO Plugin
+eteroj:io
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Eteroj IO" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:eteroj ;
+ lv2:requiredFeature urid:map, urid:unmap, work:schedule , state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive , lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData work:interface, state:interface ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "osc_in" ;
+ lv2:name "OSC Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "osc_out" ;
+ lv2:name "OSC Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ # parameters
+ patch:writable
+ eteroj:url ;
+ patch:readable
+ eteroj:connected ,
+ eteroj:error ;
+
+ # default state
+ state:state [
+ eteroj:url "osc.udp://localhost:9090" ;
+ ] .
+
+eteroj:query_refresh
+ a lv2:Parameter ;
+ rdfs:label "Refresh Introspection" ;
+ rdfs:comment "click to refresh introspection" ;
+ rdfs:range atom:Bool .
+
+# Query Plugin
+eteroj:query
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Eteroj Query" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:eteroj ;
+ lv2:requiredFeature urid:map, urid:unmap, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive , lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "osc_in" ;
+ lv2:name "OSC Input" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 65536 ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "osc_out" ;
+ lv2:name "OSC Output" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 65536 ;
+ ] ;
+
+ patch:writable
+ eteroj:query_refresh ;
+
+ state:state [
+ eteroj:query_refresh false ;
+ ] .
+
+# (De)Cloak Plugin
+eteroj:cloak
+ a lv2:Plugin,
+ lv2:ConverterPlugin;
+ doap:name "Eteroj (De)Cloak" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:eteroj ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map, urid:unmap ;
+
+ # input event port
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 0 ;
+ lv2:symbol "event_int" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] .
+
+eteroj:pack_path
+ a lv2:Parameter ;
+ rdfs:label "Path" ;
+ rdfs:comment "OSC path to be used, e.g. /midi" ;
+ rdfs:range atom:String .
+eteroj:pack_format
+ a lv2:Parameter ;
+ rdfs:label "Format" ;
+ rdfs:comment "OSC Argument format to be used for MIDI payload" ;
+ rdfs:range atom:Int ;
+ lv2:minimum 0 ;
+ lv2:maximum 2 ;
+ lv2:scalePoint [ rdfs:label "(m)idi" ; rdf:value 0 ] ;
+ lv2:scalePoint [ rdfs:label "(b)lob" ; rdf:value 1 ] .
+
+# (Un)Pack Plugin
+eteroj:pack
+ a lv2:Plugin,
+ lv2:ConverterPlugin;
+ doap:name "Eteroj (Un)Pack" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:eteroj ;
+ lv2:requiredFeature urid:map, urid:unmap, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive , lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ # input event port
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports midi:MidiEvent ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ atom:supports osc:Event ;
+ atom:supports midi:MidiEvent ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ # parameters
+ patch:writable
+ eteroj:pack_path ,
+ eteroj:pack_format;
+
+ # default state
+ state:state [
+ eteroj:pack_path "/midi" ;
+ eteroj:pack_format 0 ;
+ ] .
+
+eteroj:ninja_synchronous
+ a lv2:Parameter ;
+ rdfs:range atom:Bool ;
+ rdfs:comment "toggle to run (a)synchronously" ;
+ rdfs:label "Synchronous" .
+
+# Ninja Plugin
+eteroj:ninja
+ a lv2:Plugin,
+ lv2:ConverterPlugin;
+ doap:name "Eteroj Ninja" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:eteroj ;
+ lv2:requiredFeature urid:map, urid:unmap, work:schedule, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+
+ # input event port
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ eteroj:ninja_synchronous ;
+
+ state:state [
+ eteroj:ninja_synchronous false
+ ] .
diff --git a/eteroj_cloak.c b/eteroj_cloak.c
new file mode 100644
index 0000000..1414789
--- /dev/null
+++ b/eteroj_cloak.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <eteroj.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+
+#define BUF_SIZE 2048
+
+typedef struct _plughandle_t plughandle_t;
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ struct {
+ LV2_URID midi_MidiEvent;
+ } uris;
+
+ uint8_t buf [BUF_SIZE];
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+ LV2_Atom_Forge forge;
+ LV2_OSC_URID osc_urid;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ handle->unmap = features[i]->data;
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "%s: Host does not support urid:(un)map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->uris.midi_MidiEvent = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+#define SYSEX_START 0xf0
+#define SYSEX_END 0xf7
+
+// 11111111 22222222 33333333 44444444 55555555 66666666 77777777
+// 0xf0 07654321 01111111 02222222 03333333 04444444 05555555 06666666 07777777 0xf7
+
+static uint32_t
+_7bit_decode(uint8_t *dst, const uint8_t *src, uint32_t size)
+{
+ volatile uint8_t *to = dst;
+
+ if( (size < 2) || (src[0] != SYSEX_START) || (src[size - 1] != SYSEX_END) )
+ return 0;
+
+ uint8_t acc = 0x0;
+ for(unsigned i = 0; i < size - 2; i++)
+ {
+ const unsigned shift = i % 8;
+ const uint8_t byt = src[i + 1];
+
+ if(shift == 0)
+ {
+ acc = byt;
+ }
+ else
+ {
+ const uint8_t upp = (acc << (8 - shift)) & 0x80;
+ *to++ = byt | upp;
+ }
+ }
+
+ return to - dst;
+}
+
+static uint32_t
+_7bit_encode(uint8_t *dst, const uint8_t *src, uint32_t size)
+{
+ const unsigned rem = size % 7;
+ uint32_t written = size / 7;
+ written *= 8;
+ if(rem != 0)
+ written += 1 + rem;
+ written += 2; // SYSEX_START + SYSEX_END
+
+ volatile uint8_t *to = dst + written;
+ to--; // got to last valid position
+ *to-- = SYSEX_END;
+
+ uint8_t acc = 0x0;
+ for(int i = size - 1; i >= 0; i--)
+ {
+ const unsigned shift = i % 7;
+ const uint8_t byt = src[i];
+ const uint8_t upp = (byt & 0x80) >> (7 - shift);
+ const uint8_t low = byt & 0x7f;
+
+ acc |= upp;
+ *to-- = low;
+
+ if(shift == 0)
+ {
+ *to-- = acc;
+ acc = 0x0;
+ }
+ }
+
+ *to = SYSEX_START;
+
+ return written;
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ // prepare osc atom forge
+ const uint32_t capacity = handle->event_out->atom.size;
+ LV2_Atom_Forge *forge = &handle->forge;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->event_out, capacity);
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ if(obj->atom.type == handle->uris.midi_MidiEvent)
+ {
+ size_t size = obj->atom.size;
+ const uint8_t *buf = LV2_ATOM_BODY_CONST(&obj->atom);
+
+ memcpy(handle->buf, buf, size);
+ size = _7bit_decode(handle->buf, handle->buf, size);
+ if(size)
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_osc_forge_packet(forge, &handle->osc_urid, handle->map, handle->buf, size);
+ }
+ }
+ else
+ {
+ LV2_OSC_Writer writer;
+ lv2_osc_writer_initialize(&writer, handle->buf, BUF_SIZE);
+ lv2_osc_writer_packet(&writer, &handle->osc_urid, handle->unmap, obj->atom.size, &obj->body);
+ size_t size;
+ uint8_t *ptr = lv2_osc_writer_finalize(&writer, &size);
+
+ if(size)
+ {
+ size = _7bit_encode(handle->buf, handle->buf, size);
+
+ if(size)
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, size, handle->uris.midi_MidiEvent);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, handle->buf, size);
+ if(ref)
+ lv2_atom_forge_pad(forge, size);
+ }
+ }
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+const LV2_Descriptor eteroj_cloak = {
+ .URI = ETEROJ_CLOAK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
diff --git a/eteroj_io.c b/eteroj_io.c
new file mode 100644
index 0000000..c9ec0ee
--- /dev/null
+++ b/eteroj_io.c
@@ -0,0 +1,783 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <eteroj.h>
+#include <varchunk.h>
+#include <osc.lv2/reader.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+#include <osc.lv2/stream.h>
+#include <props.h>
+
+#define BUF_SIZE 0x100000 // 1M
+#define MTU_SIZE 1500
+#define LIST_SIZE 2048
+#define MAX_NPROPS 3
+#define STR_LEN 128
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _list_t list_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _list_t {
+ double frames;
+ size_t size;
+ uint8_t buf [MTU_SIZE];
+};
+
+struct _plugstate_t {
+ char osc_url [STR_LEN];
+ char osc_error [STR_LEN];
+ int32_t osc_connected;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Worker_Schedule *sched;
+ struct {
+ LV2_URID eteroj_connected;
+ LV2_URID eteroj_error;
+ } uris;
+
+ PROPS_T(props, MAX_NPROPS);
+ LV2_OSC_URID osc_urid;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ volatile bool status_updated;
+ bool rolling;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *osc_in;
+ LV2_Atom_Sequence *osc_out;
+
+ LV2_OSC_Schedule *osc_sched;
+ list_t list [LIST_SIZE];
+ unsigned nlist;
+
+ struct {
+ LV2_OSC_Driver driver;
+ LV2_OSC_Stream stream;
+ varchunk_t *from_worker;
+ varchunk_t *to_worker;
+ varchunk_t *to_thread;
+ } data;
+
+ char *osc_url;
+};
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+// non-rt
+static void *
+_data_recv_req(void *data, size_t size, size_t *max)
+{
+ plughandle_t *handle = data;
+
+ return varchunk_write_request_max(handle->data.from_worker, size, max);
+}
+
+// non-rt
+static void
+_data_recv_adv(void *data, size_t written)
+{
+ plughandle_t *handle = data;
+
+ varchunk_write_advance(handle->data.from_worker, written);
+}
+
+// non-rt
+static const void *
+_data_send_req(void *data, size_t *len)
+{
+ plughandle_t *handle = data;
+
+ return varchunk_read_request(handle->data.to_worker, len);
+}
+
+// non-rt
+static void
+_data_send_adv(void *data)
+{
+ plughandle_t *handle = data;
+
+ varchunk_read_advance(handle->data.to_worker);
+}
+
+// rt
+static void
+_url_change(plughandle_t *handle, const char *url)
+{
+ LV2_OSC_Writer writer;
+ uint8_t buf [STR_LEN];
+ lv2_osc_writer_initialize(&writer, buf, STR_LEN);
+ lv2_osc_writer_message_vararg(&writer, "/eteroj/url", "s", url);
+ size_t size;
+ lv2_osc_writer_finalize(&writer, &size);
+
+ if(size)
+ {
+ uint8_t *dst;
+ if((dst = varchunk_write_request(handle->data.to_thread, size))) //FIXME use request_max
+ {
+ memcpy(dst, buf, size);
+ varchunk_write_advance(handle->data.to_thread, size);
+ }
+ }
+}
+
+static void
+_intercept(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _url_change(handle, impl->value.body);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = ETEROJ_URL_URI,
+ .offset = offsetof(plugstate_t, osc_url),
+ .type = LV2_ATOM__String,
+ .max_size = STR_LEN,
+ .event_cb = _intercept
+ },
+ {
+ .property = ETEROJ_ERROR_URI,
+ .offset = offsetof(plugstate_t, osc_error),
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__String,
+ .max_size = STR_LEN
+ },
+ {
+ .property = ETEROJ_CONNECTED_URI,
+ .offset = offsetof(plugstate_t, osc_connected),
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__Bool,
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ {
+ return NULL;
+ }
+
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ {
+ handle->map = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ {
+ handle->unmap = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ {
+ handle->log = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_OSC__schedule))
+ {
+ handle->osc_sched = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_WORKER__schedule))
+ {
+ handle->sched= features[i]->data;
+ }
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:(un)map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->sched)
+ {
+ fprintf(stderr, "%s: Host does not support work:schedule\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ {
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ }
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ // init data
+ handle->data.from_worker = varchunk_new(BUF_SIZE, true);
+ handle->data.to_worker = varchunk_new(BUF_SIZE, true);
+ handle->data.to_thread = varchunk_new(BUF_SIZE, true);
+ if(!handle->data.from_worker || !handle->data.to_worker || !handle->data.to_thread)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->data.driver.write_req = _data_recv_req;
+ handle->data.driver.write_adv = _data_recv_adv;
+ handle->data.driver.read_req = _data_send_req;
+ handle->data.driver.read_adv = _data_send_adv;
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->uris.eteroj_connected = props_map(&handle->props, ETEROJ_CONNECTED_URI);
+ handle->uris.eteroj_error = props_map(&handle->props, ETEROJ_ERROR_URI);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->osc_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->osc_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline list_t *
+_add_list(plughandle_t *handle)
+{
+ if(handle->nlist >= LIST_SIZE)
+ {
+ return NULL;
+ }
+
+ return &handle->list[handle->nlist++];
+}
+
+static inline void
+_invalidate_list(list_t *list)
+{
+ list->size = 0; // invalidate
+}
+
+static int
+_cmp(const void *a, const void *b)
+{
+ const list_t *A = a;
+ const list_t *B = b;
+
+ if(A->size && B->size)
+ {
+ if(A->frames < B->frames)
+ {
+ return -1;
+ }
+ else if(A->frames > B->frames)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ else if(A->size)
+ {
+ return -1;
+ }
+ else if(B->size)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+static inline void
+_sort_list(plughandle_t *handle)
+{
+ qsort(handle->list, handle->nlist, sizeof(list_t), _cmp);
+}
+
+static inline void
+_parse(plughandle_t *handle, double frames, const uint8_t *buf, size_t size)
+{
+ if(handle->ref)
+ {
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ }
+ if(handle->ref)
+ {
+ handle->ref = lv2_osc_forge_packet(&handle->forge, &handle->osc_urid,
+ handle->map, buf, size);
+ }
+}
+
+static inline void
+_unroll(plughandle_t *handle, const uint8_t *buf, size_t size)
+{
+ LV2_OSC_Reader reader;
+ lv2_osc_reader_initialize(&reader, buf, size);
+
+ if(lv2_osc_reader_is_bundle(&reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(&reader, size);
+
+ // immediate dispatch ?
+ if( (itm->timetag == LV2_OSC_IMMEDIATE) || !handle->osc_sched )
+ {
+ _parse(handle, 0.0, buf, size);
+ }
+ else if(size <= MTU_SIZE)
+ {
+ list_t *l = _add_list(handle);
+ if(l)
+ {
+ const double frames = handle->osc_sched->osc2frames(
+ handle->osc_sched->handle, itm->timetag);
+
+ l->frames = frames;
+ l->size = size;
+ memcpy(l->buf, buf, size);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "message pool overflow");
+ }
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "message too long");
+ }
+ }
+ else if(lv2_osc_reader_is_message(&reader)) // immediate dispatch
+ {
+ _parse(handle, 0.0, buf, size);
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ uint32_t capacity;
+
+ // prepare sequence forges
+ capacity = handle->osc_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->osc_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ // write outgoing data
+ LV2_ATOM_SEQUENCE_FOREACH(handle->osc_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(obj->atom.type == forge->Object)
+ {
+ if( !props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref)
+ && lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype) )
+ {
+ uint8_t *dst;
+ size_t reserve = obj->atom.size;
+ if((dst = varchunk_write_request(handle->data.to_worker, reserve)))
+ {
+ LV2_OSC_Writer writer;
+ lv2_osc_writer_initialize(&writer, dst, reserve);
+ lv2_osc_writer_packet(&writer, &handle->osc_urid, handle->unmap, obj->atom.size, &obj->body);
+ size_t written;
+ lv2_osc_writer_finalize(&writer, &written);
+
+ if(written)
+ {
+ varchunk_write_advance(handle->data.to_worker, written);
+ }
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "output ringbuffer overflow");
+ }
+ }
+ }
+ }
+
+ // wake worker once per period
+ const int32_t dummy = 0;
+ handle->sched->schedule_work(handle->sched->handle, sizeof(int32_t), &dummy);
+
+ // reschedule scheduled bundles
+ if(handle->osc_sched)
+ {
+ for(list_t *l = handle->list; l->size; l++)
+ {
+ uint64_t time = be64toh(*(uint64_t *)(l->buf + 8));
+
+ double frames = handle->osc_sched->osc2frames(handle->osc_sched->handle, time);
+ if(frames < 0.0) // we may occasionally get -1 frames events when rescheduling
+ {
+ l->frames = 0.0;
+ }
+ else
+ {
+ l->frames = frames;
+ }
+ }
+ }
+
+ const unsigned nlist = handle->nlist;
+
+ // read incoming data
+ const uint8_t *ptr;
+ size_t size;
+ while((ptr = varchunk_read_request(handle->data.from_worker, &size)))
+ {
+ _unroll(handle, ptr, size);
+
+ varchunk_read_advance(handle->data.from_worker);
+ }
+
+ const unsigned added = handle->nlist - nlist;
+
+ if(added)
+ {
+ _sort_list(handle);
+ }
+
+ unsigned deleted = 0;
+
+ // handle scheduled bundles
+ for(list_t *l = handle->list; l->size; l++)
+ {
+ if(l->frames < 0.0) // late event
+ {
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "late event: %lf samples", l->frames);
+ }
+
+ l->frames = 0.0; // dispatch as early as possible
+ }
+ else if(l->frames >= nsamples) // not scheduled for this period
+ {
+ break;
+ }
+
+ _parse(handle, l->frames, l->buf, l->size);
+
+ _invalidate_list(l);
+ deleted++;
+ }
+
+ if(deleted)
+ {
+ _sort_list(handle);
+ handle->nlist -= deleted;
+ }
+
+ if(handle->status_updated)
+ {
+ props_set(&handle->props, forge, nsamples-1, handle->uris.eteroj_connected, &handle->ref);
+ props_set(&handle->props, forge, nsamples-1, handle->uris.eteroj_error, &handle->ref);
+
+ handle->status_updated = false;
+ }
+
+ if(handle->ref)
+ {
+ lv2_atom_forge_pop(forge, &frame);
+ }
+ else
+ {
+ lv2_atom_sequence_clear(handle->osc_out);
+ }
+}
+
+static inline void
+_handle_enum(plughandle_t *handle, LV2_OSC_Enum ev)
+{
+ const char *err = "";
+
+ if(ev & LV2_OSC_ERR)
+ {
+ char buf [STR_LEN] = { '\0' };
+ err = strerror_r(ev & LV2_OSC_ERR, buf, sizeof(buf));
+
+ if(!err)
+ {
+ err = "Unknown";
+ }
+
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s\n", err);
+ }
+ }
+
+ if(strcmp(handle->state.osc_error, err))
+ {
+ strncpy(handle->state.osc_error, err, STR_LEN-1);
+
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->uris.eteroj_error);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.String, strlen(err), err);
+ }
+
+ handle->status_updated = true;
+ }
+
+ const bool connected = (ev & LV2_OSC_CONN) == LV2_OSC_CONN;
+
+ if(handle->state.osc_connected != connected)
+ {
+#if 0
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "connected: %i\n", connected);
+#endif
+
+ handle->state.osc_connected = connected;
+ handle->status_updated = true;
+ }
+}
+
+static inline LV2_OSC_Enum
+_activate(plughandle_t *handle)
+{
+ if(!handle->rolling && handle->osc_url)
+ {
+ const LV2_OSC_Enum ev = lv2_osc_stream_init(&handle->data.stream,
+ handle->osc_url, &handle->data.driver, handle);
+
+ if( (ev & LV2_OSC_ERR) == LV2_OSC_NONE)
+ {
+ handle->rolling = true;
+ }
+
+ return ev;
+ }
+
+ return LV2_OSC_NONE;
+}
+
+static void
+activate(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ _activate(handle);
+}
+
+static inline void
+_deactivate(plughandle_t *handle)
+{
+ if(handle->rolling)
+ {
+ lv2_osc_stream_deinit(&handle->data.stream);
+ handle->rolling = false;
+ }
+}
+
+static void
+deactivate(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ _deactivate(handle);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ varchunk_free(handle->data.from_worker);
+ varchunk_free(handle->data.to_worker);
+ varchunk_free(handle->data.to_thread);
+
+ if(handle->osc_url)
+ {
+ free(handle->osc_url);
+ }
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+// non-rt thread
+static LV2_Worker_Status
+_work(LV2_Handle instance,
+ LV2_Worker_Respond_Function respond,
+ LV2_Worker_Respond_Handle target,
+ uint32_t _size,
+ const void *_body)
+{
+ plughandle_t *handle = instance;
+ char *osc_url = NULL;
+
+ size_t size;
+ const uint8_t *body;
+ while((body = varchunk_read_request(handle->data.to_thread, &size)))
+ {
+ LV2_OSC_Reader reader;
+ LV2_OSC_Arg arg = {0};
+ lv2_osc_reader_initialize(&reader, body, size);
+ lv2_osc_reader_arg_begin(&reader, &arg, size);
+
+ if(!strcmp(arg.path, "/eteroj/url"))
+ {
+ if(osc_url)
+ {
+ free(osc_url);
+ }
+ osc_url = strdup(arg.s);
+ }
+
+ varchunk_read_advance(handle->data.to_thread);
+ }
+
+ if(osc_url)
+ {
+ if(handle->osc_url)
+ {
+ free(handle->osc_url);
+ }
+ handle->osc_url = osc_url;
+
+ _deactivate(handle);
+ }
+
+ LV2_OSC_Enum ev = _activate(handle);
+
+ if(handle->rolling)
+ {
+ ev |= lv2_osc_stream_run(&handle->data.stream);
+ }
+
+ respond(target, sizeof(LV2_OSC_Enum), &ev);
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_work_response(LV2_Handle instance, uint32_t size, const void *body)
+{
+ plughandle_t *handle = instance;
+
+ if(size == sizeof(LV2_OSC_Enum))
+ {
+ const LV2_OSC_Enum *ev = body;
+
+ _handle_enum(handle, *ev);
+ }
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_end_run(LV2_Handle instance)
+{
+ // do nothing
+
+ return LV2_WORKER_SUCCESS;
+}
+
+static const LV2_Worker_Interface work_iface = {
+ .work = _work,
+ .work_response = _work_response,
+ .end_run = _end_run
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_WORKER__interface))
+ {
+ return &work_iface;
+ }
+ else if(!strcmp(uri, LV2_STATE__interface))
+ {
+ return &state_iface;
+ }
+
+ return NULL;
+}
+
+const LV2_Descriptor eteroj_io = {
+ .URI = ETEROJ_IO_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = activate,
+ .run = run,
+ .deactivate = deactivate,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/eteroj_ninja.c b/eteroj_ninja.c
new file mode 100644
index 0000000..4fa0a2c
--- /dev/null
+++ b/eteroj_ninja.c
@@ -0,0 +1,508 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <eteroj.h>
+#include <varchunk.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/forge.h>
+#include <props.h>
+
+#define NETATOM_IMPLEMENTATION
+#include <netatom.lv2/netatom.h>
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define BUF_SIZE 8192
+
+#define MAX_NPROPS 1
+
+typedef struct _atom_ser_t atom_ser_t;
+typedef struct _plughandle_t plughandle_t;
+typedef struct _plugstate_t plugstate_t;
+
+struct _atom_ser_t {
+ uint32_t size;
+ uint8_t *buf;
+ uint32_t offset;
+};
+
+struct _plugstate_t {
+ int32_t synchronous;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Worker_Schedule *sched;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ atom_ser_t ser;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+ LV2_Atom_Forge forge;
+ LV2_OSC_URID osc_urid;
+
+ struct {
+ LV2_Atom_Forge *forge;
+ LV2_Atom_Forge_Ref *ref;
+ int64_t frames;
+ } unroll;
+
+ netatom_t *netatom;
+
+ varchunk_t *to_worker;
+ varchunk_t *from_worker;
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ uint8_t buf [BUF_SIZE];
+};
+
+static const char *base_path = "/ninja";
+
+static inline LV2_Atom_Forge_Ref
+_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size)
+{
+ atom_ser_t *ser = handle;
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+
+ const uint32_t new_offset = ser->offset + size;
+ if(new_offset > ser->size)
+ {
+ uint32_t new_size = ser->size << 1;
+ while(new_offset > new_size)
+ new_size <<= 1;
+
+ if(!(ser->buf = realloc(ser->buf, new_size)))
+ return 0; // realloc failed
+
+ ser->size = new_size;
+ }
+
+ memcpy(ser->buf + ser->offset, buf, size);
+ ser->offset = new_offset;
+
+ return ref;
+}
+
+static inline LV2_Atom *
+_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ atom_ser_t *ser = handle;
+
+ const uint32_t offset = ref - 1;
+
+ return (LV2_Atom *)(ser->buf + offset);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = ETEROJ_URI"#ninja_synchronous",
+ .offset = offsetof(plugstate_t, synchronous),
+ .type = LV2_ATOM__Bool
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_WORKER__schedule))
+ handle->sched= features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->unmap)
+ {
+ fprintf(stderr, "%s: Host does not support urid:unmap\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->sched)
+ {
+ fprintf(stderr, "%s: Host does not support work:schedule\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ if(handle->log)
+ {
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ }
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->netatom= netatom_new(handle->map, handle->unmap, true);
+ if(!handle->netatom)
+ {
+ netatom_free(handle->netatom);
+ free(handle);
+ return NULL;
+ }
+
+ handle->to_worker = varchunk_new(BUF_SIZE, true);
+ handle->from_worker = varchunk_new(BUF_SIZE, true);
+
+ if(!handle->to_worker || !handle->from_worker)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->ser.size = 2018;
+ handle->ser.offset = 0;
+ handle->ser.buf = malloc(handle->ser.size); //TODO check
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+_unroll(const char *path, const LV2_Atom_Tuple *arguments, void *data)
+{
+ plughandle_t *handle = data;
+ LV2_Atom_Forge *forge = handle->unroll.forge;
+
+ if(strcmp(path, base_path))
+ return;
+
+ const LV2_Atom *itr = lv2_atom_tuple_begin(arguments);
+ if(itr->type != forge->Chunk)
+ return;
+
+ memcpy(handle->buf, LV2_ATOM_BODY(itr), itr->size);
+
+ const LV2_Atom *atom = netatom_deserialize(handle->netatom,
+ handle->buf, itr->size);
+ if(atom)
+ {
+ if(*handle->unroll.ref)
+ *handle->unroll.ref = lv2_atom_forge_frame_time(forge, handle->unroll.frames);
+ if(*handle->unroll.ref)
+ *handle->unroll.ref = lv2_atom_forge_write(forge, atom, lv2_atom_total_size(atom));
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s: failed to deserialize\n", __func__);
+ }
+}
+
+static void
+_convert_seq(plughandle_t *handle, LV2_Atom_Forge *forge, const LV2_Atom_Sequence *seq,
+ LV2_Atom_Forge_Ref *ref)
+{
+ LV2_OSC_URID *osc_urid = &handle->osc_urid;
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_osc_is_message_or_bundle_type(osc_urid, obj->body.otype))
+ {
+ handle->unroll.frames = ev->time.frames;
+ handle->unroll.ref = ref;
+ handle->unroll.forge = forge;
+
+ lv2_osc_unroll(osc_urid, obj, _unroll, handle);
+ }
+ else
+ {
+ memcpy(handle->buf, atom, lv2_atom_total_size(atom)); //FIXME check < BUF_SIZE
+
+ size_t sz;
+ const uint8_t *buf = netatom_serialize(handle->netatom, (LV2_Atom *)handle->buf, BUF_SIZE, &sz);
+ if(buf)
+ {
+ if(*ref)
+ *ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(*ref)
+ *ref = lv2_osc_forge_message_vararg(forge, osc_urid, base_path, "b", sz, buf);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s: failed to serialize\n", __func__);
+ }
+ }
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ // prepare osc atom forge
+ const uint32_t capacity = handle->event_out->atom.size;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ props_advance(&handle->props, &handle->forge, 0, obj, &ref); //NOTE 0!
+ }
+
+ if(handle->state.synchronous)
+ {
+ _convert_seq(handle, &handle->forge, handle->event_in, &ref);
+ }
+ else // asynchronous
+ {
+ // move input events to worker thread
+ if(handle->event_in->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ {
+ const size_t size = lv2_atom_total_size(&handle->event_in->atom);
+ LV2_Atom_Sequence *seq;
+ if((seq = varchunk_write_request(handle->to_worker, size)))
+ {
+ memcpy(seq, handle->event_in, size);
+ varchunk_write_advance(handle->to_worker, size);
+
+ const int32_t dummy;
+ handle->sched->schedule_work(handle->sched->handle, sizeof(int32_t), &dummy);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s: ringbuffer overflow\n", __func__);
+ }
+ }
+
+ {
+ // move output events from worker thread
+ size_t size;
+ const LV2_Atom_Sequence *seq;
+ if((seq = varchunk_read_request(handle->from_worker, &size)))
+ {
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(&handle->forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(&handle->forge, &ev->body, lv2_atom_total_size(&ev->body));
+ }
+ varchunk_read_advance(handle->from_worker);
+ }
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ if(handle->ser.buf)
+ free(handle->ser.buf);
+ if(handle->to_worker)
+ varchunk_free(handle->to_worker);
+ if(handle->from_worker)
+ varchunk_free(handle->from_worker);
+ netatom_free(handle->netatom);
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+// non-rt thread
+static LV2_Worker_Status
+_work(LV2_Handle instance,
+ LV2_Worker_Respond_Function respond,
+ LV2_Worker_Respond_Handle target,
+ uint32_t size,
+ const void *body)
+{
+ plughandle_t *handle = instance;
+ LV2_Atom_Forge forge = handle->forge; // clone forge
+ LV2_OSC_URID *osc_urid = &handle->osc_urid;
+
+ (void)respond;
+ (void)target;
+ (void)size;
+ (void)body;
+
+ size_t _size;
+ const LV2_Atom_Sequence *seq;
+ while((seq = varchunk_read_request(handle->to_worker, &_size)))
+ {
+ handle->ser.offset = 0;
+ lv2_atom_forge_set_sink(&forge, _sink, _deref, &handle->ser);
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&forge, &frame, 0);
+
+ _convert_seq(handle, &forge, seq, &ref);
+
+ if(ref)
+ {
+ lv2_atom_forge_pop(&forge, &frame);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s: failed to forge\n", __func__);
+ }
+
+ seq = (const LV2_Atom_Sequence *)handle->ser.buf;
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ {
+ const size_t len = lv2_atom_total_size(&seq->atom);
+ LV2_Atom_Sequence *dst;
+ if((dst = varchunk_write_request(handle->from_worker, len)))
+ {
+ memcpy(dst, seq, len);
+ varchunk_write_advance(handle->from_worker, len);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "%s: ringbuffer overflow\n", __func__);
+ }
+ }
+
+ varchunk_read_advance(handle->to_worker);
+ }
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_work_response(LV2_Handle instance, uint32_t size, const void *body)
+{
+ // do nothing
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_end_run(LV2_Handle instance)
+{
+ // do nothing
+
+ return LV2_WORKER_SUCCESS;
+}
+
+static const LV2_Worker_Interface work_iface = {
+ .work = _work,
+ .work_response = _work_response,
+ .end_run = _end_run
+};
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_WORKER__interface))
+ return &work_iface;
+ else if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor eteroj_ninja = {
+ .URI = ETEROJ_NINJA_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/eteroj_pack.c b/eteroj_pack.c
new file mode 100644
index 0000000..3807c62
--- /dev/null
+++ b/eteroj_pack.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <eteroj.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/forge.h>
+#include <props.h>
+
+#define MAX_NPROPS 2
+#define MAX_STRLEN 512
+
+typedef enum _pack_format_t pack_format_t;
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+enum _pack_format_t {
+ PACK_FORMAT_MIDI = 0,
+ PACK_FORMAT_BLOB = 1
+};
+
+struct _plugstate_t {
+ char pack_path [MAX_STRLEN];
+ int32_t pack_format;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ struct {
+ LV2_URID midi_MidiEvent;
+ } uris;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+
+ PROPS_T(props, MAX_NPROPS);
+ LV2_Atom_Forge forge;
+ LV2_OSC_URID osc_urid;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ int64_t frames;
+ LV2_Atom_Forge_Ref ref;
+};
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = ETEROJ_PACK_PATH_URI,
+ .offset = offsetof(plugstate_t, pack_path),
+ .type = LV2_ATOM__String,
+ .max_size = MAX_STRLEN
+ },
+ {
+ .property = ETEROJ_PACK_FORMAT_URI,
+ .offset = offsetof(plugstate_t, pack_format),
+ .type = LV2_ATOM__Int,
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate, const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ for(unsigned 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->uris.midi_MidiEvent = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+_unroll(const char *path, const LV2_Atom_Tuple *arguments, void *data)
+{
+ plughandle_t *handle = data;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Ref ref = handle->ref;
+
+ LV2_ATOM_TUPLE_FOREACH(arguments, itr)
+ {
+ bool is_midi = false;
+
+ switch((pack_format_t)handle->state.pack_format)
+ {
+ case PACK_FORMAT_MIDI:
+ if(lv2_osc_argument_type(&handle->osc_urid, itr) == LV2_OSC_MIDI)
+ is_midi = true;
+ break;
+ case PACK_FORMAT_BLOB:
+ if(lv2_osc_argument_type(&handle->osc_urid, itr) == LV2_OSC_BLOB)
+ is_midi = true;
+ break;
+ }
+
+ if(is_midi)
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, handle->frames);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, itr->size, handle->uris.midi_MidiEvent);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, LV2_ATOM_BODY_CONST(itr), itr->size);
+ if(ref)
+ lv2_atom_forge_pad(forge, itr->size);
+ }
+ }
+
+ handle->ref = ref;
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ // prepare osc atom forge
+ const uint32_t capacity = handle->event_out->atom.size;
+ LV2_Atom_Forge *forge = &handle->forge;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->event_out, capacity);
+ LV2_Atom_Forge_Frame frame;
+ handle->ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ handle->frames = ev->time.frames;
+
+ if(obj->atom.type == handle->uris.midi_MidiEvent)
+ {
+ // pack MIDI into OSC
+ switch((pack_format_t)handle->state.pack_format)
+ {
+ case PACK_FORMAT_MIDI:
+ {
+ if(obj->atom.size <= 3) // OSC 'm' type does not support more :(
+ {
+ LV2_Atom_Forge_Frame frames [2];
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_head(forge, &handle->osc_urid, frames, handle->state.pack_path);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_midi(forge, &handle->osc_urid, LV2_ATOM_BODY_CONST(&obj->atom), obj->atom.size);
+ if(handle->ref)
+ lv2_osc_forge_pop(forge, frames);
+ }
+
+ break;
+ }
+ case PACK_FORMAT_BLOB:
+ {
+ LV2_Atom_Forge_Frame frames [2];
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_head(forge, &handle->osc_urid, frames, handle->state.pack_path);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_blob(forge, &handle->osc_urid, LV2_ATOM_BODY_CONST(&obj->atom), obj->atom.size);
+ if(handle->ref)
+ lv2_osc_forge_pop(forge, frames);
+
+ break;
+ }
+ }
+ }
+ else if(!props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref))
+ {
+ // unpack MIDI from OSC
+ lv2_osc_unroll(&handle->osc_urid, obj, _unroll, handle);
+ }
+ }
+
+ if(handle->ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor eteroj_pack = {
+ .URI = ETEROJ_PACK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/eteroj_query.c b/eteroj_query.c
new file mode 100644
index 0000000..303eafc
--- /dev/null
+++ b/eteroj_query.c
@@ -0,0 +1,973 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <eteroj.h>
+#include <varchunk.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/forge.h>
+#include <props.h>
+#include <jsmn.h>
+
+#define MAX_NPROPS 1
+#define MAX_TOKENS 256
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ int32_t query_refresh;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ struct {
+ LV2_URID subject;
+
+ LV2_URID patch_message;
+ LV2_URID patch_set;
+ LV2_URID patch_get;
+ LV2_URID patch_subject;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_wildcard;
+ LV2_URID patch_patch;
+ LV2_URID patch_add;
+ LV2_URID patch_remove;
+ LV2_URID patch_writable;
+ LV2_URID patch_readable;
+
+ LV2_URID rdfs_label;
+ LV2_URID rdfs_comment;
+ LV2_URID rdfs_range;
+
+ LV2_URID rdf_value;
+
+ LV2_URID lv2_minimum;
+ LV2_URID lv2_maximum;
+ LV2_URID lv2_scale_point;
+
+ LV2_URID query_refresh;
+ } urid;
+
+ LV2_OSC_URID osc_urid;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+ int64_t frames;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *osc_in;
+ LV2_Atom_Sequence *osc_out;
+
+ int32_t cnt;
+
+ jsmn_parser parser;
+ jsmntok_t tokens [MAX_TOKENS];
+
+ varchunk_t *rb;
+};
+
+static void
+_add(plughandle_t *handle, int64_t frame_time, char type, bool read, bool write,
+ const char *json, int range_cnt, jsmntok_t *range, int values_cnt, jsmntok_t *values,
+ LV2_URID property, const char *label, uint32_t label_len, const char *comment, uint32_t comment_len)
+{
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref = handle->ref;
+
+ if(write && !read) //TODO handle this
+ return;
+
+ if(!range_cnt && !values_cnt) //TODO handle this
+ return;
+
+ LV2_URID access = write
+ ? handle->urid.patch_writable
+ : handle->urid.patch_readable;
+
+ LV2_URID _type = forge->Int;
+ switch((LV2_OSC_Type)type)
+ {
+ case LV2_OSC_INT32:
+ _type = forge->Int;
+ break;
+ case LV2_OSC_INT64:
+ _type = forge->Long;
+ break;
+ case LV2_OSC_FLOAT:
+ _type = forge->Float;
+ break;
+ case LV2_OSC_DOUBLE:
+ _type = forge->Double;
+ break;
+ case LV2_OSC_STRING:
+ _type = forge->String;
+ break;
+ default:
+ //TODO do other types
+ break;
+ }
+
+ jsmntok_t *mintok = NULL;
+ jsmntok_t *maxtok = NULL;
+ double min = 0.f;
+ double max = 1.f;
+
+ if(range_cnt && range)
+ {
+ mintok = &range[0];
+ maxtok = &range[1];
+ min = strtod(&json[mintok->start], NULL);
+ max = strtod(&json[maxtok->start], NULL);
+ if( (_type == forge->Int) && (min == 0.0) && (max == 1.0) )
+ _type = forge->Bool;
+ }
+
+ lv2_atom_forge_frame_time(forge, frame_time);
+ lv2_atom_forge_object(forge, &obj_frame, 0, handle->urid.patch_patch);
+ {
+ lv2_atom_forge_key(forge, handle->urid.patch_subject);
+ lv2_atom_forge_urid(forge, handle->urid.subject);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_remove);
+ LV2_Atom_Forge_Frame remove_frame;
+ lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ lv2_atom_forge_key(forge, access);
+ lv2_atom_forge_urid(forge, property);
+ }
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_add);
+ LV2_Atom_Forge_Frame add_frame;
+ lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ lv2_atom_forge_key(forge, access);
+ lv2_atom_forge_urid(forge, property);
+ }
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ lv2_atom_forge_frame_time(forge, frame_time);
+ lv2_atom_forge_object(forge, &obj_frame, 0, handle->urid.patch_patch);
+ {
+ lv2_atom_forge_key(forge, handle->urid.patch_subject);
+ lv2_atom_forge_urid(forge, property);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_remove);
+ LV2_Atom_Forge_Frame remove_frame;
+ lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ lv2_atom_forge_key(forge, handle->urid.rdfs_label);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.rdfs_range);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.rdfs_comment);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.lv2_minimum);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.lv2_maximum);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.lv2_scale_point);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+ }
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_add);
+ LV2_Atom_Forge_Frame add_frame;
+ lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ if(label)
+ {
+ lv2_atom_forge_key(forge, handle->urid.rdfs_label);
+ lv2_atom_forge_string(forge, label, label_len);
+ }
+
+ if(comment)
+ {
+ lv2_atom_forge_key(forge, handle->urid.rdfs_comment);
+ lv2_atom_forge_string(forge, comment, comment_len);
+ }
+
+ lv2_atom_forge_key(forge, handle->urid.rdfs_range);
+ lv2_atom_forge_urid(forge, _type);
+
+ if(mintok && (_type != forge->Bool) )
+ {
+ lv2_atom_forge_key(forge, handle->urid.lv2_minimum);
+ if(_type == forge->Int)
+ lv2_atom_forge_int(forge, min);
+ else if(_type == forge->Long)
+ lv2_atom_forge_long(forge, min);
+ else if(_type == forge->Float)
+ lv2_atom_forge_float(forge, min);
+ else if(_type == forge->Double)
+ lv2_atom_forge_double(forge, min);
+ else
+ lv2_atom_forge_atom(forge, 0, 0); // blank
+ //TODO do other types
+ }
+
+ if(maxtok && (_type != forge->Bool) )
+ {
+ lv2_atom_forge_key(forge, handle->urid.lv2_maximum);
+ if(_type == forge->Int)
+ lv2_atom_forge_int(forge, max);
+ else if(_type == forge->Long)
+ lv2_atom_forge_long(forge, max);
+ else if(_type == forge->Float)
+ lv2_atom_forge_float(forge, max);
+ else if(_type == forge->Double)
+ lv2_atom_forge_double(forge, max);
+ else
+ lv2_atom_forge_atom(forge, 0, 0); // blank
+ //TODO do other types
+ }
+
+ if(values_cnt && values)
+ {
+ LV2_Atom_Forge_Frame tuple_frame;
+ lv2_atom_forge_key(forge, handle->urid.lv2_scale_point);
+ lv2_atom_forge_tuple(forge, &tuple_frame);
+
+ //iterate over all scale_points
+ for(int i=0; i<values_cnt; i++)
+ {
+ jsmntok_t *tok = &values[i];
+
+ LV2_Atom_Forge_Frame scale_point_frame;
+ lv2_atom_forge_object(forge, &scale_point_frame, 0, 0);
+ {
+ lv2_atom_forge_key(forge, handle->urid.rdfs_label);
+ lv2_atom_forge_string(forge, &json[tok->start], tok->end - tok->start);
+
+ lv2_atom_forge_key(forge, handle->urid.rdf_value);
+ if(_type == forge->Int)
+ lv2_atom_forge_int(forge, strtol(&json[tok->start], NULL, 0));
+ else if(_type == forge->Long)
+ lv2_atom_forge_long(forge, strtol(&json[tok->start], NULL, 0));
+ else if(_type == forge->Float)
+ lv2_atom_forge_float(forge, strtod(&json[tok->start], NULL));
+ else if(_type == forge->Double)
+ lv2_atom_forge_double(forge, strtod(&json[tok->start], NULL));
+ else if(_type == forge->String)
+ lv2_atom_forge_string(forge, &json[tok->start], tok->end - tok->start);
+ else
+ lv2_atom_forge_atom(forge, 0, 0); // blank
+ //TODO do other types
+ }
+ lv2_atom_forge_pop(forge, &scale_point_frame);
+ }
+ lv2_atom_forge_pop(forge, &tuple_frame);
+ }
+ }
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ handle->ref = ref;
+}
+
+static void
+_set(plughandle_t *handle, int64_t frame_time, LV2_URID property, const LV2_Atom *atom)
+{
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref = handle->ref;
+
+ lv2_atom_forge_frame_time(forge, frame_time);
+ lv2_atom_forge_object(forge, &obj_frame, 0, handle->urid.patch_set);
+ {
+ lv2_atom_forge_key(forge, handle->urid.patch_subject);
+ lv2_atom_forge_urid(forge, handle->urid.subject);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_property);
+ lv2_atom_forge_urid(forge, property);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_value);
+ lv2_atom_forge_write(forge, atom, lv2_atom_total_size(atom));
+ }
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ handle->ref = ref;
+}
+
+static void
+_clear(plughandle_t *handle, int64_t frame_time)
+{
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref = handle->ref;
+
+ lv2_atom_forge_frame_time(forge, frame_time);
+ lv2_atom_forge_object(forge, &obj_frame, 0, handle->urid.patch_patch);
+ {
+ lv2_atom_forge_key(forge, handle->urid.patch_subject);
+ lv2_atom_forge_urid(forge, handle->urid.subject);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_remove);
+ LV2_Atom_Forge_Frame remove_frame;
+ lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ lv2_atom_forge_key(forge, handle->urid.patch_writable);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_readable);
+ lv2_atom_forge_urid(forge, handle->urid.patch_wildcard);
+ }
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ lv2_atom_forge_key(forge, handle->urid.patch_add);
+ LV2_Atom_Forge_Frame add_frame;
+ lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ // empty
+ }
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ handle->ref = ref;
+}
+
+static void
+_refresh(plughandle_t *handle, int64_t frame_time)
+{
+ _clear(handle, frame_time);
+
+ const char *destination = "/!";
+
+ // schedule on ring buffer
+ size_t len2 = strlen(destination) + 1;
+ char *ptr;
+ if((ptr = varchunk_write_request(handle->rb, len2)))
+ {
+ strncpy(ptr, destination, len2);
+ varchunk_write_advance(handle->rb, len2);
+ }
+ else if(handle->log)
+ lv2_log_trace(&handle->logger, "eteroj#query: ringbuffer full");
+}
+
+static void
+_intercept_refresh(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _refresh(handle, frames);
+
+ handle->state.query_refresh = false;
+ props_set(&handle->props, &handle->forge, frames, handle->urid.query_refresh, &handle->ref);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = ETEROJ_QUERY_REFRESH_URI,
+ .offset = offsetof(plugstate_t, query_refresh),
+ .type = LV2_ATOM__Bool,
+ .event_cb = _intercept_refresh
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ const LV2_State_Make_Path *make_path = NULL;
+
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->unmap)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:unmap\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ handle->urid.subject = handle->map->map(handle->map->handle,
+ descriptor->URI);
+
+ handle->urid.patch_message = handle->map->map(handle->map->handle,
+ LV2_PATCH__Message);
+ handle->urid.patch_set = handle->map->map(handle->map->handle,
+ LV2_PATCH__Set);
+ handle->urid.patch_get = handle->map->map(handle->map->handle,
+ LV2_PATCH__Get);
+ handle->urid.patch_subject = handle->map->map(handle->map->handle,
+ LV2_PATCH__subject);
+ handle->urid.patch_property = handle->map->map(handle->map->handle,
+ LV2_PATCH__property);
+ handle->urid.patch_value = handle->map->map(handle->map->handle,
+ LV2_PATCH__value);
+ handle->urid.patch_wildcard = handle->map->map(handle->map->handle,
+ LV2_PATCH__wildcard);
+ handle->urid.patch_patch = handle->map->map(handle->map->handle,
+ LV2_PATCH__Patch);
+ handle->urid.patch_add = handle->map->map(handle->map->handle,
+ LV2_PATCH__add);
+ handle->urid.patch_remove = handle->map->map(handle->map->handle,
+ LV2_PATCH__remove);
+ handle->urid.patch_writable = handle->map->map(handle->map->handle,
+ LV2_PATCH__writable);
+ handle->urid.patch_readable = handle->map->map(handle->map->handle,
+ LV2_PATCH__readable);
+
+ handle->urid.rdfs_label = handle->map->map(handle->map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#label");
+ handle->urid.rdfs_comment = handle->map->map(handle->map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#comment");
+ handle->urid.rdfs_range = handle->map->map(handle->map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#range");
+
+ handle->urid.rdf_value = handle->map->map(handle->map->handle,
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#value");
+
+ handle->urid.lv2_minimum = handle->map->map(handle->map->handle,
+ LV2_CORE__minimum);
+ handle->urid.lv2_maximum = handle->map->map(handle->map->handle,
+ LV2_CORE__maximum);
+ handle->urid.lv2_scale_point = handle->map->map(handle->map->handle,
+ LV2_CORE__scalePoint);
+
+ handle->cnt = 0;
+
+ handle->rb = varchunk_new(65536, false);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->urid.query_refresh = props_map(&handle->props, ETEROJ_QUERY_REFRESH_URI);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->osc_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->osc_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline int
+json_eq(const char *json, jsmntok_t *tok, const char *s)
+{
+ if( (tok->type == JSMN_STRING)
+ && ((int)strlen(s) == tok->end - tok->start)
+ && !strncmp(json + tok->start, s, tok->end - tok->start) )
+ {
+ return 0;
+ }
+
+ return -1;
+}
+
+static inline const char *
+json_string(const char *json, jsmntok_t *tok, size_t *len)
+{
+ if(tok->type != JSMN_STRING)
+ return NULL;
+
+ *len = tok->end - tok->start;
+ return json + tok->start;
+}
+
+static inline int32_t
+json_int(const char *json, jsmntok_t *tok)
+{
+ if(tok->type != JSMN_PRIMITIVE)
+ return -1;
+
+ return strtol(json + tok->start, NULL, 10);
+}
+
+static inline float
+json_float(const char *json, jsmntok_t *tok)
+{
+ if(tok->type != JSMN_PRIMITIVE)
+ return -1;
+
+ return strtod(json + tok->start, NULL);
+}
+
+static inline bool
+json_bool(const char *json, jsmntok_t *tok)
+{
+ if(tok->type != JSMN_PRIMITIVE)
+ return false;
+
+ if(json[tok->start] == 't')
+ return true;
+ else if(json[tok->start] == 'f')
+ return false;
+
+ return false;
+}
+
+// rt
+static void
+_message_cb(const char *path, const LV2_Atom_Tuple *body, void *data)
+{
+ plughandle_t *handle = data;
+ LV2_OSC_URID *osc_urid = &handle->osc_urid;
+ LV2_Atom_Forge *forge = &handle->forge;
+ jsmntok_t *t = handle->tokens;
+
+ const LV2_Atom *atom = lv2_atom_tuple_begin(body);
+
+ if(!strcmp(path, "/success"))
+ {
+ int32_t id;
+ const char *destination;
+
+ atom = lv2_osc_int32_get(osc_urid, atom, &id);
+ atom = lv2_osc_string_get(osc_urid, atom, &destination);
+ //fprintf(stderr, "success: %s\n", destination);
+
+ if(strrchr(destination, '!'))
+ {
+ const char *json;
+ atom = lv2_osc_string_get(osc_urid, atom, &json);
+ jsmn_init(&handle->parser);
+ int n_tokens = jsmn_parse(&handle->parser, json, strlen(json),
+ handle->tokens, MAX_TOKENS);
+ if( (n_tokens < 1) && handle->log)
+ lv2_log_trace(&handle->logger, "eror parsing JSON: '%s'", json);
+
+ size_t target_len = 0;
+ const char *target = NULL;
+ size_t type_len = 0;
+ const char *type = NULL;
+ size_t desc_len = 0;
+ const char *desc = NULL;
+
+ int arg_n = 0;
+ char arg_type = 'i';
+ bool arg_read = true;
+ bool arg_write = false;
+ int arg_range_cnt = 0;
+ jsmntok_t *arg_range = NULL;
+ int arg_values_cnt = 0;
+ jsmntok_t *arg_values = NULL;
+
+ for(int i = 1; i < n_tokens; )
+ {
+ jsmntok_t *key = &t[i++];
+ jsmntok_t *value = &t[i++];
+
+ if(!json_eq(json, key, "path"))
+ {
+ target = json_string(json, value, &target_len);
+ }
+ else if(!json_eq(json, key, "type"))
+ {
+ type = json_string(json, value, &type_len);
+ }
+ else if(!json_eq(json, key, "description"))
+ {
+ desc = json_string(json, value, &desc_len);
+ }
+ else if(!json_eq(json, key, "items"))
+ {
+ for(int j = 0; j < value->size; j++)
+ {
+ jsmntok_t *item = &t[i++];
+ size_t len = 0;
+ const char *s = json_string(json, item, &len);
+
+ // add request to ring buffer
+ size_t len2 = target_len + len + 2;
+ char *ptr;
+ if((ptr = varchunk_write_request(handle->rb, len2)))
+ {
+ strncpy(ptr, target, target_len);
+ strncpy(ptr + target_len, s, len);
+ ptr[target_len + len] = '!';
+ ptr[target_len + len + 1] = 0;
+
+ varchunk_write_advance(handle->rb, len2);
+ }
+ else if(handle->log)
+ lv2_log_trace(&handle->logger, "eteroj#query: ringbuffer full");
+ }
+ }
+ else if(!json_eq(json, key, "arguments"))
+ {
+ arg_n = value->size;
+
+ for(int j = 0; j < value->size; j++)
+ {
+ jsmntok_t *item = &t[i++];
+
+ for(int m = 0; m < item->size; m++)
+ {
+ jsmntok_t *item_key = &t[i++];
+ jsmntok_t *item_val = &t[i++];
+
+ if(!json_eq(json, item_key, "type"))
+ {
+ size_t len;
+ const char *s = json_string(json, item_val, &len);
+
+ if(j == 0)
+ arg_type = s[0];
+ }
+ else if(!json_eq(json, item_key, "description"))
+ {
+ size_t len = 0;
+ const char *s = json_string(json, item_val, &len);
+
+ char dest [1024];
+ strncpy(dest, s, len);
+ dest[len] = 0;
+ }
+ else if(!json_eq(json, item_key, "read"))
+ {
+ const bool boolean = json_bool(json, item_val);
+
+ if(j == 0)
+ arg_read = boolean;
+ }
+ else if(!json_eq(json, item_key, "write"))
+ {
+ const bool boolean = json_bool(json, item_val);
+
+ if(j == 0)
+ arg_write = boolean;
+ }
+ else if(!json_eq(json, item_key, "range"))
+ {
+ arg_range_cnt = item_val->size;
+ arg_range = item_val + 1;
+
+ for(int n = 0; n < item_val->size; n++)
+ {
+ jsmntok_t *range = &t[i++];
+ }
+ }
+ else if(!json_eq(json, item_key, "values"))
+ {
+ arg_values_cnt = item_val->size;
+ arg_values = item_val + 1;
+
+ for(int n = 0; n < item_val->size; n++)
+ {
+ jsmntok_t *val = &t[i++];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // register methods in UI
+ if(!strncmp(type, "method", type_len) && (arg_n == 1) ) //FIXME handle all arg_n
+ {
+ char dest[1024];
+ strncpy(dest, target, target_len);
+ dest[target_len] = 0;
+ LV2_URID property = handle->map->map(handle->map->handle, dest);
+
+ _add(handle, 0, arg_type, arg_read, arg_write, json, arg_range_cnt, arg_range,
+ arg_values_cnt, arg_values, property, target, target_len, desc, desc_len);
+ }
+ }
+ else
+ {
+ // is this a reply with arguments?
+ if(!lv2_atom_tuple_is_end(LV2_ATOM_BODY_CONST(body), body->atom.size, atom))
+ {
+ LV2_URID property = handle->map->map(handle->map->handle, destination);
+ _set(handle, handle->frames, property, atom);
+ }
+ }
+ }
+ else if(!strcmp(path, "/error"))
+ {
+ int32_t id;
+ const char *destination;
+ const char *msg;
+
+ atom = lv2_osc_int32_get(osc_urid, atom, &id);
+ atom = lv2_osc_string_get(osc_urid, atom, &destination);
+ atom = lv2_osc_string_get(osc_urid, atom, &msg);
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "error: %s (%s)", destination, msg);
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+
+ // prepare sequence forges
+ const uint32_t capacity = handle->osc_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->osc_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->osc_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ if(lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ handle->frames = frames;
+ lv2_osc_unroll(&handle->osc_urid, obj, _message_cb, handle);
+ }
+ else if(obj->atom.type == forge->Object)
+ {
+ props_advance(&handle->props, forge, frames, obj, &handle->ref);
+
+ if(obj->body.otype == handle->urid.patch_set)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom *value = NULL;
+
+ lv2_atom_object_get(obj,
+ handle->urid.patch_subject, &subject,
+ handle->urid.patch_property, &property,
+ handle->urid.patch_value, &value,
+ 0);
+
+ if(!property || !value)
+ continue;
+
+ if(subject && (subject->body != handle->urid.subject))
+ continue; // subject not matching
+
+ const char *uri = handle->unmap->unmap(handle->unmap->handle, property->body);
+
+ if( (value->type == forge->Int) || (value->type == forge->Bool) )
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, uri, "ii",
+ handle->cnt++, ((const LV2_Atom_Int *)value)->body);
+ }
+ else if(value->type == forge->Long)
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, uri, "ih",
+ handle->cnt++, ((const LV2_Atom_Long *)value)->body);
+ }
+ else if(value->type == forge->Float)
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, uri, "if",
+ handle->cnt++, ((const LV2_Atom_Float *)value)->body);
+ }
+ else if(value->type == forge->Double)
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, uri, "id",
+ handle->cnt++, ((const LV2_Atom_Double *)value)->body);
+ }
+ else if(value->type == forge->String)
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, uri, "is",
+ handle->cnt++, LV2_ATOM_BODY_CONST(value));
+ }
+ }
+ else if(obj->body.otype == handle->urid.patch_get)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+
+ lv2_atom_object_get(obj,
+ handle->urid.patch_subject, &subject,
+ handle->urid.patch_property, &property,
+ 0);
+
+ if(subject && (subject->body != handle->urid.subject))
+ continue; // subject not matching
+
+ if(!property)
+ {
+ _refresh(handle, frames);
+ }
+ else
+ {
+ const char *uri = handle->unmap->unmap(handle->unmap->handle, property->body);
+
+ // schedule on ring buffer
+ size_t len2 = strlen(uri) + 1;
+ char *ptr;
+ if((ptr = varchunk_write_request(handle->rb, len2)))
+ {
+ strncpy(ptr, uri, len2);
+ varchunk_write_advance(handle->rb, len2);
+ }
+ else if(handle->log)
+ lv2_log_trace(&handle->logger, "eteroj#query: ringbuffer full");
+ }
+ }
+ }
+ }
+
+ {
+ // send requests in ring buffer
+ size_t len2;
+ const char *ptr;
+ int cnt = 0;
+ if( (ptr = varchunk_read_request(handle->rb, &len2)) && (cnt++ < 10) ) //TODO how many?
+ {
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(forge, nsamples-1);
+ if(handle->ref)
+ handle->ref = lv2_osc_forge_message_vararg(forge, &handle->osc_urid, ptr, "i", handle->cnt++);
+
+ varchunk_read_advance(handle->rb);
+ }
+ }
+
+ if(handle->ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->osc_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ if(handle->rb)
+ varchunk_free(handle->rb);
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor eteroj_query = {
+ .URI = ETEROJ_QUERY_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/jsmn/LICENSE b/jsmn/LICENSE
new file mode 100644
index 0000000..c84fb2e
--- /dev/null
+++ b/jsmn/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Serge A. Zaitsev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/jsmn/jsmn.c b/jsmn/jsmn.c
new file mode 100644
index 0000000..a0f4f69
--- /dev/null
+++ b/jsmn/jsmn.c
@@ -0,0 +1,311 @@
+#include <stdlib.h>
+
+#include "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ int start, int end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+ int start;
+
+ start = parser->pos;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ if (tokens == NULL) {
+ parser->pos--;
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return 0;
+}
+
+/**
+ * Filsl next token with JSON string.
+ */
+static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+
+ int start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ if (tokens == NULL) {
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return 0;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\' && parser->pos + 1 < len) {
+ int i;
+ parser->pos++;
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ parser->pos++;
+ for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ /* If it isn't a hex character we have an error */
+ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmnerr_t r;
+ int i;
+ jsmntok_t *token;
+ int count = 0;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ count++;
+ if (tokens == NULL) {
+ break;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ if (tokens == NULL)
+ break;
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ' ':
+ break;
+ case ':':
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case ',':
+ if (tokens != NULL &&
+ tokens[parser->toksuper].type != JSMN_ARRAY &&
+ tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+ parser->toksuper = tokens[parser->toksuper].parent;
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+ /* And they must not be keys of the object */
+ if (tokens != NULL) {
+ jsmntok_t *t = &tokens[parser->toksuper];
+ if (t->type == JSMN_OBJECT ||
+ (t->type == JSMN_STRING && t->size != 0)) {
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ return JSMN_ERROR_PART;
+ }
+ }
+
+ return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
diff --git a/jsmn/jsmn.h b/jsmn/jsmn.h
new file mode 100644
index 0000000..95fb2ca
--- /dev/null
+++ b/jsmn/jsmn.h
@@ -0,0 +1,75 @@
+#ifndef __JSMN_H_
+#define __JSMN_H_
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * o Object
+ * o Array
+ * o String
+ * o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+ JSMN_PRIMITIVE = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3
+} jsmntype_t;
+
+typedef enum {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3
+} jsmnerr_t;
+
+/**
+ * JSON token description.
+ * @param type type (object, array, string etc.)
+ * @param start start position in JSON data string
+ * @param end end position in JSON data string
+ */
+typedef struct {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string
+ */
+typedef struct {
+ unsigned int pos; /* offset in the JSON string */
+ unsigned int toknext; /* next token to allocate */
+ int toksuper; /* superior token node, e.g parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
+ * a single JSON object.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __JSMN_H_ */
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..a33ee96
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,60 @@
+# Copyright (c) 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 owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+@prefix eteroj: <http://open-music-kontrollers.ch/lv2/eteroj#> .
+
+# Eteroj IO
+eteroj:io
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <eteroj@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <eteroj.ttl> .
+
+# Eteroj Query
+eteroj:query
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <eteroj@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <eteroj.ttl> .
+
+# Eteroj MIDI (De)Cloak
+eteroj:cloak
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <eteroj@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <eteroj.ttl> .
+
+# Eteroj MIDI (Un)Pack
+eteroj:pack
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <eteroj@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <eteroj.ttl> .
+
+# Eteroj Atom Serializer
+eteroj:ninja
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <eteroj@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <eteroj.ttl> .
diff --git a/meson.build b/meson.build
index d354d89..2b3dd74 100644
--- a/meson.build
+++ b/meson.build
@@ -1,75 +1,90 @@
-project('props.lv2', 'c', default_options : [
+project('eteroj.lv2', 'c', default_options : [
'buildtype=release',
- 'warning_level=3',
+ 'warning_level=1',
'werror=false',
'b_lto=false',
'c_std=c11'])
-add_project_arguments('-D_GNU_SOURCE', language : 'c')
-
-conf_data = configuration_data()
cc = meson.get_compiler('c')
-cp = find_program('cp')
-lv2_validate = find_program('lv2_validate', native : true, required : false)
-sord_validate = find_program('sord_validate', native : true, required : false)
-lv2lint = find_program('lv2lint', required : false)
-clone = [cp, '@INPUT@', '@OUTPUT@']
-
m_dep = cc.find_library('m')
lv2_dep = dependency('lv2', version : '>=1.14.0')
-inc_dir = []
+dsp_deps = [m_dep, lv2_dep]
+
+jsmn_inc = include_directories('jsmn')
+netatom_inc = include_directories('netatom.lv2')
+osc_inc = include_directories('osc.lv2')
+props_inc = include_directories('props.lv2')
+varchunk_inc = include_directories('varchunk')
+inc_dir = [jsmn_inc, netatom_inc, osc_inc, props_inc, varchunk_inc]
inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name())
-dsp_srcs = [join_paths('test', 'props.c')]
+rawvers = run_command('cat', 'VERSION').stdout().strip()
+version = rawvers.split('.')
+
+conf_data = configuration_data()
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+cp = find_program('cp')
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+clone = [cp, '@INPUT@', '@OUTPUT@']
c_args = ['-fvisibility=hidden',
- '-ffast-math']
+ '-ffast-math',
+ '-Wno-unused-variable',
+ '-Wno-unused-function',
+ '-Wno-misleading-indentation']
+
+dsp_srcs = ['eteroj.c',
+ 'eteroj_cloak.c',
+ 'eteroj_io.c',
+ 'eteroj_ninja.c',
+ 'eteroj_pack.c',
+ 'eteroj_query.c']
+
+dsp_srcs += join_paths('jsmn', 'jsmn.c')
+
+if host_machine.system() == 'windows'
+ dsp_deps += cc.find_library('ws2_32')
+ dsp_deps += cc.find_library('psapi')
+ dsp_deps += cc.find_library('iphlpapi')
+elif host_machine.system() == 'darwin'
+ # nothing
+else
+ # nothing
+endif
-mod = shared_module('props', dsp_srcs,
+mod = shared_module('eteroj', dsp_srcs,
c_args : c_args,
include_directories : inc_dir,
name_prefix : '',
- dependencies : [m_dep, lv2_dep],
+ dependencies : dsp_deps,
install : true,
install_dir : inst_dir)
-version = run_command('cat', 'VERSION').stdout().strip().split('.')
-conf_data.set('MAJOR_VERSION', version[0])
-conf_data.set('MINOR_VERSION', version[1])
-conf_data.set('MICRO_VERSION', version[2])
-
suffix = mod.full_path().strip().split('.')[-1]
conf_data.set('MODULE_SUFFIX', '.' + suffix)
-manifest_ttl = configure_file(
- input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+manifest_ttl = configure_file(input : 'manifest.ttl.in', output : 'manifest.ttl',
configuration : conf_data,
install : true,
install_dir : inst_dir)
-dsp_ttl = custom_target('props_ttl',
- input : join_paths('test', 'props.ttl'),
- output : 'props.ttl',
- command : clone,
- install : true,
- install_dir : inst_dir)
-custom_target('chunk_bin',
- input : join_paths('test', 'chunk.bin'),
- output : 'chunk.bin',
+
+dsp_ttl = custom_target('dsp_ttl',
+ input : 'eteroj.ttl',
+ output : 'eteroj.ttl',
command : clone,
install : true,
install_dir : inst_dir)
-props_test = executable('props_test',
- join_paths('test', 'props_test.c'),
- c_args : c_args,
- install : false)
-
-test('Test', props_test,
- timeout : 240)
-
if lv2_validate.found() and sord_validate.found()
test('LV2 validate', lv2_validate,
args : [manifest_ttl, dsp_ttl])
@@ -78,5 +93,9 @@ endif
if lv2lint.found()
test('LV2 lint', lv2lint,
args : ['-Ewarn',
- 'http://open-music-kontrollers.ch/lv2/props#test'])
+ 'http://open-music-kontrollers.ch/lv2/eteroj#cloak',
+ 'http://open-music-kontrollers.ch/lv2/eteroj#io',
+ 'http://open-music-kontrollers.ch/lv2/eteroj#ninja',
+ 'http://open-music-kontrollers.ch/lv2/eteroj#pack',
+ 'http://open-music-kontrollers.ch/lv2/eteroj#query'])
endif
diff --git a/netatom.lv2/.gitlab-ci.yml b/netatom.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..2533fd8
--- /dev/null
+++ b/netatom.lv2/.gitlab-ci.yml
@@ -0,0 +1,62 @@
+stages:
+ - test
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "netatom.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: test
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+
+.test_template: &test_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+ - cd build
+ - meson test --verbose --wrap "${CI_BUILD_NAME}.wrap"
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *test_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *test_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ before_script:
+ - ln -s /usr/lib/gcc/i686-w64-mingw32/6.3-win32/libgcc_s_sjlj-1.dll /opt/i686-w64-mingw32/lib/libgcc_s_sjlj-1.dll
+ <<: *test_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
diff --git a/netatom.lv2/COPYING b/netatom.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/netatom.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/netatom.lv2/README.md b/netatom.lv2/README.md
new file mode 100644
index 0000000..9f780e3
--- /dev/null
+++ b/netatom.lv2/README.md
@@ -0,0 +1,33 @@
+# netatom.lv2
+
+## Utility header for (de)serializing LV2 atoms in binary form
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/netatom.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/netatom.lv2/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lv2/netatom.lv2
+ cd netatom.lv2
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### License
+
+Copyright (c) 2017 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/netatom.lv2/VERSION b/netatom.lv2/VERSION
new file mode 100644
index 0000000..278cbc8
--- /dev/null
+++ b/netatom.lv2/VERSION
@@ -0,0 +1 @@
+0.1.43
diff --git a/netatom.lv2/meson.build b/netatom.lv2/meson.build
new file mode 100644
index 0000000..9a09315
--- /dev/null
+++ b/netatom.lv2/meson.build
@@ -0,0 +1,38 @@
+project('netatom.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=true',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+version = run_command('cat', 'VERSION').stdout().strip()
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+lv2_dep = dependency('lv2')
+sratom_dep = dependency('sratom-0',
+ static : meson.is_cross_build() and false) #FIXME
+deps = [lv2_dep, sratom_dep]
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('ws2_32')
+endif
+
+c_args = []
+
+if cc.has_argument('-Wno-error=format=')
+ c_args += '-Wno-error=format='
+endif
+
+netatom_test = executable('netatom_test',
+ join_paths('test', 'netatom_test.c'),
+ c_args : c_args,
+ dependencies : deps,
+ install : false)
+
+test('Test', netatom_test,
+ args : ['1000'],
+ timeout : 240)
diff --git a/netatom.lv2/netatom.lv2/endian.h b/netatom.lv2/netatom.lv2/endian.h
new file mode 100644
index 0000000..f310c51
--- /dev/null
+++ b/netatom.lv2/netatom.lv2/endian.h
@@ -0,0 +1,120 @@
+// "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__) || defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#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)
+
+# ifndef htonll
+static inline uint64_t htonll(uint64_t n)
+{
+ return (((uint64_t)htonl(n)) << 32) + htonl(n >> 32);
+}
+# endif
+
+# ifndef ntohll
+# define ntohll htonll
+# endif
+
+# 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
diff --git a/netatom.lv2/netatom.lv2/netatom.h b/netatom.lv2/netatom.lv2/netatom.h
new file mode 100644
index 0000000..570c7ec
--- /dev/null
+++ b/netatom.lv2/netatom.lv2/netatom.h
@@ -0,0 +1,535 @@
+/*
+ * Copyright (c) 2017 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 _NETATOM_H
+#define _NETATOM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <netatom.lv2/endian.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/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
+
+#ifndef NETATOM_API
+# define NETATOM_API static
+#endif
+
+typedef struct _netatom_t netatom_t;
+
+NETATOM_API uint8_t *
+netatom_serialize(netatom_t *netatom, LV2_Atom *atom, size_t size_rx,
+ size_t *size_tx);
+
+NETATOM_API const LV2_Atom *
+netatom_deserialize(netatom_t *netatom, uint8_t *buf_tx, size_t size_tx);
+
+NETATOM_API netatom_t *
+netatom_new(LV2_URID_Map *map, LV2_URID_Unmap *unmap, bool swap);
+
+NETATOM_API void
+netatom_free(netatom_t *netatom);
+
+#ifdef NETATOM_IMPLEMENTATION
+
+typedef union _netatom_union_t netatom_union_t;
+
+union _netatom_union_t {
+ LV2_Atom *atom;
+ uint8_t *buf;
+};
+
+struct _netatom_t {
+ bool swap;
+ LV2_URID_Unmap *unmap;
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ struct {
+ uint8_t *buf;
+ const uint8_t *cur;
+ const uint8_t *end;
+ } dict;
+ uint32_t MIDI_MidiEvent;
+ bool overflow;
+};
+
+static inline void
+_netatom_ser_uri(netatom_t *netatom, uint32_t *urid, const char *uri)
+{
+ if(*urid == 0)
+ return; // ignore untyped atoms
+
+ // look for matching URID in dictionary
+ uint32_t match = 0;
+
+ for(netatom_union_t ptr = { .buf = netatom->dict.buf };
+ ptr.buf < netatom->dict.cur;
+ ptr.buf += lv2_atom_pad_size(lv2_atom_total_size(ptr.atom)))
+ {
+ if(ptr.atom->type == *urid)
+ {
+ match = ptr.buf - netatom->dict.buf + 1;
+ break;
+ }
+ }
+
+ if(match) // use already matched URI in dictionary
+ {
+ *urid = match;
+ }
+ else // add new URI to dictionary
+ {
+ if(!uri)
+ uri = netatom->unmap->unmap(netatom->unmap->handle, *urid);
+
+ if(!uri) // invalid urid
+ {
+ *urid = 0;
+ }
+ else
+ {
+ const uint32_t size = strlen(uri) + 1;
+ const uint32_t tot_size = sizeof(LV2_Atom) + lv2_atom_pad_size(size);
+ const uint32_t ref = netatom->dict.cur - netatom->dict.buf + 1;
+
+ if(netatom->dict.cur + tot_size <= netatom->dict.end)
+ {
+ LV2_Atom *atom = (LV2_Atom *)netatom->dict.cur;
+ atom->size = size;
+ atom->type = *urid;
+ strncpy(LV2_ATOM_BODY(atom), uri, tot_size); // automatic padding
+
+ *urid = ref;
+ netatom->dict.cur += tot_size;
+ }
+ else // dict buffer overflow
+ {
+ *urid = 0;
+ netatom->overflow = true;
+ }
+ }
+ }
+
+ if(netatom->swap)
+ *urid = htobe32(*urid);
+}
+
+static inline void
+_netatom_ser_dict(netatom_t *netatom)
+{
+ LV2_Atom *body = NULL;
+ for(netatom_union_t ptr = { .buf = netatom->dict.buf };
+ ptr.buf < netatom->dict.cur;
+ ptr.buf += lv2_atom_pad_size(lv2_atom_total_size(ptr.atom)))
+ {
+ if( netatom->swap && body)
+ body->size = htobe32(body->size);
+ body = ptr.atom;
+ ptr.atom->type = 0; // clear key
+ }
+ if(netatom->swap && body)
+ body->size = htobe32(body->size);
+}
+
+static inline void
+_netatom_deser_uri(netatom_t *netatom, uint32_t *urid)
+{
+ if(*urid == 0)
+ return; // ignore untyped atoms
+
+ const uint32_t ref = netatom->swap
+ ? be32toh(*urid)
+ : *urid;
+
+ const LV2_Atom *atom = (const LV2_Atom *)&netatom->dict.buf[ref - 1];
+ *urid = atom->type;
+}
+
+static inline void
+_netatom_deser_dict(netatom_t *netatom)
+{
+ for(netatom_union_t ptr = { .buf = netatom->dict.buf};
+ ptr.buf < netatom->dict.cur;
+ ptr.buf += lv2_atom_pad_size(lv2_atom_total_size(ptr.atom)))
+ {
+ if(netatom->swap)
+ ptr.atom->size = be32toh(ptr.atom->size);
+ const char *uri = LV2_ATOM_BODY_CONST(ptr.atom);
+ ptr.atom->type = netatom->map->map(netatom->map->handle, uri);
+ }
+}
+
+static void
+_netatom_ser_atom(netatom_t *netatom, LV2_Atom *atom)
+{
+ LV2_Atom_Forge *forge = &netatom->forge;
+ const char *uri = NULL;
+
+ if(atom->type == forge->Bool)
+ {
+ if(netatom->swap)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ *u = htobe32(*u);
+ }
+ uri = LV2_ATOM__Bool;
+ }
+ else if(atom->type == forge->Int)
+ {
+ if(netatom->swap)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ *u = htobe32(*u);
+ }
+ uri = LV2_ATOM__Int;
+ }
+ else if(atom->type == forge->Float)
+ {
+ if(netatom->swap)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ *u = htobe32(*u);
+ }
+ uri = LV2_ATOM__Float;
+ }
+ else if(atom->type == forge->Long)
+ {
+ if(netatom->swap)
+ {
+ uint64_t *u = LV2_ATOM_BODY(atom);
+ *u = htobe64(*u);
+ }
+ uri = LV2_ATOM__Long;
+ }
+ else if(atom->type == forge->Double)
+ {
+ if(netatom->swap)
+ {
+ uint64_t *u = LV2_ATOM_BODY(atom);
+ *u = htobe64(*u);
+ }
+ uri = LV2_ATOM__Double;
+ }
+ else if(atom->type == forge->URID)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ _netatom_ser_uri(netatom, u, NULL);
+ uri = LV2_ATOM__URID;
+ }
+ else if(atom->type == forge->String)
+ {
+ uri = LV2_ATOM__String;
+ }
+ else if(atom->type == forge->Chunk)
+ {
+ uri = LV2_ATOM__Chunk;
+ }
+ else if(atom->type == netatom->MIDI_MidiEvent)
+ {
+ uri = LV2_MIDI__MidiEvent;
+ }
+ else if(atom->type == forge->Literal)
+ {
+ LV2_Atom_Literal *lit = (LV2_Atom_Literal *)atom;
+ _netatom_ser_uri(netatom, &lit->body.datatype, NULL);
+ _netatom_ser_uri(netatom, &lit->body.lang, NULL);
+ uri = LV2_ATOM__Literal;
+ }
+ else if(atom->type == forge->Object)
+ {
+ LV2_Atom_Object *obj = (LV2_Atom_Object *)atom;
+ LV2_Atom *body = NULL;
+ LV2_ATOM_OBJECT_FOREACH(obj, prop)
+ {
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ body = &prop->value;
+ _netatom_ser_uri(netatom, &prop->key, NULL);
+ _netatom_ser_uri(netatom, &prop->context, NULL);
+ }
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ _netatom_ser_uri(netatom, &obj->body.id, NULL);
+ _netatom_ser_uri(netatom, &obj->body.otype, NULL);
+ uri = LV2_ATOM__Object;
+ }
+ else if(atom->type == forge->Tuple)
+ {
+ LV2_Atom_Tuple *tup = (LV2_Atom_Tuple *)atom;
+ LV2_Atom *body = NULL;
+ LV2_ATOM_TUPLE_FOREACH(tup, item)
+ {
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ body = item;
+ }
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ uri = LV2_ATOM__Tuple;
+ }
+ else if(atom->type == forge->Sequence)
+ {
+ LV2_Atom_Sequence *seq = (LV2_Atom_Sequence *)atom;
+ LV2_Atom *body = NULL;
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ body = &ev->body;
+ if(netatom->swap)
+ ev->time.frames = htobe64(ev->time.frames);
+ }
+ if(body)
+ _netatom_ser_atom(netatom, body);
+ _netatom_ser_uri(netatom, &seq->body.unit, NULL);
+ if(netatom->swap)
+ seq->body.pad = htobe32(seq->body.pad);
+ uri = LV2_ATOM__Sequence;
+ }
+ else if(atom->type == forge->Vector)
+ {
+ LV2_Atom_Vector *vec = (LV2_Atom_Vector *)atom;
+ if(netatom->swap)
+ {
+ if(vec->body.child_size == 4)
+ {
+ const unsigned n = (vec->atom.size - sizeof(LV2_Atom_Vector_Body)) / 4;
+ uint32_t *u = LV2_ATOM_CONTENTS(LV2_Atom_Vector, atom);
+ for(unsigned i = 0; i < n; i++)
+ u[i] = htobe32(u[i]);
+ }
+ else if(vec->body.child_size == 8)
+ {
+ const unsigned n = (vec->atom.size - sizeof(LV2_Atom_Vector_Body)) / 8;
+ uint64_t *u = LV2_ATOM_CONTENTS(LV2_Atom_Vector, atom);
+ for(unsigned i = 0; i < n; i++)
+ u[i] = htobe64(u[i]);
+ }
+ vec->body.child_size = htobe32(vec->body.child_size);
+ }
+ _netatom_ser_uri(netatom, &vec->body.child_type, NULL); //TODO set uri
+ uri = LV2_ATOM__Vector;
+ }
+ else if(atom->type == forge->Path)
+ {
+ uri = LV2_ATOM__Path;
+ }
+ else if(atom->type == forge->URI)
+ {
+ uri = LV2_ATOM__URI;
+ }
+
+ if(netatom->swap)
+ atom->size = htobe32(atom->size);
+ _netatom_ser_uri(netatom, &atom->type, uri);
+}
+
+static void
+_netatom_deser_atom(netatom_t *netatom, LV2_Atom *atom)
+{
+ LV2_Atom_Forge *forge = &netatom->forge;
+
+ if(netatom->swap)
+ atom->size = be32toh(atom->size);
+ _netatom_deser_uri(netatom, &atom->type);
+
+ if( (atom->type == forge->Bool)
+ || (atom->type == forge->Int)
+ || (atom->type == forge->Float) )
+ {
+ if(netatom->swap)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ *u = be32toh(*u);
+ }
+ }
+ else if( (atom->type == forge->Long)
+ || (atom->type == forge->Double) )
+ {
+ if(netatom->swap)
+ {
+ uint64_t *u = LV2_ATOM_BODY(atom);
+ *u = be64toh(*u);
+ }
+ }
+ else if(atom->type == forge->URID)
+ {
+ uint32_t *u = LV2_ATOM_BODY(atom);
+ _netatom_deser_uri(netatom, u);
+ }
+ else if(atom->type == forge->Literal)
+ {
+ LV2_Atom_Literal *lit = (LV2_Atom_Literal *)atom;
+ _netatom_deser_uri(netatom, &lit->body.datatype);
+ _netatom_deser_uri(netatom, &lit->body.lang);
+ }
+ else if(atom->type == forge->Object)
+ {
+ LV2_Atom_Object *obj = (LV2_Atom_Object *)atom;
+ _netatom_deser_uri(netatom, &obj->body.id);
+ _netatom_deser_uri(netatom, &obj->body.otype);
+ LV2_ATOM_OBJECT_FOREACH(obj, prop)
+ {
+ _netatom_deser_uri(netatom, &prop->key);
+ _netatom_deser_uri(netatom, &prop->context);
+ _netatom_deser_atom(netatom, &prop->value);
+ }
+ }
+ else if(atom->type == forge->Tuple)
+ {
+ LV2_Atom_Tuple *tup = (LV2_Atom_Tuple *)atom;
+ LV2_ATOM_TUPLE_FOREACH(tup, item)
+ {
+ _netatom_deser_atom(netatom, item);
+ }
+ }
+ else if(atom->type == forge->Sequence)
+ {
+ LV2_Atom_Sequence *seq = (LV2_Atom_Sequence *)atom;
+ _netatom_deser_uri(netatom, &seq->body.unit);
+ if(netatom->swap)
+ seq->body.pad = be32toh(seq->body.pad);
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ _netatom_deser_atom(netatom, &ev->body);
+ if(netatom->swap)
+ ev->time.frames = be64toh(ev->time.frames);
+ }
+ }
+ else if(atom->type == forge->Vector)
+ {
+ LV2_Atom_Vector *vec = (LV2_Atom_Vector *)atom;
+ _netatom_deser_uri(netatom, &vec->body.child_type);
+ if(netatom->swap)
+ {
+ vec->body.child_size = be32toh(vec->body.child_size);
+ if(vec->body.child_size == 4)
+ {
+ const unsigned n = (vec->atom.size - sizeof(LV2_Atom_Vector_Body)) / 4;
+ uint32_t *u = LV2_ATOM_CONTENTS(LV2_Atom_Vector, atom);
+ for(unsigned i = 0; i < n; i++)
+ u[i] = be32toh(u[i]);
+ }
+ else if(vec->body.child_size == 8)
+ {
+ const unsigned n = (vec->atom.size - sizeof(LV2_Atom_Vector_Body)) / 8;
+ uint64_t *u = LV2_ATOM_CONTENTS(LV2_Atom_Vector, atom);
+ for(unsigned i = 0; i < n; i++)
+ u[i] = be64toh(u[i]);
+ }
+ }
+ }
+}
+
+NETATOM_API uint8_t *
+netatom_serialize(netatom_t *netatom, LV2_Atom *atom, size_t size_rx,
+ size_t *size_tx)
+{
+ if(!netatom || !atom)
+ return NULL;
+
+ uint8_t *buf_rx = (uint8_t *)atom;
+ const uint32_t tot_size = lv2_atom_pad_size(lv2_atom_total_size(atom));
+
+ netatom->dict.buf = buf_rx + tot_size;
+ netatom->dict.cur = netatom->dict.buf;
+ netatom->dict.end = buf_rx + size_rx;
+
+ netatom->overflow = false;
+
+ _netatom_ser_atom(netatom, atom);
+ _netatom_ser_dict(netatom);
+
+ if(netatom->overflow)
+ return NULL;
+
+ const size_t size_dict = netatom->dict.cur - netatom->dict.buf;
+ const size_t written = tot_size + size_dict;
+
+ if(size_tx)
+ *size_tx = written;
+
+ return buf_rx;
+}
+
+NETATOM_API const LV2_Atom *
+netatom_deserialize(netatom_t *netatom, uint8_t *buf_tx, size_t size_tx)
+{
+ if(!netatom || !buf_tx)
+ return NULL;
+
+ LV2_Atom *atom = (LV2_Atom *)buf_tx;
+ const uint32_t size = netatom->swap
+ ? be32toh(atom->size)
+ : atom->size;
+
+ const uint32_t tot_size = lv2_atom_pad_size(sizeof(LV2_Atom) + size);
+
+ const uint32_t dict_size = size_tx - tot_size;
+ netatom->dict.buf = buf_tx + tot_size;
+ netatom->dict.cur = netatom->dict.buf + dict_size;
+ netatom->dict.end = buf_tx + size_tx;
+
+ netatom->overflow = false;
+
+ _netatom_deser_dict(netatom);
+ _netatom_deser_atom(netatom, atom);
+
+ return atom;
+}
+
+NETATOM_API netatom_t *
+netatom_new(LV2_URID_Map *map, LV2_URID_Unmap *unmap, bool swap)
+{
+ netatom_t *netatom = calloc(1, sizeof(netatom_t));
+ if(!netatom)
+ return NULL;
+
+ netatom->swap = swap;
+ netatom->map = map;
+ netatom->unmap = unmap;
+
+ lv2_atom_forge_init(&netatom->forge, map);
+
+ netatom->MIDI_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ return netatom;
+}
+
+NETATOM_API void
+netatom_free(netatom_t *netatom)
+{
+ if(!netatom)
+ return;
+
+ free(netatom);
+}
+
+#endif // NETATOM_IMPLEMENTATION
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _NETATOM_H
diff --git a/netatom.lv2/test/netatom_test.c b/netatom.lv2/test/netatom_test.c
new file mode 100644
index 0000000..972e8c8
--- /dev/null
+++ b/netatom.lv2/test/netatom_test.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2017 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 <time.h>
+#include <sratom/sratom.h>
+
+#define NETATOM_IMPLEMENTATION
+#include <netatom.lv2/netatom.h>
+
+#define MAX_URIDS 2048
+#define MAX_BUF 4092
+
+typedef struct _urid_t urid_t;
+typedef struct _store_t store_t;
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+struct _store_t {
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ store_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)
+{
+ store_t *handle = instance;
+
+ for(urid_t *itm=handle->urids; itm->urid; itm++)
+ {
+ if(itm->urid == urid)
+ return itm->uri;
+ }
+
+ // not found
+ return NULL;
+}
+
+static void
+_freemap(store_t *handle)
+{
+ for(urid_t *itm = handle->urids; itm->urid; itm++)
+ free(itm->uri);
+}
+
+static void
+_netatom_test(LV2_URID_Map *map, LV2_URID_Unmap *unmap, bool swap,
+ const LV2_Atom *atom, unsigned iterations)
+{
+ static uint8_t buf [MAX_BUF];
+ netatom_t *netatom = netatom_new(map, unmap, swap);
+ assert(netatom);
+
+ for(unsigned i = 0; i < iterations; i++)
+ {
+ memcpy(buf, atom, lv2_atom_total_size(atom));
+
+ size_t size_tx = 0;
+ uint8_t *buf_tx = netatom_serialize(netatom, (LV2_Atom *)buf, MAX_BUF, &size_tx);
+ assert(buf_tx);
+
+ if(iterations == 1)
+ {
+ fwrite(buf_tx, size_tx, 1, stdout);
+
+#if !defined(_WIN32)
+ const size_t tot_size = lv2_atom_total_size(atom);
+ fprintf(stderr, "%zu, %zu, %lf\n", tot_size, size_tx,
+ (double)size_tx / lv2_atom_total_size(atom));
+#endif
+ }
+
+ const LV2_Atom *atom_rx = netatom_deserialize(netatom, buf_tx, size_tx);
+ assert(atom_rx);
+
+ const uint32_t size_rx = lv2_atom_total_size(atom_rx);
+
+ assert(size_rx == lv2_atom_total_size(atom));
+ assert(memcmp(atom, atom_rx, size_rx) == 0);
+ }
+
+ netatom_free(netatom);
+}
+
+static void
+_sratom_test(LV2_URID_Map *map, LV2_URID_Unmap *unmap, bool pretty,
+ const LV2_Atom *atom, unsigned iterations)
+{
+ Sratom *sratom = sratom_new(map);
+ assert(sratom);
+ sratom_set_pretty_numbers(sratom, pretty);
+ const char *base_uri = "file:///tmp/base";
+ const SerdNode subject = serd_node_from_string(SERD_URI, (const uint8_t *)(""));
+ const SerdNode predicate = serd_node_from_string(SERD_URI, (const uint8_t *)(LV2_ATOM__atomTransfer));
+
+ for(unsigned i = 0; i < iterations; i++)
+ {
+ char *ttl = sratom_to_turtle(sratom, unmap, base_uri, &subject, &predicate,
+ atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+ assert(ttl);
+
+ LV2_Atom *clone = sratom_from_turtle(sratom, base_uri, &subject, &predicate, ttl);
+ assert(clone);
+
+ assert(atom->size == clone->size);
+ assert(atom->type == clone->type);
+ //assert(memcmp(LV2_ATOM_BODY_CONST(atom), LV2_ATOM_BODY_CONST(clone), atom->size) == 0);
+
+ free(clone);
+ free(ttl);
+ }
+
+ sratom_free(sratom);
+}
+
+#define MAP(O) map.map(map.handle, "urn:netatom:test#"O)
+
+int
+main(int argc, char **argv)
+{
+ static store_t handle;
+
+ if(argc < 2)
+ return -1;
+ const unsigned iterations = atoi(argv[1]);
+
+ LV2_URID_Map map = {
+ .handle = &handle,
+ .map = _map
+ };
+
+ LV2_URID_Unmap unmap = {
+ .handle = &handle,
+ .unmap = _unmap
+ };
+
+ LV2_Atom_Forge forge;
+ lv2_atom_forge_init(&forge, &map);
+
+ union {
+ LV2_Atom atom;
+ uint8_t buf [2048];
+ } un;
+
+ lv2_atom_forge_set_buffer(&forge, un.buf, 2048);
+
+ LV2_Atom_Forge_Frame obj_frame;
+ lv2_atom_forge_object(&forge, &obj_frame, 0, MAP("otype"));
+ {
+ lv2_atom_forge_key(&forge, forge.Int);
+ lv2_atom_forge_int(&forge, 12);
+
+ lv2_atom_forge_key(&forge, forge.Bool);
+ lv2_atom_forge_bool(&forge, 1);
+
+ lv2_atom_forge_key(&forge, forge.Long);
+ lv2_atom_forge_long(&forge, 14);
+
+ lv2_atom_forge_key(&forge, forge.Float);
+ lv2_atom_forge_float(&forge, 1.5);
+
+ lv2_atom_forge_key(&forge, forge.Double);
+ lv2_atom_forge_double(&forge, 4.5);
+
+ lv2_atom_forge_key(&forge, forge.String);
+ lv2_atom_forge_string(&forge, "hello", 5);
+
+ lv2_atom_forge_key(&forge, forge.Path);
+ lv2_atom_forge_path(&forge, "/tmp", 4);
+
+ lv2_atom_forge_key(&forge, forge.Literal);
+ lv2_atom_forge_literal(&forge, "hello", 5, MAP("dtype"), MAP("lang"));
+
+ /*
+ lv2_atom_forge_key(&forge, forge.URI);
+ lv2_atom_forge_uri(&forge, LV2_URID__map, strlen(LV2_URID__map));
+ */
+
+ lv2_atom_forge_key(&forge, forge.URID);
+ lv2_atom_forge_urid(&forge, MAP("key"));
+
+ const uint8_t m [3] = {0x90, 0x2f, 0x7f};
+ lv2_atom_forge_key(&forge, map.map(map.handle, LV2_MIDI__MidiEvent));
+ lv2_atom_forge_atom(&forge, 3, map.map(map.handle, LV2_MIDI__MidiEvent));
+ lv2_atom_forge_write(&forge, m, 3);
+
+ const uint8_t b [8] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
+ lv2_atom_forge_key(&forge, forge.Chunk);
+ lv2_atom_forge_atom(&forge, 8, forge.Chunk);
+ lv2_atom_forge_write(&forge, b, 8);
+
+ LV2_Atom_Forge_Frame tup_frame;
+ lv2_atom_forge_key(&forge, forge.Tuple);
+ lv2_atom_forge_tuple(&forge, &tup_frame);
+ {
+ for(unsigned i = 0; i < 16; i++)
+ lv2_atom_forge_int(&forge, i);
+ }
+ lv2_atom_forge_pop(&forge, &tup_frame);
+
+ LV2_Atom_Forge_Frame vec_frame;
+ lv2_atom_forge_key(&forge, forge.Vector);
+ lv2_atom_forge_vector_head(&forge, &vec_frame, sizeof(int32_t), forge.Int);
+ {
+ for(unsigned i = 0; i < 16; i++)
+ lv2_atom_forge_int(&forge, i);
+ }
+ lv2_atom_forge_pop(&forge, &vec_frame);
+
+ LV2_Atom_Forge_Frame seq_frame;
+ lv2_atom_forge_key(&forge, forge.Sequence);
+ lv2_atom_forge_sequence_head(&forge, &seq_frame, MAP(LV2_ATOM__frameTime));
+ {
+ for(unsigned i = 0; i < 16; i++)
+ {
+ lv2_atom_forge_frame_time(&forge, i);
+ lv2_atom_forge_int(&forge, i);
+ }
+ }
+ lv2_atom_forge_pop(&forge, &seq_frame);
+ }
+ lv2_atom_forge_pop(&forge, &obj_frame);
+
+ // add some dummy URI to hash map
+ char tmp [32];
+ for(int i=0; i<1024; i++)
+ {
+ snprintf(tmp, 32, "urn:dummy:%i", i);
+ map.map(map.handle, tmp);
+ }
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+ struct timespec t0, t1, t2;
+ clock_gettime(CLOCK_MONOTONIC, &t0);
+#endif
+ _netatom_test(&map, &unmap, true, &un.atom, iterations);
+#if !defined(__APPLE__) && !defined(_WIN32)
+ clock_gettime(CLOCK_MONOTONIC, &t1);
+#endif
+ _sratom_test(&map, &unmap, false, &un.atom, iterations);
+#if !defined(__APPLE__) && !defined(_WIN32)
+ clock_gettime(CLOCK_MONOTONIC, &t2);
+
+ const double d1 = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) * 1e-9;
+ const double d2 = (t2.tv_sec - t1.tv_sec) + (t2.tv_nsec - t1.tv_nsec) * 1e-9;
+ fprintf(stderr, "%lf s, %lf s, x %lf\n", d1, d2, d2/d1);
+#endif
+
+ _freemap(&handle);
+
+ return 0;
+}
diff --git a/osc.lv2/.gitlab-ci.yml b/osc.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..ebc4676
--- /dev/null
+++ b/osc.lv2/.gitlab-ci.yml
@@ -0,0 +1,62 @@
+stages:
+ - test
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "osc.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: test
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+
+.test_template: &test_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+ - cd build
+ - meson test --verbose --wrap "${CI_BUILD_NAME}.wrap"
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *test_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *test_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ before_script:
+ - ln -s /usr/lib/gcc/i686-w64-mingw32/6.3-win32/libgcc_s_sjlj-1.dll /opt/i686-w64-mingw32/lib/libgcc_s_sjlj-1.dll
+ <<: *test_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
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..1a30571
--- /dev/null
+++ b/osc.lv2/README.md
@@ -0,0 +1,33 @@
+# osc.lv2
+
+## Open Sound Control Extension for the LV2 Plugin Specification
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/osc.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/osc.lv2/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lv2/osc.lv2
+ cd osc.lv2
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### License
+
+Copyright (c) 2017 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/osc.lv2/VERSION b/osc.lv2/VERSION
new file mode 100644
index 0000000..2317587
--- /dev/null
+++ b/osc.lv2/VERSION
@@ -0,0 +1 @@
+0.1.105
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/meson.build b/osc.lv2/meson.build
new file mode 100644
index 0000000..750f0dc
--- /dev/null
+++ b/osc.lv2/meson.build
@@ -0,0 +1,36 @@
+project('osc.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=true',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+version = run_command('cat', 'VERSION').stdout().strip()
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+lv2_dep = dependency('lv2')
+thread_dep = dependency('threads')
+deps = [lv2_dep, thread_dep]
+
+c_args = []
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('ws2_32')
+ c_args += '-Wno-error=format'
+ c_args += '-Wno-error=format-extra-args'
+endif
+
+osc_test = executable('osc_test',
+ join_paths('test', 'osc_test.c'),
+ c_args : c_args,
+ dependencies : deps,
+ install : false)
+
+# FIXME start virautl serial pair before test
+# socat -d -d pty,raw,echo=0 pty,raw,echo=0
+test('Test', osc_test,
+ timeout : 240)
diff --git a/osc.lv2/osc.lv2/endian.h b/osc.lv2/osc.lv2/endian.h
new file mode 100644
index 0000000..f310c51
--- /dev/null
+++ b/osc.lv2/osc.lv2/endian.h
@@ -0,0 +1,120 @@
+// "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__) || defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#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)
+
+# ifndef htonll
+static inline uint64_t htonll(uint64_t n)
+{
+ return (((uint64_t)htonl(n)) << 32) + htonl(n >> 32);
+}
+# endif
+
+# ifndef ntohll
+# define ntohll htonll
+# endif
+
+# 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
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..8e0ae45
--- /dev/null
+++ b/osc.lv2/osc.lv2/reader.h
@@ -0,0 +1,571 @@
+/*
+ * 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 <stdarg.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/stream.h b/osc.lv2/osc.lv2/stream.h
new file mode 100644
index 0000000..a86c230
--- /dev/null
+++ b/osc.lv2/osc.lv2/stream.h
@@ -0,0 +1,1379 @@
+/*
+ * 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_STREAM_H
+#define LV2_OSC_STREAM_H
+
+#include <stdbool.h>
+#include <string.h>
+#if !defined(_WIN32)
+# include <arpa/inet.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <netinet/tcp.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# include <termios.h>
+# include <limits.h>
+#endif
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <osc.lv2/osc.h>
+
+#if !defined(LV2_OSC_STREAM_SNDBUF)
+# define LV2_OSC_STREAM_SNDBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_RCVBUF)
+# define LV2_OSC_STREAM_RCVBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_REQBUF)
+# define LV2_OSC_STREAM_REQBUF 1024
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *
+(*LV2_OSC_Stream_Write_Request)(void *data, size_t minimum, size_t *maximum);
+
+typedef void
+(*LV2_OSC_Stream_Write_Advance)(void *data, size_t written);
+
+typedef const void *
+(*LV2_OSC_Stream_Read_Request)(void *data, size_t *toread);
+
+typedef void
+(*LV2_OSC_Stream_Read_Advance)(void *data);
+
+typedef struct _LV2_OSC_Address LV2_OSC_Address;
+typedef struct _LV2_OSC_Driver LV2_OSC_Driver;
+typedef struct _LV2_OSC_Stream LV2_OSC_Stream;
+
+struct _LV2_OSC_Address {
+ socklen_t len;
+ union {
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ };
+};
+
+struct _LV2_OSC_Driver {
+ LV2_OSC_Stream_Write_Request write_req;
+ LV2_OSC_Stream_Write_Advance write_adv;
+ LV2_OSC_Stream_Read_Request read_req;
+ LV2_OSC_Stream_Read_Advance read_adv;
+};
+
+struct _LV2_OSC_Stream {
+ int socket_family;
+ int socket_type;
+ int protocol;
+ bool server;
+ bool slip;
+ bool serial;
+ bool connected;
+ int sock;
+ int fd;
+ LV2_OSC_Address self;
+ LV2_OSC_Address peer;
+ const LV2_OSC_Driver *driv;
+ void *data;
+ uint8_t tx_buf [0x4000];
+ uint8_t rx_buf [0x4000];
+ size_t rx_off;
+ char url [PATH_MAX];
+};
+
+typedef enum _LV2_OSC_Enum {
+ LV2_OSC_NONE = 0x000000,
+
+ LV2_OSC_SEND = 0x800000,
+ LV2_OSC_RECV = 0x400000,
+ LV2_OSC_CONN = 0x200000,
+
+ LV2_OSC_ERR = 0x00ffff
+} LV2_OSC_Enum;
+
+static const char *udp_prefix = "osc.udp://";
+static const char *tcp_prefix = "osc.tcp://";
+static const char *tcp_slip_prefix = "osc.slip.tcp://";
+static const char *tcp_prefix_prefix = "osc.prefix.tcp://";
+static const char *ser_prefix = "osc.serial://";
+//FIXME serial
+
+
+static int
+_lv2_osc_stream_interface_attribs(int fd, int speed)
+{
+ struct termios tty;
+
+ if(tcgetattr(fd, &tty) < 0)
+ {
+ return -1;
+ }
+
+ cfsetospeed(&tty, (speed_t)speed);
+ cfsetispeed(&tty, (speed_t)speed);
+
+ tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
+ tty.c_cflag &= ~CSIZE;
+ tty.c_cflag |= CS8; /* 8-bit characters */
+ tty.c_cflag &= ~PARENB; /* no parity bit */
+ tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
+ tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
+
+ /* setup for non-canonical mode */
+ tty.c_iflag &= ~(IGNCR | ONLCR | IXON);
+ tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ tty.c_oflag &= ~OPOST;
+
+ /* fetch bytes as they become available */
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 0;
+
+ if(tcsetattr(fd, TCSANOW, &tty) != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+#define LV2_OSC_STREAM_ERRNO(EV, ERRNO) ( (EV & (~LV2_OSC_ERR)) | (ERRNO) )
+
+static void
+_close_socket(int *fd)
+{
+ if(fd)
+ {
+ if(*fd >= 0)
+ {
+ close(*fd);
+ }
+
+ *fd = -1;
+ }
+}
+
+static int
+lv2_osc_stream_deinit(LV2_OSC_Stream *stream)
+{
+ _close_socket(&stream->fd);
+ _close_socket(&stream->sock);
+
+ return 0;
+}
+
+static int
+_lv2_osc_stream_reinit(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+ lv2_osc_stream_deinit(stream);
+
+ char *dup = strdup(stream->url);
+ if(!dup)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ goto fail;
+ }
+
+ char *ptr = dup;
+ char *tmp;
+
+ if(strncmp(ptr, udp_prefix, strlen(udp_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_DGRAM;
+ stream->protocol = IPPROTO_UDP;
+ ptr += strlen(udp_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix, strlen(tcp_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix);
+ }
+ else if(strncmp(ptr, tcp_slip_prefix, strlen(tcp_slip_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_slip_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix_prefix, strlen(tcp_prefix_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix_prefix);
+ }
+ else if(strncmp(ptr, ser_prefix, strlen(ser_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->serial = true;
+ ptr += strlen(ser_prefix);
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOPROTOOPT);
+ goto fail;
+ }
+
+ if(ptr[0] == '\0')
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ if(stream->serial)
+ {
+ stream->sock = open(ptr, O_RDWR | O_NOCTTY | O_NDELAY);
+ if(stream->sock < 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(fcntl(stream->sock, F_SETFL, FNDELAY) == -1) //FIXME
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(_lv2_osc_stream_interface_attribs(stream->sock, B115200) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ stream->connected = true;
+ }
+ else // !stream->serial
+ {
+ const char *node = NULL;
+ const char *iface = NULL;
+ const char *service = NULL;
+
+ // optional IPv6
+ if(ptr[0] == '[')
+ {
+ stream->socket_family = AF_INET6;
+ ++ptr;
+ }
+
+ node = ptr;
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, '%')) )
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ iface = ++ptr;
+ }
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, ']')) )
+ if(ptr)
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ ++ptr;
+ }
+
+ // mandatory IPv4/6
+ ptr = strchr(ptr, ':');
+ if(!ptr)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ ptr[0] = '\0';
+
+ service = ++ptr;
+
+ if(strlen(node) == 0)
+ {
+ node = NULL;
+ stream->server = true;
+ }
+
+ stream->sock = socket(stream->socket_family, stream->socket_type,
+ stream->protocol);
+
+ if(stream->sock < 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(fcntl(stream->sock, F_SETFL, O_NONBLOCK) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+ const int reuseaddr = 1;
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(sendbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(recvbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->socket_family == AF_INET) // IPv4
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ memcpy(&stream->self.in4, res->ai_addr, res->ai_addrlen);
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in4,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in4);
+ stream->self.in4.sin_family = stream->socket_family;
+ stream->self.in4.sin_port = htons(0);
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in4,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->peer.len = res->ai_addrlen;
+ memcpy(&stream->peer.in4, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ const int broadcast = 1;
+
+ if(setsockopt(stream->sock, SOL_SOCKET, SO_BROADCAST,
+ &broadcast, sizeof(broadcast)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ //FIXME handle multicast
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in4,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true;
+ }
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+ else if(stream->socket_family == AF_INET6) // IPv6
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ memcpy(&stream->self.in6, res->ai_addr, res->ai_addrlen);
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in6,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in6);
+ stream->self.in6.sin6_family = stream->socket_family;
+ stream->self.in6.sin6_port = htons(0);
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in6,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->peer.len = res->ai_addrlen;
+ memcpy(&stream->peer.in6, res->ai_addr, res->ai_addrlen);
+
+ if(iface)
+ {
+ stream->peer.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ // nothing to do
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true;
+ }
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+
+ free(dup);
+
+ return ev;
+
+fail:
+ if(dup)
+ {
+ free(dup);
+ }
+
+ _close_socket(&stream->sock);
+
+ return ev;
+}
+
+static int
+lv2_osc_stream_init(LV2_OSC_Stream *stream, const char *url,
+ const LV2_OSC_Driver *driv, void *data)
+{
+ memset(stream, 0x0, sizeof(LV2_OSC_Stream));
+
+ strncpy(stream->url, url, sizeof(stream->url));
+ stream->driv = driv;
+ stream->data = data;
+ stream->sock = -1;
+ stream->fd = -1;
+
+ return _lv2_osc_stream_reinit(stream);
+}
+
+#define SLIP_END 0300 // 0xC0, 192, indicates end of packet
+#define SLIP_ESC 0333 // 0xDB, 219, indicates byte stuffing
+#define SLIP_END_REPLACE 0334 // 0xDC, 220, ESC ESC_END means END data byte
+#define SLIP_ESC_REPLACE 0335 // 0xDD, 221, ESC ESC_ESC means ESC data byte
+
+// SLIP encoding
+static size_t
+lv2_osc_slip_encode_inline(uint8_t *dst, size_t len)
+{
+ if(len == 0)
+ return 0;
+
+ const uint8_t *end = dst + len;
+
+ // estimate new size
+ size_t size = 2; // double ended SLIP
+ for(const uint8_t *from=dst; from<end; from++, size++)
+ {
+ if( (*from == SLIP_END) || (*from == SLIP_ESC))
+ size ++;
+ }
+
+ // fast track if no escaping needed
+ if(size == len + 2)
+ {
+ memmove(dst+1, dst, len);
+ dst[0] = SLIP_END;
+ dst[size-1] = SLIP_END;
+
+ return size;
+ }
+
+ // slow track if some escaping needed
+ uint8_t *to = dst + size - 1;
+ *to-- = SLIP_END;
+ for(const uint8_t *from=end-1; from>=dst; from--)
+ {
+ if(*from == SLIP_END)
+ {
+ *to-- = SLIP_END_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else if(*from == SLIP_ESC)
+ {
+ *to-- = SLIP_ESC_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else
+ *to-- = *from;
+ }
+ *to-- = SLIP_END;
+
+ return size;
+}
+
+// SLIP decoding
+static size_t
+lv2_osc_slip_decode_inline(uint8_t *dst, size_t len, size_t *size)
+{
+ const uint8_t *src = dst;
+ const uint8_t *end = dst + len;
+ uint8_t *ptr = dst;
+
+ bool whole = false;
+
+ if( (src < end) && (*src == SLIP_END) )
+ {
+ whole = true;
+ src++;
+ }
+
+ while(src < end)
+ {
+ if(*src == SLIP_ESC)
+ {
+ if(src == end-1)
+ break;
+
+ src++;
+ if(*src == SLIP_END_REPLACE)
+ *ptr++ = SLIP_END;
+ else if(*src == SLIP_ESC_REPLACE)
+ *ptr++ = SLIP_ESC;
+ src++;
+ }
+ else if(*src == SLIP_END)
+ {
+ src++;
+
+ *size = whole ? ptr - dst : 0;
+ return src - dst;
+ }
+ else
+ {
+ *ptr++ = *src++;
+ }
+ }
+
+ *size = 0;
+ return 0;
+}
+
+static LV2_OSC_Enum
+_lv2_osc_stream_run_udp(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // send everything
+ if(stream->peer.len) // has a peer
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ const ssize_t sent = sendto(stream->sock, buf, tosend, 0,
+ (struct sockaddr *)&stream->peer.in6, stream->peer.len);
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // full queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+
+ // recv everything
+ {
+ uint8_t *buf;
+ size_t max_len;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, &max_len)) )
+ {
+ struct sockaddr_in6 in;
+ socklen_t in_len = sizeof(in);
+
+ memset(&in, 0, in_len);
+ const ssize_t recvd = recvfrom(stream->sock, buf, max_len, 0,
+ (struct sockaddr *)&in, &in_len);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // peer has shut down
+ break;
+ }
+
+ stream->peer.len = in_len;
+ memcpy(&stream->peer.in6, &in, in_len);
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+
+ return ev;
+}
+
+static LV2_OSC_Enum
+_lv2_osc_stream_run_tcp(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // handle connections
+ if(!stream->connected) // no peer
+ {
+ if(stream->server)
+ {
+ stream->peer.len = sizeof(stream->peer.in6);
+ stream->fd = accept(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ &stream->peer.len);
+
+ if(stream->fd >= 0)
+ {
+ const int flag = 1;
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+
+ if(fcntl(stream->fd, F_SETFL, O_NONBLOCK) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(sendbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(recvbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ stream->connected = true; // orderly accept
+ }
+ else
+ {
+ //ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+ }
+ else
+ {
+ if(stream->sock < 0)
+ {
+ ev = _lv2_osc_stream_reinit(stream);
+ }
+
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true; // orderly (re)connect
+ }
+ else
+ {
+ //if(errno == EISCONN)
+ //{
+ // _close_socket(&stream->sock);
+ //}
+
+ //ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+ }
+ }
+
+ // send everything
+ if(stream->connected)
+ {
+ int *fd = stream->server
+ ? &stream->fd
+ : &stream->sock;
+
+ if(*fd >= 0)
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ if(stream->slip) // SLIP framed
+ {
+ if(tosend <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ memcpy(stream->tx_buf, buf, tosend);
+ tosend = lv2_osc_slip_encode_inline(stream->tx_buf, tosend);
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ const size_t nsize = tosend + sizeof(uint32_t);
+
+ if(nsize <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ const uint32_t prefix = htonl(tosend);
+
+ memcpy(stream->tx_buf, &prefix, sizeof(uint32_t));
+ memcpy(stream->tx_buf + sizeof(uint32_t), buf, tosend);
+ tosend = nsize;
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+
+ const ssize_t sent = tosend
+ ? send(*fd, stream->tx_buf, tosend, 0)
+ : 0;
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+
+ // recv everything
+ if(stream->connected)
+ {
+ int *fd = stream->server
+ ? &stream->fd
+ : &stream->sock;
+
+ if(*fd >= 0)
+ {
+ if(stream->slip) // SLIP framed
+ {
+ while(true)
+ {
+ ssize_t recvd = recv(*fd, stream->rx_buf + stream->rx_off,
+ sizeof(stream->rx_buf) - stream->rx_off, 0);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ _close_socket(fd);
+ stream->connected = false; // orderly shutdown
+ break;
+ }
+
+ uint8_t *ptr = stream->rx_buf;
+ recvd += stream->rx_off;
+
+ while(recvd > 0)
+ {
+ size_t size;
+ size_t parsed = lv2_osc_slip_decode_inline(ptr, recvd, &size);
+
+ if(size) // dispatch
+ {
+ uint8_t *buf;
+
+ if( (buf = stream->driv->write_req(stream->data, size, NULL)) )
+ {
+ memcpy(buf, ptr, size);
+
+ stream->driv->write_adv(stream->data, size);
+ ev |= LV2_OSC_RECV;
+ }
+ else
+ {
+ parsed = 0;
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ }
+ }
+
+ if(parsed)
+ {
+ ptr += parsed;
+ recvd -= parsed;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(recvd > 0) // is there remaining chunk for next call?
+ {
+ memmove(stream->rx_buf, ptr, recvd);
+ stream->rx_off = recvd;
+ }
+ else
+ {
+ stream->rx_off = 0;
+ }
+
+ break;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ uint8_t *buf;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, NULL)) )
+ {
+ uint32_t prefix;
+
+ ssize_t recvd = recv(*fd, &prefix, sizeof(uint32_t), 0);
+ if(recvd == sizeof(uint32_t))
+ {
+ prefix = ntohl(prefix); //FIXME check prefix <= max_len
+ recvd = recv(*fd, buf, prefix, 0);
+ }
+ else if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ _close_socket(fd);
+ stream->connected = false; // orderly shutdown
+ break;
+ }
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ }
+ }
+
+ if(stream->connected)
+ {
+ ev |= LV2_OSC_CONN;
+ }
+
+ return ev;
+}
+
+static LV2_OSC_Enum
+_lv2_osc_stream_run_ser(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // send everything
+ {
+ const int fd = stream->sock;
+
+ if(fd >= 0)
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ if(stream->slip) // SLIP framed
+ {
+ if(tosend <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ memcpy(stream->tx_buf, buf, tosend);
+ tosend = lv2_osc_slip_encode_inline(stream->tx_buf, tosend);
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ const size_t nsize = tosend + sizeof(uint32_t);
+
+ if(nsize <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ const uint32_t prefix = htonl(tosend);
+
+ memcpy(stream->tx_buf, &prefix, sizeof(uint32_t));
+ memcpy(stream->tx_buf + sizeof(uint32_t), buf, tosend);
+ tosend = nsize;
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+
+ const ssize_t sent = tosend
+ ? write(fd, stream->tx_buf, tosend)
+ : 0;
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+
+ // recv everything
+ {
+ const int fd = stream->sock;
+
+ if(fd >= 0)
+ {
+ if(stream->slip) // SLIP framed
+ {
+ while(true)
+ {
+ ssize_t recvd = read(fd, stream->rx_buf + stream->rx_off,
+ sizeof(stream->rx_buf) - stream->rx_off);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // orderly shutdown
+ break;
+ }
+
+ uint8_t *ptr = stream->rx_buf;
+ recvd += stream->rx_off;
+
+ while(recvd > 0)
+ {
+ size_t size;
+ size_t parsed = lv2_osc_slip_decode_inline(ptr, recvd, &size);
+
+ if(size) // dispatch
+ {
+ uint8_t *buf;
+
+ if( (buf = stream->driv->write_req(stream->data, size, NULL)) )
+ {
+ memcpy(buf, ptr, size);
+
+ stream->driv->write_adv(stream->data, size);
+ ev |= LV2_OSC_RECV;
+ }
+ else
+ {
+ parsed = 0;
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ }
+ }
+
+ if(parsed)
+ {
+ ptr += parsed;
+ recvd -= parsed;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(recvd > 0) // is there remaining chunk for next call?
+ {
+ memmove(stream->rx_buf, ptr, recvd);
+ stream->rx_off = recvd;
+ }
+ else
+ {
+ stream->rx_off = 0;
+ }
+
+ break;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ uint8_t *buf;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, NULL)) )
+ {
+ uint32_t prefix;
+
+ ssize_t recvd = read(fd, &prefix, sizeof(uint32_t));
+ if(recvd == sizeof(uint32_t))
+ {
+ prefix = ntohl(prefix); //FIXME check prefix <= max_len
+ recvd = read(fd, buf, prefix);
+ }
+ else if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // orderly shutdown
+ break;
+ }
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ }
+ }
+
+ if(stream->connected)
+ {
+ ev |= LV2_OSC_CONN;
+ }
+
+ return ev;
+}
+
+static LV2_OSC_Enum
+lv2_osc_stream_run(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ switch(stream->socket_type)
+ {
+ case SOCK_DGRAM:
+ {
+ ev |= _lv2_osc_stream_run_udp(stream);
+ } break;
+ case SOCK_STREAM:
+ {
+ ev |= _lv2_osc_stream_run_tcp(stream);
+ } break;
+ default:
+ {
+ ev |= _lv2_osc_stream_run_ser(stream);
+ } break;
+ }
+
+ return ev;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_STREAM_H
diff --git a/osc.lv2/osc.lv2/util.h b/osc.lv2/osc.lv2/util.h
new file mode 100644
index 0000000..195bb86
--- /dev/null
+++ b/osc.lv2/osc.lv2/util.h
@@ -0,0 +1,505 @@
+/*
+ * 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 <stdlib.h>
+
+#include <osc.lv2/osc.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __unused
+# define __unused __attribute__((unused))
+#endif
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_false_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_nil_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_impulse_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_symbol_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ LV2_URID *S)
+{
+ assert(S);
+ *S = ((const LV2_Atom_URID *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_midi_get(LV2_OSC_URID *osc_urid __unused, 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 __unused, 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 __unused, 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);
+
+ uint8_t *key [4] = {
+ r, g, b, a
+ };
+
+ const char *pos = str;
+ char *endptr;
+
+ for(unsigned count = 0; count < 4; count++, pos += 2)
+ {
+ char buf [5] = {'0', 'x', pos[0], pos[1], '\0'};
+
+ *key[count] = strtol(buf, &endptr, 16);
+ }
+
+ 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..037d44c
--- /dev/null
+++ b/osc.lv2/osc.lv2/writer.h
@@ -0,0 +1,579 @@
+/*
+ * 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
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef struct _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 rawlen = strlen(s) + 1;
+ const size_t padded = LV2_OSC_PADDED_SIZE(rawlen);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ const uint32_t blank = 0;
+ memcpy(writer->ptr + padded - sizeof(uint32_t), &blank, sizeof(uint32_t));
+ memcpy(writer->ptr, s, rawlen);
+ 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 rawlen = strlen(fmt) + 1;
+ const size_t padded = LV2_OSC_PADDED_SIZE(rawlen + 1);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ const uint32_t blank = 0;
+ memcpy(writer->ptr + padded - sizeof(uint32_t), &blank, sizeof(uint32_t));
+ *writer->ptr++ = ',';
+ memcpy(writer->ptr, fmt, rawlen);
+ 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:
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_blob(writer, len, 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:
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_midi(writer, len, 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:
+ {
+ const uint8_t r = va_arg(args, unsigned);
+ const uint8_t g = va_arg(args, unsigned);
+ const uint8_t b = va_arg(args, unsigned);
+ const uint8_t a = va_arg(args, unsigned);
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
+ 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 = { .ref = 0 };
+
+ 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 = { .ref = 0 };
+
+ 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->ATOM_Literal)
+ {
+ const LV2_Atom_Literal *lit = (LV2_Atom_Literal *)atom;
+
+ if(lit->body.datatype == osc_urid->OSC_Char)
+ {
+ const char c = *(const char *)LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, lit);
+ if(!lv2_osc_writer_add_char(writer, c))
+ return false;
+ }
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ {
+ const char *rgba = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ uint8_t r, g, b, a;
+ if(sscanf(rgba, "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, &r, &g, &b, &a) != 4)
+ return false;
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
+ 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/test/osc_test.c b/osc.lv2/test/osc_test.c
new file mode 100644
index 0000000..a535277
--- /dev/null
+++ b/osc.lv2/test/osc_test.c
@@ -0,0 +1,968 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <time.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/reader.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+#if !defined(_WIN32)
+# include <osc.lv2/stream.h>
+#endif
+
+#define BUF_SIZE 0x100000
+#define MAX_URIDS 512
+
+typedef void (*test_t)(LV2_OSC_Writer *writer);
+typedef struct _urid_t urid_t;
+typedef struct _app_t app_t;
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+struct _app_t {
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+static app_t __app;
+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
+};
+
+const uint8_t raw_8 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 't', 'c', 'r',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+ 0x0, 0x0, 0x0, 'o',
+ 0x1, 0x2, 0x3, 0x4
+};
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ app_t *app = instance;
+
+ urid_t *itm;
+ for(itm=app->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(app->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++app->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const char *
+_unmap(LV2_URID_Unmap_Handle instance, LV2_URID urid)
+{
+ app_t *app = instance;
+
+ urid_t *itm;
+ for(itm=app->urids; itm->urid; itm++)
+ {
+ if(itm->urid == urid)
+ return itm->uri;
+ }
+
+ // not found
+ return NULL;
+}
+
+static LV2_URID_Map map = {
+ .handle = &__app,
+ .map = _map
+};
+
+static LV2_URID_Unmap unmap = {
+ .handle = &__app,
+ .unmap = _unmap
+};
+
+//#define DUMP
+#if defined(DUMP)
+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");
+}
+#endif
+
+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 = { .ref = 0 };
+ 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 = { .ref = 0 };
+ 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);
+#if defined(DUMP)
+ if(memcmp(raw, buf0, size) != 0)
+ _dump(raw, buf0, size);
+#endif
+ 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);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
+ 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);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
+ 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",
+ (int64_t)12, (double)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)
+{
+ const uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ const int32_t len = sizeof(m);
+ assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", len, m));
+ _test_a(writer, raw_4, sizeof(raw_4));
+}
+
+static void
+test_5_a(LV2_OSC_Writer *writer)
+{
+ const uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ const int32_t len = sizeof(b);
+ assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", len, b));
+ _test_a(writer, raw_5, sizeof(raw_5));
+}
+
+static void
+test_6_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl = { .ref = 0 };
+ LV2_OSC_Writer_Frame frame_itm = { .ref = 0 };
+
+ 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] = { { .ref = 0 }, { .ref = 0 } };
+ LV2_OSC_Writer_Frame frame_itm[2] = { { .ref = 0 }, { .ref = 0 } };;
+
+ 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 void
+test_8_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "tcr",
+ 1ULL,
+ 'o',
+ 0x1, 0x2, 0x3, 0x4));
+ _test_a(writer, raw_8, sizeof(raw_8));
+}
+
+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,
+ test_8_a,
+
+ NULL
+}
+;
+static int
+_run_tests()
+{
+ 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);
+ }
+
+ assert(unmap.unmap(unmap.handle, 0)== NULL);
+
+ return 0;
+}
+
+#if !defined(_WIN32)
+typedef struct _item_t item_t;
+typedef struct _stash_t stash_t;
+
+struct _item_t {
+ size_t size;
+ uint8_t buf [];
+};
+
+struct _stash_t {
+ size_t size;
+ item_t **items;
+ item_t *rsvd;
+};
+
+static uint8_t *
+_stash_write_req(stash_t *stash, size_t minimum, size_t *maximum)
+{
+ if(!stash->rsvd || (stash->rsvd->size < minimum))
+ {
+ const size_t sz = sizeof(item_t) + minimum;
+ stash->rsvd = realloc(stash->rsvd, sz);
+ assert(stash->rsvd);
+ stash->rsvd->size = minimum;
+ }
+
+ if(maximum)
+ {
+ *maximum = stash->rsvd->size;
+ }
+
+ return stash->rsvd->buf;
+}
+
+static void
+_stash_write_adv(stash_t *stash, size_t written)
+{
+ assert(stash->rsvd);
+ assert(stash->rsvd->size >= written);
+ stash->rsvd->size = written;
+ stash->size += 1;
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+ stash->items[stash->size - 1] = stash->rsvd;
+ stash->rsvd = NULL;
+}
+
+static const uint8_t *
+_stash_read_req(stash_t *stash, size_t *size)
+{
+ if(stash->size == 0)
+ {
+ if(size)
+ {
+ *size = 0;
+ }
+
+ return NULL;
+ }
+
+ item_t *item = stash->items[0];
+
+ if(size)
+ {
+ *size = item->size;
+ }
+
+ return item->buf;
+}
+
+static void
+_stash_read_adv(stash_t *stash)
+{
+ assert(stash->size);
+
+ free(stash->items[0]);
+ stash->size -= 1;
+
+ for(unsigned i = 0; i < stash->size; i++)
+ {
+ stash->items[i] = stash->items[i+1];
+ }
+
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+}
+
+static void *
+_write_req(void *data, size_t minimum, size_t *maximum)
+{
+ stash_t *stash = data;
+
+ return _stash_write_req(&stash[0], minimum, maximum);
+}
+
+static void
+_write_adv(void *data, size_t written)
+{
+ stash_t *stash = data;
+
+ _stash_write_adv(&stash[0], written);
+}
+
+static const void *
+_read_req(void *data, size_t *toread)
+{
+ stash_t *stash = data;
+
+ return _stash_read_req(&stash[1], toread);
+}
+
+static void
+_read_adv(void *data)
+{
+ stash_t *stash = data;
+
+ _stash_read_adv(&stash[1]);
+}
+
+static const LV2_OSC_Driver driv = {
+ .write_req = _write_req,
+ .write_adv = _write_adv,
+ .read_req = _read_req,
+ .read_adv = _read_adv
+};
+
+#define COUNT 128
+
+typedef struct _pair_t pair_t;
+
+struct _pair_t {
+ const char *server;
+ const char *client;
+ bool lossy;
+};
+
+static void *
+_thread_1(void *data)
+{
+ const pair_t *pair = data;
+ const char *uri = pair->server;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ time_t t0 = time(NULL);
+ unsigned count = 0;
+ while(true)
+ {
+ const time_t t1 = time(NULL);
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 0);
+ check[arg->i] = 1;
+ }
+
+ count++;
+
+ while(true)
+ {
+ // send back
+ uint8_t *buf_tx;
+ if( (buf_tx = _stash_write_req(&stash[1], reat, NULL)) )
+ {
+ memcpy(buf_tx, buf_rx, reat);
+
+ _stash_write_adv(&stash[1], reat);
+ break;
+ }
+ }
+
+ _stash_read_adv(&stash[0]);
+ }
+
+ t0 = t1;
+ }
+
+ if(count >= COUNT)
+ {
+ break;
+ }
+ else if(pair->lossy && (difftime(t1, t0) >= 1.0) )
+ {
+ fprintf(stderr, "%s: timeout: %i\n", __func__, count);
+ break;
+ }
+ }
+
+ LV2_OSC_Enum ev;
+ do
+ {
+ ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+ } while( (ev & LV2_OSC_SEND) || (ev & LV2_OSC_CONN) );
+
+ assert(pair->lossy || (count == COUNT) );
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ free(stash[0].rsvd);
+ while(stash[0].size)
+ {
+ _stash_read_adv(&stash[0]);
+ }
+ free(stash[0].items);
+
+ free(stash[1].rsvd);
+ while(stash[1].size)
+ {
+ _stash_read_adv(&stash[1]);
+ }
+ free(stash[1].items);
+
+ return NULL;
+}
+
+static void *
+_thread_2(void *data)
+{
+ const pair_t *pair = data;
+ const char *uri = pair->client;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ unsigned count = 0;
+ for(int32_t i = 0; i < COUNT; i++)
+ {
+ LV2_OSC_Writer writer;
+
+ while(true)
+ {
+ uint8_t *buf_tx;
+ size_t max;
+ if( (buf_tx = _stash_write_req(&stash[1], 1024, &max)) )
+ {
+ size_t writ;
+ lv2_osc_writer_initialize(&writer, buf_tx, max);
+ assert(lv2_osc_writer_message_vararg(&writer, "/trip", "i", i));
+ assert(lv2_osc_writer_finalize(&writer, &writ) == buf_tx);
+ assert(writ == 16);
+ assert(check[i] == 0);
+ check[i] = 1;
+
+ _stash_write_adv(&stash[1], writ);
+ break;
+ }
+ }
+
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+ }
+ }
+
+ time_t t0 = time(NULL);
+ while(true)
+ {
+ const time_t t1 = time(NULL);
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+
+ t0 = t1;
+ }
+
+ if(count >= COUNT)
+ {
+ break;
+ }
+ else if(pair->lossy && (difftime(t1, t0) >= 1.0) )
+ {
+ fprintf(stderr, "%s: timeout: %i\n", __func__, count);
+ break;
+ }
+ }
+
+ assert(pair->lossy || (count == COUNT) );
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ free(stash[0].rsvd);
+ while(stash[0].size)
+ {
+ _stash_read_adv(&stash[0]);
+ }
+ free(stash[0].items);
+
+ free(stash[1].rsvd);
+ while(stash[1].size)
+ {
+ _stash_read_adv(&stash[1]);
+ }
+ free(stash[1].items);
+
+ return NULL;
+}
+
+static const pair_t pairs [] = {
+ {
+ .server = "osc.udp://:2222",
+ .client = "osc.udp://localhost:2222",
+ .lossy = true
+ },
+ {
+ .server = "osc.udp://[]:3333",
+ .client = "osc.udp://[::1]:3333",
+ .lossy = true
+ },
+
+ {
+ .server = "osc.udp://:3344",
+ .client = "osc.udp://255.255.255.255:3344",
+ .lossy = true
+ },
+
+ {
+ .server = "osc.tcp://:4444",
+ .client = "osc.tcp://localhost:4444",
+ .lossy = false
+ },
+ {
+ .server = "osc.tcp://[]:5555",
+ .client = "osc.tcp://[::1]:5555",
+ .lossy = false
+ },
+
+ {
+ .server = "osc.slip.tcp://:6666",
+ .client = "osc.slip.tcp://localhost:6666",
+ .lossy = false
+ },
+ {
+ .server = "osc.slip.tcp://[]:7777",
+ .client = "osc.slip.tcp://[::1]:7777",
+ .lossy = false
+ },
+
+ {
+ .server = "osc.prefix.tcp://:8888",
+ .client = "osc.prefix.tcp://localhost:8888",
+ .lossy = false
+ },
+ {
+ .server = "osc.prefix.tcp://[%lo]:9999",
+ .client = "osc.prefix.tcp://[::1%lo]:9999",
+ .lossy = false
+ },
+
+#if 0
+ {
+ .server = "osc.serial:///dev/pts/4", //FIXME baudrate
+ .client = "osc.serial:///dev/pts/5",
+ .lossy = false
+ },
+#endif
+
+ {
+ .server = NULL,
+ .client = NULL,
+ .lossy = false
+ }
+};
+#endif
+
+int
+main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ fprintf(stdout, "running main tests:\n");
+ assert(_run_tests() == 0);
+
+#if !defined(_WIN32)
+ for(const pair_t *pair = pairs; pair->server; pair++)
+ {
+ pthread_t thread_1;
+ pthread_t thread_2;
+
+ fprintf(stdout, "running stream test: <%s> <%s> %i\n",
+ pair->server, pair->client, pair->lossy);
+
+ assert(pthread_create(&thread_1, NULL, _thread_1, (void *)pair) == 0);
+ assert(pthread_create(&thread_2, NULL, _thread_2, (void *)pair) == 0);
+
+ assert(pthread_join(thread_1, NULL) == 0);
+ assert(pthread_join(thread_2, NULL) == 0);
+ }
+#endif
+
+ for(unsigned i=0; i<__app.urid; i++)
+ {
+ urid_t *itm = &__app.urids[i];
+
+ free(itm->uri);
+ }
+
+ return 0;
+}
diff --git a/props.lv2/.gitlab-ci.yml b/props.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..c027e92
--- /dev/null
+++ b/props.lv2/.gitlab-ci.yml
@@ -0,0 +1,72 @@
+stages:
+ - build
+ - test
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "props.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - mkdir build
+ - pushd build
+ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} ..
+ - cmake .. # needed for darwin
+ - make
+ - make install
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *build_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *build_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/props.lv2/COPYING b/props.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/props.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/props.lv2/README.md b/props.lv2/README.md
new file mode 100644
index 0000000..08466d2
--- /dev/null
+++ b/props.lv2/README.md
@@ -0,0 +1,20 @@
+# Props.lv2
+
+## Utility header for property based LV2 plugins
+
+### 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/props.lv2/VERSION b/props.lv2/VERSION
new file mode 100644
index 0000000..66c0104
--- /dev/null
+++ b/props.lv2/VERSION
@@ -0,0 +1 @@
+0.1.131
diff --git a/props.lv2/meson.build b/props.lv2/meson.build
new file mode 100644
index 0000000..d354d89
--- /dev/null
+++ b/props.lv2/meson.build
@@ -0,0 +1,82 @@
+project('props.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+cp = find_program('cp')
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+clone = [cp, '@INPUT@', '@OUTPUT@']
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+
+inc_dir = []
+
+inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name())
+
+dsp_srcs = [join_paths('test', 'props.c')]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+mod = shared_module('props', dsp_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : [m_dep, lv2_dep],
+ install : true,
+ install_dir : inst_dir)
+
+version = run_command('cat', 'VERSION').stdout().strip().split('.')
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+manifest_ttl = configure_file(
+ input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+dsp_ttl = custom_target('props_ttl',
+ input : join_paths('test', 'props.ttl'),
+ output : 'props.ttl',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+custom_target('chunk_bin',
+ input : join_paths('test', 'chunk.bin'),
+ output : 'chunk.bin',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+
+props_test = executable('props_test',
+ join_paths('test', 'props_test.c'),
+ c_args : c_args,
+ install : false)
+
+test('Test', props_test,
+ timeout : 240)
+
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl])
+endif
+
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-Ewarn',
+ 'http://open-music-kontrollers.ch/lv2/props#test'])
+endif
diff --git a/props.h b/props.lv2/props.h
index 06fec13..06fec13 100644
--- a/props.h
+++ b/props.lv2/props.h
diff --git a/test/chunk.bin b/props.lv2/test/chunk.bin
index b66efb8..b66efb8 100644
--- a/test/chunk.bin
+++ b/props.lv2/test/chunk.bin
Binary files differ
diff --git a/test/manifest.ttl.in b/props.lv2/test/manifest.ttl.in
index 0ecc313..0ecc313 100644
--- a/test/manifest.ttl.in
+++ b/props.lv2/test/manifest.ttl.in
diff --git a/test/props.c b/props.lv2/test/props.c
index 590c519..590c519 100644
--- a/test/props.c
+++ b/props.lv2/test/props.c
diff --git a/test/props.ttl b/props.lv2/test/props.ttl
index 0ce45d6..0ce45d6 100644
--- a/test/props.ttl
+++ b/props.lv2/test/props.ttl
diff --git a/test/props_test.c b/props.lv2/test/props_test.c
index f028e32..f028e32 100644
--- a/test/props_test.c
+++ b/props.lv2/test/props_test.c
diff --git a/varchunk/.gitlab-ci.yml b/varchunk/.gitlab-ci.yml
new file mode 100644
index 0000000..d7016d9
--- /dev/null
+++ b/varchunk/.gitlab-ci.yml
@@ -0,0 +1,63 @@
+stages:
+ - test
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "varchunk"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: test
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+
+.test_template: &test_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+ - cd build
+ - meson test --verbose --wrap "${CI_BUILD_NAME}.wrap"
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *test_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *test_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ before_script:
+ - ln -s /usr/i686-w64-mingw32/lib/libwinpthread-1.dll /opt/i686-w64-mingw32/lib/libwinpthread-1.dll
+ - ln -s /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll /opt/x86_64-w64-mingw32/lib/libwinpthread-1.dll
+ <<: *test_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
diff --git a/varchunk/COPYING b/varchunk/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/varchunk/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/varchunk/README.md b/varchunk/README.md
new file mode 100644
index 0000000..d587a9a
--- /dev/null
+++ b/varchunk/README.md
@@ -0,0 +1,112 @@
+# Varchunk
+
+## Ringbuffer optimized for realtime event handling
+
+### Properties
+
+* Is realtime-safe
+* Is lock-free
+* Supports variably sized chunks
+* Supports contiguous memory chunks
+* Supports zero copy operation
+* Uses a simplistic API
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/varchunk/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/varchunk/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lad/varchunk
+ cd varchunk
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### Usage
+
+ #include <pthread.h>
+ #include <varchunk.h>
+
+ static void *
+ producer_main(void *arg)
+ {
+ varchunk_t *varchunk = arg;
+ void *ptr;
+ const size_t towrite = sizeof(uint32_t);
+ uint32_t counter = 0;
+
+ while(counter <= 1000000)
+ {
+ if( (ptr = varchunk_write_request(varchunk, towrite)) )
+ {
+ // write 'towrite' bytes to 'ptr'
+ *(uint32_t *)ptr = counter++;
+ varchunk_write_advance(varchunk, towrite);
+ }
+ }
+
+ return NULL;
+ }
+
+ static void *
+ consumer_main(void *arg)
+ {
+ varchunk_t *varchunk = arg;
+ const void *ptr;
+ size_t toread;
+
+ while(1)
+ {
+ if( (ptr = varchunk_read_request(varchunk, &toread)) )
+ {
+ // read 'toread' bytes from 'ptr'
+ if(*(uint32_t *)ptr >= 1000000)
+ break;
+ varchunk_read_advance(varchunk);
+ }
+ }
+
+ return NULL;
+ }
+
+ int
+ main(int argc, char **argv)
+ {
+ if(!varchunk_is_lock_free())
+ return -1;
+
+ pthread_t producer;
+ pthread_t consumer;
+ varchunk_t *varchunk = varchunk_new(8192, true);
+ if(!varchunk)
+ return -1;
+
+ pthread_create(&consumer, NULL, consumer_main, varchunk);
+ pthread_create(&producer, NULL, producer_main, varchunk);
+
+ pthread_join(producer, NULL);
+ pthread_join(consumer, NULL);
+
+ varchunk_free(varchunk);
+
+ return 0;
+ }
+
+### License
+
+Copyright (c) 2015-2017 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/varchunk/VERSION b/varchunk/VERSION
new file mode 100644
index 0000000..9adbab1
--- /dev/null
+++ b/varchunk/VERSION
@@ -0,0 +1 @@
+0.1.69
diff --git a/varchunk/meson.build b/varchunk/meson.build
new file mode 100644
index 0000000..83dd626
--- /dev/null
+++ b/varchunk/meson.build
@@ -0,0 +1,30 @@
+project('varchunk', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=true',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+version = run_command('cat', 'VERSION').stdout().strip()
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+thread_dep = dependency('threads')
+deps = [thread_dep]
+
+if host_machine.system() == 'linux'
+ rt_dep = cc.find_library('rt')
+ deps += rt_dep
+endif
+
+test_varchunk = executable('test_varchunk',
+ 'test_varchunk.c',
+ dependencies : deps,
+ install : false)
+
+test('Test', test_varchunk,
+ args : ['100000'],
+ timeout : 360) # seconds
diff --git a/varchunk/test_varchunk.c b/varchunk/test_varchunk.c
new file mode 100644
index 0000000..1cce435
--- /dev/null
+++ b/varchunk/test_varchunk.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2015-2017 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 <inttypes.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <varchunk.h>
+
+#if !defined(_WIN32)
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <string.h>
+# define VARCHUNK_USE_SHARED_MEM
+
+static const struct timespec req = {
+ .tv_sec = 0,
+ .tv_nsec = 1
+};
+#endif
+
+static uint64_t iterations = 10000000;
+#define THRESHOLD (RAND_MAX / 256)
+#define PAD(SIZE) ( ( (size_t)(SIZE) + 7U ) & ( ~7U ) )
+
+static void *
+producer_main(void *arg)
+{
+ varchunk_t *varchunk = arg;
+ uint8_t *ptr;
+ const uint8_t *end;
+ size_t written;
+ uint64_t cnt = 0;
+
+ while(cnt < iterations)
+ {
+#if !defined(_WIN32)
+ if(rand() < THRESHOLD)
+ {
+ nanosleep(&req, NULL);
+ }
+#endif
+
+ written = PAD(rand() * 1024.f / RAND_MAX);
+
+ size_t maximum;
+ if( (ptr = varchunk_write_request_max(varchunk, written, &maximum)) )
+ {
+ assert(maximum >= written);
+ end = ptr + written;
+ for(uint8_t *src=ptr; src<end; src+=sizeof(uint64_t))
+ {
+ *(uint64_t *)src = cnt;
+ assert(*(uint64_t *)src == cnt);
+ }
+ varchunk_write_advance(varchunk, written);
+ cnt++;
+ }
+ else
+ {
+ // buffer full
+ }
+ }
+
+ return NULL;
+}
+
+static void *
+consumer_main(void *arg)
+{
+ varchunk_t *varchunk = arg;
+ const uint8_t *ptr;
+ const uint8_t *end;
+ size_t toread;
+ uint64_t cnt = 0;
+
+ while(cnt < iterations)
+ {
+#if !defined(_WIN32)
+ if(rand() < THRESHOLD)
+ {
+ nanosleep(&req, NULL);
+ }
+#endif
+
+ if( (ptr = varchunk_read_request(varchunk, &toread)) )
+ {
+ end = ptr + toread;
+ for(const uint8_t *src=ptr; src<end; src+=sizeof(uint64_t))
+ {
+ assert(*(const uint64_t *)src == cnt);
+ }
+ varchunk_read_advance(varchunk);
+ cnt++;
+ }
+ else
+ {
+ // buffer empty
+ }
+ }
+
+ return NULL;
+}
+
+static void
+test_threaded()
+{
+ pthread_t producer;
+ pthread_t consumer;
+ varchunk_t *varchunk = varchunk_new(8192, true);
+ assert(varchunk);
+
+ pthread_create(&consumer, NULL, consumer_main, varchunk);
+ pthread_create(&producer, NULL, producer_main, varchunk);
+
+ pthread_join(producer, NULL);
+ pthread_join(consumer, NULL);
+
+ varchunk_free(varchunk);
+}
+
+#if defined(VARCHUNK_USE_SHARED_MEM)
+typedef struct _varchunk_shm_t varchunk_shm_t;
+
+struct _varchunk_shm_t {
+ char *name;
+ int fd;
+ varchunk_t *varchunk;
+};
+
+static int
+varchunk_shm_init(varchunk_shm_t *varchunk_shm, const char *name, size_t minimum, bool release_and_acquire)
+{
+ const size_t body_size = varchunk_body_size(minimum);
+ const size_t total_size = sizeof(varchunk_t) + body_size;
+
+ varchunk_shm->name = strdup(name);
+ if(!varchunk_shm->name)
+ {
+ return -1;
+ }
+
+ bool is_first = true;
+ varchunk_shm->fd = shm_open(varchunk_shm->name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if(varchunk_shm->fd == -1)
+ {
+ is_first = false;
+ varchunk_shm->fd = shm_open(varchunk_shm->name, O_RDWR , S_IRUSR | S_IWUSR);
+ }
+ if(varchunk_shm->fd == -1)
+ {
+ free(varchunk_shm->name);
+ return -1;
+ }
+
+ if( (ftruncate(varchunk_shm->fd, total_size) == -1)
+ || ((varchunk_shm->varchunk = mmap(NULL, total_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, varchunk_shm->fd, 0)) == MAP_FAILED) )
+ {
+ shm_unlink(varchunk_shm->name);
+ close(varchunk_shm->fd);
+ free(varchunk_shm->name);
+ return -1;
+ }
+
+ if(is_first)
+ {
+ varchunk_init(varchunk_shm->varchunk, body_size, release_and_acquire);
+ }
+
+ return 0;
+}
+
+static void
+varchunk_shm_deinit(varchunk_shm_t *varchunk_shm)
+{
+ const size_t total_size = sizeof(varchunk_t) + varchunk_shm->varchunk->size;
+
+ munmap(varchunk_shm->varchunk, total_size);
+ shm_unlink(varchunk_shm->name);
+ close(varchunk_shm->fd);
+ free(varchunk_shm->name);
+}
+
+static void
+test_shared()
+{
+ const char *name = "/varchunk_shm_test";
+ pid_t pid = fork();
+
+ assert(pid != -1);
+
+ if(pid == 0) // child
+ {
+ varchunk_shm_t varchunk_shm;
+ assert(varchunk_shm_init(&varchunk_shm, name, 8192, true) == 0);
+
+ consumer_main(varchunk_shm.varchunk);
+
+ varchunk_shm_deinit(&varchunk_shm);
+ }
+ else // parent
+ {
+ varchunk_shm_t varchunk_shm;
+ assert(varchunk_shm_init(&varchunk_shm, name, 8192, true) == 0);
+
+ producer_main(varchunk_shm.varchunk);
+
+ varchunk_shm_deinit(&varchunk_shm);
+ }
+}
+#endif
+
+int
+main(int argc, char **argv)
+{
+#if !defined(_WIN32)
+ const int seed = time(NULL);
+ srand(seed);
+#endif
+
+ if(argc >= 2)
+ {
+ iterations = atoi(argv[1]);
+ }
+
+ assert(varchunk_is_lock_free());
+
+ test_threaded();
+
+#if defined(VARCHUNK_USE_SHARED_MEM)
+ test_shared();
+#endif
+
+ return 0;
+}
diff --git a/varchunk/varchunk.h b/varchunk/varchunk.h
new file mode 100644
index 0000000..6fc1d5b
--- /dev/null
+++ b/varchunk/varchunk.h
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2015-2017 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 _VARCHUNK_H
+#define _VARCHUNK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#if !defined(_WIN32)
+# include <sys/mman.h> // mlock
+#endif
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+typedef struct _varchunk_t varchunk_t;
+
+static inline bool
+varchunk_is_lock_free(void);
+
+static inline size_t
+varchunk_body_size(size_t minimum);
+
+static inline varchunk_t *
+varchunk_new(size_t minimum, bool release_and_acquire);
+
+static inline void
+varchunk_free(varchunk_t *varchunk);
+
+static inline void
+varchunk_init(varchunk_t *varchunk, size_t body_size, bool release_and_acquire);
+
+static inline void *
+varchunk_write_request_max(varchunk_t *varchunk, size_t minimum, size_t *maximum);
+
+static inline void *
+varchunk_write_request(varchunk_t *varchunk, size_t minimum);
+
+static inline void
+varchunk_write_advance(varchunk_t *varchunk, size_t written);
+
+static inline const void *
+varchunk_read_request(varchunk_t *varchunk, size_t *toread);
+
+static inline void
+varchunk_read_advance(varchunk_t *varchunk);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+#define VARCHUNK_PAD(SIZE) ( ( (size_t)(SIZE) + 7U ) & ( ~7U ) )
+
+typedef struct _varchunk_elmnt_t varchunk_elmnt_t;
+
+struct _varchunk_elmnt_t {
+ uint32_t size;
+ uint32_t gap;
+};
+
+struct _varchunk_t {
+ size_t size;
+ size_t mask;
+ size_t rsvd;
+ size_t gapd;
+
+ memory_order acquire;
+ memory_order release;
+
+ atomic_size_t head;
+ atomic_size_t tail;
+
+ uint8_t buf [] __attribute__((aligned(sizeof(varchunk_elmnt_t))));
+};
+
+static inline bool
+varchunk_is_lock_free(void)
+{
+ varchunk_t varchunk;
+
+ return atomic_is_lock_free(&varchunk.head)
+ && atomic_is_lock_free(&varchunk.tail);
+}
+
+static inline size_t
+varchunk_body_size(size_t minimum)
+{
+ size_t size = 1;
+ while(size < minimum)
+ size <<= 1; // assure size to be a power of 2
+ return size;
+}
+
+static inline void
+varchunk_init(varchunk_t *varchunk, size_t body_size, bool release_and_acquire)
+{
+ varchunk->acquire = release_and_acquire
+ ? memory_order_acquire
+ : memory_order_relaxed;
+ varchunk->release = release_and_acquire
+ ? memory_order_release
+ : memory_order_relaxed;
+
+ atomic_init(&varchunk->head, 0);
+ atomic_init(&varchunk->tail, 0);
+
+ varchunk->size = body_size;
+ varchunk->mask = varchunk->size - 1;
+}
+
+static inline varchunk_t *
+varchunk_new(size_t minimum, bool release_and_acquire)
+{
+ varchunk_t *varchunk = NULL;
+
+ const size_t body_size = varchunk_body_size(minimum);
+ const size_t total_size = sizeof(varchunk_t) + body_size;
+
+#if defined(_WIN32)
+ varchunk = _aligned_malloc(total_size, sizeof(varchunk_elmnt_t));
+#else
+ posix_memalign((void **)&varchunk, sizeof(varchunk_elmnt_t), total_size);
+ mlock(varchunk, total_size); // prevent memory from being flushed to disk
+#endif
+
+ if(varchunk)
+ varchunk_init(varchunk, body_size, release_and_acquire);
+
+ return varchunk;
+}
+
+static inline void
+varchunk_free(varchunk_t *varchunk)
+{
+ if(varchunk)
+ {
+#if !defined(_WIN32)
+ munlock(varchunk->buf, varchunk->size);
+#endif
+ free(varchunk);
+ }
+}
+
+static inline void
+_varchunk_write_advance_raw(varchunk_t *varchunk, size_t head, size_t written)
+{
+ // only producer is allowed to advance write head
+ const size_t new_head = (head + written) & varchunk->mask;
+ atomic_store_explicit(&varchunk->head, new_head, varchunk->release);
+}
+
+static inline void *
+varchunk_write_request_max(varchunk_t *varchunk, size_t minimum, size_t *maximum)
+{
+ assert(varchunk);
+
+ size_t space; // size of writable buffer
+ size_t end; // virtual end of writable buffer
+ const size_t head = atomic_load_explicit(&varchunk->head, memory_order_relaxed); // read head
+ const size_t tail = atomic_load_explicit(&varchunk->tail, varchunk->acquire); // read tail (consumer modifies it any time)
+ const size_t padded = 2*sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(minimum);
+
+ // calculate writable space
+ if(head > tail)
+ space = ((tail - head + varchunk->size) & varchunk->mask) - 1;
+ else if(head < tail)
+ space = (tail - head) - 1;
+ else // head == tail
+ space = varchunk->size - 1;
+ end = head + space;
+
+ if(end > varchunk->size) // available region wraps over at end of buffer
+ {
+ // get first part of available buffer
+ uint8_t *buf1 = varchunk->buf + head;
+ const size_t len1 = varchunk->size - head;
+
+ if(len1 < padded) // not enough space left on first part of buffer
+ {
+ // get second part of available buffer
+ uint8_t *buf2 = varchunk->buf;
+ const size_t len2 = end & varchunk->mask;
+
+ if(len2 < padded) // not enough space left on second buffer, either
+ {
+ varchunk->rsvd = 0;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return NULL;
+ }
+ else // enough space left on second buffer, use it!
+ {
+ varchunk->rsvd = len2;
+ varchunk->gapd = len1;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf2 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // enough space left on first part of buffer, use it!
+ {
+ varchunk->rsvd = len1;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf1 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // available region is contiguous
+ {
+ uint8_t *buf = varchunk->buf + head;
+
+ if(space < padded) // no space left on contiguous buffer
+ {
+ varchunk->rsvd = 0;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return NULL;
+ }
+ else // enough space left on contiguous buffer, use it!
+ {
+ varchunk->rsvd = space;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf + sizeof(varchunk_elmnt_t);
+ }
+ }
+}
+
+static inline void *
+varchunk_write_request(varchunk_t *varchunk, size_t minimum)
+{
+ return varchunk_write_request_max(varchunk, minimum, NULL);
+}
+
+static inline void
+varchunk_write_advance(varchunk_t *varchunk, size_t written)
+{
+ assert(varchunk);
+ // fail miserably if stupid programmer tries to write more than rsvd
+ assert(written <= varchunk->rsvd);
+
+ // write elmnt header at head
+ const size_t head = atomic_load_explicit(&varchunk->head, memory_order_relaxed);
+ if(varchunk->gapd > 0)
+ {
+ // fill end of first buffer with gap
+ varchunk_elmnt_t *elmnt = (varchunk_elmnt_t *)(varchunk->buf + head);
+ elmnt->size = varchunk->gapd - sizeof(varchunk_elmnt_t);
+ elmnt->gap = 1;
+
+ // fill written element header
+ elmnt = (void *)varchunk->buf;
+ elmnt->size = written;
+ elmnt->gap = 0;
+ }
+ else // varchunk->gapd == 0
+ {
+ // fill written element header
+ varchunk_elmnt_t *elmnt = (varchunk_elmnt_t *)(varchunk->buf + head);
+ elmnt->size = written;
+ elmnt->gap = 0;
+ }
+
+ // advance write head
+ _varchunk_write_advance_raw(varchunk, head,
+ varchunk->gapd + sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(written));
+}
+
+static inline void
+_varchunk_read_advance_raw(varchunk_t *varchunk, size_t tail, size_t read)
+{
+ // only consumer is allowed to advance read tail
+ const size_t new_tail = (tail + read) & varchunk->mask;
+ atomic_store_explicit(&varchunk->tail, new_tail, varchunk->release);
+}
+
+static inline const void *
+varchunk_read_request(varchunk_t *varchunk, size_t *toread)
+{
+ assert(varchunk);
+ size_t space; // size of available buffer
+ const size_t tail = atomic_load_explicit(&varchunk->tail, memory_order_relaxed); // read tail
+ const size_t head = atomic_load_explicit(&varchunk->head, varchunk->acquire); // read head (producer modifies it any time)
+
+ // calculate readable space
+ if(head > tail)
+ space = head - tail;
+ else
+ space = (head - tail + varchunk->size) & varchunk->mask;
+
+ if(space > 0) // there may be chunks available for reading
+ {
+ const size_t end = tail + space; // virtual end of available buffer
+
+ if(end > varchunk->size) // available buffer wraps around at end
+ {
+ // first part of available buffer
+ const uint8_t *buf1 = varchunk->buf + tail;
+ const size_t len1 = varchunk->size - tail;
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)buf1;
+
+ if(elmnt->gap) // gap elmnt?
+ {
+ // skip gap
+ _varchunk_read_advance_raw(varchunk, tail, len1);
+
+ // second part of available buffer
+ const uint8_t *buf2 = varchunk->buf;
+ // there will always be at least on element after a gap
+ elmnt = (const varchunk_elmnt_t *)buf2;
+
+ *toread = elmnt->size;
+ return buf2 + sizeof(varchunk_elmnt_t);
+ }
+ else // valid chunk, use it!
+ {
+ *toread = elmnt->size;
+ return buf1 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // available buffer is contiguous
+ {
+ // get buffer
+ const uint8_t *buf = varchunk->buf + tail;
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)buf;
+
+ *toread = elmnt->size;
+ return buf + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // no chunks available aka empty buffer
+ {
+ *toread = 0;
+ return NULL;
+ }
+}
+
+static inline void
+varchunk_read_advance(varchunk_t *varchunk)
+{
+ assert(varchunk);
+ // get elmnt header from tail (for size)
+ const size_t tail = atomic_load_explicit(&varchunk->tail, memory_order_relaxed);
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)(varchunk->buf + tail);
+
+ // advance read tail
+ _varchunk_read_advance_raw(varchunk, tail,
+ sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(elmnt->size));
+}
+
+#undef VARCHUNK_PAD
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_VARCHUNK_H