aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2016-05-12 08:54:35 +0200
committerHanspeter Portner <dev@open-music-kontrollers.ch>2016-05-12 08:54:35 +0200
commit0a421cbafe24a4245de05e6f93e93ddf8b0d695b (patch)
tree5e0267cef5c61bd7750ecf68011e7b544e90c7ce
parent6c406c5aab594789362cf918a3fbc6cea193dada (diff)
downloadsherlock.lv2-0a421cbafe24a4245de05e6f93e93ddf8b0d695b.tar.xz
add reader/writer/forge headers.
-rw-r--r--CMakeLists.txt8
-rw-r--r--forge.h86
-rw-r--r--osc.h (renamed from lv2_osc.h)49
-rw-r--r--osc_test.c341
-rw-r--r--reader.h570
-rw-r--r--test/osc.c28
-rw-r--r--writer.h414
7 files changed, 1489 insertions, 7 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3ff706..db71d43 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,3 +22,11 @@ install(TARGETS osc DESTINATION ${DEST})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${DEST})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/osc.ttl DESTINATION ${DEST})
+
+include(CTest)
+
+if(${BUILD_TESTING})
+ add_executable(osc_test
+ osc_test.c)
+ add_test(NAME API-Test COMMAND osc_test)
+endif()
diff --git a/forge.h b/forge.h
new file mode 100644
index 0000000..935ea05
--- /dev/null
+++ b/forge.h
@@ -0,0 +1,86 @@
+/*
+ * 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 <writer.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_initialize(LV2_Atom_Forge *forge, LV2_OSC_Writer *writer,
+ LV2_Atom_Forge_Frame *frame, LV2_URID osc_event)
+{
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_atom(forge, 0, osc_event);
+ if(ref)
+ {
+ frame->ref = ref;
+ LV2_Atom *atom = lv2_atom_forge_deref(forge, frame->ref);
+
+ osc_writer_initialize(writer, LV2_ATOM_BODY(atom), forge->size - forge->offset);
+ }
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_finalize(LV2_Atom_Forge *forge, LV2_OSC_Writer *writer,
+ LV2_Atom_Forge_Frame *frame)
+{
+ size_t size;
+ uint8_t *osc = osc_writer_finalize(writer, &size);
+
+ if(osc)
+ {
+ LV2_Atom *atom = lv2_atom_forge_deref(forge, frame->ref);
+ atom->size = size;
+
+ return lv2_atom_forge_write(forge, osc, size);
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+osc_forge_message_vararg(LV2_Atom_Forge *forge, LV2_URID osc_event,
+ const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ LV2_OSC_Writer writer;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = osc_forge_initialize(forge, &writer, &frame, osc_event);
+
+ if(ref && osc_writer_message_varlist(&writer, path, fmt, args))
+ ref = osc_forge_finalize(forge, &writer, &frame);
+
+ va_end(args);
+
+ return ref;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_FORGE_H
diff --git a/lv2_osc.h b/osc.h
index e2775e7..54ff4ca 100644
--- a/lv2_osc.h
+++ b/osc.h
@@ -20,11 +20,14 @@
#include <stdint.h>
-#define LV2_OSC_URI "http://open-music-kontrollers.ch/lv2/osc"
-#define LV2_OSC_PREFIX LV2_OSC_URI "#"
+#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 type
-#define LV2_OSC__schedule LV2_OSC_PREFIX "schedule" // feature
+#define LV2_OSC__Event LV2_OSC_PREFIX "Event" // atom type
+#define LV2_OSC__schedule LV2_OSC_PREFIX "schedule" // feature
+
+#define LV2_OSC_PADDED_SIZE(size) ( ( (size_t)(size) + 3 ) & ( ~3 ) )
+#define LV2_OSC_IMMEDIATE 1ULL
#ifdef __cplusplus
extern "C" {
@@ -40,12 +43,48 @@ typedef uint64_t (*LV2_OSC_Schedule_Frames2OSC)(
LV2_OSC_Schedule_Handle handle,
double frames);
-typedef struct _LV2_OSC_Schedule{
+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;
+};
+
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/osc_test.c b/osc_test.c
new file mode 100644
index 0000000..bb6e789
--- /dev/null
+++ b/osc_test.c
@@ -0,0 +1,341 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <osc.h>
+#include <reader.h>
+#include <writer.h>
+
+#define BUF_SIZE 8192
+
+typedef void (*test_t)(LV2_OSC_Writer *writer);
+
+static uint8_t buf0 [BUF_SIZE];
+static uint8_t buf1 [BUF_SIZE];
+
+const uint8_t raw_0 [] = {
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_1 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'i', 'f', 's',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x59, 0x99, 0x9a,
+ 'w', 'o', 'r', 'l',
+ 'd', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_2 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'h', 'd', 'S',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x0b, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33,
+ 'h', 't', 't', 'p',
+ ':', '/', '/', 'e',
+ 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c',
+ 'o', 'm', 0x0, 0x0
+};
+
+const uint8_t raw_3 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'T', 'F', 'N',
+ 'I', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_4 [] = {
+ '/', 'm', 'i', 'd',
+ 'i', 0x0, 0x0, 0x0,
+ ',', 'm', 0x0, 0x0,
+ 0x0, 0x90, 24, 0x7f
+};
+
+const uint8_t raw_5 [] = {
+ '/', 'b', 'l', 'o',
+ 'b', 0x0, 0x0, 0x0,
+ ',', 'b', 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x6,
+ 0x1, 0x2, 0x3, 0x4,
+ 0x5, 0x6, 0x0, 0x0
+};
+
+const uint8_t raw_6 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_7 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x1c,
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+static void
+_dump(const uint8_t *src, const uint8_t *dst, size_t size)
+{
+ for(size_t i = 0; i < size; i++)
+ printf("%zu %02x %02x\n", i, src[i], dst[i]);
+ printf("\n");
+}
+
+static void
+_clone(LV2_OSC_Reader *reader, LV2_OSC_Writer *writer, size_t size)
+{
+ if(osc_reader_is_bundle(reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(reader, size);
+ assert(itm);
+
+ LV2_OSC_Writer_Frame frame_bndl;
+ assert(osc_writer_push_bundle(writer, &frame_bndl, itm->timetag));
+
+ OSC_READER_BUNDLE_ITERATE(reader, itm)
+ {
+ LV2_OSC_Reader reader2;
+ osc_reader_initialize(&reader2, itm->body, itm->size);
+
+ LV2_OSC_Writer_Frame frame_itm;
+ assert(osc_writer_push_item(writer, &frame_itm));
+ _clone(&reader2, writer, itm->size);
+ assert(osc_writer_pop_item(writer, &frame_itm));
+ }
+
+ assert(osc_writer_pop_bundle(writer, &frame_bndl));
+ }
+ else if(osc_reader_is_message(reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, size);
+ assert(arg);
+
+ assert(osc_writer_add_path(writer, arg->path));
+ assert(osc_writer_add_format(writer, arg->type));
+
+ OSC_READER_MESSAGE_ITERATE(reader, arg)
+ {
+ switch((LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ assert(osc_writer_add_int32(writer, arg->i));
+ break;
+ case LV2_OSC_FLOAT:
+ assert(osc_writer_add_float(writer, arg->f));
+ break;
+ case LV2_OSC_STRING:
+ assert(osc_writer_add_string(writer, arg->s));
+ break;
+ case LV2_OSC_BLOB:
+ assert(osc_writer_add_blob(writer, arg->size, arg->b));
+ break;
+
+ case LV2_OSC_INT64:
+ assert(osc_writer_add_int64(writer, arg->h));
+ break;
+ case LV2_OSC_DOUBLE:
+ assert(osc_writer_add_double(writer, arg->d));
+ break;
+ case LV2_OSC_TIMETAG:
+ assert(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(osc_writer_add_midi(writer, arg->size, arg->m));
+ break;
+ case LV2_OSC_SYMBOL:
+ assert(osc_writer_add_symbol(writer, arg->S));
+ break;
+ case LV2_OSC_CHAR:
+ assert(osc_writer_add_char(writer, arg->c));
+ break;
+ case LV2_OSC_RGBA:
+ assert(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)
+{
+ size_t len;
+ assert(osc_writer_finalize(writer, &len) == buf0);
+ assert(len == size);
+ //_dump(raw, buf0, size);
+ assert(memcmp(raw, buf0, size) == 0);
+
+ LV2_OSC_Reader reader;
+ osc_reader_initialize(&reader, buf0, size);
+ osc_writer_initialize(writer, buf1, BUF_SIZE);
+
+ _clone(&reader, writer, size);
+
+ assert(osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+ //_dump(raw, buf0, size);
+ assert(memcmp(raw, buf1, size) == 0);
+}
+
+static void
+test_0_a(LV2_OSC_Writer *writer)
+{
+ assert(osc_writer_message_vararg(writer, "/", ""));
+ _test_a(writer, raw_0, sizeof(raw_0));
+}
+
+static void
+test_1_a(LV2_OSC_Writer *writer)
+{
+ assert(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(osc_writer_message_vararg(writer, "/ping", "hdS",
+ 12, 3.4, "http://example.com"));
+ _test_a(writer, raw_2, sizeof(raw_2));
+}
+
+static void
+test_3_a(LV2_OSC_Writer *writer)
+{
+ assert(osc_writer_message_vararg(writer, "/ping", "TFNI"));
+ _test_a(writer, raw_3, sizeof(raw_3));
+}
+
+static void
+test_4_a(LV2_OSC_Writer *writer)
+{
+ uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ assert(osc_writer_message_vararg(writer, "/midi", "m", 4, m));
+ _test_a(writer, raw_4, sizeof(raw_4));
+}
+
+static void
+test_5_a(LV2_OSC_Writer *writer)
+{
+ uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ assert(osc_writer_message_vararg(writer, "/blob", "b", 6, b));
+ _test_a(writer, raw_5, sizeof(raw_5));
+}
+
+static void
+test_6_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl, frame_itm;
+
+ assert(osc_writer_push_bundle(writer, &frame_bndl, LV2_OSC_IMMEDIATE));
+ {
+ assert(osc_writer_push_item(writer, &frame_itm));
+ {
+ assert(osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(osc_writer_pop_item(writer, &frame_itm));
+ }
+ assert(osc_writer_pop_bundle(writer, &frame_bndl));
+
+ _test_a(writer, raw_6, sizeof(raw_6));
+}
+
+static void
+test_7_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl[2], frame_itm[2];
+
+ assert(osc_writer_push_bundle(writer, &frame_bndl[0], LV2_OSC_IMMEDIATE));
+ {
+ assert(osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(osc_writer_push_bundle(writer, &frame_bndl[1], LV2_OSC_IMMEDIATE));
+ {
+ assert(osc_writer_push_item(writer, &frame_itm[1]));
+ {
+ assert(osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(osc_writer_pop_item(writer, &frame_itm[1]));
+ }
+ assert(osc_writer_pop_bundle(writer, &frame_bndl[1]));
+ }
+ assert(osc_writer_pop_item(writer, &frame_itm[0]));
+
+ assert(osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(osc_writer_pop_item(writer, &frame_itm[0]));
+ }
+ assert(osc_writer_pop_bundle(writer, &frame_bndl[0]));
+
+ _test_a(writer, raw_7, sizeof(raw_7));
+}
+
+static test_t tests [] = {
+ test_0_a,
+ test_1_a,
+ test_2_a,
+ test_3_a,
+ test_4_a,
+ test_5_a,
+ test_6_a,
+ test_7_a,
+
+ NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ LV2_OSC_Writer writer;
+
+ for(test_t *test=tests; *test; test++)
+ {
+ test_t cb = *test;
+
+ memset(buf0, 0x0, BUF_SIZE);
+ memset(buf1, 0x0, BUF_SIZE);
+
+ osc_writer_initialize(&writer, buf0, BUF_SIZE);
+
+ cb(&writer);
+ }
+
+ return 0;
+}
diff --git a/reader.h b/reader.h
new file mode 100644
index 0000000..6e609f2
--- /dev/null
+++ b/reader.h
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_READER_H
+#define LV2_OSC_READER_H
+
+#include <stdbool.h>
+#include <string.h>
+#include <endian.h>
+
+#include <osc.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
+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
+osc_reader_overflow(LV2_OSC_Reader *reader, size_t size)
+{
+ return reader->ptr + size > reader->end;
+}
+
+static inline bool
+osc_reader_be32toh(LV2_OSC_Reader *reader, union swap32_t *s32)
+{
+ if(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
+osc_reader_be64toh(LV2_OSC_Reader *reader, union swap64_t *s64)
+{
+ if(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
+osc_reader_get_int32(LV2_OSC_Reader *reader, int32_t *i)
+{
+ union swap32_t s32;
+ if(!osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *i = s32.i;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_float(LV2_OSC_Reader *reader, float *f)
+{
+ union swap32_t s32;
+ if(!osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *f = s32.f;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_int64(LV2_OSC_Reader *reader, int64_t *h)
+{
+ union swap64_t s64;
+ if(!osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *h = s64.h;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_timetag(LV2_OSC_Reader *reader, uint64_t *t)
+{
+ union swap64_t s64;
+ if(!osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *t = s64.u;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_double(LV2_OSC_Reader *reader, double *d)
+{
+ union swap64_t s64;
+ if(!osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *d = s64.d;
+
+ return true;
+}
+
+static inline bool
+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(osc_reader_overflow(reader, padded ))
+ return false;
+
+ *s = str;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_symbol(LV2_OSC_Reader *reader, const char **S)
+{
+ return osc_reader_get_string(reader, S);
+}
+
+static inline bool
+osc_reader_get_midi(LV2_OSC_Reader *reader, const uint8_t **m)
+{
+ if(osc_reader_overflow(reader, 4))
+ return false;
+
+ *m = reader->ptr;
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_blob(LV2_OSC_Reader *reader, int32_t *len, const uint8_t **body)
+{
+ if(!osc_reader_get_int32(reader, len))
+ return false;
+
+ const size_t padded = LV2_OSC_PADDED_SIZE(*len);
+ if(osc_reader_overflow(reader, padded))
+ return false;
+
+ *body = reader->ptr;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+osc_reader_get_rgba(LV2_OSC_Reader *reader, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ if(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
+osc_reader_get_char(LV2_OSC_Reader *reader, char *c)
+{
+ int32_t i;
+ if(!osc_reader_get_int32(reader, &i))
+ return false;
+
+ *c = i;
+
+ return true;
+}
+
+static inline LV2_OSC_Item *
+osc_reader_item_raw(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ if(!osc_reader_get_int32(reader, &itm->size))
+ return NULL;
+
+ if(osc_reader_overflow(reader, itm->size))
+ return NULL;
+
+ itm->body = reader->ptr;
+
+ return itm;
+}
+
+static inline LV2_OSC_Item *
+osc_reader_item_begin(LV2_OSC_Reader *reader, LV2_OSC_Item *itm, size_t len)
+{
+ if(osc_reader_overflow(reader, len))
+ return NULL;
+
+ itm->end = reader->ptr + len;
+
+ if(osc_reader_overflow(reader, 16))
+ return NULL;
+
+ if(strncmp((const char *)reader->ptr, "#bundle", 8))
+ return NULL;
+ reader->ptr += 8;
+
+ if(!osc_reader_get_timetag(reader, &itm->timetag))
+ return NULL;
+
+ return osc_reader_item_raw(reader, itm);
+}
+
+static inline bool
+osc_reader_item_is_end(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ return reader->ptr > itm->end;
+}
+
+static inline LV2_OSC_Item *
+osc_reader_item_next(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ reader->ptr += itm->size;
+
+ return osc_reader_item_raw(reader, itm);
+}
+
+#define OSC_READER_BUNDLE_BEGIN(reader, len) \
+ 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 && !osc_reader_item_is_end((reader), (itm)); \
+ itm = 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 && !osc_reader_item_is_end((reader), (itm)); \
+ itm = osc_reader_item_next((reader), (itm)))
+
+static inline LV2_OSC_Arg *
+osc_reader_arg_raw(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!osc_reader_get_int32(reader, &arg->i))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!osc_reader_get_float(reader, &arg->f))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!osc_reader_get_string(reader, &arg->s))
+ return NULL;
+ arg->size = strlen(arg->s) + 1;
+
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!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(!osc_reader_get_int64(reader, &arg->h))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!osc_reader_get_double(reader, &arg->d))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!osc_reader_get_timetag(reader, &arg->t))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+
+ case LV2_OSC_MIDI:
+ {
+ if(!osc_reader_get_midi(reader, &arg->m))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_SYMBOL:
+ {
+ if(!osc_reader_get_symbol(reader, &arg->S))
+ return NULL;
+ arg->size = strlen(arg->S) + 1;
+
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!osc_reader_get_char(reader, &arg->c))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!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 *
+osc_reader_arg_begin(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg, size_t len)
+{
+ if(osc_reader_overflow(reader, len))
+ return NULL;
+
+ arg->end = reader->ptr + len;
+
+ if(!osc_reader_get_string(reader, &arg->path)) //TODO check for validity
+ return NULL;
+
+ if(!osc_reader_get_string(reader, &arg->type)) //TODO check for validity
+ return NULL;
+
+ if(*arg->type != ',')
+ return NULL;
+
+ arg->type++; // skip ','
+
+ return osc_reader_arg_raw(reader, arg);
+}
+
+static inline bool
+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 *
+osc_reader_arg_next(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ arg->type++;
+
+ return osc_reader_arg_raw(reader, arg);
+}
+
+#define OSC_READER_MESSAGE_BEGIN(reader, len) \
+ 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 && !osc_reader_arg_is_end((reader), (arg)); \
+ arg = 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 && !osc_reader_arg_is_end((reader), (arg)); \
+ arg = osc_reader_arg_next((reader), (arg)))
+
+static inline bool
+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(!osc_reader_get_int32(reader, va_arg(args, int32_t *)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!osc_reader_get_float(reader, va_arg(args, float *)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!osc_reader_get_string(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!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(!osc_reader_get_int64(reader, va_arg(args, int64_t *)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!osc_reader_get_double(reader, va_arg(args, double *)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!osc_reader_get_timetag(reader, va_arg(args, uint64_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!osc_reader_get_midi(reader, va_arg(args, const uint8_t **)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!osc_reader_get_symbol(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!osc_reader_get_char(reader, va_arg(args, char *)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!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
+osc_reader_arg_vararg(LV2_OSC_Reader *reader, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = osc_reader_arg_varlist(reader, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+osc_reader_is_bundle(LV2_OSC_Reader *reader)
+{
+ return strncmp((const char *)reader->ptr, "#bundle", 8) == 0;
+}
+
+static inline bool
+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/test/osc.c b/test/osc.c
index 5c65874..1d53c41 100644
--- a/test/osc.c
+++ b/test/osc.c
@@ -19,7 +19,10 @@
#include <stdio.h>
#include <stdlib.h>
-#include <lv2_osc.h>
+#include <osc.h>
+#include <writer.h>
+#include <reader.h>
+#include <forge.h>
#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
@@ -91,6 +94,22 @@ connect_port(LV2_Handle instance, uint32_t port, void *data)
}
}
+static inline void
+_osc_packet(plughandle_t *handle, LV2_OSC_Reader *reader, uint64_t timetag, int32_t size)
+{
+ if(osc_reader_is_bundle(reader))
+ {
+ OSC_READER_BUNDLE_FOREACH(reader, itm, size)
+ _osc_packet(handle, reader, itm->timetag, itm->size);
+ }
+ else if(osc_reader_is_message(reader))
+ {
+ OSC_READER_MESSAGE_FOREACH(reader, arg, size)
+ printf("(%c %u) ", *arg->type, arg->size);
+ printf("\n");
+ }
+}
+
static void
run(LV2_Handle instance, uint32_t nsamples)
{
@@ -108,12 +127,17 @@ run(LV2_Handle instance, uint32_t nsamples)
if(atom->type == handle->osc_event)
{
const uint8_t *body = LV2_ATOM_BODY_CONST(atom);
+ LV2_OSC_Reader reader;
+ osc_reader_initialize(&reader, body, atom->size);
- //TODO
+ _osc_packet(handle, &reader, LV2_OSC_IMMEDIATE, atom->size);
}
}
if(handle->ref)
+ handle->ref = osc_forge_message_vararg(&handle->forge, handle->osc_event, "/", "");
+
+ if(handle->ref)
lv2_atom_forge_pop(&handle->forge, &frame);
else
lv2_atom_sequence_clear(handle->event_out);
diff --git a/writer.h b/writer.h
new file mode 100644
index 0000000..9365f3b
--- /dev/null
+++ b/writer.h
@@ -0,0 +1,414 @@
+/*
+ * 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 <endian.h>
+
+#include <osc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _LV2_OSC_Writer LV2_OSC_Writer;
+typedef struct _LV2_OSC_Writer_Frame LV2_OSC_Writer_Frame;
+
+struct _LV2_OSC_Writer {
+ uint8_t *buf;
+ uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Writer_Frame {
+ uint8_t *ref;
+};
+
+static inline void
+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
+osc_writer_get_size(LV2_OSC_Writer *writer)
+{
+ if(writer->ptr > writer->buf)
+ return writer->ptr - writer->buf;
+
+ return 0;
+}
+
+static inline uint8_t *
+osc_writer_finalize(LV2_OSC_Writer *writer, size_t *size)
+{
+ *size = osc_writer_get_size(writer);
+
+ if(*size)
+ return writer->buf;
+
+ return NULL;
+}
+
+static inline bool
+osc_writer_overflow(LV2_OSC_Writer *writer, size_t size)
+{
+ return writer->ptr + size >= writer->end;
+}
+
+static inline bool
+osc_writer_htobe32(LV2_OSC_Writer *writer, union swap32_t *s32)
+{
+ if(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
+osc_writer_htobe64(LV2_OSC_Writer *writer, union swap64_t *s64)
+{
+ if(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
+osc_writer_add_int32(LV2_OSC_Writer *writer, int32_t i)
+{
+ return osc_writer_htobe32(writer, &(union swap32_t){ .i = i });
+}
+
+static inline bool
+osc_writer_add_float(LV2_OSC_Writer *writer, float f)
+{
+ return osc_writer_htobe32(writer, &(union swap32_t){ .f = f });
+}
+
+static inline bool
+osc_writer_add_string(LV2_OSC_Writer *writer, const char *s)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(s) + 1);
+ if(osc_writer_overflow(writer, padded))
+ return false;
+
+ strncpy((char *)writer->ptr, s, padded);
+ writer->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+osc_writer_add_symbol(LV2_OSC_Writer *writer, const char *S)
+{
+ return osc_writer_add_string(writer, S);
+}
+
+static inline bool
+osc_writer_add_int64(LV2_OSC_Writer *writer, int64_t h)
+{
+ return osc_writer_htobe64(writer, &(union swap64_t){ .h = h });
+}
+
+static inline bool
+osc_writer_add_double(LV2_OSC_Writer *writer, double d)
+{
+ return osc_writer_htobe64(writer, &(union swap64_t){ .d = d });
+}
+
+static inline bool
+osc_writer_add_timetag(LV2_OSC_Writer *writer, uint64_t u)
+{
+ return osc_writer_htobe64(writer, &(union swap64_t){ .u = u });
+}
+
+static inline bool
+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(osc_writer_overflow(writer, size))
+ return false;
+
+ if(!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
+osc_writer_add_blob(LV2_OSC_Writer *writer, int32_t len, const uint8_t *body)
+{
+ uint8_t *dst;
+ if(!osc_writer_add_blob_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, body, len);
+
+ return true;
+}
+
+static inline bool
+osc_writer_add_midi_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **m)
+{
+ if( (len > 4) || 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
+osc_writer_add_midi(LV2_OSC_Writer *writer, int32_t len, const uint8_t *m)
+{
+ uint8_t *dst;
+ if(!osc_writer_add_midi_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, m, len);
+
+ return true;
+}
+
+static inline bool
+osc_writer_add_rgba(LV2_OSC_Writer *writer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if(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
+osc_writer_add_char(LV2_OSC_Writer *writer, char c)
+{
+ return osc_writer_add_int32(writer, (int32_t)c);
+}
+
+static inline bool
+osc_writer_push_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame, uint64_t t)
+{
+ if(osc_writer_overflow(writer, 16))
+ return false;
+
+ frame->ref = writer->ptr;
+
+ strncpy((char *)writer->ptr, "#bundle", 8);
+ writer->ptr += 8;
+
+ return osc_writer_add_timetag(writer, t);
+}
+
+static inline bool
+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
+osc_writer_push_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ if(osc_writer_overflow(writer, 4))
+ return false;
+
+ frame->ref = writer->ptr;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+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
+osc_writer_add_path(LV2_OSC_Writer *writer, const char *path)
+{
+ return osc_writer_add_string(writer, path);
+}
+
+static inline bool
+osc_writer_add_format(LV2_OSC_Writer *writer, const char *fmt)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(fmt) + 2);
+ if(osc_writer_overflow(writer, padded))
+ return false;
+
+ *writer->ptr++ = ',';
+ strncpy((char *)writer->ptr, fmt, padded - 1);
+ writer->ptr += padded - 1;
+
+ return true;
+}
+
+static inline bool
+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(!osc_writer_add_int32(writer, va_arg(args, int32_t)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!osc_writer_add_float(writer, (float)va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!osc_writer_add_string(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!osc_writer_add_blob(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!osc_writer_add_int64(writer, va_arg(args, int64_t)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!osc_writer_add_double(writer, va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!osc_writer_add_timetag(writer, va_arg(args, uint64_t)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!osc_writer_add_midi(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!osc_writer_add_symbol(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!osc_writer_add_char(writer, va_arg(args, int)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!osc_writer_add_rgba(writer, va_arg(args, unsigned), va_arg(args, unsigned),
+ va_arg(args, unsigned), va_arg(args, unsigned)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+osc_writer_arg_vararg(LV2_OSC_Writer *writer, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = osc_writer_arg_varlist(writer, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+osc_writer_message_varlist(LV2_OSC_Writer *writer, const char *path, const char *fmt, va_list args)
+{
+ if(!osc_writer_add_path(writer, path))
+ return false;
+
+ if(!osc_writer_add_format(writer, fmt))
+ return false;
+
+ return osc_writer_arg_varlist(writer, fmt, args);
+}
+
+static inline bool
+osc_writer_message_vararg(LV2_OSC_Writer *writer, const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = osc_writer_message_varlist(writer, path, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_WRITER_H