diff options
-rw-r--r-- | Doxyfile | 5 | ||||
-rw-r--r-- | ext.wscript | 59 | ||||
-rw-r--r-- | lv2/ns/ext/atom/atom-buffer.h | 163 | ||||
-rw-r--r-- | lv2/ns/ext/atom/atom-helpers.h | 306 | ||||
-rw-r--r-- | lv2/ns/ext/atom/atom.h | 279 | ||||
-rw-r--r-- | lv2/ns/ext/atom/atom.ttl | 455 | ||||
-rw-r--r-- | lv2/ns/ext/atom/forge.h | 424 | ||||
-rw-r--r-- | lv2/ns/ext/atom/manifest.ttl | 2 | ||||
-rw-r--r-- | lv2/ns/ext/state/state.ttl | 13 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 112 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler.ttl | 9 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler_ui.c | 32 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/uris.h | 10 | ||||
-rw-r--r-- | wscript | 2 |
14 files changed, 1128 insertions, 743 deletions
@@ -566,7 +566,6 @@ WARN_LOGFILE = INPUT = \ ../doc/mainpage.dox \ - ns/ext/atom/atom-buffer.h \ ns/ext/atom/atom-helpers.h \ ns/ext/atom/atom.h \ ns/ext/atom/forge.h \ @@ -722,13 +721,13 @@ STRIP_CODE_COMMENTS = YES # then for each documented function all documented # functions referencing it will be listed. -REFERENCED_BY_RELATION = YES +REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. -REFERENCES_RELATION = YES +REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from diff --git a/ext.wscript b/ext.wscript index b7af3f5..dd46a5f 100644 --- a/ext.wscript +++ b/ext.wscript @@ -4,6 +4,7 @@ import os import shutil import sys from waflib.extras import autowaf as autowaf +from waflib.TaskGen import feature, before import waflib.Scripting as Scripting import waflib.Logs as Logs import waflib.Options as Options @@ -12,6 +13,19 @@ import waflib.Utils as Utils info = None +# A rule for making a link in the build directory to a source file +def link(task): + func = os.symlink + if not func: + func = shutil.copy # Symlinks unavailable, make a copy + + try: + os.remove(task.outputs[0].abspath()) # Remove old target + except: + pass # No old target, whatever + + func(task.inputs[0].abspath(), task.outputs[0].abspath()) + try: # Read version information from lv2extinfo.py (in a release tarball) import lv2extinfo @@ -58,7 +72,10 @@ top = '.' out = 'build' def options(opt): + opt.load('compiler_c') autowaf.set_options(opt) + opt.add_option('--test', action='store_true', default=False, dest='build_tests', + help="Build unit tests") opt.add_option('--copy-headers', action='store_true', default=False, dest='copy_headers', help='Copy headers instead of linking to bundle') @@ -72,7 +89,15 @@ def should_build(ctx): info.MINOR > 0 and info.MICRO % 2 == 0) def configure(conf): + try: + conf.load('compiler_c') + except: + Options.options.build_tests = False + + conf.env['BUILD_TESTS'] = Options.options.build_tests + conf.env['COPY_HEADERS'] = Options.options.copy_headers conf.env['EXPERIMENTAL'] = Options.options.experimental + if not should_build(conf): return @@ -80,9 +105,12 @@ def configure(conf): conf.fatal( 'os.path.relpath missing, get Python 2.6 or use --copy-headers') + # Check for gcov library (for test coverage) + if conf.env['BUILD_TESTS']: + conf.check_cc(lib='gcov', define_name='HAVE_GCOV', mandatory=False) + autowaf.configure(conf) autowaf.display_header('LV2 %s Configuration' % info.NAME) - conf.env['COPY_HEADERS'] = Options.options.copy_headers autowaf.display_msg(conf, 'LV2 bundle directory', conf.env['LV2DIR']) autowaf.display_msg(conf, 'URI', info.URI) autowaf.display_msg(conf, 'Version', VERSION) @@ -108,6 +136,29 @@ def build(bld): NAME = info.NAME, VERSION = VERSION, DESCRIPTION = info.SHORTDESC) + + if bld.env['BUILD_TESTS'] and bld.path.find_node('%s-test.c' % info.NAME): + test_lib = [] + test_cflags = [''] + if bld.is_defined('HAVE_GCOV'): + test_lib += ['gcov'] + test_cflags += ['-fprofile-arcs', '-ftest-coverage'] + + # Copy headers to URI-style include paths in build directory + for i in bld.path.ant_glob('*.h'): + obj = bld(rule = link, + name = 'link', + cwd = 'build/lv2/%s/%s' % (include_base, info.NAME), + source = '%s' % i, + target = 'lv2/%s/%s/%s' % (include_base, info.NAME, i)) + + # Unit test program + obj = bld(features = 'c cprogram', + source = '%s-test.c' % info.NAME, + lib = test_lib, + target = '%s-test' % info.NAME, + install_path = '', + cflags = test_cflags) # Install bundle bld.install_files(bundle_dir, @@ -121,6 +172,12 @@ def build(bld): bld.symlink_as(os.path.join(include_dir, info.NAME), os.path.relpath(bundle_dir, include_dir)) +def test(ctx): + autowaf.pre_test(ctx, APPNAME, dirs=['.']) + os.environ['PATH'] = '.' + os.pathsep + os.getenv('PATH') + autowaf.run_tests(ctx, APPNAME, ['%s-test' % info.NAME], dirs=['.']) + autowaf.post_test(ctx, APPNAME, dirs=['.']) + def write_news(): import rdflib import textwrap diff --git a/lv2/ns/ext/atom/atom-buffer.h b/lv2/ns/ext/atom/atom-buffer.h deleted file mode 100644 index f4b90dd..0000000 --- a/lv2/ns/ext/atom/atom-buffer.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - Copyright 2008-2011 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 atom-event-buffer.h Helper functions for atom:EventBuffer. - - Note that these functions are all static inline which basically means: - do not take the address of these functions. -*/ - -#ifndef LV2_ATOM_EVENT_BUFFER_H -#define LV2_ATOM_EVENT_BUFFER_H - -#include <stdint.h> -#include <stdbool.h> -#include <string.h> -#include <stdlib.h> - -#include "lv2/lv2plug.in/ns/ext/atom/atom.h" - -/** - Initialize an existing atom buffer. - All fields of @c buf are reset, except capacity which is unmodified. -*/ -static inline void -lv2_atom_buffer_reset(LV2_Atom_Buffer* buf) -{ - buf->event_count = 0; - buf->size = 0; -} - -/** - Allocate a new, empty atom buffer. -*/ -static inline LV2_Atom_Buffer* -lv2_atom_buffer_new(uint32_t capacity) -{ - const uint32_t size = sizeof(LV2_Atom_Buffer) + capacity; - LV2_Atom_Buffer* buf = (LV2_Atom_Buffer*)malloc(size); - if (buf) { - buf->data = (uint8_t*)(buf + 1); - buf->capacity = capacity; - lv2_atom_buffer_reset(buf); - } - return buf; -} - -/** - Free an atom buffer allocated with lv2_atom_buffer_new(). -*/ -static inline void -lv2_atom_buffer_free(LV2_Atom_Buffer* buf) -{ - free(buf); -} - -/** - An iterator over an LV2_Atom_Buffer. -*/ -typedef struct { - LV2_Atom_Buffer* buf; - uint32_t offset; -} LV2_Atom_Buffer_Iterator; - -/** - Return an iterator to the beginning of @c buf. -*/ -static inline LV2_Atom_Buffer_Iterator -lv2_atom_buffer_begin(LV2_Atom_Buffer* buf) -{ - const LV2_Atom_Buffer_Iterator i = { buf, 0 }; - return i; -} - -/** - Return true iff @c i points to a valid atom. -*/ -static inline bool -lv2_atom_buffer_is_valid(LV2_Atom_Buffer_Iterator i) -{ - return i.offset < i.buf->size; -} - -/** - Return the iterator to the next element after @c i. - @param i A valid iterator to an atom in a buffer. -*/ -static inline LV2_Atom_Buffer_Iterator -lv2_atom_buffer_next(LV2_Atom_Buffer_Iterator i) -{ - if (!lv2_atom_buffer_is_valid(i)) { - return i; - } - const LV2_Atom_Event* const ev = (LV2_Atom_Event*)(i.buf->data + i.offset); - i.offset += lv2_atom_pad_size(sizeof(LV2_Atom_Event) + ev->body.size); - return i; -} - -/** - Return a pointer to the atom currently pointed to by @c i. -*/ -static inline LV2_Atom_Event* -lv2_atom_buffer_get(LV2_Atom_Buffer_Iterator i) -{ - if (!lv2_atom_buffer_is_valid(i)) { - return NULL; - } - return (LV2_Atom_Event*)(i.buf->data + i.offset); -} - -/** - Write an atom to a buffer. - - The atom will be written at the location pointed to by @c i, which will be - incremented to point to the location where the next atom should be written - (which is likely now garbage). Thus, this function can be called repeatedly - with a single @c i to write a sequence of atoms to the buffer. - - @return True if atom was written, otherwise false (buffer is full). -*/ -static inline bool -lv2_atom_buffer_write(LV2_Atom_Buffer_Iterator* i, - uint32_t frames, - uint32_t subframes, - uint32_t type, - uint32_t size, - const uint8_t* data) -{ - const uint32_t free_space = i->buf->capacity - i->buf->size; - if (free_space < sizeof(LV2_Atom_Event) + size) { - return false; - } - - LV2_Atom_Event* const ev = (LV2_Atom_Event*)(i->buf->data + i->offset); - - ev->frames = frames; - ev->subframes = subframes; - ev->body.type = type; - ev->body.size = size; - memcpy((uint8_t*)ev + sizeof(LV2_Atom_Event), data, size); - ++i->buf->event_count; - - size = lv2_atom_pad_size(sizeof(LV2_Atom_Event) + size); - i->buf->size += size; - i->offset += size; - - return true; -} - -#endif /* LV2_ATOM_EVENT_BUFFER_H */ diff --git a/lv2/ns/ext/atom/atom-helpers.h b/lv2/ns/ext/atom/atom-helpers.h index 4e51c89..6fff74b 100644 --- a/lv2/ns/ext/atom/atom-helpers.h +++ b/lv2/ns/ext/atom/atom-helpers.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2012 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,153 +17,285 @@ /** @file atom-helpers.h Helper functions for the LV2 Atom extension. - These functions are provided for convenience only, use of them is not - required for supporting atoms. + Note these functions are all static inline, do not take their address. - Note that these functions are all static inline which basically means: - do not take the address of these functions. + This header is non-normative, it is provided for convenience. */ #ifndef LV2_ATOM_HELPERS_H #define LV2_ATOM_HELPERS_H -#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include "lv2/lv2plug.in/ns/ext/atom/atom.h" -typedef LV2_Atom_Property* LV2_Thing_Iter; +#ifdef __cplusplus +extern "C" { +#else +# include <stdbool.h> +#endif -/** Get an iterator pointing to @c prop in some LV2_Thing */ -static inline LV2_Thing_Iter -lv2_thing_begin(const LV2_Thing* obj) +/** Pad a size to 64 bits. */ +static inline uint32_t +lv2_atom_pad_size(uint32_t size) { - return (LV2_Thing_Iter)(obj->properties); + return (size + 7) & (~7); } -/** Return true iff @c iter has reached the end of @c thing */ +/** Return true iff @p atom is null. */ static inline bool -lv2_thing_iter_is_end(const LV2_Thing* obj, LV2_Thing_Iter iter) +lv2_atom_is_null(const LV2_Atom* atom) { - return (uint8_t*)iter >= ((uint8_t*)obj + sizeof(LV2_Atom) + obj->size); + return !atom || (atom->type == 0 && atom->size == 0); } -/** Return true iff @c l points to the same property as @c r */ +/** Return true iff @p a is equal to @p b. */ static inline bool -lv2_thing_iter_equals(const LV2_Thing_Iter l, const LV2_Thing_Iter r) +lv2_atom_equals(const LV2_Atom* a, const LV2_Atom* b) { - return l == r; + return (a == b) || ( + (a->type == b->type) && + (a->size == b->size) && + !memcmp(LV2_ATOM_CONTENTS(LV2_Atom, a), + LV2_ATOM_CONTENTS(LV2_Atom, b), + a->size)); +} + +/** + @name Sequence Iterator + @{ +*/ + +/** An iterator over the elements of an LV2_Atom_Sequence. */ +typedef LV2_Atom_Event* LV2_Atom_Sequence_Iter; + +/** Get an iterator pointing to the first element in @p tup. */ +static inline LV2_Atom_Sequence_Iter +lv2_sequence_begin(const LV2_Atom_Sequence* seq) +{ + return (LV2_Atom_Sequence_Iter)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, seq)); } -/** Return an iterator to the property following @c iter */ -static inline LV2_Thing_Iter -lv2_thing_iter_next(const LV2_Thing_Iter iter) +/** Return true iff @p i has reached the end of @p tup. */ +static inline bool +lv2_sequence_is_end(const LV2_Atom_Sequence* seq, LV2_Atom_Sequence_Iter i) { - return (LV2_Thing_Iter)((uint8_t*)iter - + sizeof(LV2_Atom_Property) - + lv2_atom_pad_size(iter->value.size)); + return (uint8_t*)i >= ((uint8_t*)seq + sizeof(LV2_Atom) + seq->atom.size); } -/** Return the property pointed to by @c iter */ -static inline LV2_Atom_Property* -lv2_thing_iter_get(LV2_Thing_Iter iter) +/** Return an iterator to the element following @p i. */ +static inline LV2_Atom_Sequence_Iter +lv2_sequence_iter_next(const LV2_Atom_Sequence_Iter i) { - return (LV2_Atom_Property*)iter; + return (LV2_Atom_Sequence_Iter)((uint8_t*)i + + sizeof(LV2_Atom_Event) + + lv2_atom_pad_size(i->body.size)); +} + +/** Return the element pointed to by @p i. */ +static inline LV2_Atom_Event* +lv2_sequence_iter_get(LV2_Atom_Sequence_Iter i) +{ + return (LV2_Atom_Event*)i; } /** - A macro for iterating over all properties of an Thing. - @param thing The thing to iterate over + A macro for iterating over all events in a Sequence. + @param sequence 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.: - <pre> - LV2_THING_FOREACH(thing, i) { - LV2_Atom_Property* prop = lv2_thing_iter_get(i); - // Do things with prop here... + @code + LV2_SEQUENCE_FOREACH(sequence, i) { + LV2_Atom_Event* ev = lv2_sequence_iter_get(i); + // Do something with ev here... } - </pre> + @endcode */ -#define LV2_THING_FOREACH(thing, iter) \ - for (LV2_Thing_Iter (iter) = lv2_thing_begin(thing); \ - !lv2_thing_iter_is_end(thing, (iter)); \ - (iter) = lv2_thing_iter_next(iter)) +#define LV2_SEQUENCE_FOREACH(sequence, iter) \ + for (LV2_Atom_Sequence_Iter (iter) = lv2_sequence_begin(sequence); \ + !lv2_sequence_is_end(sequence, (iter)); \ + (iter) = lv2_sequence_iter_next(iter)) /** - Append a Property body to an Atom that contains properties (e.g. atom:Thing). - @param thing Pointer to the atom that contains the property to add. thing.size - must be valid, but thing.type is ignored. - @param key The key of the new property - @param value_type The type of the new value - @param value_size The size of the new value - @param value_body Pointer to the new value's data - @return a pointer to the new LV2_Atom_Property in @c body. - - This function will write the property body (not including an LV2_Thing - header) at lv2_atom_pad_size(body + size). Thus, it can be used with any - Atom type that contains headerless 32-bit aligned properties. + @} + @name Tuple Iterator + @{ */ -static inline LV2_Atom_Property* -lv2_thing_append(LV2_Thing* thing, - uint32_t key, - uint32_t value_type, - uint32_t value_size, - const void* value_body) -{ - thing->size = lv2_atom_pad_size(thing->size); - LV2_Atom_Property* prop = (LV2_Atom_Property*)( - (uint8_t*)thing + sizeof(LV2_Atom) + thing->size); - prop->key = key; - prop->value.type = value_type; - prop->value.size = value_size; - memcpy(prop->value.body, value_body, value_size); - thing->size += sizeof(LV2_Atom_Property) + value_size; - return prop; -} - -/** Return true iff @c atom is NULL */ + +/** An iterator over the elements of an LV2_Atom_Tuple. */ +typedef LV2_Atom* LV2_Atom_Tuple_Iter; + +/** Get an iterator pointing to the first element in @p tup. */ +static inline LV2_Atom_Tuple_Iter +lv2_tuple_begin(const LV2_Atom_Tuple* tup) +{ + return (LV2_Atom_Tuple_Iter)(LV2_ATOM_BODY(tup)); +} + +/** Return true iff @p i has reached the end of @p tup. */ static inline bool -lv2_atom_is_null(LV2_Atom* atom) +lv2_tuple_is_end(const LV2_Atom_Tuple* tup, LV2_Atom_Tuple_Iter i) { - return !atom || (atom->type == 0 && atom->size == 0); + return (uint8_t*)i >= ((uint8_t*)tup + sizeof(LV2_Atom) + tup->atom.size); } -/** A single entry in an Thing query. */ +/** Return an iterator to the element following @p i. */ +static inline LV2_Atom_Tuple_Iter +lv2_tuple_iter_next(const LV2_Atom_Tuple_Iter i) +{ + return (LV2_Atom_Tuple_Iter)( + (uint8_t*)i + sizeof(LV2_Atom) + lv2_atom_pad_size(i->size)); +} + +/** Return the element pointed to by @p i. */ +static inline LV2_Atom* +lv2_tuple_iter_get(LV2_Atom_Tuple_Iter i) +{ + return (LV2_Atom*)i; +} + +/** + 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_TUPLE_FOREACH(tuple, i) { + LV2_Atom_Property* prop = lv2_tuple_iter_get(i); + // Do something with prop here... + } + @endcode +*/ +#define LV2_TUPLE_FOREACH(tuple, iter) \ + for (LV2_Atom_Tuple_Iter (iter) = lv2_tuple_begin(tuple); \ + !lv2_tuple_is_end(tuple, (iter)); \ + (iter) = lv2_tuple_iter_next(iter)) + +/** + @} + @name Object Iterator + @{ +*/ + +/** An iterator over the properties of an LV2_Atom_Object. */ +typedef LV2_Atom_Property_Body* LV2_Atom_Object_Iter; + +/** Get an iterator pointing to the first property in @p obj. */ +static inline LV2_Atom_Object_Iter +lv2_object_begin(const LV2_Atom_Object* obj) +{ + return (LV2_Atom_Object_Iter)(LV2_ATOM_CONTENTS(LV2_Atom_Object, obj)); +} + +/** Return true iff @p i has reached the end of @p obj. */ +static inline bool +lv2_object_is_end(const LV2_Atom_Object* obj, LV2_Atom_Object_Iter i) +{ + return (uint8_t*)i >= ((uint8_t*)obj + sizeof(LV2_Atom) + obj->atom.size); +} + +/** Return true iff @p l points to the same property as @p r. */ +static inline bool +lv2_object_iter_equals(const LV2_Atom_Object_Iter l, + const LV2_Atom_Object_Iter r) +{ + return l == r; +} + +/** Return an iterator to the property following @p i. */ +static inline LV2_Atom_Object_Iter +lv2_object_iter_next(const LV2_Atom_Object_Iter i) +{ + const LV2_Atom* const value = (LV2_Atom*)((uint8_t*)i + sizeof(i)); + return (LV2_Atom_Object_Iter)((uint8_t*)i + + sizeof(LV2_Atom_Property_Body) + + lv2_atom_pad_size(value->size)); +} + +/** Return the property pointed to by @p i. */ +static inline LV2_Atom_Property_Body* +lv2_object_iter_get(LV2_Atom_Object_Iter i) +{ + return (LV2_Atom_Property_Body*)i; +} + +/** + A macro for iterating over all properties of an Object. + @param object 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_OBJECT_FOREACH(object, i) { + LV2_Atom_Property* prop = lv2_object_iter_get(i); + // Do something with prop here... + } + @endcode +*/ +#define LV2_OBJECT_FOREACH(object, iter) \ + for (LV2_Atom_Object_Iter (iter) = lv2_object_begin(object); \ + !lv2_object_is_end(object, (iter)); \ + (iter) = lv2_object_iter_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_Thing_Query; +} LV2_Atom_Object_Query; -static const LV2_Thing_Query LV2_THING_QUERY_END = { 0, NULL }; +static const LV2_Atom_Object_Query LV2_OBJECT_QUERY_END = { 0, NULL }; /** - "Query" an thing, getting a pointer to the values for various keys. + Get an object's values for various keys. - The value pointer of each item in @c query will be set to the location of - the corresponding value in @c thing. Every value pointer in @c query MUST - be initialised to NULL. This function reads @c thing in a single linear - sweep. By allocating @c query on the stack, things can be "queried" + The value pointer of each item in @p query will be set to the location of + the corresponding value in @p object. Every value pointer in @p query MUST + be initialised to NULL. This function reads @p object in a single linear + sweep. By allocating @p query on the stack, objects can be "queried" quickly without allocating any memory. This function is realtime safe. + + 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_OBJECT_QUERY_END + }; + lv2_object_get(obj, q); + // name and age are now set to the appropriate values in obj, or NULL. + @endcode */ static inline int -lv2_thing_query(const LV2_Thing* thing, LV2_Thing_Query* query) +lv2_object_get(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_Thing_Query* q = query; q->key; ++q) + for (LV2_Atom_Object_Query* q = query; q->key; ++q) { ++n_queries; + } - LV2_THING_FOREACH(thing, o) { - const LV2_Atom_Property* prop = lv2_thing_iter_get(o); - for (LV2_Thing_Query* q = query; q->key; ++q) { + LV2_OBJECT_FOREACH(object, o) { + const LV2_Atom_Property_Body* prop = lv2_object_iter_get(o); + 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) + if (++matches == n_queries) { return matches; + } break; } } @@ -171,4 +303,8 @@ lv2_thing_query(const LV2_Thing* thing, LV2_Thing_Query* query) return matches; } +/** + @} +*/ + #endif /* LV2_ATOM_HELPERS_H */ diff --git a/lv2/ns/ext/atom/atom.h b/lv2/ns/ext/atom/atom.h index 569ab49..7a29dae 100644 --- a/lv2/ns/ext/atom/atom.h +++ b/lv2/ns/ext/atom/atom.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2012 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -36,148 +36,152 @@ extern "C" { #endif +/** 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]; + /** - An LV2 Atom. + Return a pointer to the contents of a variable-sized atom. + @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))) - An "Atom" is a generic chunk of memory with a given type and size. - The type field defines how to interpret an atom. +/** Return a pointer to the body of @p atom (just past the LV2_Atom head). */ +#define LV2_ATOM_BODY(atom) LV2_ATOM_CONTENTS(LV2_Atom, atom) - All atoms are by definition Plain Old Data (POD) and may be safely copied - (e.g. with memcpy) using the size field, except atoms with type 0. An atom - with type 0 is a reference, and may only be used via the functions provided - in LV2_Blob_Support (e.g. it MUST NOT be manually copied). -*/ +/** The header of an atom:Atom. */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ - uint8_t body[]; /**< Body of length @ref size bytes. */ + uint32_t type; /**< Type of this atom (mapped URI). */ + uint32_t size; /**< Size in bytes, not including type and size. */ } LV2_Atom; -/** - An atom:String. - May be cast to LV2_Atom. -*/ +/** An atom:Int32 or atom:Bool. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; + int32_t value; +} LV2_Atom_Int32; + +/** An atom:Int64. May be cast to LV2_Atom. */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ - uint8_t str[]; /**< Null-terminated string data in UTF-8 encoding. */ + LV2_Atom atom; + int64_t value; +} LV2_Atom_Int64; + +/** An atom:Float. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; + float value; +} LV2_Atom_Float; + +/** An atom:Double. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; + double value; +} LV2_Atom_Double; + +/** An atom:Bool. May be cast to LV2_Atom. */ +typedef LV2_Atom_Int32 LV2_Atom_Bool; + +/** An atom:URID. May be cast to LV2_Atom. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + uint32_t id; /**< URID. */ +} LV2_Atom_URID; + +/** The complete header of an atom:String. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + /* Contents (a null-terminated UTF-8 string) follow here. */ } LV2_Atom_String; -/** - An atom:Literal. - May be cast to LV2_Atom. -*/ +/** The header of an atom:Literal body. */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ uint32_t datatype; /**< The ID of the datatype of this literal. */ uint32_t lang; /**< The ID of the language of this literal. */ - uint8_t str[]; /**< Null-terminated string data in UTF-8 encoding. */ +} LV2_Atom_Literal_Head; + +/** The complete header of an atom:Literal. */ +typedef struct { + LV2_Atom atom; /**< Atom header. */ + LV2_Atom_Literal_Head literal; /**< Literal body header. */ + /* Contents (a null-terminated UTF-8 string) follow here. */ } LV2_Atom_Literal; -/** - An atom:URID or atom:BlankID. - May be cast to LV2_Atom. -*/ +/** The complete header of an atom:Tuple. */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ - uint32_t id; /**< URID (integer mapped URI) or blank node ID. */ -} LV2_Atom_URID; + LV2_Atom atom; /**< Atom header. */ + /* Contents (a series of complete atoms) follow here. */ +} LV2_Atom_Tuple; -/** - An atom:Vector. - May be cast to LV2_Atom. -*/ +/** The complete header of an atom:Vector. */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ + LV2_Atom atom; /**< Atom header. */ uint32_t elem_count; /**< The number of elements in the vector */ uint32_t elem_type; /**< The type of each element in the vector */ - uint8_t elems[]; /**< Sequence of element bodies */ + /* Contents (a series of packed atom bodies) follow here. */ } LV2_Atom_Vector; -/** - The body of an atom:Property. - Note this type is not an LV2_Atom. -*/ -typedef struct _LV2_Atom_Property { - uint32_t key; /**< Key (predicate) (mapped URI). */ - LV2_Atom value; /**< Value (object) */ -} LV2_Atom_Property; - -/** - An atom:Thing (Resource, Blank, or Message). - May be cast to LV2_Atom. -*/ +/** The header of an atom:Property body (e.g. in an atom:Object). */ typedef struct { - uint32_t type; /**< Type of this atom (mapped URI). */ - uint32_t size; /**< Size in bytes, not including type and size. */ - uint32_t context; /**< ID of context graph, or 0 for default */ - uint32_t id; /**< URID (for Resource) or blank ID (for Blank) */ - uint8_t properties[]; /**< Sequence of LV2_Atom_Property */ -} LV2_Thing; + uint32_t key; /**< Key (predicate) (mapped URI). */ + uint32_t context; /**< Context URID (may be, and generally is, 0). */ + LV2_Atom value; /**< Value atom header. */ +} LV2_Atom_Property_Body; -/** - An atom:Event, a timestamped Atom. - Note this type is not an LV2_Atom, but contains an Atom as payload. -*/ +/** The complete header of an atom:Property. */ typedef struct { - uint32_t frames; /**< Time in frames relative to this block. */ - uint32_t subframes; /**< Fractional time in 1/(2^32)ths of a frame. */ - LV2_Atom body; /**< Event body. */ -} LV2_Atom_Event; + LV2_Atom atom; /**< Atom header. */ + 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; -/** - An atom:Int32, a signed 32-bit integer. - May be cast to LV2_Atom. -*/ +/** The complete header of an atom:Object. */ typedef struct { - uint32_t type; - uint32_t size; - int32_t value; -} LV2_Atom_Int32; + LV2_Atom atom; /**< Atom header. */ + uint32_t id; /**< URID for atom:Resource, or blank ID for atom:Blank. */ + uint32_t type; /**< Type URID (same as rdf:type, for fast dispatch). */ + /* Contents (a series of property bodies) follow here. */ +} LV2_Atom_Object; -/** - An atom:Int64, a signed 64-bit integer. - May be cast to LV2_Atom. -*/ +/** The complete header of an atom:Response. */ typedef struct { - uint32_t type; - uint32_t size; - int64_t value; -} LV2_Atom_Int64; - -/** - An atom:Float, a 32-bit IEEE-754 floating point number. - May be cast to LV2_Atom. -*/ + LV2_Atom atom; /**< Atom header. */ + uint32_t source; /**< ID of message this is a response to (may be 0). */ + uint32_t type; /**< Specific response type URID (may be 0). */ + uint32_t seq; /**< Response sequence number, 0 for end. */ + LV2_Atom body; /**< Body atom header (may be empty). */ + /* Body optionally follows here. */ +} LV2_Atom_Response; + +/** A time stamp in frames. Note this type is NOT an LV2_Atom. */ typedef struct { - uint32_t type; - uint32_t size; - float value; -} LV2_Atom_Float; + uint32_t frames; /**< Time in frames relative to this block. */ + uint32_t subframes; /**< Fractional time in 1/(2^32)ths of a frame. */ +} LV2_Atom_Audio_Time; -/** - An atom:Double, a 64-bit IEEE-754 floating point number. - May be cast to LV2_Atom. -*/ +/** The header of an atom:Event. Note this type is NOT an LV2_Atom. */ typedef struct { - uint32_t type; - uint32_t size; - double value; -} LV2_Atom_Double; + /** Time stamp. Which type is valid is determined by context. */ + union { + LV2_Atom_Audio_Time audio; /**< 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; /** - A buffer of events (the contents of an atom:EventPort). + A sequence of events (time-stamped atoms). - The host MAY elect to allocate buffers as a single chunk of POD by using - this struct as a header much like LV2_Atom, or it may choose to point to - a fragment of a buffer elsewhere. In either case, @ref data points to the - start of the data contained in this buffer. + This is used as the contents of an atom:EventPort, but is a generic Atom + type which can be used anywhere. - The buffer at @ref data contains a sequence of LV2_Atom_Event padded such - that the start of each event is aligned to 64 bits, e.g.: + 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 | | | | | | | | | @@ -186,60 +190,11 @@ typedef struct { </pre> */ typedef struct { - - /** - The contents of the event buffer. This may or may not reside in the - same block of memory as this header, plugins must not assume either. - The host guarantees this points to at least capacity bytes of allocated - memory (though only size bytes of that are valid events). - */ - uint8_t* data; - - /** - The number of events in this buffer. - - INPUTS: The host must set this field to the number of events contained - in the data buffer before calling run(). The plugin must not change - this field. - - OUTPUTS: The plugin must set this field to the number of events it has - written to the buffer before returning from run(). Any initial value - should be ignored by the plugin. - */ - uint32_t event_count; - - /** - The capacity of the data buffer in bytes. - This is set by the host and must not be changed by the plugin. - The host is allowed to change this between run() calls. - */ - uint32_t capacity; - - /** - The size of the initial portion of the data buffer containing data. - - INPUTS: The host must set this field to the number of bytes used - by all events it has written to the buffer (including headers) - before calling the plugin's run(). - The plugin must not change this field. - - OUTPUTS: The plugin must set this field to the number of bytes - used by all events it has written to the buffer (including headers) - before returning from run(). - Any initial value should be ignored by the plugin. - */ - uint32_t size; - -} LV2_Atom_Buffer; - -/** - Pad a size to 64 bits. -*/ -static inline uint32_t -lv2_atom_pad_size(uint32_t size) -{ - return (size + 7) & (~7); -} + LV2_Atom atom; /**< Atom header. */ + uint32_t capacity; /**< Maximum size of contents. */ + uint32_t pad; + /* Contents (a series of events) follow here. */ +} LV2_Atom_Sequence; #ifdef __cplusplus } /* extern "C" */ diff --git a/lv2/ns/ext/atom/atom.ttl b/lv2/ns/ext/atom/atom.ttl index c28b610..7d534d0 100644 --- a/lv2/ns/ext/atom/atom.ttl +++ b/lv2/ns/ext/atom/atom.ttl @@ -27,10 +27,11 @@ doap:name "LV2 Atom" ; doap:shortdesc "A generic value container and several data types." ; doap:license <http://opensource.org/licenses/isc> ; - rdfs:seeAlso <atom-buffer.h> ; + rdfs:seeAlso <atom-helpers.h> , + <forge.h> ; doap:release [ - doap:revision "0.3" ; - doap:created "2012-01-28" + doap:revision "0.4" ; + doap:created "2012-02-07" ] ; doap:maintainer [ a foaf:Person ; @@ -39,68 +40,118 @@ rdfs:seeAlso <http://drobilla.net/drobilla.rdf> ] ; lv2:documentation """ -<p>This extension defines a generic format for a typed piece of data, called an -lv2:Atom (e.g. integers, strings, buffers, data structures, -etc). Atoms allow LV2 plugins and hosts to communicate, process, serialise, -and store values of any type via a generic mechanism (e.g. LV2 ports, events, -disk, shared memory, network). Atoms are, with one exception, Plain -Old Data (POD) and may be safely copied (e.g. with a simple call to -<code>memcpy</code>).</p> +<p>This extension defines a generic container for data, called an <q>Atom</q>, +and several basic Atom types which can be used to express structured data. +Atoms allow LV2 plugins and hosts to communicate, process, serialise, and store +values of any type via a generic mechanism (e.g. ports, files, networks, +ringbuffers, etc.). Atoms are, with one exception, Plain Old Data (POD) which +may safely be copied (e.g. with a simple call to <code>memcpy</code>).</p> <p>Since Atom communication can be implemented generically, plugins that understand some type can be used together in a host that does not understand that type, and plugins (e.g. routers, delays) can process atoms of unknown type.</p> -<p>An Atom can be trivially constructed in-place from an -<a href="http://lv2plug.in/ns/ext/event#Event">Event</a> as defined by the -<a href="http://lv2plug.in/ns/ext/event">LV2 Event</a> extension. In other -words, an Event is simply an Atom with a time stamp header. Atoms SHOULD -be used anywhere a "value" needs to be stored or communicated, to allow -implementations to be polymorphic and extensible.</p> +<p>Atoms can and should be used anywhere values of various types must be stored +or transmitted. This extension defines port types, atom:ValuePort and +atom:MessagePort, which are connected to an Atom. The atom:Sequence type in +conjunction with atom:MessagePort is intended to replace the <a +href="http://lv2plug.in/ns/ext/event">LV2 event</a> extension.</p> -<p>Atoms (the start of the LV2_Atom header) MUST be 32-bit aligned.</p> - -<p>Atoms can be communicated in many ways. Since an Atom is the payload of an -Event, an <a href="http://lv2plug.in/ns/ext/event#EventPort">EventPort</a> can -be used for communicating Atoms in realtime with sub-sample time stamp -accuracy. This extension also defines two port types for connecting directly -to a single Atom: atom:ValuePort and atom:MessagePort, which both have the same -buffer format but different semantics (with respect to how the run() callback -interprets the Atom).</p> +<p>The types defined in this extension should be powerful enough to express +almost any structure. Implementers SHOULD build structures out of the types +provided here, rather than define new binary formats (e.g. use atom:Object +rather than a new C <code>struct</code> type). New binary formats are an +implementation burden which harms interoperabilty, and should only be defined +where absolutely necessary.</p> <p>Implementing this extension requires a facility for mapping URIs to integers, such as the <a href="http://lv2plug.in/ns/ext/urid">LV2 URID</a> extension.</p> """ . +atom:cType + a rdf:Property , + owl:DatatypeProperty ; + rdfs:label "C type" ; + rdfs:domain rdfs:Class ; + rdfs:range xsd:string ; + rdfs:comment """ +The identifier for a C type describing the in-memory representation of +an instance of this class. +""" . + 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>type</code> and <code>size</code> followed by a <code>body</code> of -<code>size</code> bytes.</p> +<code>type</code> and <code>size</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 <code>body</code>.</p> +binary layout for their body.</p> -<p>The <code>type</code> field is the URI of a subclass of Atom mapped to an -integer using the <a href="http://lv2plug.in/ns/ext/uri-map">URI Map</a> -extension's LV2_URI_Map_Feature::uri_to_id() with -<code>map = "http://lv2plug.in/ns/ext/event"</code>. If a plugin or host -does not understand <code>type</code>, that atom SHOULD be gracefully ignored -(or copied if it does not have type 0).</p> +<p>The <code>type</code> field is the URI of an Atom type mapped to an integer. +Implementations SHOULD gracefully ignore, or pass through, 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, actual specification of how references are used is left -to another extension.</p> +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). 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:Bang + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Bang" ; + rdfs:comment "Generic activity or trigger, with no body." . + +atom:Number + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Number" . + +atom:Int32 + a rdfs:Class ; + rdfs:subClassOf atom:Number ; + rdfs:label "Signed 32-bit integer" ; + atom:cType "LV2_Atom_Int32" . + +atom:Int64 + a rdfs:Class ; + rdfs:subClassOf atom:Number ; + rdfs:label "Signed 64-bit integer" ; + atom:cType "LV2_Atom_Int64" . + +atom:Float + a rdfs:Class ; + rdfs:subClassOf atom:Number ; + rdfs:label "32-bit IEEE-754 floating point number" ; + atom:cType "LV2_Atom_Float" . + +atom:Double + a rdfs:Class ; + rdfs:subClassOf atom:Number ; + rdfs:label "64-bit IEEE-754 floating point number" ; + atom:cType "LV2_Atom_Double" . + +atom:Bool + a rdfs:Class ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Boolean" ; + atom:cType "LV2_Atom_Bool" ; + rdfs:comment "An Int32 where 0 is false and any other value is true." . + atom:String a rdfs:Class ; rdfs:subClassOf atom:Atom ; @@ -128,7 +179,7 @@ atom:Literal <p>A UTF-8 encoded string literal, with an optional datatype or language.</p> <p>This type is compatible with rdf:Literal and is capable of expressing a -string in any language, or a value of any type. A Literal has 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 @@ -143,22 +194,26 @@ 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->type = map(expand("atom:Literal")); - lit->size = 14; - lit->datatype = 0; - lit->lang = map("http://lexvo.org/id/term/en"); - memcpy(lit->str, "Hello", sizeof("Hello")); // Assumes enough space + lit->atom.type = map(expand("atom:Literal")); + lit->atom.size = 14; + lit->datatype = 0; + lit->lang = map("http://lexvo.org/id/term/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->type = map(expand("atom:Literal")); - lit->size = 64; - lit->datatype = map("http://www.w3.org/2008/turtle#turtle"); - lit->lang = 0; - memcpy(lit->str, ttl, strlen(ttl) + 1); // Assumes enough space + lit->atom.type = map(expand("atom:Literal")); + lit->atom.size = 64; + lit->datatype = map("http://www.w3.org/2008/turtle#turtle"); + lit->lang = 0; + memcpy(LV2_ATOM_CONTENTS(LV2_Atom_Literal, lit), + ttl, + strlen(ttl) + 1); // Assumes enough space } </pre> """ . @@ -169,21 +224,7 @@ atom:URID rdfs:label "Integer ID mapped from a URI" ; atom:cType "LV2_Atom_ID" ; lv2:documentation """ -<p>An unsigned 32-bit integer mapped from a URI using the <a -href="http://lv2plug.in/ns/ext/uri-map">URI Map</a> extension's -LV2_URI_Map_Feature::uri_to_id() with <code>map = NULL</code>.</p> -""" . - -atom:BlankID - a rdfs:Class ; - rdfs:subClassOf atom:Atom ; - rdfs:label "Integer ID for a blank node" ; - atom:cType "LV2_Atom_ID" ; - lv2:documentation """ -<p>An unsigned 32-bit integer identifier for a blank node. A BlankID is only -meaningful within a limited scope (e.g. the Atom in which it appears), and -MUST NOT be used as a global identifier. In particular, a BlankID is NOT a -URID, and can not be mapped to/from a URI.</p> +<p>An unsigned 32-bit integer mapped from a URI (e.g. with LV2_URID_Map).</p> """ . atom:Vector @@ -192,7 +233,7 @@ atom:Vector rdfs:label "Vector" ; atom:cType "LV2_Atom_Vector" ; lv2:documentation """ -<p>A homogeneous sequence of atoms with equivalent type and size.</p> +<p>A homogeneous series of atom bodies with equivalent type and size.</p> <p>An LV2_Atom_Vector is a 32-bit <code>elem_count</code> and <code>elem_type</code> followed by <code>elem_count</code> atom bodies of type @@ -221,125 +262,84 @@ atom:Tuple rdfs:subClassOf atom:Atom ; rdfs:label "Tuple" ; lv2:documentation """ -<p>A sequence of lv2:Atom with varying <code>type</code> -and <code>size</code>.</p> +<p>A series of Atoms with varying <code>type</code> and <code>size</code>.</p> -<p>The body of a Tuple is simply a sequence of complete atoms, each aligned to -32 bits.</p> +<p>The body of a Tuple is simply a series of complete atoms, each aligned to +64 bits.</p> """ . -atom:Thing +atom:Property a rdfs:Class ; rdfs:subClassOf atom:Atom ; - rdfs:label "Thing" ; - atom:cType "LV2_Thing" ; + rdfs:label "Property" ; + atom:cType "LV2_Atom_Property" ; lv2:documentation """ -<p>Abstract base class for a "Thing", i.e. an atom:Atom with a number of -properties. An LV2_Object is an unsigned 32-bit integer <code>context</code> -and <code>id</code> followed by a sequence of LV2_Atom_Property .</p> - -<p>The <code>context</code> is mapped using the <a -href="http://lv2plug.in/ns/ext/uri-map">URI Map</a> extension's -LV2_URI_Map_Feature::uri_to_id() with <code>map = NULL</code>, and may be 0 -(the default context).</p> - -<p>Note this is an abstract class, i.e. no Atom can exist with <code>type = -uri_to_id(atom:Thing)</code>. An Object is either an atom:Resource or an -atom:Blank, but the <code>body</code> always has the same binary format, -LV2_Object. Thus, both named and anonymous objects can be handled with common -code using only a 64-bit header for both.</p> +<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> """ . -atom:Resource +atom:Object a rdfs:Class ; - rdfs:subClassOf atom:Thing ; - atom:cType "LV2_Thing" ; + rdfs:subClassOf atom:Atom ; + rdfs:label "Object" ; + atom:cType "LV2_Atom_Object" ; lv2:documentation """ -<p>An atom:Thing where <code>id</code> is the URI of the resource mapped to an -atom:URID.</p> +<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 has a uint32_t <code>id</code> and uint32_t +<code>type</code>, followed by a series of atom:Property bodies (without +headers, i.e. LV2_Atom_Property_Body). The LV2_Atom_Object::type field is +semantically equivalent to a property with key rdf:type, but is included in the +structure to allow for fast dispatch.</p> + +<p>This is an abstract Atom type, an Object is always either a atom:Resource +or a atom:Blank.</p> """ . -atom:Blank +atom:Resource a rdfs:Class ; - rdfs:subClassOf atom:Thing ; - atom:cType "LV2_Thing" ; + rdfs:subClassOf atom:Object ; + rdfs:label "Resource" ; + atom:cType "LV2_Atom_Object" ; lv2:documentation """ -<p>An atom:Thing where <code>id</code> is the blank node ID of the object, -which is only meaningful within a certain limited scope (e.g. the container of -the Blank) and MUST NOT be used as a global ID. In particular, <code>id</code> -is NOT a URID.</p> +<p>An atom:Object where the <code>id</code> field is a URID, i.e. an Object +with a URI.</p> """ . -atom:Message +atom:Blank a rdfs:Class ; - rdfs:subClassOf atom:Thing ; - atom:cType "LV2_Thing" ; + rdfs:subClassOf atom:Object ; + rdfs:label "Blank" ; + atom:cType "LV2_Atom_Object" ; lv2:documentation """ -<p>A atom:Thing where <code>id</code> is a message type ID. Conceptually, a -Message is identical to a Blank, but is a distinct type with a single type -field to allow simple and fast dispatch by handling code.</p> - -<p>A Message may be serialised as a Blank by adding an rdf:type property with -the value <code>id</code> unmapped to a URI.</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:Event a rdfs:Class ; rdfs:label "Event" ; atom:cType "LV2_Atom_Event" ; lv2:documentation """ -<p>An atom with a time stamp header prepended, typically for sample accurate -transmission via LV2 ports. See struct LV2_Atom_Event.</p> +<p>An atom with a time stamp header prepended, typically an element of an +atom:Sequence. Note this is not an Atom type.</p> """ . -atom:Bang - a rdfs:Class ; - rdfs:subClassOf atom:Atom ; - rdfs:label "Bang (activity) (size = 0)" . - -atom:Number +atom:Sequence a rdfs:Class ; rdfs:subClassOf atom:Atom ; - rdfs:label "Number (abstract class)." . - -atom:Int32 - a rdfs:Class ; - rdfs:subClassOf atom:Number ; - rdfs:label "Signed 32-bit integer" ; - atom:cType "LV2_Atom_Int32" . - -atom:Int64 - a rdfs:Class ; - rdfs:subClassOf atom:Number ; - rdfs:label "Signed 64-bit integer" ; - atom:cType "LV2_Atom_Int64" . - -atom:Bool - a rdfs:Class ; - rdfs:subClassOf atom:Atom ; - rdfs:label "An atom:Int32 where 0 is false and all other values true" ; - atom:cType "LV2_Atom_Int32" . - -atom:Float - a rdfs:Class ; - rdfs:subClassOf atom:Number ; - rdfs:label "32-bit IEEE-754 floating point number" ; - atom:cType "LV2_Atom_Float" . - -atom:Double - a rdfs:Class ; - rdfs:subClassOf atom:Number ; - rdfs:label "64-bit IEEE-754 floating point number" ; - atom:cType "LV2_Atom_Double" . - -atom:blobSupport - a lv2:Feature ; - rdfs:label "Blob support" ; + rdfs:label "Sequence" ; + atom:cType "LV2_Atom_Sequence" ; lv2:documentation """ -<p>Support for dynamically allocated blobs. If a host supports this feature, -it MUST pass a LV2_Feature with <code>URI</code> -http://lv2plug.in/ns/ext/atom#blobSupport and <code>data</code> pointing to a -LV2_Blob_Support.</p> +<p>A sequence of atom:Event, i.e. a series of time-stamped Atoms.</p> """ . atom:AtomPort @@ -347,21 +347,20 @@ atom:AtomPort rdfs:subClassOf lv2:Port ; rdfs:label "Atom Port" ; lv2:documentation """ -<p>A port which contains an lv2:Atom. Ports of this type will -be connected to a 32-bit aligned LV2_Atom immediately followed by -<code>size</code> bytes of data.</p> - -<p>This is an abstract port type, i.e. a port MUST NOT only be an AtomPort, -but must be a more descriptive type that is a subclass of AtomPort which -defines the port's semantics (typically atom:ValuePort or atom:MessagePort). -</p> - -<p>Before calling a method on the plugin that writes to an AtomPort output, -the host MUST set the size of the Atom in that output to the amount of -available memory immediately following the Atom header. The plugin MUST -write a valid Atom to that port (leaving it untouched is illegal). If there -is no reasonable value to write to the port, the plugin MUST write NULL -(the Atom with both <code>type = 0</code> and <code>size = 0</code>).</p> +<p>A port which contains an lv2:Atom. Ports of this type will be connected to +a 64-bit aligned LV2_Atom immediately followed by <code>size</code> bytes of +data.</p> + +<p>This is an abstract port type with incomplete semantics which can not be +used directly as a port type. Atom ports should be either a atom:ValuePort or +a atom:MessagePort.</p> + +<p>Before calling a method on a plugin that writes to an AtomPort output, the +host MUST set the size of the Atom in that output to the amount of available +memory immediately following the Atom header. The plugin MUST write a valid +Atom to that port; leaving it untouched is illegal. If there is no reasonable +value to write to the port, the plugin MUST write null (the Atom with both +<code>type</code> and <code>size</code> 0).</p> """ . atom:ValuePort @@ -369,67 +368,95 @@ atom:ValuePort rdfs:subClassOf atom:AtomPort ; rdfs:label "Value Port" ; lv2:documentation """ -<p>An AtomPort that interprets its data as a persistent and time-independent -"value".</p> + +<p>An AtomPort that contains a persistent <em>value</em>. A <q>value</q> is +time-independent and may be used numerous times. A ValuePort is <q>pure</q> in +the sense that it may affect output but MUST NOT affect persistent plugin state +in any externally visible way.</p> + <ul> -<li>If a plugin has fixed input values for all ports, all ValuePort outputs -are also fixed regardless of the number of times the plugin is run.</li> -<li>If a plugin has fixed input values for all ports except a ValuePort, -each value V of that ValuePort corresponds to a single set of outputs -for all ports.</li> -<li>If a ValuePort contains a reference then the blob it refers to is -constant; plugin MUST NOT modify the blob in any way.</li> +<li>If a plugin has fixed values for all inputs, all ValuePort outputs are also +fixed regardless of the number of times the plugin is run.</li> + +<li>If a plugin has fixed input values for all ports except a ValuePort, each +value of that port corresponds to a single set of values for all +ValuePort outputs.</li> + +<li>If the plugin saves state other than port values (e.g. using the <a +href="http://lv2plug.in/ns/ext/state">LV2 State</a> extension), changing only +the value of a ValuePort input MUST NOT change that state. In other words, +value port changes MUST NOT trigger a state change that requires a save.</li> </ul> -<p>Value ports can be thought of as purely functional ports: if a plugin -callback has only value ports, then the plugin callback is a pure function.</p> + +<p>Value ports are essentially purely functional ports: if a plugin has only +value ports, that plugin is purely functional. Hosts may elect to cache output +and avoid calling run() if the output is already known according to these +rules.</p> """ . atom:MessagePort a rdfs:Class ; rdfs:subClassOf atom:AtomPort ; rdfs:label "Message Port" ; - rdfs:comment """ -An AtomPort that "receives", "consumes", "executes", or "sends" its value. -The Atom contained in a MessagePort is considered transient and/or -time-dependent, and is only valid for a single run invocation. Unlike a -ValuePort, a MessagePort may be used to manipulate internal plugin state. - -Intuitively, a MessagePort contains a "message" or "command" or "event" -which is reacted to, NOT a "value" or "signal" (which is computed with). -""" . - -atom:cType - a rdf:Property , - owl:DatatypeProperty ; - rdfs:label "C type" ; - rdfs:domain rdfs:Class ; - rdfs:range xsd:string ; - rdfs:comment """ -The identifier for a C type describing the in-memory representation of -an instance of this class. + lv2:documentation """ +<p>An AtomPort that contains transient data which is <em>consumed</em> or +<em>sent</em>. The Atom contained in a MessagePort is time-dependent and only +valid for a single run invocation. Unlike a ValuePort, a MessagePort may be +used to manipulate internal plugin state.</p> + +<p>Intuitively, a MessagePort contains a <q>message</q> or <q>event</q> which +is reacted to <em>once</em> (not a <q>value</q> which is computed with any +number of times).</p> """ . -atom:EventPort - a rdfs:Class ; - rdfs:label "Event port" ; - rdfs:subClassOf lv2:Port ; +atom:bufferType + a rdf:Property ; + rdfs:domain atom:AtomPort ; + rdfs:label "buffer type" ; lv2:documentation """ -<p>A port used for communicating time-stamped atoms in the audio context. -Ports of this type are connected to an LV2_Atom_Buffer, which contains a flat -time-stamped sequence of atom:Event.</p> +<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:ValuePort ; + atom:bufferType atom:Double ; + ] . +</pre> -<p>This port type is intended as a simpler and atom compatible successor to <a -href="http://lv2plug.in/ns/ext/event#EventPort">ev:EventPort</a>.</p> +<p>Note this property only indicates the atom types a port may be directly +connected to, it is not <q>recursive</q>. If a port can be connected to a +collection, use atom:supports to indicate which element types are understood. +If a port supports heterogeneous collections (collections that can contain +several types of elements at once), implementations MUST gracefully handle any +types that are present in the collection, even if those types are not +explicitly supported.</p> """ . atom:supports a rdf:Property ; - rdfs:domain lv2:Port ; - rdfs:range atom:Atom ; rdfs:label "supports" ; lv2:documentation """ -<p>Indicates that a Port supports a certain atom:Atom type. This is distinct from -the port type - e.g. the port type ValuePort can hold atoms with many different -types. This property is used to describe which Atom types a Port expects to -receive or send.</p> +<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 +supported 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:MessagePort ; + atom:bufferType atom:Sequence ; + atom:supports midi:MidiEvent ; + ] . +</pre> """ . diff --git a/lv2/ns/ext/atom/forge.h b/lv2/ns/ext/atom/forge.h index e8f5d40..2790778 100644 --- a/lv2/ns/ext/atom/forge.h +++ b/lv2/ns/ext/atom/forge.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2012 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,57 +15,427 @@ */ /** - @file forge.h Helper constructor functions for LV2 atoms. + @file forge.h An API for constructing LV2 atoms. + + This file provides a simple API which can be used to create complex nested + atoms by calling the provided functions to append atoms (or atom headers) in + the correct order. The size of the parent atom is automatically updated, + but the caller must handle this situation if atoms are more deeply nested. + + All output is written to a user-provided buffer. This entire API is + realtime safe and suitable for writing to output port buffers in the run + method. + + Note these functions are all static inline, do not take their address. + + This header is non-normative, it is provided for convenience. */ #ifndef LV2_ATOM_FORGE_H #define LV2_ATOM_FORGE_H +#include <assert.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom-helpers.h" #include "lv2/lv2plug.in/ns/ext/atom/atom.h" #include "lv2/lv2plug.in/ns/ext/urid/urid.h" #ifdef __cplusplus extern "C" { +#else +# include <stdbool.h> #endif +/** + A "forge" for creating atoms by appending to a buffer. +*/ typedef struct { - uint32_t ID; - uint32_t Message; - uint32_t Property; + uint8_t* buf; + size_t offset; + size_t size; + + LV2_URID Bool; + LV2_URID Double; + LV2_URID Float; + LV2_URID Int32; + LV2_URID Int64; + LV2_URID Literal; + LV2_URID Object; + LV2_URID Property; + LV2_URID Sequence; + LV2_URID String; + LV2_URID Tuple; + LV2_URID URID; + LV2_URID Vector; } LV2_Atom_Forge; -static inline LV2_Atom_Forge* -lv2_atom_forge_new(LV2_URID_Map* map) +/** Set the output buffer where @c forge will write atoms. */ +static inline void +lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size) { - LV2_Atom_Forge* forge = (LV2_Atom_Forge*)malloc(sizeof(LV2_Atom_Forge)); - forge->ID = map->map(map->handle, LV2_ATOM_URI "#ID"); - forge->Message = map->map(map->handle, LV2_ATOM_URI "#Message"); - forge->Property = map->map(map->handle, LV2_ATOM_URI "#Property"); - return forge; + forge->buf = buf; + forge->size = size; + forge->offset = 0; } +/** + Initialise @c forge. + + URIs will be mapped using @c map and stored, a reference to @c map itself is + not held. +*/ static inline void -lv2_atom_forge_free(LV2_Atom_Forge* forge) +lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map) { - free(forge); + lv2_atom_forge_set_buffer(forge, NULL, 0); + forge->Bool = map->map(map->handle, LV2_ATOM_URI "#Bool"); + forge->Double = map->map(map->handle, LV2_ATOM_URI "#Double"); + forge->Float = map->map(map->handle, LV2_ATOM_URI "#Float"); + forge->Int32 = map->map(map->handle, LV2_ATOM_URI "#Int32"); + forge->Int64 = map->map(map->handle, LV2_ATOM_URI "#Int64"); + forge->Literal = map->map(map->handle, LV2_ATOM_URI "#Literal"); + forge->Object = map->map(map->handle, LV2_ATOM_URI "#Object"); + forge->Property = map->map(map->handle, LV2_ATOM_URI "#Property"); + forge->Sequence = map->map(map->handle, LV2_ATOM_URI "#Sequence"); + forge->String = map->map(map->handle, LV2_ATOM_URI "#String"); + forge->Tuple = map->map(map->handle, LV2_ATOM_URI "#Tuple"); + forge->URID = map->map(map->handle, LV2_ATOM_URI "#URID"); + forge->Vector = map->map(map->handle, LV2_ATOM_URI "#Vector"); } -static inline LV2_Atom_ID -lv2_atom_forge_make_id(LV2_Atom_Forge* forge, uint32_t id) +/** + Reserve @c size bytes in the output buffer (used internally). + @return The start of the reserved memory, or NULL if out of space. +*/ +static inline uint8_t* +lv2_atom_forge_reserve(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t size) { - const LV2_Atom_ID atom = { forge->ID, sizeof(uint32_t), id }; - return atom; + uint8_t* const out = forge->buf + forge->offset; + const uint32_t padded_size = lv2_atom_pad_size(size); + if (forge->offset + padded_size > forge->size) { + return NULL; + } + if (parent) { + parent->size += padded_size; + } + forge->offset += padded_size; + return out; } -static inline void -lv2_atom_forge_set_message(LV2_Atom_Forge* forge, - LV2_Thing* msg, - uint32_t id) -{ - msg->type = forge->Message; - msg->size = sizeof(LV2_Thing) - sizeof(LV2_Atom); - msg->context = 0; - msg->id = id; +/** + Write the header of an atom:Atom. + + Space for the complete atom will be reserved, but uninitialised. +*/ +static inline LV2_Atom* +lv2_atom_forge_atom_head(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t type, + uint32_t size) +{ + LV2_Atom* const out = (LV2_Atom*)lv2_atom_forge_reserve( + forge, parent, size); + if (out) { + out->type = type; + out->size = size - sizeof(LV2_Atom); + } + return out; +} + +/** Write an atom:Int32. */ +static inline LV2_Atom_Int32* +lv2_atom_forge_int32(LV2_Atom_Forge* forge, LV2_Atom* parent, int32_t val) +{ + LV2_Atom_Int32* out = (LV2_Atom_Int32*)lv2_atom_forge_atom_head( + forge, parent, forge->Int32, sizeof(LV2_Atom_Int32)); + if (out) { + out->value = val; + } + return out; +} + +/** Write an atom:Int64. */ +static inline LV2_Atom_Int64* +lv2_atom_forge_int64(LV2_Atom_Forge* forge, LV2_Atom* parent, int64_t val) +{ + LV2_Atom_Int64* out = (LV2_Atom_Int64*)lv2_atom_forge_atom_head( + forge, parent, forge->Int64, sizeof(LV2_Atom_Int64)); + if (out) { + out->value = val; + } + return out; +} + +/** Write an atom:Float. */ +static inline LV2_Atom_Float* +lv2_atom_forge_float(LV2_Atom_Forge* forge, LV2_Atom* parent, float val) +{ + LV2_Atom_Float* out = (LV2_Atom_Float*)lv2_atom_forge_atom_head( + forge, parent, forge->Float, sizeof(LV2_Atom_Float)); + if (out) { + out->value = val; + } + return out; +} + +/** Write an atom:Double. */ +static inline LV2_Atom_Double* +lv2_atom_forge_double(LV2_Atom_Forge* forge, LV2_Atom* parent, double val) +{ + LV2_Atom_Double* out = (LV2_Atom_Double*)lv2_atom_forge_atom_head( + forge, parent, forge->Double, sizeof(LV2_Atom_Double)); + if (out) { + out->value = val; + } + return out; +} + +/** Write an atom:Bool. */ +static inline LV2_Atom_Bool* +lv2_atom_forge_bool(LV2_Atom_Forge* forge, LV2_Atom* parent, bool val) +{ + LV2_Atom_Bool* out = (LV2_Atom_Bool*)lv2_atom_forge_atom_head( + forge, parent, forge->Bool, sizeof(LV2_Atom_Bool)); + if (out) { + out->value = val ? 1 : 0; + } + return out; +} + +/** Write an atom:URID. */ +static inline LV2_Atom_URID* +lv2_atom_forge_urid(LV2_Atom_Forge* forge, LV2_Atom* parent, LV2_URID id) +{ + LV2_Atom_URID* out = (LV2_Atom_URID*)lv2_atom_forge_reserve( + forge, parent, sizeof(LV2_Atom_URID)); + if (out) { + out->atom.type = forge->URID; + out->atom.size = sizeof(uint32_t); + out->id = id; + } + return out; +} + +/** Write an atom:String. */ +static inline LV2_Atom_String* +lv2_atom_forge_string(LV2_Atom_Forge* forge, + LV2_Atom* parent, + const uint8_t* str, + size_t len) +{ + LV2_Atom_String* out = (LV2_Atom_String*)lv2_atom_forge_reserve( + forge, parent, sizeof(LV2_Atom_String) + len + 1); + if (out) { + out->atom.type = forge->String; + out->atom.size = len + 1; + assert(LV2_ATOM_CONTENTS(LV2_Atom_String, out) == LV2_ATOM_BODY(out)); + uint8_t* buf = LV2_ATOM_CONTENTS(LV2_Atom_String, out); + memcpy(buf, str, len); + buf[len] = '\0'; + } + return out; +} + +/** Write an atom:Literal. */ +static inline LV2_Atom_Literal* +lv2_atom_forge_literal(LV2_Atom_Forge* forge, + LV2_Atom* parent, + const uint8_t* str, + size_t len, + uint32_t datatype, + uint32_t lang) +{ + LV2_Atom_Literal* out = (LV2_Atom_Literal*)lv2_atom_forge_reserve( + forge, parent, sizeof(LV2_Atom_Literal) + len + 1); + if (out) { + out->atom.type = forge->Literal; + out->atom.size = sizeof(LV2_Atom_Literal_Head) + len + 1; + out->literal.datatype = datatype; + out->literal.lang = lang; + uint8_t* buf = LV2_ATOM_CONTENTS(LV2_Atom_Literal, out); + memcpy(buf, str, len); + buf[len] = '\0'; + } + return out; +} + +/** Write an atom:Vector header and reserve space for the body. */ +static inline LV2_Atom_Vector* +lv2_atom_forge_reserve_vector(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t elem_count, + uint32_t elem_type, + uint32_t elem_size) +{ + const size_t size = sizeof(LV2_Atom_Vector) + (elem_size * elem_count); + LV2_Atom_Vector* out = (LV2_Atom_Vector*)lv2_atom_forge_reserve( + forge, parent, size); + if (out) { + out->atom.type = forge->Vector; + out->atom.size = size - sizeof(LV2_Atom); + out->elem_count = elem_count; + out->elem_type = elem_type; + } + return out; +} + +/** Write an atom:Vector. */ +static inline LV2_Atom_Vector* +lv2_atom_forge_vector(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t elem_count, + uint32_t elem_type, + uint32_t elem_size, + void* elems) +{ + LV2_Atom_Vector* out = lv2_atom_forge_reserve_vector( + forge, parent, elem_count, elem_type, elem_size); + if (out) { + uint8_t* buf = LV2_ATOM_CONTENTS(LV2_Atom_Vector, out); + memcpy(buf, elems, elem_size * elem_count); + } + return out; +} + +/** + Write the header of an atom:Tuple. + + To complete the tuple, write a sequence of atoms, always passing the + returned tuple as the @c parent parameter (or otherwise ensuring the size is + updated correctly). + + For example: + @code + // Write tuple (1, 2.0) + LV2_Atom* tup = (LV2_Atom*)lv2_atom_forge_tuple(forge, NULL); + lv2_atom_forge_int32(forge, tup, 1); + lv2_atom_forge_float(forge, tup, 2.0); + @endcode +*/ +static inline LV2_Atom_Tuple* +lv2_atom_forge_tuple(LV2_Atom_Forge* forge, + LV2_Atom* parent) +{ + return (LV2_Atom_Tuple*)lv2_atom_forge_atom_head( + forge, parent, forge->Tuple, sizeof(LV2_Atom)); +} + +/** + Write the header of an atom:Object. + + To complete the object, write a sequence of properties, always passing the + object as the @c parent parameter (or otherwise ensuring the size is updated + correctly). + + For example: + @code + LV2_URID eg_Cat = map("http://example.org/Cat"); + LV2_URID eg_name = map("http://example.org/name"); + + // Write object header + LV2_Atom* obj = (LV2_Atom*)lv2_atom_forge_object(forge, NULL, 0, eg_Cat); + + // Write property: eg:name = "Hobbes" + lv2_atom_forge_property_head(forge, obj, eg_name, 0); + lv2_atom_forge_string(forge, obj, "Hobbes", strlen("Hobbes")); + @endcode +*/ +static inline LV2_Atom_Object* +lv2_atom_forge_object(LV2_Atom_Forge* forge, + LV2_Atom* parent, + LV2_URID id, + LV2_URID type) +{ + LV2_Atom_Object* out = (LV2_Atom_Object*)lv2_atom_forge_reserve( + forge, parent, sizeof(LV2_Atom_Object)); + if (out) { + out->atom.type = forge->Object; + out->atom.size = sizeof(LV2_Atom_Object) - sizeof(LV2_Atom); + out->id = id; + out->type = type; + } + return out; +} + +/** + Write the header for a property body (likely in an Object). + See lv2_atom_forge_object() documentation for an example. +*/ +static inline LV2_Atom_Property_Body* +lv2_atom_forge_property_head(LV2_Atom_Forge* forge, + LV2_Atom* parent, + LV2_URID key, + LV2_URID context) +{ + LV2_Atom_Property_Body* out = (LV2_Atom_Property_Body*) + lv2_atom_forge_reserve(forge, parent, 2 * sizeof(uint32_t)); + if (out) { + out->key = key; + out->context = context; + } + return out; +} + +/** + Write the header for a Sequence. + The size of the returned sequence will be 0, so passing it as the parent + parameter to other forge methods will do the right thing. +*/ +static inline LV2_Atom_Sequence* +lv2_atom_forge_sequence_head(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t capacity) +{ + LV2_Atom_Sequence* out = (LV2_Atom_Sequence*) + lv2_atom_forge_reserve(forge, parent, sizeof(LV2_Atom_Sequence)); + if (out) { + out->atom.type = forge->Sequence; + out->atom.size = sizeof(LV2_Atom_Sequence) - sizeof(LV2_Atom); + out->capacity = capacity; + out->pad = 0; + } + return out; +} + +/** + 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, passing + the same @c parent parameter. Note the returned LV2_Event is NOT an Atom. +*/ +static inline LV2_Atom_Event* +lv2_atom_forge_audio_time(LV2_Atom_Forge* forge, + LV2_Atom* parent, + uint32_t frames, + uint32_t subframes) +{ + LV2_Atom_Event* out = (LV2_Atom_Event*) + lv2_atom_forge_reserve(forge, parent, 2 * sizeof(uint32_t)); + if (out) { + out->time.audio.frames = frames; + out->time.audio.subframes = subframes; + } + return out; +} + +/** + 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, passing + the same @c parent parameter. Note the returned LV2_Event is NOT an Atom. +*/ +static inline LV2_Atom_Event* +lv2_atom_forge_beat_time(LV2_Atom_Forge* forge, + LV2_Atom* parent, + double beats) +{ + LV2_Atom_Event* out = (LV2_Atom_Event*) + lv2_atom_forge_reserve(forge, parent, sizeof(double)); + if (out) { + out->time.beats = beats; + } + return out; } #ifdef __cplusplus diff --git a/lv2/ns/ext/atom/manifest.ttl b/lv2/ns/ext/atom/manifest.ttl index 20c974d..37f0f58 100644 --- a/lv2/ns/ext/atom/manifest.ttl +++ b/lv2/ns/ext/atom/manifest.ttl @@ -4,6 +4,6 @@ <http://lv2plug.in/ns/ext/atom> a lv2:Specification ; lv2:minorVersion 0 ; - lv2:microVersion 3 ; + lv2:microVersion 4 ; rdfs:seeAlso <atom.ttl> . diff --git a/lv2/ns/ext/state/state.ttl b/lv2/ns/ext/state/state.ttl index 84cda42..6c0656a 100644 --- a/lv2/ns/ext/state/state.ttl +++ b/lv2/ns/ext/state/state.ttl @@ -14,12 +14,20 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -@prefix state: <http://lv2plug.in/ns/ext/state#> . +@prefix dcs: <http://ontologi.es/doap-changeset#> . @prefix doap: <http://usefulinc.com/ns/doap#> . @prefix foaf: <http://xmlns.com/foaf/0.1/> . @prefix lv2: <http://lv2plug.in/ns/lv2core#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix state: <http://lv2plug.in/ns/ext/state#> . + +<http://drobilla.net/drobilla#me> + a foaf:Person ; + foaf:name "David Robillard" ; + foaf:homepage <http://drobilla.net/> ; + foaf:mbox <mailto:d@drobilla.net> ; + rdfs:seeAlso <http://drobilla.net/drobilla> . <http://lv2plug.in/ns/ext/state> a lv2:Specification ; @@ -28,7 +36,8 @@ doap:license <http://opensource.org/licenses/isc> ; doap:release [ doap:revision "0.5" ; - doap:created "2012-01-29" + doap:created "2012-01-29" ; + dcs:blame <http://drobilla.net/drobilla#me> ] ; doap:developer [ a foaf:Person ; diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c index 5584143..ae5434a 100644 --- a/plugins/eg-sampler.lv2/sampler.c +++ b/plugins/eg-sampler.lv2/sampler.c @@ -1,8 +1,8 @@ /* LV2 Sampler Example Plugin + Copyright 2011-2012 David Robillard <d@drobilla.net> Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org> Copyright 2011 James Morris <jwm.art.net@gmail.com> - Copyright 2011 David Robillard <d@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 @@ -24,11 +24,11 @@ incoming events) and also triggers their playback (based on incoming MIDI note events). The sample must be monophonic. - So that the runSampler() method stays real-time safe, the plugin creates a - worker thread (worker_thread_main) that listens for file loading events. It - loads everything in plugin->pending_samp and then signals the runSampler() - that it's time to install it. runSampler() just has to swap pointers... so - the change happens very fast and atomically. + So that the run() method stays real-time safe, the plugin creates a worker + thread (worker_thread_main) that listens for file loading events. It loads + everything in plugin->pending_samp and then signals the run() that it's time + to install it. run() just has to swap pointers... so the change happens + very fast and atomically. */ #include <assert.h> @@ -41,7 +41,8 @@ #include <sndfile.h> -#include "lv2/lv2plug.in/ns/ext/atom/atom-buffer.h" +#include <semaphore.h> + #include "lv2/lv2plug.in/ns/ext/atom/atom-helpers.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" #include "lv2/lv2plug.in/ns/ext/urid/urid.h" @@ -69,22 +70,21 @@ typedef struct { LV2_URID_Map* map; /* Sample */ - SampleFile* samp; - SampleFile* pending_samp; - pthread_mutex_t pending_samp_mutex; /**< Protects pending_samp */ - pthread_cond_t pending_samp_cond; /**< Signaling mechanism */ - int pending_sample_ready; + SampleFile* samp; + SampleFile* pending_samp; + sem_t signal; + int pending_sample_ready; /* Ports */ - float* output_port; - LV2_Atom_Buffer* event_port; + float* output_port; + LV2_Atom_Sequence* event_port; /* URIs */ struct { - LV2_URID midi_event; - LV2_URID atom_message; + LV2_URID midi_Event; + LV2_URID atom_Object; LV2_URID set_message; - LV2_URID state_path; + LV2_URID state_Path; LV2_URID filename_key; } uris; @@ -139,16 +139,16 @@ worker_thread_main(void* arg) { Sampler* plugin = (Sampler*)arg; - pthread_mutex_lock(&plugin->pending_samp_mutex); while (true) { /* Wait for run() to signal that we need to load a sample */ - pthread_cond_wait(&plugin->pending_samp_cond, - &plugin->pending_samp_mutex); + if (sem_wait(&plugin->signal)) { + fprintf(stderr, "Odd, sem_wait failed...\n"); + continue; + } /* Then load it */ handle_load_sample(plugin); } - pthread_mutex_unlock(&plugin->pending_samp_mutex); return 0; } @@ -159,6 +159,7 @@ cleanup(LV2_Handle instance) Sampler* plugin = (Sampler*)instance; pthread_cancel(plugin->worker_thread); pthread_join(plugin->worker_thread, 0); + sem_destroy(&plugin->signal); free(plugin->samp->data); free(plugin->pending_samp->data); @@ -176,7 +177,7 @@ connect_port(LV2_Handle instance, switch (port) { case SAMPLER_CONTROL: - plugin->event_port = (LV2_Atom_Buffer*)data; + plugin->event_port = (LV2_Atom_Sequence*)data; break; case SAMPLER_OUT: plugin->output_port = (float*)data; @@ -196,7 +197,7 @@ instantiate(const LV2_Descriptor* descriptor, if (!plugin) { return NULL; } - + plugin->samp = (SampleFile*)malloc(sizeof(SampleFile)); plugin->pending_samp = (SampleFile*)malloc(sizeof(SampleFile)); if (!plugin->samp || !plugin->pending_samp) { @@ -207,15 +208,13 @@ instantiate(const LV2_Descriptor* descriptor, memset(plugin->pending_samp, 0, sizeof(SampleFile)); memset(&plugin->uris, 0, sizeof(plugin->uris)); - /* Initialise mutexes and conditions for the worker thread */ - if (pthread_mutex_init(&plugin->pending_samp_mutex, 0)) { - fprintf(stderr, "Could not initialize next_sample_mutex.\n"); - goto fail; - } - if (pthread_cond_init(&plugin->pending_samp_cond, 0)) { - fprintf(stderr, "Could not initialize next_sample_waitcond.\n"); + /* Create signal for waking up worker thread */ + if (sem_init(&plugin->signal, 0, 0)) { + fprintf(stderr, "Could not initialize semaphore.\n"); goto fail; } + + /* Create worker thread */ if (pthread_create(&plugin->worker_thread, 0, worker_thread_main, plugin)) { fprintf(stderr, "Could not initialize worker thread.\n"); goto fail; @@ -225,13 +224,13 @@ instantiate(const LV2_Descriptor* descriptor, for (int i = 0; features[i]; ++i) { if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { plugin->map = (LV2_URID_Map*)features[i]->data; - plugin->uris.midi_event = plugin->map->map( + plugin->uris.midi_Event = plugin->map->map( plugin->map->handle, MIDI_EVENT_URI); - plugin->uris.atom_message = plugin->map->map( - plugin->map->handle, ATOM_MESSAGE_URI); + plugin->uris.atom_Object = plugin->map->map( + plugin->map->handle, ATOM_OBJECT_URI); plugin->uris.set_message = plugin->map->map( plugin->map->handle, SET_MESSAGE_URI); - plugin->uris.state_path = plugin->map->map( + plugin->uris.state_Path = plugin->map->map( plugin->map->handle, LV2_STATE_PATH_URI); plugin->uris.filename_key = plugin->map->map( plugin->map->handle, FILENAME_URI); @@ -267,39 +266,35 @@ run(LV2_Handle instance, float* output = plugin->output_port; /* Read incoming events */ - for (LV2_Atom_Buffer_Iterator i = lv2_atom_buffer_begin(plugin->event_port); - lv2_atom_buffer_is_valid(i); - i = lv2_atom_buffer_next(i)) { - - LV2_Atom_Event* const ev = lv2_atom_buffer_get(i); - if (ev->body.type == plugin->uris.midi_event) { + LV2_SEQUENCE_FOREACH(plugin->event_port, i) { + LV2_Atom_Event* const ev = lv2_sequence_iter_get(i); + if (ev->body.type == plugin->uris.midi_Event) { uint8_t* const data = (uint8_t* const)(ev + 1); if ((data[0] & 0xF0) == 0x90) { - start_frame = ev->frames; + start_frame = ev->time.audio.frames; plugin->frame = 0; plugin->play = true; } - } else if (ev->body.type == plugin->uris.atom_message) { - const LV2_Thing* msg = (LV2_Thing*)&ev->body; - if (msg->id == plugin->uris.set_message) { + } else if (ev->body.type == plugin->uris.atom_Object) { + const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body; + if (obj->type == plugin->uris.set_message) { const LV2_Atom* filename = NULL; - LV2_Thing_Query q[] = { + LV2_Atom_Object_Query q[] = { { plugin->uris.filename_key, &filename }, - LV2_THING_QUERY_END + LV2_OBJECT_QUERY_END }; - lv2_thing_query(msg, q); + lv2_object_get(obj, q); if (filename) { - memcpy(plugin->pending_samp->filepath, - filename->body, - filename->size); - pthread_cond_signal(&plugin->pending_samp_cond); - + char* str = (char*)LV2_ATOM_BODY(filename); + fprintf(stderr, "Request to load %s\n", str); + memcpy(plugin->pending_samp->filepath, str, filename->size); + sem_post(&plugin->signal); } else { fprintf(stderr, "Ignored set message with no filename\n"); } } else { - fprintf(stderr, "Unknown message type %d\n", msg->id); + fprintf(stderr, "Unknown message type %d\n", obj->id); } } else { @@ -328,18 +323,13 @@ run(LV2_Handle instance, } /* Check if we have a sample pending */ - if (!plugin->play - && plugin->pending_sample_ready - && pthread_mutex_trylock(&plugin->pending_samp_mutex)) { + if (!plugin->play && plugin->pending_sample_ready) { /* Install the new sample */ - SampleFile* tmp; - tmp = plugin->samp; + SampleFile* tmp = plugin->samp; plugin->samp = plugin->pending_samp; plugin->pending_samp = tmp; plugin->pending_sample_ready = 0; free(plugin->pending_samp->data); // FIXME: non-realtime! - - pthread_mutex_unlock(&plugin->pending_samp_mutex); } /* Add zeros to end if sample not long enough (or not playing) */ @@ -376,7 +366,7 @@ save(LV2_Handle instance, map_uri(plugin, FILENAME_URI), apath, strlen(plugin->samp->filepath) + 1, - plugin->uris.state_path, + plugin->uris.state_Path, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); free(apath); diff --git a/plugins/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl index 54eb36b..7548396 100644 --- a/plugins/eg-sampler.lv2/sampler.ttl +++ b/plugins/eg-sampler.lv2/sampler.ttl @@ -25,13 +25,14 @@ a lv2:Plugin ; doap:name "Example Sampler" ; doap:license <http://opensource.org/licenses/isc> ; - lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#Mapper> ; - lv2:optionalFeature lv2:hardRtCapable ; + lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> ; + lv2:optionalFeature lv2:hardRTCapable ; lv2:extensionData <http://lv2plug.in/ns/ext/state#Interface> ; ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ; lv2:port [ a lv2:InputPort , - atom:EventPort ; + atom:MessagePort ; + atom:bufferType atom:Sequence ; atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ; lv2:index 0 ; lv2:symbol "control" ; @@ -46,5 +47,5 @@ <http://lv2plug.in/plugins/eg-sampler#ui> a ui:GtkUI ; - lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#Mapper> ; + lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> ; ui:binary <sampler_ui.so> . diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c index dcc808f..ea57cc1 100644 --- a/plugins/eg-sampler.lv2/sampler_ui.c +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -34,8 +34,9 @@ #define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui" typedef struct { + LV2_Atom_Forge forge; + LV2_URID_Map* map; - LV2_Atom_Forge* forge; LV2UI_Write_Function write; LV2UI_Controller controller; @@ -71,19 +72,20 @@ on_load_clicked(GtkWidget* widget, char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); const size_t filename_len = strlen(filename); gtk_widget_destroy(dialog); - - uint8_t msg_buf[4096]; - LV2_Thing* msg = (LV2_Thing*)msg_buf; - lv2_atom_forge_set_message(ui->forge, msg, uri_to_id(ui, SET_MESSAGE_URI)); - lv2_thing_append(msg, - uri_to_id(ui, FILENAME_URI), - uri_to_id(ui, NS_ATOM "String"), - filename_len, - filename); - - ui->write(ui->controller, 0, sizeof(LV2_Atom) + msg->size, + +#define OBJ_BUF_SIZE 1024 + uint8_t obj_buf[OBJ_BUF_SIZE]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE); + + LV2_Atom* obj = (LV2_Atom*)lv2_atom_forge_object( + &ui->forge, NULL, 0, uri_to_id(ui, SET_MESSAGE_URI)); + lv2_atom_forge_property_head(&ui->forge, obj, + uri_to_id(ui, FILENAME_URI), 0); + lv2_atom_forge_string(&ui->forge, obj, (uint8_t*)filename, filename_len); + + ui->write(ui->controller, 0, sizeof(LV2_Atom) + obj->size, uri_to_id(ui, NS_ATOM "atomTransfer"), - msg); + obj); g_free(filename); } @@ -98,7 +100,7 @@ instantiate(const LV2UI_Descriptor* descriptor, const LV2_Feature* const* features) { SamplerUI* ui = (SamplerUI*)malloc(sizeof(SamplerUI)); - ui->map = NULL; + ui->map = NULL; ui->write = write_function; ui->controller = controller; ui->button = NULL; @@ -117,7 +119,7 @@ instantiate(const LV2UI_Descriptor* descriptor, return NULL; } - ui->forge = lv2_atom_forge_new(ui->map); + lv2_atom_forge_init(&ui->forge, ui->map); ui->button = gtk_button_new_with_label("Load Sample"); g_signal_connect(ui->button, "clicked", diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h index d4df237..5e968ed 100644 --- a/plugins/eg-sampler.lv2/uris.h +++ b/plugins/eg-sampler.lv2/uris.h @@ -18,8 +18,8 @@ #define NS_ATOM "http://lv2plug.in/ns/ext/atom#" #define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" -#define SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" -#define MIDI_EVENT_URI "http://lv2plug.in/ns/ext/midi#MidiEvent" -#define FILENAME_URI SAMPLER_URI "#filename" -#define ATOM_MESSAGE_URI "http://lv2plug.in/ns/ext/atom#Message" -#define SET_MESSAGE_URI "http: //example.org/set" +#define SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler" +#define MIDI_EVENT_URI "http://lv2plug.in/ns/ext/midi#MidiEvent" +#define FILENAME_URI SAMPLER_URI "#filename" +#define ATOM_OBJECT_URI NS_ATOM "Object" +#define SET_MESSAGE_URI "http: //example.org/set" @@ -26,6 +26,8 @@ def options(opt): opt.load('compiler_cc') opt.load('compiler_cxx') autowaf.set_options(opt) + opt.add_option('--test', action='store_true', default=False, dest='build_tests', + help="Build unit tests") opt.add_option('--experimental', action='store_true', default=False, dest='experimental', help='Install unreleased experimental extensions') |