diff options
author | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2017-03-19 16:11:02 +0100 |
---|---|---|
committer | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2017-03-19 16:11:02 +0100 |
commit | e4741bb28c46602dec6580abab8ef774a23c2a1b (patch) | |
tree | 6f66c70da8a1719eeb975a9ad2d4270b0ed779cb | |
parent | 272d71acb6b97df57a1b27ed3ca9e8db370e146e (diff) | |
parent | d52738538423c10c1c5c4056d94daa89cf327625 (diff) | |
download | vm.lv2-e4741bb28c46602dec6580abab8ef774a23c2a1b.tar.xz |
Add 'props.lv2/' from commit 'd52738538423c10c1c5c4056d94daa89cf327625'
git-subtree-dir: props.lv2
git-subtree-mainline: 272d71acb6b97df57a1b27ed3ca9e8db370e146e
git-subtree-split: d52738538423c10c1c5c4056d94daa89cf327625
-rw-r--r-- | props.lv2/CMakeLists.txt | 41 | ||||
-rw-r--r-- | props.lv2/COPYING | 201 | ||||
-rw-r--r-- | props.lv2/README.md | 20 | ||||
-rw-r--r-- | props.lv2/props.h | 1035 | ||||
-rw-r--r-- | props.lv2/test/chunk.bin | bin | 0 -> 16 bytes | |||
-rw-r--r-- | props.lv2/test/manifest.ttl.in | 28 | ||||
-rw-r--r-- | props.lv2/test/props.c | 397 | ||||
-rw-r--r-- | props.lv2/test/props.ttl | 153 |
8 files changed, 1875 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..35081eb --- /dev/null +++ b/props.lv2/props.h @@ -0,0 +1,1035 @@ +/* + * 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/ext/worker/worker.h> + +/***************************************************************************** + * API START + *****************************************************************************/ + +// enumerations +typedef enum _props_event_t props_event_t; + +// structures +typedef struct _props_def_t props_def_t; +typedef struct _props_impl_t props_impl_t; +typedef struct _props_work_t props_work_t; +typedef struct _props_t props_t; +typedef union _props_magic_t props_magic_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); + +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_def_t { + const char *property; + const char *type; + const char *access; + size_t offset; + + uint32_t max_size; + props_event_t event_mask; + props_event_cb_t event_cb; +}; + +struct _props_impl_t { + LV2_URID property; + LV2_URID type; + LV2_URID access; + + struct { + uint32_t size; + void *body; + } value; + struct { + uint32_t size; + void *body; + } stash; + + const props_t *props; + const props_def_t *def; + + atomic_flag lock; + bool stashing; +}; + +union _props_magic_t { + uint8_t u64; + uint8_t u8 [8]; +}; + +struct _props_work_t { + props_magic_t magic; + LV2_URID property; + LV2_URID size; + LV2_URID type; + uint8_t body []; +}; + +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 patch_sequence; + LV2_URID patch_error; + LV2_URID patch_ack; + + 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_path; + LV2_URID atom_literal; + LV2_URID atom_vector; + LV2_URID atom_object; + LV2_URID atom_sequence; + } urid; + + LV2_URID_Map *map; + void *data; + + 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 int +props_register(props_t *props, const props_def_t *defs, int num, + void *value_base, void *stash_base); + +// 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); + +// rt-safe +static inline LV2_URID +props_map(props_t *props, const char *property); + +// rt-safe +static inline const char * +props_unmap(props_t *props, LV2_URID property); + +// non-rt +static inline LV2_State_Status +props_save(props_t *props, 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_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features); + +// non-rt +static inline LV2_Worker_Status +props_work(props_t *props, LV2_Worker_Respond_Function respond, +LV2_Worker_Respond_Handle handle, uint32_t size, const void *data); + +// rt-safe +static inline LV2_Worker_Status +props_work_response(props_t *props, uint32_t size, const void *body); + +/***************************************************************************** + * API END + *****************************************************************************/ + +static const props_magic_t props_magic = { + .u8 = {'p', 'r', 'o', 'p', 's', 'l', 'v', '2'} +}; + +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 void +_impl_qsort(props_impl_t *A, int n) +{ + if(n < 2) + return; + + const props_impl_t *p = A; + + int i = -1; + int j = n; + + while(true) + { + do { + i += 1; + } while(A[i].property < p->property); + + do { + j -= 1; + } while(A[j].property > p->property); + + if(i >= j) + break; + + const props_impl_t tmp = A[i]; + A[i] = A[j]; + A[j] = tmp; + } + + _impl_qsort(A, j + 1); + _impl_qsort(A + j + 1, n - j - 1); +} + +static inline props_impl_t * +_impl_bsearch(LV2_URID p, props_impl_t *a, int n) +{ + props_impl_t *base = a; + + for(int N = n, half; N > 1; N -= half) + { + half = N/2; + props_impl_t *dst = &base[half]; + base = (dst->property > p) ? base : dst; + } + + return (base->property == p) ? base : 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, int32_t sequence_num) +{ + 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(sequence_num) // is optional + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + + 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 = lv2_atom_forge_atom(forge, impl->value.size, impl->type); + if(ref) + ref = lv2_atom_forge_write(forge, impl->value.body, impl->value.size); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_props_error(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, int32_t sequence_num) +{ + 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_error); + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + if(ref) + lv2_atom_forge_pop(forge, &obj_frame); + + return ref; +} + +static inline LV2_Atom_Forge_Ref +_props_ack(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, int32_t sequence_num) +{ + 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_ack); + { + if(ref) + ref = lv2_atom_forge_key(forge, props->urid.patch_sequence); + if(ref) + ref = lv2_atom_forge_int(forge, sequence_num); + } + 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)) + { + impl->stash.size = impl->value.size; + memcpy(impl->stash.body, impl->value.body, 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 size, const void *body) +{ + if( (impl->type == type) + && ( (impl->def->max_size == 0) || (size <= impl->def->max_size)) ) + { + impl->value.size = size; + memcpy(impl->value.body, body, size); + + _props_stash(props, impl); + } +} + +static inline int +_props_register_single(props_t *props, const props_def_t *def, + void *value_base, void *stash_base) +{ + if(props->nimpls >= props->max_nimpls) + return 0; + + if(!def->property || !def->type) + return 0; + + const LV2_URID type = props->map->map(props->map->handle, def->type); + const LV2_URID property = props->map->map(props->map->handle, def->property); + const LV2_URID access = def->access + ? props->map->map(props->map->handle, def->access) + : props->map->map(props->map->handle, LV2_PATCH__writable); + + if(!type || !property || !access) + return 0; + + props_impl_t *impl = &props->impls[props->nimpls++]; + + impl->props = props; + impl->property = property; + impl->access = access; + impl->def = def; + impl->value.body = value_base + def->offset; + impl->stash.body = stash_base + def->offset; + + uint32_t size; + if( (type == props->urid.atom_int) + || (type == props->urid.atom_float) + || (type == props->urid.atom_bool) + || (type == props->urid.atom_urid) ) + { + size = 4; + } + else if((type == props->urid.atom_long) + || (type == props->urid.atom_double) ) + { + size = 8; + } + else if(type == props->urid.atom_literal) + { + size = sizeof(LV2_Atom_Literal_Body); + } + else if(type == props->urid.atom_vector) + { + size = sizeof(LV2_Atom_Vector_Body); + } + else if(type == props->urid.atom_object) + { + size = sizeof(LV2_Atom_Object_Body); + } + else if(type == props->urid.atom_sequence) + { + size = sizeof(LV2_Atom_Sequence_Body); + } + else + { + size = 0; // assume everything else as having size 0 + } + + impl->type = type; + impl->value.size = impl->stash.size = size; + + atomic_flag_clear_explicit(&impl->lock, memory_order_relaxed); + + // update maximal value size + const uint32_t max_size = def->max_size + ? def->max_size + : size; + + if(max_size > props->max_size) + { + props->max_size = max_size; + } + + //TODO register? + + return 1; +} + +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.patch_sequence = map->map(map->handle, LV2_PATCH__sequenceNumber); + props->urid.patch_ack = map->map(map->handle, LV2_PATCH__Ack); + props->urid.patch_error = map->map(map->handle, LV2_PATCH__Error); + + 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_path = map->map(map->handle, LV2_ATOM__Path); + props->urid.atom_literal = map->map(map->handle, LV2_ATOM__Literal); + props->urid.atom_vector = map->map(map->handle, LV2_ATOM__Vector); + props->urid.atom_object = map->map(map->handle, LV2_ATOM__Object); + props->urid.atom_sequence = map->map(map->handle, LV2_ATOM__Sequence); + + return 1; +} + +static inline int +props_register(props_t *props, const props_def_t *defs, int num, + void *value_base, void *stash_base) +{ + if(!defs || !value_base || !stash_base) + return 0; + + int status = 1; + for(int i = 0; i < num; i++) + { + status = status && _props_register_single(props, &defs[i], value_base, stash_base); + } + + _impl_qsort(props->impls, props->nimpls); + + return status; +} + +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; + const LV2_Atom_Int *sequence = 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_sequence, (const LV2_Atom **)&sequence }, + 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; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + 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(*ref) + *ref = _props_get(props, forge, frames, impl, sequence_num); + 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, sequence_num); + + 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(sequence_num) + { + *ref = _props_error(props, forge, frames, sequence_num); + } + } + else if(sequence_num) + { + *ref = _props_error(props, forge, frames, sequence_num); + } + } + else if(obj->body.otype == props->urid.patch_set) + { + const LV2_Atom_URID *subject = NULL; + const LV2_Atom_URID *property = NULL; + const LV2_Atom_Int *sequence = 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_sequence, (const LV2_Atom **)&sequence }, + { props->urid.patch_value, &value }, + 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; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + if(!property || (property->atom.type != props->urid.atom_urid) || !value) + { + if(sequence_num) + { + *ref = _props_error(props, forge, frames, sequence_num); + } + + 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); + + if(sequence_num) + { + *ref = _props_ack(props, forge, frames, sequence_num); + } + + return 1; + } + else if(sequence_num) + { + *ref = _props_error(props, forge, frames, sequence_num); + } + } + else if(obj->body.otype == props->urid.patch_put) + { + const LV2_Atom_URID *subject = NULL; + const LV2_Atom_Int *sequence = NULL; + const LV2_Atom_Object *body = NULL; + + LV2_Atom_Object_Query q [] = { + { props->urid.patch_subject, (const LV2_Atom **)&subject }, + { props->urid.patch_sequence, (const LV2_Atom **)&sequence}, + { props->urid.patch_body, (const LV2_Atom **)&body }, + 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; + } + + int32_t sequence_num = 0; + if(sequence && (sequence->atom.type == props->urid.atom_int)) + { + sequence_num = sequence->body; + } + + if(!body || !lv2_atom_forge_is_object_type(forge, body->atom.type)) + { + if(sequence_num) + { + *ref = _props_error(props, forge, frames, sequence_num); + } + + 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); + } + } + + if(sequence_num) + { + *ref = _props_ack(props, forge, frames, sequence_num); + } + + 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, 0); //TODO use patch:sequenceNumber + } +} + +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_URID +props_map(props_t *props, const char *property) +{ + for(unsigned i = 0; i < props->nimpls; i++) + { + props_impl_t *impl = &props->impls[i]; + + if(!strcmp(impl->def->property, property)) + return impl->property; + } + + return 0; +} + +static inline const char * +props_unmap(props_t *props, LV2_URID property) +{ + props_impl_t *impl = _props_impl_search(props, property); + + if(impl) + return impl->def->property; + + return NULL; +} + +static inline LV2_State_Status +props_save(props_t *props, 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 *body = malloc(props->max_size); // create memory to store widest value + if(body) + { + 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->stash.size; + const LV2_URID type = impl->type; + memcpy(body, impl->stash.body, impl->stash.size); + + _impl_unlock(impl); + + if( map_path && (type == props->urid.atom_path) ) + { + const char *path = strstr(body, "file://") + ? body + 7 // skip "file://" + : body; + char *abstract = map_path->abstract_path(map_path->handle, path); + if(abstract && strcmp(abstract, path)) + { + store(state, impl->property, abstract, strlen(abstract) + 1, type, flags); + free(abstract); + } + } + else // !Path + { + store(state, impl->property, body, size, type, flags); + } + + const props_def_t *def = impl->def; + if(def->event_cb && (def->event_mask & PROP_EVENT_SAVE) ) + def->event_cb(props->data, NULL, 0, PROP_EVENT_SAVE, impl); + } + + free(body); + } + + return LV2_STATE_SUCCESS; +} + +static inline void +_props_schedule(const LV2_Worker_Schedule *work_sched, LV2_URID property, + LV2_URID type, uint32_t size, const void *body) +{ + const uint32_t sz = sizeof(props_work_t) + size; + props_work_t *job = malloc(sz); + if(job) + { + job->magic = props_magic; + job->property = property; + job->type = type; + job->size = size; + memcpy(job->body, body, size); + + work_sched->schedule_work(work_sched->handle, sz, job); + + free(job); + } +} + +static inline LV2_State_Status +props_restore(props_t *props, 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; + const LV2_Worker_Schedule *work_sched = NULL; + + for(unsigned i = 0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_STATE__mapPath)) + map_path = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_WORKER__schedule)) + work_sched = 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 *body = retrieve(state, impl->property, &_size, &_type, &_flags); + + if(body) + { + if( map_path && (_type == props->urid.atom_path) ) + { + char *absolute = map_path->absolute_path(map_path->handle, body); + if(absolute) + { + const uint32_t sz = strlen(absolute) + 1; + if(work_sched) // host supports state:threadSafeRestore + { + _props_schedule(work_sched, impl->property, _type, sz, absolute); + } + else + { + _props_set(props, impl, _type, sz, absolute); + + const props_def_t *def = impl->def; + if(def->event_cb && (def->event_mask & PROP_EVENT_RESTORE) ) + def->event_cb(props->data, NULL, 0, PROP_EVENT_RESTORE, impl); + } + + free(absolute); + } + } + else // !Path + { + if(work_sched) // host supports state:threadSafeRestore + { + _props_schedule(work_sched, impl->property, _type, _size, body); + } + else + { + _props_set(props, impl, _type, _size, body); + + const props_def_t *def = impl->def; + if(def->event_cb && (def->event_mask & PROP_EVENT_RESTORE) ) + def->event_cb(props->data, NULL, 0, PROP_EVENT_RESTORE, impl); + } + } + } + else + { + fprintf(stderr, "props_restore: no property '%s'.\n", impl->def->property); + } + } + + return LV2_STATE_SUCCESS; +} + +static inline LV2_Worker_Status +props_work(props_t *props, LV2_Worker_Respond_Function respond, +LV2_Worker_Respond_Handle worker, uint32_t size, const void *body) +{ + const props_work_t *job = body; + + if(job && (job->magic.u64 == props_magic.u64) ) + { + const LV2_Worker_Status stat = respond(worker, size, body); + (void)stat; + + return LV2_WORKER_SUCCESS; + } + + return LV2_WORKER_ERR_UNKNOWN; +} + +static inline LV2_Worker_Status +props_work_response(props_t *props, uint32_t size, const void *body) +{ + const props_work_t *job = body; + + if(job && (job->magic.u64 == props_magic.u64) ) + { + props_impl_t *impl = _props_impl_search(props, job->property); + if(impl) + { + _props_set(props, impl, job->type, job->size, job->body); + + const props_def_t *def = impl->def; + if(def->event_cb && (def->event_mask & PROP_EVENT_RESTORE) ) + def->event_cb(props->data, NULL, 0, PROP_EVENT_RESTORE, impl); + } + + return LV2_WORKER_SUCCESS; + } + + return LV2_WORKER_ERR_UNKNOWN; +} + +#ifdef __cplusplus +} +#endif + +#endif // _LV2_PROPS_H_ diff --git a/props.lv2/test/chunk.bin b/props.lv2/test/chunk.bin Binary files differnew file mode 100644 index 0000000..b66efb8 --- /dev/null +++ b/props.lv2/test/chunk.bin 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..1883230 --- /dev/null +++ b/props.lv2/test/props.c @@ -0,0 +1,397 @@ +/* + * 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> +#include <lv2/lv2plug.in/ns/ext/log/logger.h> + +#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#" +#define PROPS_TEST_URI PROPS_PREFIX"test" + +#define MAX_NPROPS 7 +#define MAX_STRLEN 256 + +typedef struct _plugstate_t plugstate_t; +typedef struct _plughandle_t plughandle_t; + +struct _plugstate_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_Log_Logger logger; + LV2_Atom_Forge forge; + LV2_Atom_Forge_Ref ref; + + PROPS_T(props, MAX_NPROPS); + plugstate_t state; + plugstate_t stash; + + struct { + LV2_URID val2; + LV2_URID val4; + } urid; + + const LV2_Atom_Sequence *event_in; + LV2_Atom_Sequence *event_out; +}; + +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: + { + lv2_log_trace(&handle->logger, "GET : %s\n", impl->def->property); + break; + } + case PROP_EVENT_SET: + { + lv2_log_trace(&handle->logger, "SET : %s\n", impl->def->property); + break; + } + case PROP_EVENT_REGISTER: + { + lv2_log_note(&handle->logger, "REGISTER: %s\n", impl->def->property); + break; + } + case PROP_EVENT_SAVE: + { + lv2_log_note(&handle->logger, "SAVE : %s\n", impl->def->property); + break; + } + case PROP_EVENT_RESTORE: + { + lv2_log_note(&handle->logger, "RESTORE : %s\n", impl->def->property); + break; + } + } +} + +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->state.val2 = handle->state.val1 * 2; + + if(forge) + props_set(&handle->props, forge, frames, handle->urid.val2, &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->state.val4 = handle->state.val3 * 2; + + if(forge) + props_set(&handle->props, forge, frames, handle->urid.val4, &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->state.val6, "file://") + ? handle->state.val6 + 7 // skip "file://" + : handle->state.val6; + FILE *f = fopen(path, "wb"); // create empty file + if(f) + fclose(f); + } +} + +static const props_def_t defs [MAX_NPROPS] = { + { + .property = PROPS_PREFIX"statInt", + .offset = offsetof(plugstate_t, val1), + .type = LV2_ATOM__Int, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept_stat1, + }, + { + .property = PROPS_PREFIX"statLong", + .access = LV2_PATCH__readable, + .offset = offsetof(plugstate_t, val2), + .type = LV2_ATOM__Long, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept, + }, + { + .property = PROPS_PREFIX"statFloat", + .offset = offsetof(plugstate_t, val3), + .type = LV2_ATOM__Float, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept_stat3, + }, + { + .property = PROPS_PREFIX"statDouble", + .access = LV2_PATCH__readable, + .offset = offsetof(plugstate_t, val4), + .type = LV2_ATOM__Double, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept, + }, + { + .property = PROPS_PREFIX"statString", + .offset = offsetof(plugstate_t, val5), + .type = LV2_ATOM__String, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept, + .max_size = MAX_STRLEN // strlen + }, + { + .property = PROPS_PREFIX"statPath", + .offset = offsetof(plugstate_t, val6), + .type = LV2_ATOM__Path, + .event_mask = PROP_EVENT_ALL, + .event_cb = _intercept_stat6, + .max_size = MAX_STRLEN // strlen + }, + { + .property = PROPS_PREFIX"statChunk", + .offset = offsetof(plugstate_t, val7), + .type = LV2_ATOM__Chunk, + .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; + } + + lv2_log_logger_init(&handle->logger, handle->map, handle->log); + lv2_atom_forge_init(&handle->forge, handle->map); + + if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle)) + { + lv2_log_error(&handle->logger, "failed to initialize property structure\n"); + free(handle); + return NULL; + } + + if(!props_register(&handle->props, defs, MAX_NPROPS, &handle->state, &handle->stash)) + { + lv2_log_error(&handle->logger, "ERR : registering\n"); + free(handle); + return NULL; + } + + handle->urid.val2 = props_map(&handle->props, PROPS_PREFIX"statLong"); + handle->urid.val4 = props_map(&handle->props, PROPS_PREFIX"statDouble"); + + 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, store, state, flags, features); +} + +static LV2_State_Status +_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, + LV2_State_Handle state, uint32_t flags, + const LV2_Feature *const *features) +{ + plughandle_t *handle = (plughandle_t *)instance; + + return props_restore(&handle->props, retrieve, state, flags, features); +} + +LV2_State_Interface state_iface = { + .save = _state_save, + .restore = _state_restore +}; + +static inline LV2_Worker_Status +_work(LV2_Handle instance, LV2_Worker_Respond_Function respond, +LV2_Worker_Respond_Handle worker, uint32_t size, const void *body) +{ + plughandle_t *handle = instance; + + return props_work(&handle->props, respond, worker, size, body); +} + +static inline LV2_Worker_Status +_work_response(LV2_Handle instance, uint32_t size, const void *body) +{ + plughandle_t *handle = instance; + + return props_work_response(&handle->props, size, body); +} + +static const LV2_Worker_Interface work_iface = { + .work = _work, + .work_response = _work_response, + .end_run = NULL +}; + +static const void * +extension_data(const char *uri) +{ + if(!strcmp(uri, LV2_STATE__interface)) + return &state_iface; + else if(!strcmp(uri, LV2_WORKER__interface)) + return &work_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..68d6b43 --- /dev/null +++ b/props.lv2/test/props.ttl @@ -0,0 +1,153 @@ +# 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 work: <http://lv2plug.in/ns/ext/worker#> . +@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:requiredFeature urid:map, log:log, state:loadDefaultState, work:schedule ; + lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore ; + lv2:extensionData state:interface, work:interface ; + + 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 "AQIDBAUGBw=="^^xsd:base64Binary ; + ] . |