aboutsummaryrefslogtreecommitdiff
path: root/props.lv2
diff options
context:
space:
mode:
Diffstat (limited to 'props.lv2')
-rw-r--r--props.lv2/CMakeLists.txt41
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/props.h1333
-rw-r--r--props.lv2/test/chunk.binbin0 -> 16 bytes
-rw-r--r--props.lv2/test/manifest.ttl.in28
-rw-r--r--props.lv2/test/props.c580
-rw-r--r--props.lv2/test/props.ttl152
8 files changed, 2355 insertions, 0 deletions
diff --git a/props.lv2/CMakeLists.txt b/props.lv2/CMakeLists.txt
new file mode 100644
index 0000000..2fff6f0
--- /dev/null
+++ b/props.lv2/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(props.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+include_directories(${PROJECT_BINARY_DIR})
+
+set(CMAKE_C_FLAGS "-std=gnu99 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}")
+add_definitions("-D_GNU_SOURCE=1") # asprintf
+
+set(PROPS_MAJOR_VERSION 0)
+set(PROPS_MINOR_VERSION 1)
+set(PROPS_MICRO_VERSION 1)
+set(PROPS_VERSION "${PROPS_MAJOR_VERSION}.${PROPS_MINOR_VERSION}.${PROPS_MICRO_VERSION}")
+add_definitions("-DPROPS_VERSION=\"${PROPS_VERSION}\"")
+
+set(DEST lib/lv2/props.lv2)
+if(WIN32)
+ set(LIB_EXT ".dll")
+elseif(APPLE)
+ set(LIB_EXT ".so")
+else()
+ set(LIB_EXT ".so")
+endif()
+
+find_package(PkgConfig) # ${PKG_CONFIG_FOUND}
+
+# props
+add_library(props MODULE
+ test/props.c)
+target_link_libraries(props ${LIBS})
+set_target_properties(props PROPERTIES PREFIX "")
+install(TARGETS props DESTINATION ${DEST})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/props.ttl DESTINATION ${DEST})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/chunk.bin DESTINATION ${DEST})
+
+# manifest
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
+install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${DEST})
diff --git a/props.lv2/COPYING b/props.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/props.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/props.lv2/README.md b/props.lv2/README.md
new file mode 100644
index 0000000..08466d2
--- /dev/null
+++ b/props.lv2/README.md
@@ -0,0 +1,20 @@
+# Props.lv2
+
+## Utility header for property based LV2 plugins
+
+### License
+
+Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/props.lv2/props.h b/props.lv2/props.h
new file mode 100644
index 0000000..d147e04
--- /dev/null
+++ b/props.lv2/props.h
@@ -0,0 +1,1333 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _LV2_PROPS_H_
+#define _LV2_PROPS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdatomic.h>
+#include <stdio.h>
+
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+#include <lv2/lv2plug.in/ns/extensions/units/units.h>
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+// definitions
+#define PROPS_TYPE_N 10
+
+// unions
+typedef union _props_raw_t props_raw_t;
+
+// enumerations
+typedef enum _props_mode_t props_mode_t;
+typedef enum _props_event_t props_event_t;
+
+// structures
+typedef struct _props_scale_point_t props_scale_point_t;
+typedef struct _props_def_t props_def_t;
+typedef struct _props_type_t props_type_t;
+typedef struct _props_impl_t props_impl_t;
+typedef struct _props_t props_t;
+
+// function callbacks
+typedef void (*props_event_cb_t)(
+ void *data,
+ LV2_Atom_Forge *forge,
+ int64_t frames,
+ props_event_t event,
+ props_impl_t *impl);
+
+typedef uint32_t (*props_type_size_cb_t)(
+ const void *value);
+
+typedef LV2_Atom_Forge_Ref (*props_type_get_cb_t)(
+ LV2_Atom_Forge *forge,
+ const void *value);
+
+typedef void (*props_type_set_cb_t)(
+ props_impl_t *impl,
+ void *value,
+ LV2_URID new_type,
+ uint32_t sz,
+ const void *new_value);
+
+union _props_raw_t {
+ const int32_t i; // Int
+ const int64_t h; // Long
+ const float f; // Float
+ const double d; // Double
+ const int32_t b; // Bool
+ const uint32_t u; // URID
+ //TODO more types
+};
+
+enum _props_mode_t {
+ PROP_MODE_STATIC = 0,
+ PROP_MODE_DYNAMIC = 1
+};
+
+enum _props_event_t {
+ PROP_EVENT_GET = (1 << 0),
+ PROP_EVENT_SET = (1 << 1),
+ PROP_EVENT_SAVE = (1 << 2),
+ PROP_EVENT_RESTORE = (1 << 3),
+ PROP_EVENT_REGISTER = (1 << 4)
+};
+
+#define PROP_EVENT_NONE (0)
+#define PROP_EVENT_READ (PROP_EVENT_GET | PROP_EVENT_SAVE)
+#define PROP_EVENT_WRITE (PROP_EVENT_SET | PROP_EVENT_RESTORE)
+#define PROP_EVENT_RW (PROP_EVENT_READ | PROP_EVENT_WRITE)
+#define PROP_EVENT_ALL (PROP_EVENT_RW | PROP_EVENT_REGISTER)
+
+struct _props_scale_point_t {
+ const char *label;
+ props_raw_t value;
+};
+
+struct _props_def_t {
+ const char *property;
+ const char *type;
+ const char *access;
+ const char *unit;
+ props_mode_t mode;
+ props_event_t event_mask;
+ props_event_cb_t event_cb;
+ uint32_t max_size;
+
+ const char *label;
+ const char *comment;
+ props_raw_t minimum;
+ props_raw_t maximum;
+ const props_scale_point_t *scale_points;
+};
+
+struct _props_type_t {
+ LV2_URID urid;
+ uint32_t size;
+ props_type_size_cb_t size_cb;
+ props_type_get_cb_t get_cb;
+ props_type_set_cb_t set_cb;
+};
+
+struct _props_impl_t {
+ LV2_URID property;
+ LV2_URID access;
+ LV2_URID unit;
+ const props_t *props;
+ const props_type_t *type;
+ const props_def_t *def;
+ void *value;
+ void *stash;
+ atomic_flag lock;
+ bool stashing;
+};
+
+struct _props_t {
+ struct {
+ LV2_URID subject;
+
+ LV2_URID patch_get;
+ LV2_URID patch_set;
+ LV2_URID patch_put;
+ LV2_URID patch_patch;
+ LV2_URID patch_wildcard;
+ LV2_URID patch_add;
+ LV2_URID patch_remove;
+ LV2_URID patch_subject;
+ LV2_URID patch_body;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_writable;
+ LV2_URID patch_readable;
+
+ LV2_URID rdf_value;
+
+ LV2_URID rdfs_label;
+ LV2_URID rdfs_range;
+ LV2_URID rdfs_comment;
+
+ LV2_URID lv2_minimum;
+ LV2_URID lv2_maximum;
+ LV2_URID lv2_scale_point;
+
+ LV2_URID atom_int;
+ LV2_URID atom_long;
+ LV2_URID atom_float;
+ LV2_URID atom_double;
+ LV2_URID atom_bool;
+ LV2_URID atom_urid;
+ LV2_URID atom_string;
+ LV2_URID atom_path;
+ LV2_URID atom_uri;
+ LV2_URID atom_chunk;
+
+ LV2_URID units_unit;
+ } urid;
+
+ LV2_URID_Map *map;
+ void *data;
+
+ props_type_t types [PROPS_TYPE_N];
+
+ bool stashing;
+
+ unsigned max_size;
+ unsigned max_nimpls;
+ unsigned nimpls;
+ props_impl_t impls [0];
+};
+
+#define PROPS_T(PROPS, MAX_NIMPLS) \
+ props_t (PROPS); \
+ props_impl_t _impls [(MAX_NIMPLS)];
+
+// rt-safe
+static inline int
+props_init(props_t *props, const size_t max_nimpls, const char *subject,
+ LV2_URID_Map *map, void *data);
+
+// rt-safe
+static inline LV2_URID
+props_register(props_t *props, const props_def_t *def, void *value, void *stash);
+
+// rt-safe
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, LV2_URID property,
+ LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_stash(props_t *props, LV2_URID property);
+
+// non-rt
+static inline LV2_State_Status
+props_save(props_t *props, LV2_Atom_Forge *forge, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+// non-rt
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_Atom_Forge *forge, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+static inline void
+_impl_spin_lock(props_impl_t *impl)
+{
+ while(atomic_flag_test_and_set_explicit(&impl->lock, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_impl_try_lock(props_impl_t *impl)
+{
+ return atomic_flag_test_and_set_explicit(&impl->lock, memory_order_acquire) == false;
+}
+
+static inline void
+_impl_unlock(props_impl_t *impl)
+{
+ atomic_flag_clear_explicit(&impl->lock, memory_order_release);
+}
+
+static inline uint32_t
+_impl_size_get(props_impl_t *impl)
+{
+ return impl->type->size_cb
+ ? impl->type->size_cb(impl->value)
+ : impl->type->size;
+}
+
+static LV2_Atom_Forge_Ref
+_props_int_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_int(forge, *(const int32_t *)value);
+}
+static LV2_Atom_Forge_Ref
+_props_bool_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_bool(forge, *(const int32_t *)value);
+}
+static void
+_props_int_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ int32_t *ref = value;
+
+ if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_long_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_long(forge, *(const int64_t *)value);
+}
+static void
+_props_long_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ int64_t *ref = value;
+
+ if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_float_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_float(forge, *(const float *)value);
+}
+static void
+_props_float_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ float *ref = value;
+
+ if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_double_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_double(forge, *(const double *)value);
+}
+static void
+_props_double_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ double *ref = value;
+
+ if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_urid_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_urid(forge, *(const uint32_t *)value);
+}
+static void
+_props_urid_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ uint32_t *ref = value;
+
+ if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static uint32_t
+_props_string_size_cb(const void *value)
+{
+ return strlen((const char *)value) + 1;
+}
+static LV2_Atom_Forge_Ref
+_props_string_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_string(forge, (const char *)value, strlen((const char *)value));
+}
+static LV2_Atom_Forge_Ref
+_props_path_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_path(forge, (const char *)value, strlen((const char *)value));
+}
+static LV2_Atom_Forge_Ref
+_props_uri_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_uri(forge, (const char *)value, strlen((const char *)value));
+}
+static void
+_props_string_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+
+ if( (new_type == props->urid.atom_string)
+ || (new_type == props->urid.atom_path)
+ || (new_type == props->urid.atom_uri) )
+ strncpy((char *)value, (const char *)new_value, impl->def->max_size);
+}
+
+static uint32_t
+_props_chunk_size_cb(const void *value)
+{
+ const uint32_t sz = *(uint32_t *)value;
+ return sz;
+}
+static LV2_Atom_Forge_Ref
+_props_chunk_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ const uint32_t sz = *(uint32_t *)value;
+ const uint8_t *src = value + sizeof(uint32_t);
+ LV2_Atom_Forge_Ref ref;
+
+ return (ref = lv2_atom_forge_atom(forge, sz, forge->Chunk))
+ && (ref = lv2_atom_forge_write(forge, src, sz));
+}
+static void
+_props_chunk_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+
+ if(new_type == props->urid.atom_chunk)
+ {
+ *(uint32_t *)value = sz; // set chunk size
+ uint8_t *dst = value + sizeof(uint32_t);
+ const uint32_t msz = sz < impl->def->max_size - sizeof(uint32_t)
+ ? sz
+ : impl->def->max_size - sizeof(uint32_t);
+ memcpy(dst, new_value, msz);
+ }
+}
+
+static inline void
+_type_qsort(props_type_t *a, unsigned n)
+{
+ if(n < 2)
+ return;
+
+ const props_type_t *p = &a[n/2];
+
+ unsigned i, j;
+ for(i=0, j=n-1; ; i++, j--)
+ {
+ while(a[i].urid < p->urid)
+ i++;
+
+ while(p->urid < a[j].urid)
+ j--;
+
+ if(i >= j)
+ break;
+
+ const props_type_t t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+
+ _type_qsort(a, i);
+ _type_qsort(&a[i], n - i);
+}
+
+static inline props_type_t *
+_type_bsearch(LV2_URID p, props_type_t *a, unsigned n)
+{
+ unsigned start = 0;
+ unsigned end = n;
+
+ while(start < end)
+ {
+ const unsigned mid = start + (end - start)/2;
+ props_type_t *dst = &a[mid];
+
+ if(p < dst->urid)
+ end = mid;
+ else if(p > dst->urid)
+ start = mid + 1;
+ else
+ return dst;
+ }
+
+ return NULL;
+}
+
+static inline void
+_impl_qsort(props_impl_t *a, unsigned n)
+{
+ if(n < 2)
+ return;
+
+ const props_impl_t *p = &a[n/2];
+
+ unsigned i, j;
+ for(i=0, j=n-1; ; i++, j--)
+ {
+ while(a[i].property < p->property)
+ i++;
+
+ while(p->property < a[j].property)
+ j--;
+
+ if(i >= j)
+ break;
+
+ const props_impl_t t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+
+ _impl_qsort(a, i);
+ _impl_qsort(&a[i], n - i);
+}
+
+static inline props_impl_t *
+_impl_bsearch(LV2_URID p, props_impl_t *a, unsigned n)
+{
+ unsigned start = 0;
+ unsigned end = n;
+
+ while(start < end)
+ {
+ const unsigned mid = start + (end - start)/2;
+ props_impl_t *dst = &a[mid];
+
+ if(p < dst->property)
+ end = mid;
+ else if(p > dst->property)
+ start = mid + 1;
+ else
+ return dst;
+ }
+
+ return NULL;
+}
+
+static inline props_impl_t *
+_props_impl_search(props_t *props, LV2_URID property)
+{
+ return _impl_bsearch(property, props->impls, props->nimpls);
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, props_impl_t *impl)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_set);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+
+ if(ref)
+ lv2_atom_forge_key(forge, props->urid.patch_value);
+ if(ref)
+ ref = impl->type->get_cb(forge, impl->value);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline void
+_props_stash(props_t *props, props_impl_t *impl)
+{
+ if(_impl_try_lock(impl))
+ {
+ const uint32_t size = _impl_size_get(impl);
+ memcpy(impl->stash, impl->value, size);
+
+ _impl_unlock(impl);
+ }
+ else
+ {
+ impl->stashing = true;
+ props->stashing= true;
+ }
+}
+
+static inline void
+_props_set(props_t *props, props_impl_t *impl, LV2_URID type, uint32_t sz, const void *value)
+{
+ impl->type->set_cb(impl, impl->value, type, sz, value);
+ _props_stash(props, impl);
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_reg(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, props_impl_t *impl)
+{
+ const props_def_t *def = impl->def;
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Frame add_frame;
+ LV2_Atom_Forge_Frame remove_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_patch);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_remove);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->access);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_add);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->access);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_patch);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_remove);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_range);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_comment);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_minimum);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_maximum);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.units_unit);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_scale_point);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_add);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_range);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->type->urid);
+
+ if(def->label)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, def->label, strlen(def->label));
+ }
+
+ if(def->comment)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_comment);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, def->comment, strlen(def->comment));
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_minimum);
+ if(ref)
+ ref = impl->type->get_cb(forge, &def->minimum);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_maximum);
+ if(ref)
+ ref = impl->type->get_cb(forge, &def->maximum);
+
+ if(props->urid.units_unit)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.units_unit);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->unit);
+ }
+
+ if(def->scale_points)
+ {
+ LV2_Atom_Forge_Frame tuple_frame;
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_scale_point);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &tuple_frame);
+
+ for(const props_scale_point_t *sp = def->scale_points; sp->label; sp++)
+ {
+ LV2_Atom_Forge_Frame scale_point_frame;
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &scale_point_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, sp->label, strlen(sp->label));
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdf_value);
+ if(ref)
+ ref = impl->type->get_cb(forge, &sp->value);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &scale_point_frame);
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &tuple_frame);
+ }
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline int
+props_init(props_t *props, const size_t max_nimpls, const char *subject,
+ LV2_URID_Map *map, void *data)
+{
+ if(!map)
+ return 0;
+
+ props->nimpls = 0;
+ props->max_nimpls = max_nimpls;
+ props->map = map;
+ props->data = data;
+
+ props->urid.subject = subject ? map->map(map->handle, subject) : 0;
+
+ props->urid.patch_get = map->map(map->handle, LV2_PATCH__Get);
+ props->urid.patch_set = map->map(map->handle, LV2_PATCH__Set);
+ props->urid.patch_put = map->map(map->handle, LV2_PATCH__Put);
+ props->urid.patch_patch = map->map(map->handle, LV2_PATCH__Patch);
+ props->urid.patch_wildcard = map->map(map->handle, LV2_PATCH__wildcard);
+ props->urid.patch_add = map->map(map->handle, LV2_PATCH__add);
+ props->urid.patch_remove = map->map(map->handle, LV2_PATCH__remove);
+ props->urid.patch_subject = map->map(map->handle, LV2_PATCH__subject);
+ props->urid.patch_body = map->map(map->handle, LV2_PATCH__body);
+ props->urid.patch_property = map->map(map->handle, LV2_PATCH__property);
+ props->urid.patch_value = map->map(map->handle, LV2_PATCH__value);
+ props->urid.patch_writable = map->map(map->handle, LV2_PATCH__writable);
+ props->urid.patch_readable = map->map(map->handle, LV2_PATCH__readable);
+
+ props->urid.rdf_value = map->map(map->handle,
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#value");
+
+ props->urid.rdfs_label = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#label");
+ props->urid.rdfs_range = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#range");
+ props->urid.rdfs_comment = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#comment");
+
+ props->urid.lv2_minimum = map->map(map->handle, LV2_CORE__minimum);
+ props->urid.lv2_maximum = map->map(map->handle, LV2_CORE__maximum);
+ props->urid.lv2_scale_point = map->map(map->handle, LV2_CORE__scalePoint);
+
+ props->urid.atom_int = map->map(map->handle, LV2_ATOM__Int);
+ props->urid.atom_long = map->map(map->handle, LV2_ATOM__Long);
+ props->urid.atom_float = map->map(map->handle, LV2_ATOM__Float);
+ props->urid.atom_double = map->map(map->handle, LV2_ATOM__Double);
+ props->urid.atom_bool = map->map(map->handle, LV2_ATOM__Bool);
+ props->urid.atom_urid = map->map(map->handle, LV2_ATOM__URID);
+ props->urid.atom_string = map->map(map->handle, LV2_ATOM__String);
+ props->urid.atom_path = map->map(map->handle, LV2_ATOM__Path);
+ props->urid.atom_uri = map->map(map->handle, LV2_ATOM__URI);
+ props->urid.atom_chunk = map->map(map->handle, LV2_ATOM__Chunk);
+
+ props->urid.units_unit = map->map(map->handle, LV2_UNITS__unit);
+
+ // Int
+ unsigned ptr = 0;
+ props->types[ptr].urid = props->urid.atom_int;
+ props->types[ptr].size = sizeof(int32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_int_get_cb;
+ props->types[ptr].set_cb = _props_int_set_cb;
+ ptr++;
+
+ // Long
+ props->types[ptr].urid = props->urid.atom_long;
+ props->types[ptr].size = sizeof(int64_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_long_get_cb;
+ props->types[ptr].set_cb = _props_long_set_cb;
+ ptr++;
+
+ // Float
+ props->types[ptr].urid = props->urid.atom_float;
+ props->types[ptr].size = sizeof(float);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_float_get_cb;
+ props->types[ptr].set_cb = _props_float_set_cb;
+ ptr++;
+
+ // double
+ props->types[ptr].urid = props->urid.atom_double;
+ props->types[ptr].size = sizeof(double);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_double_get_cb;
+ props->types[ptr].set_cb = _props_double_set_cb;
+ ptr++;
+
+ // Bool
+ props->types[ptr].urid = props->urid.atom_bool;
+ props->types[ptr].size = sizeof(int32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_bool_get_cb;
+ props->types[ptr].set_cb = _props_int_set_cb;
+ ptr++;
+
+ // URID
+ props->types[ptr].urid = props->urid.atom_urid;
+ props->types[ptr].size = sizeof(uint32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_urid_get_cb;
+ props->types[ptr].set_cb = _props_urid_set_cb;
+ ptr++;
+
+ // String
+ props->types[ptr].urid = props->urid.atom_string;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_string_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // Path
+ props->types[ptr].urid = props->urid.atom_path;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_path_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // URI
+ props->types[ptr].urid = props->urid.atom_uri;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_uri_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // URI
+ props->types[ptr].urid = props->urid.atom_chunk;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_chunk_size_cb;
+ props->types[ptr].get_cb = _props_chunk_get_cb;
+ props->types[ptr].set_cb = _props_chunk_set_cb;
+ ptr++;
+
+ assert(ptr == PROPS_TYPE_N);
+ _type_qsort(props->types, PROPS_TYPE_N);
+
+ return 1;
+}
+
+static inline LV2_URID
+props_register(props_t *props, const props_def_t *def, void *value, void *stash)
+{
+ if(props->nimpls >= props->max_nimpls)
+ return 0;
+
+ if(!def || !def->property || !def->access || !def->type || !value || !stash)
+ return 0;
+
+ const LV2_URID type = props->map->map(props->map->handle, def->type);
+ const props_type_t *props_type = _type_bsearch(type, props->types, PROPS_TYPE_N);
+ const LV2_URID property = props->map->map(props->map->handle, def->property);
+ const LV2_URID access = props->map->map(props->map->handle, def->access);
+
+ if(!props_type || !property || !access)
+ return 0;
+
+ props_impl_t *impl = &props->impls[props->nimpls++];
+
+ impl->props = props;
+ impl->property = property;
+ impl->access = access;
+ impl->unit = def->unit ? props->map->map(props->map->handle, def->unit) : 0;
+ impl->type = props_type;
+ impl->def = def;
+ impl->value = value;
+ impl->stash = stash;
+ atomic_flag_clear_explicit(&impl->lock, memory_order_relaxed);
+
+ // update maximal value size
+ if(props_type->size && (props_type->size > props->max_size) )
+ props->max_size = props_type->size;
+ else if(def->max_size && (def->max_size > props->max_size) )
+ props->max_size = def->max_size;
+
+ _impl_qsort(props->impls, props->nimpls);
+
+ //TODO register?
+
+ return property;
+}
+
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref)
+{
+ if(props->stashing)
+ {
+ props->stashing = false;
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->stashing)
+ {
+ impl->stashing= false;
+ _props_stash(props, impl);
+ }
+ }
+ }
+
+ if(!lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ return 0;
+
+ if(obj->body.otype == props->urid.patch_get)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_property, (const LV2_Atom **)&property },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ if(!property)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+ const props_def_t *def = impl->def;
+
+ if(impl->def->mode == PROP_MODE_DYNAMIC)
+ {
+ if(*ref)
+ *ref = _props_reg(props, forge, frames, impl);
+ if(def->event_cb && (def->event_mask & PROP_EVENT_REGISTER) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_REGISTER, impl);
+ }
+
+ if(*ref)
+ *ref = _props_get(props, forge, frames, impl);
+ if(def->event_cb && (def->event_mask & PROP_EVENT_GET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_GET, impl);
+ }
+ return 1;
+ }
+ else if(property->atom.type == props->urid.atom_urid)
+ {
+ props_impl_t *impl = _props_impl_search(props, property->body);
+
+ if(impl)
+ {
+ *ref = _props_get(props, forge, frames, impl);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_GET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_GET, impl);
+
+ return 1;
+ }
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_set)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom *value = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_property, (const LV2_Atom **)&property },
+ { props->urid.patch_value, &value },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ if(!property || (property->atom.type != props->urid.atom_urid) || !value)
+ {
+ return 0;
+ }
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ props_impl_t *impl = _props_impl_search(props, property->body);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_set(props, impl, value->type, value->size, LV2_ATOM_BODY_CONST(value));
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_SET, impl);
+
+ return 1;
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_put)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Object *body = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_body, (const LV2_Atom **)&body },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ if(!body || !lv2_atom_forge_is_object_type(forge, body->atom.type))
+ {
+ return 0;
+ }
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ LV2_ATOM_OBJECT_FOREACH(body, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ props_impl_t *impl = _props_impl_search(props, property);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_set(props, impl, value->type, value->size, LV2_ATOM_BODY_CONST(value));
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_SET, impl);
+ }
+ }
+ return 1;
+ }
+
+ return 0; // did not handle a patch event
+}
+
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, LV2_URID property,
+ LV2_Atom_Forge_Ref *ref)
+{
+ props_impl_t *impl = _props_impl_search(props, property);
+
+ if(impl)
+ {
+ _props_stash(props, impl);
+ if(*ref)
+ *ref = _props_get(props, forge, frames, impl);
+ }
+}
+
+static inline void
+props_stash(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_search(props, property);
+
+ if(impl)
+ _props_stash(props, impl);
+}
+
+static inline LV2_State_Status
+props_save(props_t *props, LV2_Atom_Forge *forge, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+
+ // set POD flag if not already set by host
+ flags |= LV2_STATE_IS_POD;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ {
+ map_path = features[i]->data;
+ break;
+ }
+ }
+
+ void *value = malloc(props->max_size); // create memory to store widest value
+ if(value)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ // create lockfree copy of value, store() may well be blocking
+ _impl_spin_lock(impl);
+
+ const uint32_t size = _impl_size_get(impl);
+ memcpy(value, impl->stash, size);
+
+ _impl_unlock(impl);
+
+ if( map_path && (impl->type->urid == forge->Path) )
+ {
+ const char *path = strstr(value, "file://")
+ ? value + 7 // skip "file://"
+ : value;
+ char *abstract = map_path->abstract_path(map_path->handle, path);
+ if(abstract && strcmp(abstract, path))
+ {
+ store(state, impl->property, abstract, strlen(abstract) + 1, impl->type->urid, flags);
+ free(abstract);
+ }
+ }
+ else // !Path
+ {
+ store(state, impl->property, value, size, impl->type->urid, flags);
+ }
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SAVE) )
+ def->event_cb(props->data, forge, 0, PROP_EVENT_SAVE, impl);
+ }
+
+ free(value);
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_Atom_Forge *forge, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ map_path = features[i]->data;
+ }
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ size_t size;
+ uint32_t type;
+ uint32_t _flags;
+ const void *value = retrieve(state, impl->property, &size, &type, &_flags);
+
+ if(value)
+ {
+ if( map_path && (impl->type->urid == forge->Path) )
+ {
+ char *absolute = map_path->absolute_path(map_path->handle, value);
+ if(absolute)
+ {
+ _props_set(props, impl, type, strlen(absolute) + 1, absolute);
+ free(absolute);
+ }
+ }
+ else // !Path
+ {
+ _props_set(props, impl, type, size, value);
+ }
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_RESTORE) )
+ def->event_cb(props->data, forge, 0, PROP_EVENT_RESTORE, impl);
+ }
+ else
+ {
+ fprintf(stderr, "props_restore: no property '%s'.\n", impl->def->property);
+ }
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+// undefinitions
+#undef PROPS_TYPE_N
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LV2_PROPS_H_
diff --git a/props.lv2/test/chunk.bin b/props.lv2/test/chunk.bin
new file mode 100644
index 0000000..b66efb8
--- /dev/null
+++ b/props.lv2/test/chunk.bin
Binary files differ
diff --git a/props.lv2/test/manifest.ttl.in b/props.lv2/test/manifest.ttl.in
new file mode 100644
index 0000000..c477aeb
--- /dev/null
+++ b/props.lv2/test/manifest.ttl.in
@@ -0,0 +1,28 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Orbit Looper
+props:test
+ a lv2:Plugin ;
+ lv2:minorVersion @PROPS_MINOR_VERSION@ ;
+ lv2:microVersion @PROPS_MICRO_VERSION@ ;
+ lv2:binary <props@LIB_EXT@> ;
+ rdfs:seeAlso <props.ttl> .
diff --git a/props.lv2/test/props.c b/props.lv2/test/props.c
new file mode 100644
index 0000000..516d48c
--- /dev/null
+++ b/props.lv2/test/props.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+
+#include <props.h>
+
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+
+#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#"
+#define PROPS_TEST_URI PROPS_PREFIX"test"
+
+#define MAX_NPROPS 33
+#define MAX_STRLEN 256
+
+typedef struct _plugstate0_t plugstate0_t;
+typedef struct _plugstate1_t plugstate1_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate0_t {
+ int32_t val1;
+ int64_t val2;
+ float val3;
+ double val4;
+ int32_t val5;
+ int32_t val6;
+ char val7 [MAX_STRLEN];
+};
+
+struct _plugstate1_t {
+ int32_t val1;
+ int64_t val2;
+ float val3;
+ double val4;
+ char val5 [MAX_STRLEN];
+ char val6 [MAX_STRLEN];
+ uint8_t val7 [MAX_STRLEN];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Log_Log *log;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_URID log_trace;
+ LV2_URID log_note;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ plugstate0_t dyn;
+ plugstate0_t _dyn;
+
+ plugstate1_t stat;
+ plugstate1_t _stat;
+
+ struct {
+ LV2_URID stat2;
+ LV2_URID stat4;
+ LV2_URID dyn2;
+ LV2_URID dyn4;
+ } urid;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+};
+
+static int
+_log_vprintf(plughandle_t *handle, LV2_URID type, const char *fmt, va_list args)
+{
+ return handle->log->vprintf(handle->log->handle, type, fmt, args);
+}
+
+// non-rt || rt with LV2_LOG__Trace
+static int
+_log_printf(plughandle_t *handle, LV2_URID type, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(handle, type, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+static void
+_intercept(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ switch(event)
+ {
+ case PROP_EVENT_GET:
+ {
+ _log_printf(handle, handle->log_trace, "GET : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_SET:
+ {
+ _log_printf(handle, handle->log_trace, "SET : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_REGISTER:
+ {
+ _log_printf(handle, handle->log_trace, "REGISTER: %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_SAVE:
+ {
+ _log_printf(handle, handle->log_note, "SAVE : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_RESTORE:
+ {
+ _log_printf(handle, handle->log_note, "RESTORE : %s", impl->def->label);
+ break;
+ }
+ }
+}
+
+static void
+_intercept_dyn1(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->dyn.val2 = handle->dyn.val1 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.dyn2, &handle->ref);
+ }
+}
+
+static void
+_intercept_dyn3(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->dyn.val4 = handle->dyn.val3 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.dyn4, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat1(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->stat.val2 = handle->stat.val1 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.stat2, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat3(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->stat.val4 = handle->stat.val3 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.stat4, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat6(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ const char *path = strstr(handle->stat.val6, "file://")
+ ? handle->stat.val6 + 7 // skip "file://"
+ : handle->stat.val6;
+ FILE *f = fopen(path, "wb"); // create empty file
+ if(f)
+ fclose(f);
+ }
+}
+
+static const props_def_t dyn1 = {
+ .label = "Int",
+ .comment = "This is a 32-bit integer",
+ .property = PROPS_PREFIX"Int",
+ .access = LV2_PATCH__writable,
+ .unit = LV2_UNITS__hz,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_dyn1,
+ .minimum.i = 0,
+ .maximum.i = 10
+};
+
+static const props_def_t dyn2 = {
+ .label = "Long",
+ .comment = "This is a 64-bit integer",
+ .property = PROPS_PREFIX"Long",
+ .access = LV2_PATCH__readable,
+ .unit = LV2_UNITS__khz,
+ .type = LV2_ATOM__Long,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.h = 0,
+ .maximum.h = 20
+};
+
+static const props_def_t dyn3 = {
+ .label = "Float",
+ .comment = "This is a 32-bit floating point",
+ .property = PROPS_PREFIX"Float",
+ .access = LV2_PATCH__writable,
+ .unit = LV2_UNITS__mhz,
+ .type = LV2_ATOM__Float,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_dyn3,
+ .minimum.f = -0.5f,
+ .maximum.f = 0.5f
+};
+
+static const props_def_t dyn4 = {
+ .label = "Double",
+ .comment = "This is a 64-bit floating point",
+ .property = PROPS_PREFIX"Double",
+ .access = LV2_PATCH__readable,
+ .unit = LV2_UNITS__db,
+ .type = LV2_ATOM__Double,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.d = -1.0,
+ .maximum.d = 1.0
+};
+
+static const props_scale_point_t scale_points5 [] = {
+ {.label = "One", .value.i = 0},
+ {.label = "Two", .value.i = 1},
+ {.label = "Three", .value.i = 2},
+ {.label = "Four", .value.i = 3},
+ {.label = NULL } // sentinel
+};
+
+static const props_def_t dyn5 = {
+ .label = "scaleInt",
+ .comment = "This is a 32-bit integer enumeration",
+ .property = PROPS_PREFIX"scaleInt",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.i = 0,
+ .maximum.i = 3,
+ .scale_points = scale_points5
+};
+
+static const props_def_t dyn6 = {
+ .label = "Bool",
+ .comment = "This is a boolean",
+ .property = PROPS_PREFIX"Bool",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Bool,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.d = 0,
+ .maximum.d = 1
+};
+
+static const props_def_t dyn7 = {
+ .label = "String",
+ .comment = "This is a string",
+ .property = PROPS_PREFIX"String",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__String,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat1 = {
+ .label = "statInt",
+ .property = PROPS_PREFIX"statInt",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat1,
+};
+
+static const props_def_t stat2 = {
+ .label = "statLong",
+ .property = PROPS_PREFIX"statLong",
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__Long,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+};
+
+static const props_def_t stat3 = {
+ .label = "statFloat",
+ .property = PROPS_PREFIX"statFloat",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Float,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat3,
+};
+
+static const props_def_t stat4 = {
+ .label = "statDouble",
+ .property = PROPS_PREFIX"statDouble",
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__Double,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+};
+
+static const props_def_t stat5 = {
+ .label = "statString",
+ .property = PROPS_PREFIX"statString",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__String,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat6 = {
+ .label = "statPath",
+ .property = PROPS_PREFIX"statPath",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Path,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat6,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat7 = {
+ .label = "statChunk",
+ .property = PROPS_PREFIX"statChunk",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Chunk,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->log)
+ {
+ fprintf(stderr,
+ "%s: Host does not support log:log\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->log_trace = handle->map->map(handle->map->handle, LV2_LOG__Trace);
+ handle->log_note = handle->map->map(handle->map->handle, LV2_LOG__Note);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ plugstate0_t *dyn = &handle->dyn;
+ plugstate0_t *_dyn = &handle->_dyn;
+ plugstate1_t *stat = &handle->stat;
+ plugstate1_t *_stat = &handle->_stat;
+
+ if( !props_register(&handle->props, &dyn1, &dyn->val1, &_dyn->val1)
+ || !(handle->urid.dyn2 =
+ props_register(&handle->props, &dyn2, &dyn->val2, &_dyn->val2))
+ || !props_register(&handle->props, &dyn3, &dyn->val3, &_dyn->val3)
+ || !(handle->urid.dyn4 =
+ props_register(&handle->props, &dyn4, &dyn->val4, &_dyn->val4))
+ || !props_register(&handle->props, &dyn5, &dyn->val5, &_dyn->val5)
+ || !props_register(&handle->props, &dyn6, &dyn->val6, &_dyn->val6)
+ || !props_register(&handle->props, &dyn7, &dyn->val7, &_dyn->val7)
+
+ || !props_register(&handle->props, &stat1, &stat->val1, &_stat->val1)
+ || !(handle->urid.stat2 =
+ props_register(&handle->props, &stat2, &stat->val2, &_stat->val2))
+ || !props_register(&handle->props, &stat3, &stat->val3, &_stat->val3)
+ || !(handle->urid.stat4 =
+ props_register(&handle->props, &stat4, &stat->val4, &_stat->val4))
+ || !props_register(&handle->props, &stat5, &stat->val5, &_stat->val5)
+ || !props_register(&handle->props, &stat6, &stat->val6, &_stat->val6)
+ || !props_register(&handle->props, &stat7, &stat->val7, &_stat->val7) )
+ {
+ _log_printf(handle, handle->log_trace, "ERR : registering");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ uint32_t capacity = handle->event_out->atom.size;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(handle->ref)
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref); //TODO handle return
+ }
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, &handle->forge, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, &handle->forge, retrieve, state, flags, features);
+}
+
+LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor props_test = {
+ .URI = PROPS_TEST_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &props_test;
+ default:
+ return NULL;
+ }
+}
diff --git a/props.lv2/test/props.ttl b/props.lv2/test/props.ttl
new file mode 100644
index 0000000..17a8041
--- /dev/null
+++ b/props.lv2/test/props.ttl
@@ -0,0 +1,152 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:props
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Props Bundle" .
+
+props:statInt
+ a lv2:Parameter ;
+ rdfs:range atom:Int ;
+ rdfs:label "statInt" ;
+ rdfs:comment "This is a 32-bit integer" ;
+ units:unit units:hz ;
+ lv2:minimum 0 ;
+ lv2:maximum 10 .
+
+props:statLong
+ a lv2:Parameter ;
+ rdfs:range atom:Long ;
+ rdfs:label "statLong" ;
+ rdfs:comment "This is a 64-bit integer" ;
+ units:unit units:khz ;
+ lv2:minimum 0 ;
+ lv2:maximum 20 .
+
+props:statFloat
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "statFloat" ;
+ rdfs:comment "This is a 32-bit float" ;
+ units:unit units:mhz ;
+ lv2:minimum -0.5 ;
+ lv2:maximum 0.5 .
+
+props:statDouble
+ a lv2:Parameter ;
+ rdfs:range atom:Double ;
+ rdfs:label "statDouble" ;
+ rdfs:comment "This is a 64-bit double" ;
+ units:unit units:db ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 .
+
+props:statString
+ a lv2:Parameter ;
+ rdfs:range atom:String ;
+ rdfs:label "statString" ;
+ rdfs:comment "This is a string" .
+
+props:statPath
+ a lv2:Parameter ;
+ rdfs:range atom:Path ;
+ rdfs:label "statPath" ;
+ rdfs:comment "This is a path" .
+
+props:statChunk
+ a lv2:Parameter ;
+ rdfs:range atom:Chunk;
+ rdfs:label "statChunk" ;
+ rdfs:comment "This is a chunk" .
+
+# Looper Test
+props:test
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Props Test" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:props ;
+ lv2:extensionData state:interface ;
+ lv2:requiredFeature urid:map, log:log, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ props:statInt ,
+ props:statFloat ,
+ props:statString ,
+ props:statPath ,
+ props:statChunk ;
+
+ patch:readable
+ props:statLong ,
+ props:statDouble ;
+
+ state:state [
+ props:statInt 4 ;
+ props:statFloat "0.4"^^xsd:float ;
+ props:statString "Hello world" ;
+ props:statPath <manifest.ttl> ;
+ props:statChunk "Q"^^xsd:base64Binary ;
+ ] .