diff options
Diffstat (limited to 'lv2/atom')
-rw-r--r-- | lv2/atom/atom-test-utils.c | 71 | ||||
-rw-r--r-- | lv2/atom/atom-test.c | 363 | ||||
-rw-r--r-- | lv2/atom/atom.h | 256 | ||||
-rw-r--r-- | lv2/atom/atom.ttl | 602 | ||||
-rw-r--r-- | lv2/atom/forge-overflow-test.c | 237 | ||||
-rw-r--r-- | lv2/atom/forge.h | 696 | ||||
-rw-r--r-- | lv2/atom/lv2-atom.doap.ttl | 103 | ||||
-rw-r--r-- | lv2/atom/manifest.ttl | 8 | ||||
-rw-r--r-- | lv2/atom/util.h | 509 |
9 files changed, 2845 insertions, 0 deletions
diff --git a/lv2/atom/atom-test-utils.c b/lv2/atom/atom-test-utils.c new file mode 100644 index 0000000..057dada --- /dev/null +++ b/lv2/atom/atom-test-utils.c @@ -0,0 +1,71 @@ +/* + Copyright 2012-2018 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/urid/urid.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +char** uris = NULL; +uint32_t n_uris = 0; + +static char* +copy_string(const char* str) +{ + const size_t len = strlen(str); + char* dup = (char*)malloc(len + 1); + memcpy(dup, str, len + 1); + return dup; +} + +static LV2_URID +urid_map(LV2_URID_Map_Handle handle, const char* uri) +{ + for (uint32_t i = 0; i < n_uris; ++i) { + if (!strcmp(uris[i], uri)) { + return i + 1; + } + } + + uris = (char**)realloc(uris, ++n_uris * sizeof(char*)); + uris[n_uris - 1] = copy_string(uri); + return n_uris; +} + +static void +free_urid_map(void) +{ + for (uint32_t i = 0; i < n_uris; ++i) { + free(uris[i]); + } + + free(uris); +} + +static int +test_fail(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, args); + va_end(args); + return 1; +} diff --git a/lv2/atom/atom-test.c b/lv2/atom/atom-test.c new file mode 100644 index 0000000..5189d9d --- /dev/null +++ b/lv2/atom/atom-test.c @@ -0,0 +1,363 @@ +/* + Copyright 2012-2015 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/atom/atom-test-utils.c" +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/urid/urid.h" + +#include <stdint.h> +#include <stdlib.h> + +int +main(void) +{ + LV2_URID_Map map = { NULL, urid_map }; + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + + LV2_URID eg_Object = urid_map(NULL, "http://example.org/Object"); + LV2_URID eg_one = urid_map(NULL, "http://example.org/one"); + LV2_URID eg_two = urid_map(NULL, "http://example.org/two"); + LV2_URID eg_three = urid_map(NULL, "http://example.org/three"); + LV2_URID eg_four = urid_map(NULL, "http://example.org/four"); + LV2_URID eg_true = urid_map(NULL, "http://example.org/true"); + LV2_URID eg_false = urid_map(NULL, "http://example.org/false"); + LV2_URID eg_path = urid_map(NULL, "http://example.org/path"); + LV2_URID eg_uri = urid_map(NULL, "http://example.org/uri"); + LV2_URID eg_urid = urid_map(NULL, "http://example.org/urid"); + LV2_URID eg_string = urid_map(NULL, "http://example.org/string"); + LV2_URID eg_literal = urid_map(NULL, "http://example.org/literal"); + LV2_URID eg_tuple = urid_map(NULL, "http://example.org/tuple"); + LV2_URID eg_vector = urid_map(NULL, "http://example.org/vector"); + LV2_URID eg_vector2 = urid_map(NULL, "http://example.org/vector2"); + LV2_URID eg_seq = urid_map(NULL, "http://example.org/seq"); + +#define BUF_SIZE 1024 +#define NUM_PROPS 15 + + uint8_t buf[BUF_SIZE]; + lv2_atom_forge_set_buffer(&forge, buf, BUF_SIZE); + + LV2_Atom_Forge_Frame obj_frame; + LV2_Atom* obj = lv2_atom_forge_deref( + &forge, lv2_atom_forge_object(&forge, &obj_frame, 0, eg_Object)); + + // eg_one = (Int)1 + lv2_atom_forge_key(&forge, eg_one); + LV2_Atom_Int* one = (LV2_Atom_Int*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_int(&forge, 1)); + if (one->body != 1) { + return test_fail("%d != 1\n", one->body); + } + + // eg_two = (Long)2 + lv2_atom_forge_key(&forge, eg_two); + LV2_Atom_Long* two = (LV2_Atom_Long*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_long(&forge, 2)); + if (two->body != 2) { + return test_fail("%ld != 2\n", two->body); + } + + // eg_three = (Float)3.0 + lv2_atom_forge_key(&forge, eg_three); + LV2_Atom_Float* three = (LV2_Atom_Float*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_float(&forge, 3.0f)); + if (three->body != 3) { + return test_fail("%f != 3\n", three->body); + } + + // eg_four = (Double)4.0 + lv2_atom_forge_key(&forge, eg_four); + LV2_Atom_Double* four = (LV2_Atom_Double*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_double(&forge, 4.0)); + if (four->body != 4) { + return test_fail("%ld != 4\n", four->body); + } + + // eg_true = (Bool)1 + lv2_atom_forge_key(&forge, eg_true); + LV2_Atom_Bool* t = (LV2_Atom_Bool*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_bool(&forge, true)); + if (t->body != 1) { + return test_fail("%ld != 1 (true)\n", t->body); + } + + // eg_false = (Bool)0 + lv2_atom_forge_key(&forge, eg_false); + LV2_Atom_Bool* f = (LV2_Atom_Bool*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_bool(&forge, false)); + if (f->body != 0) { + return test_fail("%ld != 0 (false)\n", f->body); + } + + // eg_path = (Path)"/foo/bar" + const char* pstr = "/foo/bar"; + const uint32_t pstr_len = (uint32_t)strlen(pstr); + lv2_atom_forge_key(&forge, eg_path); + LV2_Atom_String* path = (LV2_Atom_String*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_uri(&forge, pstr, pstr_len)); + char* pbody = (char*)LV2_ATOM_BODY(path); + if (strcmp(pbody, pstr)) { + return test_fail("%s != \"%s\"\n", pbody, pstr); + } + + // eg_uri = (URI)"http://example.org/value" + const char* ustr = "http://example.org/value"; + const uint32_t ustr_len = (uint32_t)strlen(ustr); + lv2_atom_forge_key(&forge, eg_uri); + LV2_Atom_String* uri = (LV2_Atom_String*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_uri(&forge, ustr, ustr_len)); + char* ubody = (char*)LV2_ATOM_BODY(uri); + if (strcmp(ubody, ustr)) { + return test_fail("%s != \"%s\"\n", ubody, ustr); + } + + // eg_urid = (URID)"http://example.org/value" + LV2_URID eg_value = urid_map(NULL, "http://example.org/value"); + lv2_atom_forge_key(&forge, eg_urid); + LV2_Atom_URID* urid = (LV2_Atom_URID*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_urid(&forge, eg_value)); + if (urid->body != eg_value) { + return test_fail("%u != %u\n", urid->body, eg_value); + } + + // eg_string = (String)"hello" + lv2_atom_forge_key(&forge, eg_string); + LV2_Atom_String* string = (LV2_Atom_String*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_string( + &forge, "hello", strlen("hello"))); + char* sbody = (char*)LV2_ATOM_BODY(string); + if (strcmp(sbody, "hello")) { + return test_fail("%s != \"hello\"\n", sbody); + } + + // eg_literal = (Literal)"hello"@fr + lv2_atom_forge_key(&forge, eg_literal); + LV2_Atom_Literal* literal = (LV2_Atom_Literal*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_literal( + &forge, "bonjour", strlen("bonjour"), + 0, urid_map(NULL, "http://lexvo.org/id/term/fr"))); + char* lbody = (char*)LV2_ATOM_CONTENTS(LV2_Atom_Literal, literal); + if (strcmp(lbody, "bonjour")) { + return test_fail("%s != \"bonjour\"\n", lbody); + } + + // eg_tuple = "foo",true + lv2_atom_forge_key(&forge, eg_tuple); + LV2_Atom_Forge_Frame tuple_frame; + LV2_Atom_Tuple* tuple = (LV2_Atom_Tuple*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_tuple(&forge, &tuple_frame)); + LV2_Atom_String* tup0 = (LV2_Atom_String*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_string( + &forge, "foo", strlen("foo"))); + LV2_Atom_Bool* tup1 = (LV2_Atom_Bool*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_bool(&forge, true)); + lv2_atom_forge_pop(&forge, &tuple_frame); + LV2_Atom* i = lv2_atom_tuple_begin(tuple); + if (lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), tuple->atom.size, i)) { + return test_fail("Tuple iterator is empty\n"); + } + LV2_Atom* tup0i = i; + if (!lv2_atom_equals((LV2_Atom*)tup0, tup0i)) { + return test_fail("Corrupt tuple element 0\n"); + } + i = lv2_atom_tuple_next(i); + if (lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), tuple->atom.size, i)) { + return test_fail("Premature end of tuple iterator\n"); + } + LV2_Atom* tup1i = i; + if (!lv2_atom_equals((LV2_Atom*)tup1, tup1i)) { + return test_fail("Corrupt tuple element 1\n"); + } + i = lv2_atom_tuple_next(i); + if (!lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), tuple->atom.size, i)) { + return test_fail("Tuple iter is not at end\n"); + } + + // eg_vector = (Vector<Int>)1,2,3,4 + lv2_atom_forge_key(&forge, eg_vector); + int32_t elems[] = { 1, 2, 3, 4 }; + LV2_Atom_Vector* vector = (LV2_Atom_Vector*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_vector( + &forge, sizeof(int32_t), forge.Int, 4, elems)); + void* vec_body = LV2_ATOM_CONTENTS(LV2_Atom_Vector, vector); + if (memcmp(elems, vec_body, sizeof(elems))) { + return test_fail("Corrupt vector\n"); + } + + // eg_vector2 = (Vector<Int>)1,2,3,4 + lv2_atom_forge_key(&forge, eg_vector2); + LV2_Atom_Forge_Frame vec_frame; + LV2_Atom_Vector* vector2 = (LV2_Atom_Vector*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_vector_head( + &forge, &vec_frame, sizeof(int32_t), forge.Int)); + for (unsigned e = 0; e < sizeof(elems) / sizeof(int32_t); ++e) { + lv2_atom_forge_int(&forge, elems[e]); + } + lv2_atom_forge_pop(&forge, &vec_frame); + if (!lv2_atom_equals(&vector->atom, &vector2->atom)) { + return test_fail("Vector != Vector2\n"); + } + + // eg_seq = (Sequence)1, 2 + lv2_atom_forge_key(&forge, eg_seq); + LV2_Atom_Forge_Frame seq_frame; + LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)lv2_atom_forge_deref( + &forge, lv2_atom_forge_sequence_head(&forge, &seq_frame, 0)); + lv2_atom_forge_frame_time(&forge, 0); + lv2_atom_forge_int(&forge, 1); + lv2_atom_forge_frame_time(&forge, 1); + lv2_atom_forge_int(&forge, 2); + lv2_atom_forge_pop(&forge, &seq_frame); + + lv2_atom_forge_pop(&forge, &obj_frame); + + // Test equality + LV2_Atom_Int itwo = { { forge.Int, sizeof(int32_t) }, 2 }; + if (lv2_atom_equals((LV2_Atom*)one, (LV2_Atom*)two)) { + return test_fail("1 == 2.0\n"); + } else if (lv2_atom_equals((LV2_Atom*)one, (LV2_Atom*)&itwo)) { + return test_fail("1 == 2\n"); + } else if (!lv2_atom_equals((LV2_Atom*)one, (LV2_Atom*)one)) { + return test_fail("1 != 1\n"); + } + + unsigned n_events = 0; + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + if (ev->time.frames != n_events) { + return test_fail("Corrupt event %u has bad time\n", n_events); + } else if (ev->body.type != forge.Int) { + return test_fail("Corrupt event %u has bad type\n", n_events); + } else if (((LV2_Atom_Int*)&ev->body)->body != (int)n_events + 1) { + return test_fail("Event %u != %d\n", n_events, n_events + 1); + } + ++n_events; + } + + int n_props = 0; + LV2_ATOM_OBJECT_FOREACH((LV2_Atom_Object*)obj, prop) { + if (!prop->key) { + return test_fail("Corrupt property %u has no key\n", n_props); + } else if (prop->context) { + return test_fail("Corrupt property %u has context\n", n_props); + } + ++n_props; + } + + if (n_props != NUM_PROPS) { + return test_fail("Corrupt object has %u properties != %u\n", + n_props, NUM_PROPS); + } + + struct { + const LV2_Atom* one; + const LV2_Atom* two; + const LV2_Atom* three; + const LV2_Atom* four; + const LV2_Atom* affirmative; + const LV2_Atom* negative; + const LV2_Atom* path; + const LV2_Atom* uri; + const LV2_Atom* urid; + const LV2_Atom* string; + const LV2_Atom* literal; + const LV2_Atom* tuple; + const LV2_Atom* vector; + const LV2_Atom* vector2; + const LV2_Atom* seq; + } matches; + + memset(&matches, 0, sizeof(matches)); + + LV2_Atom_Object_Query q[] = { + { eg_one, &matches.one }, + { eg_two, &matches.two }, + { eg_three, &matches.three }, + { eg_four, &matches.four }, + { eg_true, &matches.affirmative }, + { eg_false, &matches.negative }, + { eg_path, &matches.path }, + { eg_uri, &matches.uri }, + { eg_urid, &matches.urid }, + { eg_string, &matches.string }, + { eg_literal, &matches.literal }, + { eg_tuple, &matches.tuple }, + { eg_vector, &matches.vector }, + { eg_vector2, &matches.vector2 }, + { eg_seq, &matches.seq }, + LV2_ATOM_OBJECT_QUERY_END + }; + + int n_matches = lv2_atom_object_query((LV2_Atom_Object*)obj, q); + for (int n = 0; n < 2; ++n) { + if (n_matches != n_props) { + return test_fail("Query failed, %u matches != %u\n", + n_matches, n_props); + } else if (!lv2_atom_equals((LV2_Atom*)one, matches.one)) { + return test_fail("Bad match one\n"); + } else if (!lv2_atom_equals((LV2_Atom*)two, matches.two)) { + return test_fail("Bad match two\n"); + } else if (!lv2_atom_equals((LV2_Atom*)three, matches.three)) { + return test_fail("Bad match three\n"); + } else if (!lv2_atom_equals((LV2_Atom*)four, matches.four)) { + return test_fail("Bad match four\n"); + } else if (!lv2_atom_equals((LV2_Atom*)t, matches.affirmative)) { + return test_fail("Bad match true\n"); + } else if (!lv2_atom_equals((LV2_Atom*)f, matches.negative)) { + return test_fail("Bad match false\n"); + } else if (!lv2_atom_equals((LV2_Atom*)path, matches.path)) { + return test_fail("Bad match path\n"); + } else if (!lv2_atom_equals((LV2_Atom*)uri, matches.uri)) { + return test_fail("Bad match URI\n"); + } else if (!lv2_atom_equals((LV2_Atom*)string, matches.string)) { + return test_fail("Bad match string\n"); + } else if (!lv2_atom_equals((LV2_Atom*)literal, matches.literal)) { + return test_fail("Bad match literal\n"); + } else if (!lv2_atom_equals((LV2_Atom*)tuple, matches.tuple)) { + return test_fail("Bad match tuple\n"); + } else if (!lv2_atom_equals((LV2_Atom*)vector, matches.vector)) { + return test_fail("Bad match vector\n"); + } else if (!lv2_atom_equals((LV2_Atom*)vector, matches.vector2)) { + return test_fail("Bad match vector2\n"); + } else if (!lv2_atom_equals((LV2_Atom*)seq, matches.seq)) { + return test_fail("Bad match sequence\n"); + } + memset(&matches, 0, sizeof(matches)); + n_matches = lv2_atom_object_get((LV2_Atom_Object*)obj, + eg_one, &matches.one, + eg_two, &matches.two, + eg_three, &matches.three, + eg_four, &matches.four, + eg_true, &matches.affirmative, + eg_false, &matches.negative, + eg_path, &matches.path, + eg_uri, &matches.uri, + eg_urid, &matches.urid, + eg_string, &matches.string, + eg_literal, &matches.literal, + eg_tuple, &matches.tuple, + eg_vector, &matches.vector, + eg_vector2, &matches.vector2, + eg_seq, &matches.seq, + 0); + } + + free_urid_map(); + + return 0; +} diff --git a/lv2/atom/atom.h b/lv2/atom/atom.h new file mode 100644 index 0000000..dd332ce --- /dev/null +++ b/lv2/atom/atom.h @@ -0,0 +1,256 @@ +/* + Copyright 2008-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @defgroup atom Atom + + A generic value container and several data types, see + <http://lv2plug.in/ns/ext/atom> for details. + + @{ +*/ + +#ifndef LV2_ATOM_H +#define LV2_ATOM_H + +#include <stddef.h> +#include <stdint.h> + +#define LV2_ATOM_URI "http://lv2plug.in/ns/ext/atom" ///< http://lv2plug.in/ns/ext/atom +#define LV2_ATOM_PREFIX LV2_ATOM_URI "#" ///< http://lv2plug.in/ns/ext/atom# + +#define LV2_ATOM__Atom LV2_ATOM_PREFIX "Atom" ///< http://lv2plug.in/ns/ext/atom#Atom +#define LV2_ATOM__AtomPort LV2_ATOM_PREFIX "AtomPort" ///< http://lv2plug.in/ns/ext/atom#AtomPort +#define LV2_ATOM__Blank LV2_ATOM_PREFIX "Blank" ///< http://lv2plug.in/ns/ext/atom#Blank +#define LV2_ATOM__Bool LV2_ATOM_PREFIX "Bool" ///< http://lv2plug.in/ns/ext/atom#Bool +#define LV2_ATOM__Chunk LV2_ATOM_PREFIX "Chunk" ///< http://lv2plug.in/ns/ext/atom#Chunk +#define LV2_ATOM__Double LV2_ATOM_PREFIX "Double" ///< http://lv2plug.in/ns/ext/atom#Double +#define LV2_ATOM__Event LV2_ATOM_PREFIX "Event" ///< http://lv2plug.in/ns/ext/atom#Event +#define LV2_ATOM__Float LV2_ATOM_PREFIX "Float" ///< http://lv2plug.in/ns/ext/atom#Float +#define LV2_ATOM__Int LV2_ATOM_PREFIX "Int" ///< http://lv2plug.in/ns/ext/atom#Int +#define LV2_ATOM__Literal LV2_ATOM_PREFIX "Literal" ///< http://lv2plug.in/ns/ext/atom#Literal +#define LV2_ATOM__Long LV2_ATOM_PREFIX "Long" ///< http://lv2plug.in/ns/ext/atom#Long +#define LV2_ATOM__Number LV2_ATOM_PREFIX "Number" ///< http://lv2plug.in/ns/ext/atom#Number +#define LV2_ATOM__Object LV2_ATOM_PREFIX "Object" ///< http://lv2plug.in/ns/ext/atom#Object +#define LV2_ATOM__Path LV2_ATOM_PREFIX "Path" ///< http://lv2plug.in/ns/ext/atom#Path +#define LV2_ATOM__Property LV2_ATOM_PREFIX "Property" ///< http://lv2plug.in/ns/ext/atom#Property +#define LV2_ATOM__Resource LV2_ATOM_PREFIX "Resource" ///< http://lv2plug.in/ns/ext/atom#Resource +#define LV2_ATOM__Sequence LV2_ATOM_PREFIX "Sequence" ///< http://lv2plug.in/ns/ext/atom#Sequence +#define LV2_ATOM__Sound LV2_ATOM_PREFIX "Sound" ///< http://lv2plug.in/ns/ext/atom#Sound +#define LV2_ATOM__String LV2_ATOM_PREFIX "String" ///< http://lv2plug.in/ns/ext/atom#String +#define LV2_ATOM__Tuple LV2_ATOM_PREFIX "Tuple" ///< http://lv2plug.in/ns/ext/atom#Tuple +#define LV2_ATOM__URI LV2_ATOM_PREFIX "URI" ///< http://lv2plug.in/ns/ext/atom#URI +#define LV2_ATOM__URID LV2_ATOM_PREFIX "URID" ///< http://lv2plug.in/ns/ext/atom#URID +#define LV2_ATOM__Vector LV2_ATOM_PREFIX "Vector" ///< http://lv2plug.in/ns/ext/atom#Vector +#define LV2_ATOM__atomTransfer LV2_ATOM_PREFIX "atomTransfer" ///< http://lv2plug.in/ns/ext/atom#atomTransfer +#define LV2_ATOM__beatTime LV2_ATOM_PREFIX "beatTime" ///< http://lv2plug.in/ns/ext/atom#beatTime +#define LV2_ATOM__bufferType LV2_ATOM_PREFIX "bufferType" ///< http://lv2plug.in/ns/ext/atom#bufferType +#define LV2_ATOM__childType LV2_ATOM_PREFIX "childType" ///< http://lv2plug.in/ns/ext/atom#childType +#define LV2_ATOM__eventTransfer LV2_ATOM_PREFIX "eventTransfer" ///< http://lv2plug.in/ns/ext/atom#eventTransfer +#define LV2_ATOM__frameTime LV2_ATOM_PREFIX "frameTime" ///< http://lv2plug.in/ns/ext/atom#frameTime +#define LV2_ATOM__supports LV2_ATOM_PREFIX "supports" ///< http://lv2plug.in/ns/ext/atom#supports +#define LV2_ATOM__timeUnit LV2_ATOM_PREFIX "timeUnit" ///< http://lv2plug.in/ns/ext/atom#timeUnit + +#define LV2_ATOM_REFERENCE_TYPE 0 ///< The special type for a reference atom + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond */ +/** This expression will fail to compile if double does not fit in 64 bits. */ +typedef char lv2_atom_assert_double_fits_in_64_bits[ + ((sizeof(double) <= sizeof(uint64_t)) * 2) - 1]; +/** @endcond */ + +/** + Return a pointer to the contents of an Atom. The "contents" of an atom + is the data past the complete type-specific header. + @param type The type of the atom, e.g. LV2_Atom_String. + @param atom A variable-sized atom. +*/ +#define LV2_ATOM_CONTENTS(type, atom) \ + ((void*)((uint8_t*)(atom) + sizeof(type))) + +/** + Const version of LV2_ATOM_CONTENTS. +*/ +#define LV2_ATOM_CONTENTS_CONST(type, atom) \ + ((const void*)((const uint8_t*)(atom) + sizeof(type))) + +/** + Return a pointer to the body of an Atom. The "body" of an atom is the + data just past the LV2_Atom head (i.e. the same offset for all types). +*/ +#define LV2_ATOM_BODY(atom) LV2_ATOM_CONTENTS(LV2_Atom, atom) + +/** + Const version of LV2_ATOM_BODY. +*/ +#define LV2_ATOM_BODY_CONST(atom) LV2_ATOM_CONTENTS_CONST(LV2_Atom, atom) + +/** The header of an atom:Atom. */ +typedef struct { + uint32_t size; /**< Size in bytes, not including type and size. */ + uint32_t type; /**< Type of this atom (mapped URI). */ +} LV2_Atom; + +/** An atom:Int or atom:Bool. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + int32_t body; /**< Integer value. */ +} LV2_Atom_Int; + +/** An atom:Long. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + int64_t body; /**< Integer value. */ +} LV2_Atom_Long; + +/** An atom:Float. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + float body; /**< Floating point value. */ +} LV2_Atom_Float; + +/** An atom:Double. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + double body; /**< Floating point value. */ +} LV2_Atom_Double; + +/** An atom:Bool. May be cast to LV2_Atom. */ +typedef LV2_Atom_Int LV2_Atom_Bool; + +/** An atom:URID. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + uint32_t body; /**< URID. */ +} LV2_Atom_URID; + +/** An atom:String. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + /* Contents (a null-terminated UTF-8 string) follow here. */ +} LV2_Atom_String; + +/** The body of an atom:Literal. */ +typedef struct { + uint32_t datatype; /**< Datatype URID. */ + uint32_t lang; /**< Language URID. */ + /* Contents (a null-terminated UTF-8 string) follow here. */ +} LV2_Atom_Literal_Body; + +/** An atom:Literal. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Literal_Body body; /**< Body. */ +} LV2_Atom_Literal; + +/** An atom:Tuple. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + /* Contents (a series of complete atoms) follow here. */ +} LV2_Atom_Tuple; + +/** The body of an atom:Vector. */ +typedef struct { + uint32_t child_size; /**< The size of each element in the vector. */ + uint32_t child_type; /**< The type of each element in the vector. */ + /* Contents (a series of packed atom bodies) follow here. */ +} LV2_Atom_Vector_Body; + +/** An atom:Vector. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Vector_Body body; /**< Body. */ +} LV2_Atom_Vector; + +/** The body of an atom:Property (e.g. in an atom:Object). */ +typedef struct { + uint32_t key; /**< Key (predicate) (mapped URI). */ + uint32_t context; /**< Context URID (may be, and generally is, 0). */ + LV2_Atom value; /**< Value atom header. */ + /* Value atom body follows here. */ +} LV2_Atom_Property_Body; + +/** An atom:Property. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Property_Body body; /**< Body. */ +} LV2_Atom_Property; + +/** The body of an atom:Object. May be cast to LV2_Atom. */ +typedef struct { + uint32_t id; /**< URID, or 0 for blank. */ + uint32_t otype; /**< Type URID (same as rdf:type, for fast dispatch). */ + /* Contents (a series of property bodies) follow here. */ +} LV2_Atom_Object_Body; + +/** An atom:Object. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Object_Body body; /**< Body. */ +} LV2_Atom_Object; + +/** The header of an atom:Event. Note this type is NOT an LV2_Atom. */ +typedef struct { + /** Time stamp. Which type is valid is determined by context. */ + union { + int64_t frames; /**< Time in audio frames. */ + double beats; /**< Time in beats. */ + } time; + LV2_Atom body; /**< Event body atom header. */ + /* Body atom contents follow here. */ +} LV2_Atom_Event; + +/** + The body of an atom:Sequence (a sequence of events). + + The unit field is either a URID that described an appropriate time stamp + type, or may be 0 where a default stamp type is known. For + LV2_Descriptor::run(), the default stamp type is audio frames. + + The contents of a sequence is a series of LV2_Atom_Event, each aligned + to 64-bits, e.g.: + <pre> + | Event 1 (size 6) | Event 2 + | | | | | | | | | + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + |FRAMES |SUBFRMS|TYPE |SIZE |DATADATADATAPAD|FRAMES |SUBFRMS|... + </pre> +*/ +typedef struct { + uint32_t unit; /**< URID of unit of event time stamps. */ + uint32_t pad; /**< Currently unused. */ + /* Contents (a series of events) follow here. */ +} LV2_Atom_Sequence_Body; + +/** An atom:Sequence. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Sequence_Body body; /**< Body. */ +} LV2_Atom_Sequence; + +/** + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV2_ATOM_H */ diff --git a/lv2/atom/atom.ttl b/lv2/atom/atom.ttl new file mode 100644 index 0000000..891cd1f --- /dev/null +++ b/lv2/atom/atom.ttl @@ -0,0 +1,602 @@ +@prefix atom: <http://lv2plug.in/ns/ext/atom#> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix units: <http://lv2plug.in/ns/extensions/units#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . + +<http://lv2plug.in/ns/ext/atom> + a owl:Ontology ; + rdfs:seeAlso <atom.h> , + <util.h> , + <forge.h> , + <lv2-atom.doap.ttl> ; + lv2:documentation """ + +<p>An #Atom is a simple generic data container for holding any type of Plain +Old Data (POD). An #Atom can contain simple primitive types like integers, +floating point numbers, and strings; as well as structured data like lists and +dictionary-like <q>Objects</q>. Since Atoms are POD, they can be easily copied +(e.g. using <code>memcpy</code>) anywhere and are suitable for use in real-time +code.</p> + +<p>Every atom starts with an LV2_Atom header, followed by the contents. This +allows code to process atoms without requiring special code for every type of +data. For example, plugins that mutually understand a type can be used +together in a host that does not understand that type, because the host is only +required to copy atoms, not interpret their contents. Similarly, plugins (such +as routers, delays, or data structures) can meaningfully process atoms of a +type unknown to them.</p> + +<p>Atoms should be used anywhere values of various types must be stored or +transmitted. The port type #AtomPort can be used to transmit atoms via ports. +An #AtomPort that contains an #Sequence can be used for sample accurate event +communication, such as MIDI, and replaces the earlier event extension.</p> + +<h3>Serialisation</h3> + +<p>Each Atom type defines a binary format for use at runtime, but also a +serialisation that is natural to express in Turtle format. Thus, this +specification defines a powerful real-time appropriate data model, as well as a +portable way to serialise any data in that model. This is particularly useful +for inter-process communication, saving/restoring state, and describing values +in plugin data files.</p> + +<h3>Custom Atom Types</h3> + +<p>While it is possible to define new Atom types for any binary format, the +standard types defined here are powerful enough to describe almost anything. +Implementations SHOULD build structures out of the types provided here, rather +than define new binary formats (e.g. use #Tuple or #Object rather than +a new C <code>struct</code> type). Current implementations have support for +serialising all standard types, so new binary formats are an implementation +burden which harms interoperabilty. In particular, plugins SHOULD NOT expect +UI communication or state saving with custom Atom types to work. In general, +new Atom types should only be defined where absolutely necessary due to +performance reasons and serialisation is not a concern.</p> +""" . + +atom:cType + a rdf:Property , + owl:DatatypeProperty , + owl:FunctionalProperty ; + rdfs:label "C type" ; + rdfs:domain rdfs:Class ; + rdfs:range lv2:Symbol ; + rdfs:comment """The identifier for a C type describing the binary representation of an Atom of this type.""" . + +atom:Atom + a rdfs:Class ; + rdfs:label "Atom" ; + atom:cType "LV2_Atom" ; + lv2:documentation """ +<p>Abstract base class for all atoms. An LV2_Atom has a 32-bit +<code>size</code> and <code>type</code> followed by a body of <code>size</code> +bytes. Atoms MUST be 64-bit aligned.</p> + +<p>All concrete Atom types (subclasses of this class) MUST define a precise +binary layout for their body.</p> + +<p>The <code>type</code> field is the URI of an Atom type mapped to an integer. +Implementations SHOULD gracefully pass through, or ignore, atoms with unknown +types.</p> + +<p>All atoms are POD by definition except references, which as a special case +have <code>type = 0</code>. An Atom MUST NOT contain a Reference. It is safe +to copy any non-reference Atom with a simple <code>memcpy</code>, even if the +implementation does not understand <code>type</code>. Though this extension +reserves the type 0 for references, the details of reference handling are +currently unspecified. A future revision of this extension, or a different +extension, may define how to use non-POD data and references. Implementations +MUST NOT send references to another implementation unless the receiver is +explicitly known to support references (e.g. by supporting a feature).</p> + +<p>The atom with both <code>type</code> <em>and</em> <code>size</code> 0 is +<q>null</q>, which is not considered a Reference.</p> +""" . + +atom:Chunk + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Chunk of memory" ; + owl:onDatatype xsd:base64Binary ; + lv2:documentation """ +<p>A chunk of memory with undefined contents. This type is used to indicate a +certain amount of space is available. For example, output ports with a +variably sized type are connected to a Chunk so the plugin knows the size of +the buffer available for writing.</p> + +<p>The use of a Chunk should be constrained to a local scope, since +interpreting it is impossible without context. However, if serialised to RDF, +a Chunk may be represented directly as an xsd:base64Binary string, e.g.:</p> + +<pre class="turtle-code"> +[] eg:someChunk "vu/erQ=="^^xsd:base64Binary . +</pre> +""" . + +atom:Number + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Number" . + +atom:Int + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Number ; + rdfs:label "Signed 32-bit integer" ; + atom:cType "LV2_Atom_Int" ; + owl:onDatatype xsd:int . + +atom:Long + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Number ; + rdfs:label "Signed 64-bit integer" ; + atom:cType "LV2_Atom_Long" ; + owl:onDatatype xsd:long . + +atom:Float + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Number ; + rdfs:label "32-bit floating point number" ; + atom:cType "LV2_Atom_Float" ; + owl:onDatatype xsd:float . + +atom:Double + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Number ; + rdfs:label "64-bit floating point number" ; + atom:cType "LV2_Atom_Double" ; + owl:onDatatype xsd:double . + +atom:Bool + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Boolean" ; + atom:cType "LV2_Atom_Bool" ; + owl:onDatatype xsd:boolean ; + rdfs:comment "An Int where 0 is false and any other value is true." . + +atom:String + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:Atom ; + rdfs:label "String" ; + atom:cType "LV2_Atom_String" ; + owl:onDatatype xsd:string ; + lv2:documentation """ +<p>A UTF-8 encoded string.</p> + +<p>The body of an LV2_Atom_String is a C string in UTF-8 encoding, i.e. an +array of bytes (<code>uint8_t</code>) terminated with a NULL byte +(<code>'\\0'</code>).</p> + +<p>This type is for free-form strings, but SHOULD NOT be used for typed data or +text in any language. Use atom:Literal unless translating the string does not +make sense and the string has no meaningful datatype.</p> +""" . + +atom:Literal + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "String Literal" ; + atom:cType "LV2_Atom_Literal" ; + lv2:documentation """ +<p>A UTF-8 encoded string literal, with an optional datatype or language.</p> + +<p>This type is compatible with rdfs:Literal and is capable of expressing a +string in any language or a value of any type. A Literal has a +<code>datatype</code> and <code>lang</code> followed by string data in UTF-8 +encoding. The length of the string data in bytes is <code>size - +sizeof(LV2_Atom_Literal)</code>, including the terminating NULL character. The +<code>lang</code> field SHOULD be a URI of the form +<http://lexvo.org/id/iso639-3/LANG> or +<http://lexvo.org/id/iso639-1/LANG> where LANG is a 3-character ISO 693-3 +language code, or a 2-character ISO 693-1 language code, respectively.</p> + +<p>A Literal may have a <code>datatype</code> OR a <code>lang</code>, but never +both.</p> + +<p>For example, a Literal can be "Hello" in English:</p> +<pre class="c-code"> +void set_to_hello_in_english(LV2_Atom_Literal* lit) { + lit->atom.type = map(expand("atom:Literal")); + lit->atom.size = 14; + lit->body.datatype = 0; + lit->body.lang = map("http://lexvo.org/id/iso639-1/en"); + memcpy(LV2_ATOM_CONTENTS(LV2_Atom_Literal, lit), + "Hello", + sizeof("Hello")); // Assumes enough space +} +</pre> + +<p>or a Turtle string:</p> +<pre class="c-code"> +void set_to_turtle_string(LV2_Atom_Literal* lit, const char* ttl) { + lit->atom.type = map(expand("atom:Literal")); + lit->atom.size = 64; + lit->body.datatype = map("http://www.w3.org/2008/turtle#turtle"); + lit->body.lang = 0; + memcpy(LV2_ATOM_CONTENTS(LV2_Atom_Literal, lit), + ttl, + strlen(ttl) + 1); // Assumes enough space +} +</pre> +""" . + +atom:Path + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:URI ; + owl:onDatatype atom:URI ; + rdfs:label "File path string" ; + lv2:documentation """ +<p>A local file path.</p> + +<p>A Path is a URI reference with only a path component: no scheme, authority, +query, or fragment. In particular, paths to files in the same bundle may be +cleanly written in Turtle files as a relative URI. However, implementations +may assume any binary Path (e.g. in an event payload) is a valid file path +which can passed to system functions like fopen() directly, without any +character encoding or escape expansion required.</p> + +<p>Any implemenation that creates a Path atom to transmit to another is +responsible for ensuring it is valid. A Path SHOULD always be absolute, unless +there is some mechanism in place that defines a base path. Since this is not +the case for plugin instances, effectively any Path sent to or received from a +plugin instance MUST be absolute.</p> +""" . + +atom:URI + a rdfs:Class , + rdfs:Datatype ; + rdfs:subClassOf atom:String ; + owl:onDatatype xsd:anyURI ; + rdfs:label "URI string" ; + lv2:documentation """ +<p>A URI string. This is useful when a URI is needed but mapping is +inappropriate, for example with temporary or relative URIs. Since the ability +to distinguish URIs from plain strings is often necessary, URIs MUST NOT be +transmitted as atom:String.</p> + +<p>This is not strictly a URI, since UTF-8 is allowed. Escaping and related +issues are the host's responsibility.</p> +""" . + +atom:URID + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Integer URID" ; + atom:cType "LV2_Atom_URID" ; + lv2:documentation """ +<p>An unsigned 32-bit integer mapped from a URI (e.g. with LV2_URID_Map).</p> +""" . + +atom:Vector + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Vector" ; + atom:cType "LV2_Atom_Vector" ; + lv2:documentation """ +<p>A homogeneous series of atom bodies with equivalent type and size.</p> + +<p>An LV2_Atom_Vector is a 32-bit <code>child_size</code> and +<code>child_type</code> followed by <code>size / child_size</code> atom +bodies.</p> + +<p>For example, an atom:Vector containing 42 elements of type atom:Float:</p> +<pre class="c-code"> +struct VectorOf42Floats { + uint32_t size; // sizeof(LV2_Atom_Vector_Body) + (42 * sizeof(float); + uint32_t type; // map(expand("atom:Vector")) + uint32_t child_size; // sizeof(float) + uint32_t child_type; // map(expand("atom:Float")) + float elems[42]; +}; +</pre> + +<p>Note that it is possible to construct a valid Atom for each element +of the vector, even by an implementation which does not understand +<code>child_type</code>.</p> + +<p>If serialised to RDF, a Vector SHOULD have the form:</p> + +<pre class="turtle-code"> +eg:someVector + a atom:Vector ; + atom:childType atom:Int ; + rdf:value ( + "1"^^xsd:int + "2"^^xsd:int + "3"^^xsd:int + "4"^^xsd:int + ) . +</pre> +""" . + +atom:Tuple + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Tuple" ; + lv2:documentation """ +<p>A series of Atoms with varying <code>type</code> and <code>size</code>.</p> + +<p>The body of a Tuple is simply a series of complete atoms, each aligned to +64 bits.</p> + +<p>If serialised to RDF, a Tuple SHOULD have the form:</p> + +<pre class="turtle-code"> +eg:someVector + a atom:Tuple ; + rdf:value ( + "1"^^xsd:int + "3.5"^^xsd:float + "etc" + ) . +</pre> + +""" . + +atom:Property + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Property" ; + atom:cType "LV2_Atom_Property" ; + lv2:documentation """ +<p>A property of an atom:Object. An LV2_Atom_Property has a URID +<code>key</code> and <code>context</code>, and an Atom <code>value</code>. +This corresponds to an RDF Property, where the <q>key</q> is the <q>predicate</q> +and the <q>value</q> is the object.</p> + +<p>The <code>context</code> field can be used to specify a different context +for each property, where this is useful. Otherwise, it may be 0.</p> + +<p>Properties generally only exist as part of an atom:Object. Accordingly, +they will typically be represented directly as properties in RDF (see +atom:Object). If this is not possible, they may be expressed as partial +reified statements, e.g.:</p> + +<pre class="turtle-code"> +eg:someProperty + rdf:predicate eg:theKey ; + rdf:object eg:theValue . +</pre> +""" . + +atom:Object + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Object" ; + atom:cType "LV2_Atom_Object" ; + lv2:documentation """ +<p>An <q>Object</q> is an atom with a set of properties. This corresponds to +an RDF Resource, and can be thought of as a dictionary with URID keys.</p> + +<p>An LV2_Atom_Object body has a uint32_t <code>id</code> and +<code>type</code>, followed by a series of atom:Property bodies +(LV2_Atom_Property_Body). The LV2_Atom_Object_Body::otype field is equivalent +to a property with key rdf:type, but is included in the structure to allow for +fast dispatching.</p> + +<p>Code SHOULD check for objects using lv2_atom_forge_is_object() or +lv2_atom_forge_is_blank() if a forge is available, rather than checking the +atom type directly. This will correctly handle the deprecated atom:Resource +and atom:Blank types.</p> + +<p>When serialised to RDF, an Object is represented as a resource, e.g.:</p> + +<pre class="turtle-code"> +eg:someObject + eg:firstPropertyKey "first property value" ; + eg:secondPropertyKey "first loser" ; + eg:andSoOn "and so on" . +</pre> +""" . + +atom:Resource + a rdfs:Class ; + rdfs:subClassOf atom:Object ; + rdfs:label "Resource" ; + owl:deprecated "true"^^xsd:boolean ; + atom:cType "LV2_Atom_Object" ; + lv2:documentation """ +<p>This class is deprecated. Use atom:Object instead.</p> + +<p>An atom:Object where the <code>id</code> field is a URID, i.e. an Object +with a URI.</p> +""" . + +atom:Blank + a rdfs:Class ; + rdfs:subClassOf atom:Object ; + rdfs:label "Blank" ; + owl:deprecated "true"^^xsd:boolean ; + atom:cType "LV2_Atom_Object" ; + lv2:documentation """ +<p>This class is deprecated. Use atom:Object with ID 0 instead.</p> + +<p>An atom:Object where the LV2_Atom_Object::id is a blank node ID (NOT a URI). +The ID of a Blank is valid only within the context the Blank appears in. For +ports this is the context of the associated run() call, i.e. all ports share +the same context so outputs can contain IDs that correspond to IDs of blanks in +the input.</p> +""" . + +atom:Sound + a rdfs:Class ; + rdfs:subClassOf atom:Vector ; + rdfs:label "Sound" ; + atom:cType "LV2_Atom_Sound" ; + lv2:documentation """ +<p>An atom:Vector of atom:Float which represents an audio waveform. The format +is the same as the buffer format for lv2:AudioPort (except the size may be +arbitrary). An atom:Sound inherently depends on the sample rate, which is +assumed to be known from context. Because of this, directly serialising an +atom:Sound is probably a bad idea, use a standard format like WAV instead.</p> +""" . + +atom:frameTime + a rdf:Property , + owl:DatatypeProperty , + owl:FunctionalProperty ; + rdfs:range xsd:decimal ; + rdfs:label "frame time" ; + lv2:documentation """ +<p>Time stamp in audio frames. Typically used for events.</p> +""" . + +atom:beatTime + a rdf:Property , + owl:DatatypeProperty , + owl:FunctionalProperty ; + rdfs:range xsd:decimal ; + rdfs:label "beat time" ; + lv2:documentation """ +<p>Time stamp in beats. Typically used for events.</p> +""" . + +atom:Event + a rdfs:Class ; + rdfs:label "Event" ; + atom:cType "LV2_Atom_Event" ; + lv2:documentation """ +<p>An atom with a time stamp prefix, typically an element of an atom:Sequence. +Note this is not an Atom type.</p> +""" . + +atom:Sequence + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Sequence" ; + atom:cType "LV2_Atom_Sequence" ; + lv2:documentation """ +<p>A sequence of atom:Event, i.e. a series of time-stamped Atoms.</p> + +<p>LV2_Atom_Sequence_Body.unit describes the time unit for the contained atoms. +If the unit is known from context (e.g. run() stamps are always audio frames), +this field may be zero. Otherwise, it SHOULD be either units:frame or +units:beat, in which case ev.time.frames or ev.time.beats is valid, +respectively.</p> + +<p>If serialised to RDF, a Sequence has a similar form to atom:Vector, but for +brevity the elements may be assumed to be atom:Event, e.g.:</p> + +<pre class="turtle-code"> +eg:someSequence + a atom:Sequence ; + rdf:value ( + [ + atom:frameTime 1 ; + rdf:value "901A01"^^midi:MidiEvent + ] [ + atom:frameTime 3 ; + rdf:value "902B02"^^midi:MidiEvent + ] + ) . +</pre> +""" . + +atom:AtomPort + a rdfs:Class ; + rdfs:subClassOf lv2:Port ; + rdfs:label "Atom Port" ; + lv2:documentation """ +<p>A port which contains an atom:Atom. Ports of this type are connected to an +LV2_Atom with a type specified by atom:bufferType.</p> + +<p>Output ports with a variably sized type MUST be initialised by the host +before every run() to an atom:Chunk with size set to the available space. The +plugin reads this size to know how much space is available for writing. In all +cases, the plugin MUST write a complete atom (including header) to outputs. +However, to be robust, hosts SHOULD initialise output ports to a safe sentinel +(e.g. the null Atom) before calling run().</p> +""" . + +atom:bufferType + a rdf:Property , + owl:ObjectProperty ; + rdfs:domain atom:AtomPort ; + rdfs:range rdfs:Class ; + rdfs:label "buffer type" ; + lv2:documentation """ +<p>Indicates that an AtomPort may be connected to a certain Atom type. A port +MAY support several buffer types. The host MUST NOT connect a port to an Atom +with a type not explicitly listed with this property. The value of this +property MUST be a sub-class of atom:Atom. For example, an input port that is +connected directly to an LV2_Atom_Double value is described like so:</p> + +<pre class="turtle-code"> +<plugin> + lv2:port [ + a lv2:InputPort , atom:AtomPort ; + atom:bufferType atom:Double ; + ] . +</pre> + +<p>This property only describes the types a port may be <em>directly</em> +connected to. It says nothing about the expected contents of containers. For +that, use atom:supports.</p> +""" . + +atom:childType + a rdf:Property , + owl:ObjectProperty ; + rdfs:label "child type" ; + rdfs:comment "The type of a container's children." . + +atom:supports + a rdf:Property ; + rdfs:label "supports" ; + rdfs:range rdfs:Class ; + lv2:documentation """ +<p>Indicates that a particular Atom type is supported.</p> + +<p>This property is defined loosely, it may be used to indicate that anything +<q>supports</q> an Atom type, wherever that may be useful. It applies +<q>recursively</q> where collections are involved.</p> + +<p>In particular, this property can be used to describe which event types are +expected by a port. For example, a port that receives MIDI events is described +like so:</p> + +<pre class="turtle-code"> +<plugin> + lv2:port [ + a lv2:InputPort , atom:AtomPort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + ] . +</pre> +""" . + +atom:eventTransfer + a ui:PortProtocol ; + rdfs:label "event transfer" ; + lv2:documentation """ +<p>Transfer of individual events in a port buffer. Useful as the +<code>format</code> for a LV2UI_Write_Function.</p> + +<p>This protocol applies to ports which contain events, usually in an +atom:Sequence. The host must transfer each individual event to the recipient. +The format of the received data is an LV2_Atom, there is no timestamp +header.</p> +""" . + +atom:atomTransfer + a ui:PortProtocol ; + rdfs:label "atom transfer" ; + lv2:documentation """ +<p>Transfer of the complete atom in a port buffer. Useful as the +<code>format</code> for a LV2UI_Write_Function.</p> + +<p>This protocol applies to atom ports. The host must transfer the complete +atom contained in the port, including header.</p> +""" . diff --git a/lv2/atom/forge-overflow-test.c b/lv2/atom/forge-overflow-test.c new file mode 100644 index 0000000..a580d69 --- /dev/null +++ b/lv2/atom/forge-overflow-test.c @@ -0,0 +1,237 @@ +/* + Copyright 2019 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/atom/atom-test-utils.c" +#include "lv2/atom/atom.h" +#include "lv2/atom/forge.h" +#include "lv2/atom/util.h" +#include "lv2/urid/urid.h" + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static int +test_string_overflow(void) +{ +#define MAX_CHARS 15 + + static const size_t capacity = sizeof(LV2_Atom_String) + MAX_CHARS + 1; + static const char* str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + uint8_t* buf = (uint8_t*)malloc(capacity); + LV2_URID_Map map = { NULL, urid_map }; + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + + // Check that writing increasingly long strings fails at the right point + for (size_t count = 0; count < MAX_CHARS; ++count) { + lv2_atom_forge_set_buffer(&forge, buf, capacity); + + const LV2_Atom_Forge_Ref ref = + lv2_atom_forge_string(&forge, str, count); + if (!ref) { + return test_fail("Failed to write %zu byte string\n", count); + } + } + + // Failure writing to an exactly full forge + LV2_Atom_Forge_Ref ref = 0; + if ((ref = lv2_atom_forge_string(&forge, str, MAX_CHARS + 1))) { + return test_fail("Successfully wrote past end of buffer\n"); + } + + // Failure writing body after successfully writing header + lv2_atom_forge_set_buffer(&forge, buf, sizeof(LV2_Atom) + 1); + if ((ref = lv2_atom_forge_string(&forge, "AB", 2))) { + return test_fail("Successfully wrote atom header past end\n"); + } + + free(buf); + return 0; +} + +static int +test_literal_overflow(void) +{ + static const size_t capacity = sizeof(LV2_Atom_Literal) + 2; + + uint8_t* buf = (uint8_t*)malloc(capacity); + LV2_URID_Map map = { NULL, urid_map }; + LV2_Atom_Forge_Ref ref = 0; + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + + // Failure in atom header + lv2_atom_forge_set_buffer(&forge, buf, 1); + if ((ref = lv2_atom_forge_literal(&forge, "A", 1, 0, 0))) { + return test_fail("Successfully wrote atom header past end\n"); + } + + // Failure in literal header + lv2_atom_forge_set_buffer(&forge, buf, sizeof(LV2_Atom) + 1); + if ((ref = lv2_atom_forge_literal(&forge, "A", 1, 0, 0))) { + return test_fail("Successfully wrote literal header past end\n"); + } + + // Success (only room for one character + null terminator) + lv2_atom_forge_set_buffer(&forge, buf, capacity); + if (!(ref = lv2_atom_forge_literal(&forge, "A", 1, 0, 0))) { + return test_fail("Failed to write small enough literal\n"); + } + + // Failure in body + lv2_atom_forge_set_buffer(&forge, buf, capacity); + if ((ref = lv2_atom_forge_literal(&forge, "AB", 2, 0, 0))) { + return test_fail("Successfully wrote literal body past end\n"); + } + + free(buf); + return 0; +} + +static int +test_sequence_overflow(void) +{ + static const size_t size = sizeof(LV2_Atom_Sequence) + 6 * sizeof(LV2_Atom); + LV2_URID_Map map = { NULL, urid_map }; + + // Test over a range that fails in the sequence header and event components + for (size_t capacity = 1; capacity < size; ++capacity) { + uint8_t* buf = (uint8_t*)malloc(capacity); + + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + lv2_atom_forge_set_buffer(&forge, buf, capacity); + + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref = + lv2_atom_forge_sequence_head(&forge, &frame, 0); + + assert(capacity >= sizeof(LV2_Atom_Sequence) || !frame.ref); + assert(capacity >= sizeof(LV2_Atom_Sequence) || !ref); + + lv2_atom_forge_frame_time(&forge, 0); + lv2_atom_forge_int(&forge, 42); + lv2_atom_forge_pop(&forge, &frame); + + free(buf); + } + + return 0; +} + +static int +test_vector_head_overflow(void) +{ + static const size_t size = sizeof(LV2_Atom_Vector) + 3 * sizeof(LV2_Atom); + LV2_URID_Map map = { NULL, urid_map }; + + // Test over a range that fails in the vector header and elements + for (size_t capacity = 1; capacity < size; ++capacity) { + uint8_t* buf = (uint8_t*)malloc(capacity); + + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + lv2_atom_forge_set_buffer(&forge, buf, capacity); + + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref = lv2_atom_forge_vector_head( + &forge, &frame, sizeof(int32_t), forge.Int); + + assert(capacity >= sizeof(LV2_Atom_Vector) || !frame.ref); + assert(capacity >= sizeof(LV2_Atom_Vector) || !ref); + + lv2_atom_forge_int(&forge, 1); + lv2_atom_forge_int(&forge, 2); + lv2_atom_forge_int(&forge, 3); + lv2_atom_forge_pop(&forge, &frame); + + free(buf); + } + + return 0; +} + +static int +test_vector_overflow(void) +{ + static const size_t size = sizeof(LV2_Atom_Vector) + 3 * sizeof(LV2_Atom); + static const int32_t vec[] = { 1, 2, 3 }; + LV2_URID_Map map = { NULL, urid_map }; + + // Test over a range that fails in the vector header and elements + for (size_t capacity = 1; capacity < size; ++capacity) { + uint8_t* buf = (uint8_t*)malloc(capacity); + + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + lv2_atom_forge_set_buffer(&forge, buf, capacity); + + LV2_Atom_Forge_Ref ref = lv2_atom_forge_vector( + &forge, sizeof(int32_t), forge.Int, 3, vec); + + assert(capacity >= sizeof(LV2_Atom_Vector) || !ref); + + free(buf); + } + + return 0; +} + +static int +test_tuple_overflow(void) +{ + static const size_t size = sizeof(LV2_Atom_Tuple) + 3 * sizeof(LV2_Atom); + LV2_URID_Map map = { NULL, urid_map }; + + // Test over a range that fails in the tuple header and elements + for (size_t capacity = 1; capacity < size; ++capacity) { + uint8_t* buf = (uint8_t*)malloc(capacity); + + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, &map); + lv2_atom_forge_set_buffer(&forge, buf, capacity); + + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge_Ref ref = lv2_atom_forge_tuple(&forge, &frame); + + assert(capacity >= sizeof(LV2_Atom_Tuple) || !frame.ref); + assert(capacity >= sizeof(LV2_Atom_Tuple) || !ref); + + lv2_atom_forge_int(&forge, 1); + lv2_atom_forge_float(&forge, 2.0f); + lv2_atom_forge_string(&forge, "three", 5); + lv2_atom_forge_pop(&forge, &frame); + + free(buf); + } + + return 0; +} + +int +main(void) +{ + const int ret = test_string_overflow() || test_literal_overflow() || + test_sequence_overflow() || test_vector_head_overflow() || + test_vector_overflow() || test_tuple_overflow(); + + free_urid_map(); + + return ret; +} diff --git a/lv2/atom/forge.h b/lv2/atom/forge.h new file mode 100644 index 0000000..5bcd816 --- /dev/null +++ b/lv2/atom/forge.h @@ -0,0 +1,696 @@ +/* + Copyright 2008-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file forge.h An API for constructing LV2 atoms. + + This file provides an API for constructing Atoms which makes it relatively + simple to build nested atoms of arbitrary complexity without requiring + dynamic memory allocation. + + The API is based on successively appending the appropriate pieces to build a + complete Atom. The size of containers is automatically updated. Functions + that begin a container return (via their frame argument) a stack frame which + must be popped when the container is finished. + + All output is written to a user-provided buffer or sink function. This + makes it popssible to create create atoms on the stack, on the heap, in LV2 + port buffers, in a ringbuffer, or elsewhere, all using the same API. + + This entire API is realtime safe if used with a buffer or a realtime safe + sink, except lv2_atom_forge_init() which is only realtime safe if the URI + map function is. + + Note these functions are all static inline, do not take their address. + + This header is non-normative, it is provided for convenience. +*/ + +/** + @defgroup forge Forge + @ingroup atom + @{ +*/ + +#ifndef LV2_ATOM_FORGE_H +#define LV2_ATOM_FORGE_H + +#include "lv2/atom/atom.h" +#include "lv2/atom/util.h" +#include "lv2/core/attributes.h" +#include "lv2/urid/urid.h" + +#include <assert.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Disable deprecation warnings for Blank and Resource +LV2_DISABLE_DEPRECATION_WARNINGS + +/** Handle for LV2_Atom_Forge_Sink. */ +typedef void* LV2_Atom_Forge_Sink_Handle; + +/** A reference to a chunk of written output. */ +typedef intptr_t LV2_Atom_Forge_Ref; + +/** Sink function for writing output. See lv2_atom_forge_set_sink(). */ +typedef LV2_Atom_Forge_Ref +(*LV2_Atom_Forge_Sink)(LV2_Atom_Forge_Sink_Handle handle, + const void* buf, + uint32_t size); + +/** Function for resolving a reference. See lv2_atom_forge_set_sink(). */ +typedef LV2_Atom* +(*LV2_Atom_Forge_Deref_Func)(LV2_Atom_Forge_Sink_Handle handle, + LV2_Atom_Forge_Ref ref); + +/** A stack frame used for keeping track of nested Atom containers. */ +typedef struct _LV2_Atom_Forge_Frame { + struct _LV2_Atom_Forge_Frame* parent; + LV2_Atom_Forge_Ref ref; +} LV2_Atom_Forge_Frame; + +/** A "forge" for creating atoms by appending to a buffer. */ +typedef struct { + uint8_t* buf; + uint32_t offset; + uint32_t size; + + LV2_Atom_Forge_Sink sink; + LV2_Atom_Forge_Deref_Func deref; + LV2_Atom_Forge_Sink_Handle handle; + + LV2_Atom_Forge_Frame* stack; + + LV2_URID Blank LV2_DEPRECATED; + LV2_URID Bool; + LV2_URID Chunk; + LV2_URID Double; + LV2_URID Float; + LV2_URID Int; + LV2_URID Long; + LV2_URID Literal; + LV2_URID Object; + LV2_URID Path; + LV2_URID Property; + LV2_URID Resource LV2_DEPRECATED; + LV2_URID Sequence; + LV2_URID String; + LV2_URID Tuple; + LV2_URID URI; + LV2_URID URID; + LV2_URID Vector; +} LV2_Atom_Forge; + +static inline void +lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size); + +/** + Initialise `forge`. + + URIs will be mapped using `map` and stored, a reference to `map` itself is + not held. +*/ +static inline void +lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map) +{ + lv2_atom_forge_set_buffer(forge, NULL, 0); + forge->Blank = map->map(map->handle, LV2_ATOM__Blank); + forge->Bool = map->map(map->handle, LV2_ATOM__Bool); + forge->Chunk = map->map(map->handle, LV2_ATOM__Chunk); + forge->Double = map->map(map->handle, LV2_ATOM__Double); + forge->Float = map->map(map->handle, LV2_ATOM__Float); + forge->Int = map->map(map->handle, LV2_ATOM__Int); + forge->Long = map->map(map->handle, LV2_ATOM__Long); + forge->Literal = map->map(map->handle, LV2_ATOM__Literal); + forge->Object = map->map(map->handle, LV2_ATOM__Object); + forge->Path = map->map(map->handle, LV2_ATOM__Path); + forge->Property = map->map(map->handle, LV2_ATOM__Property); + forge->Resource = map->map(map->handle, LV2_ATOM__Resource); + forge->Sequence = map->map(map->handle, LV2_ATOM__Sequence); + forge->String = map->map(map->handle, LV2_ATOM__String); + forge->Tuple = map->map(map->handle, LV2_ATOM__Tuple); + forge->URI = map->map(map->handle, LV2_ATOM__URI); + forge->URID = map->map(map->handle, LV2_ATOM__URID); + forge->Vector = map->map(map->handle, LV2_ATOM__Vector); +} + +/** Access the Atom pointed to by a reference. */ +static inline LV2_Atom* +lv2_atom_forge_deref(LV2_Atom_Forge* forge, LV2_Atom_Forge_Ref ref) +{ + return forge->buf ? (LV2_Atom*)ref : forge->deref(forge->handle, ref); +} + +/** + @name Object Stack + @{ +*/ + +/** + Push a stack frame. + This is done automatically by container functions (which take a stack frame + pointer), but may be called by the user to push the top level container when + writing to an existing Atom. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_push(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + LV2_Atom_Forge_Ref ref) +{ + frame->parent = forge->stack; + frame->ref = ref; + + if (ref) { + forge->stack = frame; // Don't push, so walking the stack is always safe + } + + return ref; +} + +/** Pop a stack frame. This must be called when a container is finished. */ +static inline void +lv2_atom_forge_pop(LV2_Atom_Forge* forge, LV2_Atom_Forge_Frame* frame) +{ + if (frame->ref) { + // If frame has a valid ref, it must be the top of the stack + assert(frame == forge->stack); + forge->stack = frame->parent; + } + // Otherwise, frame was not pushed because of overflow, do nothing +} + +/** Return true iff the top of the stack has the given type. */ +static inline bool +lv2_atom_forge_top_is(LV2_Atom_Forge* forge, uint32_t type) +{ + return forge->stack && forge->stack->ref && + (lv2_atom_forge_deref(forge, forge->stack->ref)->type == type); +} + +/** Return true iff `type` is an atom:Object. */ +static inline bool +lv2_atom_forge_is_object_type(const LV2_Atom_Forge* forge, uint32_t type) +{ + return (type == forge->Object || + type == forge->Blank || + type == forge->Resource); +} + +/** Return true iff `type` is an atom:Object with a blank ID. */ +static inline bool +lv2_atom_forge_is_blank(const LV2_Atom_Forge* forge, + uint32_t type, + const LV2_Atom_Object_Body* body) +{ + return (type == forge->Blank || + (type == forge->Object && body->id == 0)); +} + +/** + @} + @name Output Configuration + @{ +*/ + +/** Set the output buffer where `forge` will write atoms. */ +static inline void +lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size) +{ + forge->buf = buf; + forge->size = (uint32_t)size; + forge->offset = 0; + forge->deref = NULL; + forge->sink = NULL; + forge->handle = NULL; + forge->stack = NULL; +} + +/** + Set the sink function where `forge` will write output. + + The return value of forge functions is an LV2_Atom_Forge_Ref which is an + integer type safe to use as a pointer but is otherwise opaque. The sink + function must return a ref that can be dereferenced to access as least + sizeof(LV2_Atom) bytes of the written data, so sizes can be updated. For + ringbuffers, this should be possible as long as the size of the buffer is a + multiple of sizeof(LV2_Atom), since atoms are always aligned. + + Note that 0 is an invalid reference, so if you are using a buffer offset be + sure to offset it such that 0 is never a valid reference. You will get + confusing errors otherwise. +*/ +static inline void +lv2_atom_forge_set_sink(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Sink sink, + LV2_Atom_Forge_Deref_Func deref, + LV2_Atom_Forge_Sink_Handle handle) +{ + forge->buf = NULL; + forge->size = forge->offset = 0; + forge->deref = deref; + forge->sink = sink; + forge->handle = handle; + forge->stack = NULL; +} + +/** + @} + @name Low Level Output + @{ +*/ + +/** + Write raw output. This is used internally, but is also useful for writing + atom types not explicitly supported by the forge API. Note the caller is + responsible for ensuring the output is approriately padded. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_raw(LV2_Atom_Forge* forge, const void* data, uint32_t size) +{ + LV2_Atom_Forge_Ref out = 0; + if (forge->sink) { + out = forge->sink(forge->handle, data, size); + } else { + out = (LV2_Atom_Forge_Ref)forge->buf + forge->offset; + uint8_t* mem = forge->buf + forge->offset; + if (forge->offset + size > forge->size) { + return 0; + } + forge->offset += size; + memcpy(mem, data, size); + } + for (LV2_Atom_Forge_Frame* f = forge->stack; f; f = f->parent) { + lv2_atom_forge_deref(forge, f->ref)->size += size; + } + return out; +} + +/** Pad output accordingly so next write is 64-bit aligned. */ +static inline void +lv2_atom_forge_pad(LV2_Atom_Forge* forge, uint32_t written) +{ + const uint64_t pad = 0; + const uint32_t pad_size = lv2_atom_pad_size(written) - written; + lv2_atom_forge_raw(forge, &pad, pad_size); +} + +/** Write raw output, padding to 64-bits as necessary. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_write(LV2_Atom_Forge* forge, const void* data, uint32_t size) +{ + LV2_Atom_Forge_Ref out = lv2_atom_forge_raw(forge, data, size); + if (out) { + lv2_atom_forge_pad(forge, size); + } + return out; +} + +/** Write a null-terminated string body. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_string_body(LV2_Atom_Forge* forge, + const char* str, + uint32_t len) +{ + LV2_Atom_Forge_Ref out = lv2_atom_forge_raw(forge, str, len); + if (out && (out = lv2_atom_forge_raw(forge, "", 1))) { + lv2_atom_forge_pad(forge, len + 1); + } + return out; +} + +/** + @} + @name Atom Output + @{ +*/ + +/** Write an atom:Atom header. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_atom(LV2_Atom_Forge* forge, uint32_t size, uint32_t type) +{ + const LV2_Atom a = { size, type }; + return lv2_atom_forge_raw(forge, &a, sizeof(a)); +} + +/** Write a primitive (fixed-size) atom. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_primitive(LV2_Atom_Forge* forge, const LV2_Atom* a) +{ + return (lv2_atom_forge_top_is(forge, forge->Vector) + ? lv2_atom_forge_raw(forge, LV2_ATOM_BODY_CONST(a), a->size) + : lv2_atom_forge_write( + forge, a, (uint32_t)sizeof(LV2_Atom) + a->size)); +} + +/** Write an atom:Int. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_int(LV2_Atom_Forge* forge, int32_t val) +{ + const LV2_Atom_Int a = { { sizeof(val), forge->Int }, val }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom:Long. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_long(LV2_Atom_Forge* forge, int64_t val) +{ + const LV2_Atom_Long a = { { sizeof(val), forge->Long }, val }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom:Float. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_float(LV2_Atom_Forge* forge, float val) +{ + const LV2_Atom_Float a = { { sizeof(val), forge->Float }, val }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom:Double. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_double(LV2_Atom_Forge* forge, double val) +{ + const LV2_Atom_Double a = { { sizeof(val), forge->Double }, val }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom:Bool. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_bool(LV2_Atom_Forge* forge, bool val) +{ + const LV2_Atom_Bool a = { { sizeof(int32_t), forge->Bool }, val ? 1 : 0 }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom:URID. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_urid(LV2_Atom_Forge* forge, LV2_URID id) +{ + const LV2_Atom_URID a = { { sizeof(id), forge->URID }, id }; + return lv2_atom_forge_primitive(forge, &a.atom); +} + +/** Write an atom compatible with atom:String. Used internally. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_typed_string(LV2_Atom_Forge* forge, + uint32_t type, + const char* str, + uint32_t len) +{ + const LV2_Atom_String a = { { len + 1, type } }; + LV2_Atom_Forge_Ref out = lv2_atom_forge_raw(forge, &a, sizeof(a)); + if (out) { + if (!lv2_atom_forge_string_body(forge, str, len)) { + LV2_Atom* atom = lv2_atom_forge_deref(forge, out); + atom->size = atom->type = 0; + out = 0; + } + } + return out; +} + +/** Write an atom:String. Note that `str` need not be NULL terminated. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_string(LV2_Atom_Forge* forge, const char* str, uint32_t len) +{ + return lv2_atom_forge_typed_string(forge, forge->String, str, len); +} + +/** + Write an atom:URI. Note that `uri` need not be NULL terminated. + This does not map the URI, but writes the complete URI string. To write + a mapped URI, use lv2_atom_forge_urid(). +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_uri(LV2_Atom_Forge* forge, const char* uri, uint32_t len) +{ + return lv2_atom_forge_typed_string(forge, forge->URI, uri, len); +} + +/** Write an atom:Path. Note that `path` need not be NULL terminated. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_path(LV2_Atom_Forge* forge, const char* path, uint32_t len) +{ + return lv2_atom_forge_typed_string(forge, forge->Path, path, len); +} + +/** Write an atom:Literal. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_literal(LV2_Atom_Forge* forge, + const char* str, + uint32_t len, + uint32_t datatype, + uint32_t lang) +{ + const LV2_Atom_Literal a = { + { (uint32_t)(sizeof(LV2_Atom_Literal) - sizeof(LV2_Atom) + len + 1), + forge->Literal }, + { datatype, + lang } + }; + LV2_Atom_Forge_Ref out = lv2_atom_forge_raw(forge, &a, sizeof(a)); + if (out) { + if (!lv2_atom_forge_string_body(forge, str, len)) { + LV2_Atom* atom = lv2_atom_forge_deref(forge, out); + atom->size = atom->type = 0; + out = 0; + } + } + return out; +} + +/** Start an atom:Vector. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_vector_head(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + uint32_t child_size, + uint32_t child_type) +{ + const LV2_Atom_Vector a = { + { sizeof(LV2_Atom_Vector_Body), forge->Vector }, + { child_size, child_type } + }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** Write a complete atom:Vector. */ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_vector(LV2_Atom_Forge* forge, + uint32_t child_size, + uint32_t child_type, + uint32_t n_elems, + const void* elems) +{ + const LV2_Atom_Vector a = { + { (uint32_t)(sizeof(LV2_Atom_Vector_Body) + n_elems * child_size), + forge->Vector }, + { child_size, child_type } + }; + LV2_Atom_Forge_Ref out = lv2_atom_forge_write(forge, &a, sizeof(a)); + if (out) { + lv2_atom_forge_write(forge, elems, child_size * n_elems); + } + return out; +} + +/** + Write the header of an atom:Tuple. + + The passed frame will be initialised to represent this tuple. To complete + the tuple, write a sequence of atoms, then pop the frame with + lv2_atom_forge_pop(). + + For example: + @code + // Write tuple (1, 2.0) + LV2_Atom_Forge_Frame frame; + LV2_Atom* tup = (LV2_Atom*)lv2_atom_forge_tuple(forge, &frame); + lv2_atom_forge_int32(forge, 1); + lv2_atom_forge_float(forge, 2.0); + lv2_atom_forge_pop(forge, &frame); + @endcode +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_tuple(LV2_Atom_Forge* forge, LV2_Atom_Forge_Frame* frame) +{ + const LV2_Atom_Tuple a = { { 0, forge->Tuple } }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** + Write the header of an atom:Object. + + The passed frame will be initialised to represent this object. To complete + the object, write a sequence of properties, then pop the frame with + lv2_atom_forge_pop(). + + For example: + @code + LV2_URID eg_Cat = map("http://example.org/Cat"); + LV2_URID eg_name = map("http://example.org/name"); + + // Start object with type eg_Cat and blank ID + LV2_Atom_Forge_Frame frame; + lv2_atom_forge_object(forge, &frame, 0, eg_Cat); + + // Append property eg:name = "Hobbes" + lv2_atom_forge_key(forge, eg_name); + lv2_atom_forge_string(forge, "Hobbes", strlen("Hobbes")); + + // Finish object + lv2_atom_forge_pop(forge, &frame); + @endcode +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_object(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + LV2_URID id, + LV2_URID otype) +{ + const LV2_Atom_Object a = { + { (uint32_t)sizeof(LV2_Atom_Object_Body), forge->Object }, + { id, otype } + }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** + The same as lv2_atom_forge_object(), but for object:Resource. + + This function is deprecated and should not be used in new code. + Use lv2_atom_forge_object() directly instead. +*/ +LV2_DEPRECATED +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_resource(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + LV2_URID id, + LV2_URID otype) +{ + const LV2_Atom_Object a = { + { (uint32_t)sizeof(LV2_Atom_Object_Body), forge->Resource }, + { id, otype } + }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** + The same as lv2_atom_forge_object(), but for object:Blank. + + This function is deprecated and should not be used in new code. + Use lv2_atom_forge_object() directly instead. +*/ +LV2_DEPRECATED +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_blank(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + uint32_t id, + LV2_URID otype) +{ + const LV2_Atom_Object a = { + { (uint32_t)sizeof(LV2_Atom_Object_Body), forge->Blank }, + { id, otype } + }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** + Write a property key in an Object, to be followed by the value. + + See lv2_atom_forge_object() documentation for an example. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_key(LV2_Atom_Forge* forge, + LV2_URID key) +{ + const LV2_Atom_Property_Body a = { key, 0, { 0, 0 } }; + return lv2_atom_forge_write(forge, &a, 2 * (uint32_t)sizeof(uint32_t)); +} + +/** + Write the header for a property body in an object, with context. + + If you do not need the context, which is almost certainly the case, + use the simpler lv2_atom_forge_key() instead. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_property_head(LV2_Atom_Forge* forge, + LV2_URID key, + LV2_URID context) +{ + const LV2_Atom_Property_Body a = { key, context, { 0, 0 } }; + return lv2_atom_forge_write(forge, &a, 2 * (uint32_t)sizeof(uint32_t)); +} + +/** + Write the header for a Sequence. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_sequence_head(LV2_Atom_Forge* forge, + LV2_Atom_Forge_Frame* frame, + uint32_t unit) +{ + const LV2_Atom_Sequence a = { + { (uint32_t)sizeof(LV2_Atom_Sequence_Body), forge->Sequence }, + { unit, 0 } + }; + return lv2_atom_forge_push( + forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); +} + +/** + Write the time stamp header of an Event (in a Sequence) in audio frames. + After this, call the appropriate forge method(s) to write the body. Note + the returned reference is to an LV2_Event which is NOT an Atom. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_frame_time(LV2_Atom_Forge* forge, int64_t frames) +{ + return lv2_atom_forge_write(forge, &frames, sizeof(frames)); +} + +/** + Write the time stamp header of an Event (in a Sequence) in beats. After + this, call the appropriate forge method(s) to write the body. Note the + returned reference is to an LV2_Event which is NOT an Atom. +*/ +static inline LV2_Atom_Forge_Ref +lv2_atom_forge_beat_time(LV2_Atom_Forge* forge, double beats) +{ + return lv2_atom_forge_write(forge, &beats, sizeof(beats)); +} + +/** + @} + @} +*/ + +LV2_RESTORE_WARNINGS + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV2_ATOM_FORGE_H */ diff --git a/lv2/atom/lv2-atom.doap.ttl b/lv2/atom/lv2-atom.doap.ttl new file mode 100644 index 0000000..1613c64 --- /dev/null +++ b/lv2/atom/lv2-atom.doap.ttl @@ -0,0 +1,103 @@ +@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#> . + +<http://lv2plug.in/ns/ext/atom> + a doap:Project ; + doap:name "LV2 Atom" ; + doap:shortdesc "A generic value container and several data types." ; + doap:license <http://opensource.org/licenses/isc> ; + doap:created "2007-00-00" ; + doap:developer <http://drobilla.net/drobilla#me> ; + doap:release [ + doap:revision "2.2" ; + doap:created "2019-02-03" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.16.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Add lv2_atom_object_get_typed() for easy type-safe access to object properties." + ] + ] + ] , [ + doap:revision "2.0" ; + doap:created "2014-08-08" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.10.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Deprecate Blank and Resource in favour of just Object." + ] , [ + rdfs:label "Add lv2_atom_forge_is_object_type() and lv2_atom_forge_is_blank() to ease backwards compatibility." + ] , [ + rdfs:label "Add lv2_atom_forge_key() for terser object writing." + ] , [ + rdfs:label "Add lv2_atom_sequence_clear() and lv2_atom_sequence_append_event() helper functions." + ] + ] + ] , [ + doap:revision "1.8" ; + doap:created "2014-01-04" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.8.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Make lv2_atom_*_is_end() arguments const." + ] + ] + ] , [ + doap:revision "1.6" ; + doap:created "2013-05-26" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.6.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Fix crash in forge.h when pushing atoms to a full buffer." + ] + ] + ] , [ + doap:revision "1.4" ; + doap:created "2013-01-27" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.4.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Fix lv2_atom_sequence_end()." + ] , [ + rdfs:label "Remove atom:stringType in favour of owl:onDatatype so generic tools can understand and validate atom literals." + ] , [ + rdfs:label "Improve atom documentation." + ] + ] + ] , [ + doap:revision "1.2" ; + doap:created "2012-10-14" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.2.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Fix implicit conversions in forge.h that are invalid in C++11." + ] , [ + rdfs:label "Fix lv2_atom_object_next() on 32-bit platforms." + ] , [ + rdfs:label "Add lv2_atom_object_body_get()." + ] , [ + rdfs:label "Fix outdated documentation in forge.h." + ] , [ + rdfs:label "Use consistent label style." + ] , [ + rdfs:label "Add LV2_ATOM_CONTENTS_CONST and LV2_ATOM_BODY_CONST." + ] + ] + ] , [ + doap:revision "1.0" ; + doap:created "2012-04-17" ; + doap:file-release <http://lv2plug.in/spec/lv2-1.0.0.tar.bz2> ; + dcs:blame <http://drobilla.net/drobilla#me> ; + dcs:changeset [ + dcs:item [ + rdfs:label "Initial release." + ] + ] + ] . diff --git a/lv2/atom/manifest.ttl b/lv2/atom/manifest.ttl new file mode 100644 index 0000000..ae6195b --- /dev/null +++ b/lv2/atom/manifest.ttl @@ -0,0 +1,8 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://lv2plug.in/ns/ext/atom> + a lv2:Specification ; + lv2:minorVersion 2 ; + lv2:microVersion 2 ; + rdfs:seeAlso <atom.ttl> . diff --git a/lv2/atom/util.h b/lv2/atom/util.h new file mode 100644 index 0000000..051a3cb --- /dev/null +++ b/lv2/atom/util.h @@ -0,0 +1,509 @@ +/* + Copyright 2008-2015 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file util.h Helper functions for the LV2 Atom extension. + + Note these functions are all static inline, do not take their address. + + This header is non-normative, it is provided for convenience. +*/ + +/** + @defgroup util Utilities + @ingroup atom + @{ +*/ + +#ifndef LV2_ATOM_UTIL_H +#define LV2_ATOM_UTIL_H + +#include "lv2/atom/atom.h" + +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** Pad a size to 64 bits. */ +static inline uint32_t +lv2_atom_pad_size(uint32_t size) +{ + return (size + 7U) & (~7U); +} + +/** Return the total size of `atom`, including the header. */ +static inline uint32_t +lv2_atom_total_size(const LV2_Atom* atom) +{ + return (uint32_t)sizeof(LV2_Atom) + atom->size; +} + +/** Return true iff `atom` is null. */ +static inline bool +lv2_atom_is_null(const LV2_Atom* atom) +{ + return !atom || (atom->type == 0 && atom->size == 0); +} + +/** Return true iff `a` is equal to `b`. */ +static inline bool +lv2_atom_equals(const LV2_Atom* a, const LV2_Atom* b) +{ + return (a == b) || ((a->type == b->type) && + (a->size == b->size) && + !memcmp(a + 1, b + 1, a->size)); +} + +/** + @name Sequence Iterator + @{ +*/ + +/** Get an iterator pointing to the first event in a Sequence body. */ +static inline LV2_Atom_Event* +lv2_atom_sequence_begin(const LV2_Atom_Sequence_Body* body) +{ + return (LV2_Atom_Event*)(body + 1); +} + +/** Get an iterator pointing to the end of a Sequence body. */ +static inline LV2_Atom_Event* +lv2_atom_sequence_end(const LV2_Atom_Sequence_Body* body, uint32_t size) +{ + return (LV2_Atom_Event*)((const uint8_t*)body + lv2_atom_pad_size(size)); +} + +/** Return true iff `i` has reached the end of `body`. */ +static inline bool +lv2_atom_sequence_is_end(const LV2_Atom_Sequence_Body* body, + uint32_t size, + const LV2_Atom_Event* i) +{ + return (const uint8_t*)i >= ((const uint8_t*)body + size); +} + +/** Return an iterator to the element following `i`. */ +static inline LV2_Atom_Event* +lv2_atom_sequence_next(const LV2_Atom_Event* i) +{ + return (LV2_Atom_Event*)((const uint8_t*)i + + sizeof(LV2_Atom_Event) + + lv2_atom_pad_size(i->body.size)); +} + +/** + A macro for iterating over all events in a Sequence. + @param seq The sequence to iterate over + @param iter The name of the iterator + + This macro is used similarly to a for loop (which it expands to), e.g.: + @code + LV2_ATOM_SEQUENCE_FOREACH(sequence, ev) { + // Do something with ev (an LV2_Atom_Event*) here... + } + @endcode +*/ +#define LV2_ATOM_SEQUENCE_FOREACH(seq, iter) \ + for (LV2_Atom_Event* (iter) = lv2_atom_sequence_begin(&(seq)->body); \ + !lv2_atom_sequence_is_end(&(seq)->body, (seq)->atom.size, (iter)); \ + (iter) = lv2_atom_sequence_next(iter)) + +/** Like LV2_ATOM_SEQUENCE_FOREACH but for a headerless sequence body. */ +#define LV2_ATOM_SEQUENCE_BODY_FOREACH(body, size, iter) \ + for (LV2_Atom_Event* (iter) = lv2_atom_sequence_begin(body); \ + !lv2_atom_sequence_is_end(body, size, (iter)); \ + (iter) = lv2_atom_sequence_next(iter)) + +/** + @} + @name Sequence Utilities + @{ +*/ + +/** + Clear all events from `sequence`. + + This simply resets the size field, the other fields are left untouched. +*/ +static inline void +lv2_atom_sequence_clear(LV2_Atom_Sequence* seq) +{ + seq->atom.size = sizeof(LV2_Atom_Sequence_Body); +} + +/** + Append an event at the end of `sequence`. + + @param seq Sequence to append to. + @param capacity Total capacity of the sequence atom + (e.g. as set by the host for sequence output ports). + @param event Event to write. + + @return A pointer to the newly written event in `seq`, + or NULL on failure (insufficient space). +*/ +static inline LV2_Atom_Event* +lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq, + uint32_t capacity, + const LV2_Atom_Event* event) +{ + const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size; + if (capacity - seq->atom.size < total_size) { + return NULL; + } + + LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size); + memcpy(e, event, total_size); + + seq->atom.size += lv2_atom_pad_size(total_size); + + return e; +} + +/** + @} + @name Tuple Iterator + @{ +*/ + +/** Get an iterator pointing to the first element in `tup`. */ +static inline LV2_Atom* +lv2_atom_tuple_begin(const LV2_Atom_Tuple* tup) +{ + return (LV2_Atom*)(LV2_ATOM_BODY(tup)); +} + +/** Return true iff `i` has reached the end of `body`. */ +static inline bool +lv2_atom_tuple_is_end(const void* body, uint32_t size, const LV2_Atom* i) +{ + return (const uint8_t*)i >= ((const uint8_t*)body + size); +} + +/** Return an iterator to the element following `i`. */ +static inline LV2_Atom* +lv2_atom_tuple_next(const LV2_Atom* i) +{ + return (LV2_Atom*)( + (const uint8_t*)i + sizeof(LV2_Atom) + lv2_atom_pad_size(i->size)); +} + +/** + A macro for iterating over all properties of a Tuple. + @param tuple The tuple to iterate over + @param iter The name of the iterator + + This macro is used similarly to a for loop (which it expands to), e.g.: + @code + LV2_ATOM_TUPLE_FOREACH(tuple, elem) { + // Do something with elem (an LV2_Atom*) here... + } + @endcode +*/ +#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)) + +/** Like LV2_ATOM_TUPLE_FOREACH but for a headerless tuple body. */ +#define LV2_ATOM_TUPLE_BODY_FOREACH(body, size, iter) \ + for (LV2_Atom* (iter) = (LV2_Atom*)(body); \ + !lv2_atom_tuple_is_end(body, size, (iter)); \ + (iter) = lv2_atom_tuple_next(iter)) + +/** + @} + @name Object Iterator + @{ +*/ + +/** Return a pointer to the first property in `body`. */ +static inline LV2_Atom_Property_Body* +lv2_atom_object_begin(const LV2_Atom_Object_Body* body) +{ + return (LV2_Atom_Property_Body*)(body + 1); +} + +/** Return true iff `i` has reached the end of `obj`. */ +static inline bool +lv2_atom_object_is_end(const LV2_Atom_Object_Body* body, + uint32_t size, + const LV2_Atom_Property_Body* i) +{ + return (const uint8_t*)i >= ((const uint8_t*)body + size); +} + +/** Return an iterator to the property following `i`. */ +static inline LV2_Atom_Property_Body* +lv2_atom_object_next(const LV2_Atom_Property_Body* i) +{ + const LV2_Atom* const value = (const LV2_Atom*)( + (const uint8_t*)i + 2 * sizeof(uint32_t)); + return (LV2_Atom_Property_Body*)( + (const uint8_t*)i + lv2_atom_pad_size( + (uint32_t)sizeof(LV2_Atom_Property_Body) + value->size)); +} + +/** + A macro for iterating over all properties of an Object. + @param obj The object to iterate over + @param iter The name of the iterator + + This macro is used similarly to a for loop (which it expands to), e.g.: + @code + LV2_ATOM_OBJECT_FOREACH(object, i) { + // Do something with i (an LV2_Atom_Property_Body*) here... + } + @endcode +*/ +#define LV2_ATOM_OBJECT_FOREACH(obj, iter) \ + for (LV2_Atom_Property_Body* (iter) = lv2_atom_object_begin(&(obj)->body); \ + !lv2_atom_object_is_end(&(obj)->body, (obj)->atom.size, (iter)); \ + (iter) = lv2_atom_object_next(iter)) + +/** Like LV2_ATOM_OBJECT_FOREACH but for a headerless object body. */ +#define LV2_ATOM_OBJECT_BODY_FOREACH(body, size, iter) \ + for (LV2_Atom_Property_Body* (iter) = lv2_atom_object_begin(body); \ + !lv2_atom_object_is_end(body, size, (iter)); \ + (iter) = lv2_atom_object_next(iter)) + +/** + @} + @name Object Query + @{ +*/ + +/** A single entry in an Object query. */ +typedef struct { + uint32_t key; /**< Key to query (input set by user) */ + const LV2_Atom** value; /**< Found value (output set by query function) */ +} LV2_Atom_Object_Query; + +/** Sentinel for lv2_atom_object_query(). */ +static const LV2_Atom_Object_Query LV2_ATOM_OBJECT_QUERY_END = { 0, NULL }; + +/** + Get an object's values for various keys. + + The value pointer of each item in `query` will be set to the location of + the corresponding value in `object`. Every value pointer in `query` MUST + be initialised to NULL. This function reads `object` in a single linear + sweep. By allocating `query` on the stack, objects can be "queried" + quickly without allocating any memory. This function is realtime safe. + + This function can only do "flat" queries, it is not smart enough to match + variables in nested objects. + + For example: + @code + const LV2_Atom* name = NULL; + const LV2_Atom* age = NULL; + LV2_Atom_Object_Query q[] = { + { urids.eg_name, &name }, + { urids.eg_age, &age }, + LV2_ATOM_OBJECT_QUERY_END + }; + lv2_atom_object_query(obj, q); + // name and age are now set to the appropriate values in obj, or NULL. + @endcode +*/ +static inline int +lv2_atom_object_query(const LV2_Atom_Object* object, + LV2_Atom_Object_Query* query) +{ + int matches = 0; + int n_queries = 0; + + /* Count number of query keys so we can short-circuit when done */ + for (LV2_Atom_Object_Query* q = query; q->key; ++q) { + ++n_queries; + } + + LV2_ATOM_OBJECT_FOREACH(object, prop) { + for (LV2_Atom_Object_Query* q = query; q->key; ++q) { + if (q->key == prop->key && !*q->value) { + *q->value = &prop->value; + if (++matches == n_queries) { + return matches; + } + break; + } + } + } + return matches; +} + +/** + Body only version of lv2_atom_object_get(). +*/ +static inline int +lv2_atom_object_body_get(uint32_t size, const LV2_Atom_Object_Body* body, ...) +{ + int matches = 0; + int n_queries = 0; + + /* Count number of keys so we can short-circuit when done */ + va_list args; + va_start(args, body); + for (n_queries = 0; va_arg(args, uint32_t); ++n_queries) { + if (!va_arg(args, const LV2_Atom**)) { + return -1; + } + } + va_end(args); + + LV2_ATOM_OBJECT_BODY_FOREACH(body, size, prop) { + va_start(args, body); + for (int i = 0; i < n_queries; ++i) { + uint32_t qkey = va_arg(args, uint32_t); + const LV2_Atom** qval = va_arg(args, const LV2_Atom**); + if (qkey == prop->key && !*qval) { + *qval = &prop->value; + if (++matches == n_queries) { + return matches; + } + break; + } + } + va_end(args); + } + return matches; +} + +/** + Variable argument version of lv2_atom_object_query(). + + This is nicer-looking in code, but a bit more error-prone since it is not + type safe and the argument list must be terminated. + + The arguments should be a series of uint32_t key and const LV2_Atom** value + pairs, terminated by a zero key. The value pointers MUST be initialized to + NULL. For example: + + @code + const LV2_Atom* name = NULL; + const LV2_Atom* age = NULL; + lv2_atom_object_get(obj, + uris.name_key, &name, + uris.age_key, &age, + 0); + @endcode +*/ +static inline int +lv2_atom_object_get(const LV2_Atom_Object* object, ...) +{ + int matches = 0; + int n_queries = 0; + + /* Count number of keys so we can short-circuit when done */ + va_list args; + va_start(args, object); + for (n_queries = 0; va_arg(args, uint32_t); ++n_queries) { + if (!va_arg(args, const LV2_Atom**)) { + return -1; + } + } + va_end(args); + + LV2_ATOM_OBJECT_FOREACH(object, prop) { + va_start(args, object); + for (int i = 0; i < n_queries; ++i) { + uint32_t qkey = va_arg(args, uint32_t); + const LV2_Atom** qval = va_arg(args, const LV2_Atom**); + if (qkey == prop->key && !*qval) { + *qval = &prop->value; + if (++matches == n_queries) { + return matches; + } + break; + } + } + va_end(args); + } + return matches; +} + +/** + Variable argument version of lv2_atom_object_query() with types. + + This is like lv2_atom_object_get(), but each entry has an additional + parameter to specify the required type. Only atoms with a matching type + will be selected. + + The arguments should be a series of uint32_t key, const LV2_Atom**, uint32_t + type triples, terminated by a zero key. The value pointers MUST be + initialized to NULL. For example: + + @code + const LV2_Atom_String* name = NULL; + const LV2_Atom_Int* age = NULL; + lv2_atom_object_get(obj, + uris.name_key, &name, uris.atom_String, + uris.age_key, &age, uris.atom_Int + 0); + @endcode +*/ +static inline int +lv2_atom_object_get_typed(const LV2_Atom_Object* object, ...) +{ + int matches = 0; + int n_queries = 0; + + /* Count number of keys so we can short-circuit when done */ + va_list args; + va_start(args, object); + for (n_queries = 0; va_arg(args, uint32_t); ++n_queries) { + if (!va_arg(args, const LV2_Atom**) || + !va_arg(args, uint32_t)) { + return -1; + } + } + va_end(args); + + LV2_ATOM_OBJECT_FOREACH(object, prop) { + va_start(args, object); + for (int i = 0; i < n_queries; ++i) { + const uint32_t qkey = va_arg(args, uint32_t); + const LV2_Atom** qval = va_arg(args, const LV2_Atom**); + const uint32_t qtype = va_arg(args, uint32_t); + if (!*qval && qkey == prop->key && qtype == prop->value.type) { + *qval = &prop->value; + if (++matches == n_queries) { + return matches; + } + break; + } + } + va_end(args); + } + return matches; +} + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV2_ATOM_UTIL_H */ |