aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml72
-rw-r--r--README.md27
-rw-r--r--VERSION1
-rw-r--r--canvas.lv2/COPYING201
-rw-r--r--canvas.lv2/README.md18
-rw-r--r--canvas.lv2/canvas.lv2/canvas.h (renamed from canvas.lv2/canvas.h)0
-rw-r--r--canvas.lv2/canvas.lv2/forge.h (renamed from canvas.lv2/forge.h)0
-rw-r--r--canvas.lv2/canvas.lv2/idisp.h (renamed from canvas.lv2/idisp.h)0
-rw-r--r--canvas.lv2/canvas.lv2/lv2_extensions.h (renamed from canvas.lv2/lv2_extensions.h)0
-rw-r--r--canvas.lv2/canvas.lv2/render.h (renamed from canvas.lv2/render.h)0
-rw-r--r--canvas.lv2/canvas.lv2/render_cairo.h (renamed from canvas.lv2/render_cairo.h)0
-rw-r--r--canvas.lv2/canvas.lv2/render_nanovg.h (renamed from canvas.lv2/render_nanovg.h)0
-rw-r--r--manifest.ttl.in28
-rw-r--r--meson.build54
-rw-r--r--props.lv2/.gitlab-ci.yml72
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/VERSION1
-rw-r--r--props.lv2/meson.build59
-rw-r--r--props.lv2/props.h1012
-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.c323
-rw-r--r--props.lv2/test/props.ttl152
-rw-r--r--widgets.c31
-rw-r--r--widgets.h90
-rw-r--r--widgets.ttl168
-rw-r--r--widgets_xy_pad.c462
28 files changed, 3018 insertions, 2 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..dceebfe
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,72 @@
+stages:
+ - build
+ - test
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "widgets.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --prefix="/opt/${CI_BUILD_NAME}" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
+ - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' -e '/framework/s/-Wl,-soname,.*dylib//g' build/build.ninja
+ - ninja -C build
+ - ninja -C build install
+ - mkdir -p "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/${BASE_NAME}"
+ - cp -r "/opt/${CI_BUILD_NAME}/lib/lv2/${BASE_NAME}/" "${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *build_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *build_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/README.md b/README.md
index 06b87f2..b134594 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,31 @@
-# Canvas LV2 plugin extension
+# Widgets.lv2
+
+## LV2 widgets plugins
+
+### Webpage
+
+Get more information at: [http://open-music-kontrollers.ch/lv2/widgets](http://open-music-kontrollers.ch/lv2/widgets)
+
+### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/widgets.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/widgets.lv2/commits/master)
+
+### Dependencies
+
+* [LV2](http://lv2plug.in) (LV2 Plugin Standard)
+
+### Build / install
+
+ git clone https://git.open-music-kontrollers.ch/lv2/widgets.lv2
+ cd widgets.lv2
+ meson build
+ cd build
+ ninja -j4
+ sudo ninja install
### License
-Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+Copyright (c) 2018 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
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..a4c528c
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.53
diff --git a/canvas.lv2/COPYING b/canvas.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/canvas.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/canvas.lv2/README.md b/canvas.lv2/README.md
new file mode 100644
index 0000000..06b87f2
--- /dev/null
+++ b/canvas.lv2/README.md
@@ -0,0 +1,18 @@
+# Canvas LV2 plugin extension
+
+### License
+
+Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/canvas.lv2/canvas.h b/canvas.lv2/canvas.lv2/canvas.h
index 7cca0fa..7cca0fa 100644
--- a/canvas.lv2/canvas.h
+++ b/canvas.lv2/canvas.lv2/canvas.h
diff --git a/canvas.lv2/forge.h b/canvas.lv2/canvas.lv2/forge.h
index c5ce5fe..c5ce5fe 100644
--- a/canvas.lv2/forge.h
+++ b/canvas.lv2/canvas.lv2/forge.h
diff --git a/canvas.lv2/idisp.h b/canvas.lv2/canvas.lv2/idisp.h
index afe7371..afe7371 100644
--- a/canvas.lv2/idisp.h
+++ b/canvas.lv2/canvas.lv2/idisp.h
diff --git a/canvas.lv2/lv2_extensions.h b/canvas.lv2/canvas.lv2/lv2_extensions.h
index 64fc3bc..64fc3bc 100644
--- a/canvas.lv2/lv2_extensions.h
+++ b/canvas.lv2/canvas.lv2/lv2_extensions.h
diff --git a/canvas.lv2/render.h b/canvas.lv2/canvas.lv2/render.h
index b809804..b809804 100644
--- a/canvas.lv2/render.h
+++ b/canvas.lv2/canvas.lv2/render.h
diff --git a/canvas.lv2/render_cairo.h b/canvas.lv2/canvas.lv2/render_cairo.h
index a41c72c..a41c72c 100644
--- a/canvas.lv2/render_cairo.h
+++ b/canvas.lv2/canvas.lv2/render_cairo.h
diff --git a/canvas.lv2/render_nanovg.h b/canvas.lv2/canvas.lv2/render_nanovg.h
index a98bc95..a98bc95 100644
--- a/canvas.lv2/render_nanovg.h
+++ b/canvas.lv2/canvas.lv2/render_nanovg.h
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..4736f34
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,28 @@
+# Copyright (c) 2018 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 widgets: <http://open-music-kontrollers.ch/lv2/widgets#> .
+
+# XY Pad
+widgets:xy_pad
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <widgets@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <widgets.ttl> .
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..e7c4d37
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,54 @@
+project('widgets.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=1',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+cp = find_program('cp')
+clone = [cp, '@INPUT@', '@OUTPUT@']
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+
+canvas_inc = include_directories('canvas.lv2')
+
+inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name())
+
+srcs = ['widgets.c',
+ 'widgets_xy_pad.c']
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+mod = shared_module('widgets', srcs,
+ c_args : c_args,
+ name_prefix : '',
+ include_directories : [canvas_inc],
+ dependencies : [m_dep, lv2_dep],
+ install : true,
+ install_dir : inst_dir)
+
+version = run_command('cat', 'VERSION').stdout().strip().split('.')
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+configure_file(input : 'manifest.ttl.in', output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+custom_target('widgets_ttl',
+ input : 'widgets.ttl',
+ output : 'widgets.ttl',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
diff --git a/props.lv2/.gitlab-ci.yml b/props.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..c027e92
--- /dev/null
+++ b/props.lv2/.gitlab-ci.yml
@@ -0,0 +1,72 @@
+stages:
+ - build
+ - test
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "props.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - mkdir build
+ - pushd build
+ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_CI_BUILD_NAME=${CI_BUILD_NAME} ..
+ - cmake .. # needed for darwin
+ - make
+ - make install
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *build_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *build_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
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/VERSION b/props.lv2/VERSION
new file mode 100644
index 0000000..8b06068
--- /dev/null
+++ b/props.lv2/VERSION
@@ -0,0 +1 @@
+0.1.117
diff --git a/props.lv2/meson.build b/props.lv2/meson.build
new file mode 100644
index 0000000..176a8bb
--- /dev/null
+++ b/props.lv2/meson.build
@@ -0,0 +1,59 @@
+project('props.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+cp = find_program('cp')
+clone = [cp, '@INPUT@', '@OUTPUT@']
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+
+inc_dir = []
+
+inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name())
+
+dsp_srcs = [join_paths('test', 'props.c')]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+mod = shared_module('props', dsp_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : [m_dep, lv2_dep],
+ install : true,
+ install_dir : inst_dir)
+
+version = run_command('cat', 'VERSION').stdout().strip().split('.')
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+configure_file(input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+custom_target('props_ttl',
+ input : join_paths('test', 'props.ttl'),
+ output : 'props.ttl',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+custom_target('chunk_bin',
+ input : join_paths('test', 'chunk.bin'),
+ output : 'chunk.bin',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
diff --git a/props.lv2/props.h b/props.lv2/props.h
new file mode 100644
index 0000000..06fec13
--- /dev/null
+++ b/props.lv2/props.h
@@ -0,0 +1,1012 @@
+/*
+ * 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>
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+// structures
+typedef struct _props_def_t props_def_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,
+ int64_t frames,
+ props_impl_t *impl);
+
+struct _props_def_t {
+ const char *property;
+ const char *type;
+ const char *access;
+ size_t offset;
+ bool hidden;
+
+ uint32_t max_size;
+ 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_def_t *def;
+
+ atomic_int state;
+ 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 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;
+
+ void *data;
+
+ bool stashing;
+ atomic_bool restoring;
+
+ uint32_t max_size;
+
+ unsigned nimpls;
+ props_impl_t impls [1];
+};
+
+#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 char *subject,
+ const props_def_t *defs, int nimpls,
+ void *value_base, void *stash_base,
+ LV2_URID_Map *map, void *data);
+
+// rt-safe
+static inline void
+props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_Atom_Forge_Ref *ref);
+
+// 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_get(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);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+// enumerations
+typedef enum _props_state_t {
+ PROP_STATE_NONE = 0,
+ PROP_STATE_LOCK = 1,
+ PROP_STATE_RESTORE = 2
+} props_state_t;
+
+static inline void
+_props_impl_spin_lock(props_impl_t *impl, int from, int to)
+{
+ int expected = from;
+ const int desired = to;
+
+ while(!atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired,
+ memory_order_acquire, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_props_impl_try_lock(props_impl_t *impl, int from, int to)
+{
+ int expected = from;
+ const int desired = to;
+
+ return atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired,
+ memory_order_acquire, memory_order_acquire);
+}
+
+static inline void
+_props_impl_unlock(props_impl_t *impl, int to)
+{
+ atomic_store_explicit(&impl->state, to, memory_order_release);
+}
+
+static inline bool
+_props_restoring_get(props_t *props)
+{
+ return atomic_exchange_explicit(&props->restoring, false, memory_order_acquire);
+}
+
+static inline void
+_props_restoring_set(props_t *props)
+{
+ atomic_store_explicit(&props->restoring, true, memory_order_release);
+}
+
+static inline void
+_props_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;
+ }
+
+ _props_qsort(A, j + 1);
+ _props_qsort(A + j + 1, n - j - 1);
+}
+
+static inline props_impl_t *
+_props_impl_get(props_t *props, LV2_URID property)
+{
+ props_impl_t *base = props->impls;
+
+ for(int N = props->nimpls, half; N > 1; N -= half)
+ {
+ half = N/2;
+ props_impl_t *dst = &base[half];
+ base = (dst->property > property) ? base : dst;
+ }
+
+ return (base->property == property) ? base : NULL;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_set(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_patch_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_get);
+ {
+ 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_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_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_patch_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_impl_stash(props_t *props, props_impl_t *impl)
+{
+ if(_props_impl_try_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK))
+ {
+ impl->stashing = false;
+ impl->stash.size = impl->value.size;
+ memcpy(impl->stash.body, impl->value.body, impl->value.size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+ }
+ else
+ {
+ impl->stashing = true; // try again later
+ props->stashing = true;
+ }
+}
+
+static inline void
+_props_impl_restore(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, LV2_Atom_Forge_Ref *ref)
+{
+ if(_props_impl_try_lock(impl, PROP_STATE_RESTORE, PROP_STATE_LOCK))
+ {
+ impl->stashing = false; // makes no sense to stash a recently restored value
+ impl->value.size = impl->stash.size;
+ memcpy(impl->value.body, impl->stash.body, impl->stash.size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, 0);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb)
+ def->event_cb(props->data, 0, impl);
+ }
+}
+
+static inline void
+_props_impl_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_impl_stash(props, impl);
+ }
+}
+
+static inline int
+_props_impl_init(props_t *props, props_impl_t *impl, const props_def_t *def,
+ void *value_base, void *stash_base, LV2_URID_Map *map)
+{
+ if(!def->property || !def->type)
+ return 0;
+
+ const LV2_URID type = map->map(map->handle, def->type);
+ const LV2_URID property = map->map(map->handle, def->property);
+ const LV2_URID access = def->access
+ ? map->map(map->handle, def->access)
+ : map->map(map->handle, LV2_PATCH__writable);
+
+ if(!type || !property || !access)
+ return 0;
+
+ impl->property = property;
+ impl->access = access;
+ impl->def = def;
+ impl->value.body = (uint8_t *)value_base + def->offset;
+ impl->stash.body = (uint8_t *)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 = size;
+ impl->stash.size = size;
+
+ atomic_init(&impl->state, PROP_STATE_NONE);
+
+ // 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;
+ }
+
+ return 1;
+}
+
+static inline int
+props_init(props_t *props, const char *subject,
+ const props_def_t *defs, int nimpls,
+ void *value_base, void *stash_base,
+ LV2_URID_Map *map, void *data)
+{
+ if(!props || !defs || !value_base || !stash_base || !map)
+ return 0;
+
+ props->nimpls = nimpls;
+ 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);
+
+ atomic_init(&props->restoring, false);
+
+ int status = 1;
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ status = status
+ && _props_impl_init(props, impl, &defs[i], value_base, stash_base, map);
+ }
+
+ _props_qsort(props->impls, props->nimpls);
+
+ return status;
+}
+
+static inline void
+props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_Atom_Forge_Ref *ref)
+{
+ if(_props_restoring_get(props))
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ _props_impl_restore(props, forge, frames, impl, 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)
+ _props_impl_stash(props, impl);
+ }
+ }
+}
+
+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(!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_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_property, &property,
+ props->urid.patch_sequence, &sequence,
+ 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;
+ }
+
+ 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];
+
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(property->atom.type == props->urid.atom_urid)
+ {
+ props_impl_t *impl = _props_impl_get(props, property->body);
+
+ if(impl)
+ {
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ return 1;
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_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_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_property, &property,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_value, &value,
+ 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;
+ }
+
+ 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)
+ {
+ if(ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+
+ return 0;
+ }
+
+ props_impl_t *impl = _props_impl_get(props, property->body);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_impl_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_cb(props->data, frames, impl);
+
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_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_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_body, &body,
+ 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;
+ }
+
+ 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)
+ {
+ if(*ref)
+ *ref = _props_patch_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_get(props, property);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_impl_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_cb(props->data, frames, impl);
+ }
+ }
+
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_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_get(props, property);
+
+ if(impl)
+ {
+ _props_impl_stash(props, impl);
+
+ if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber
+ *ref = _props_patch_set(props, forge, frames, impl, 0);
+ }
+}
+
+static inline void
+props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_URID property, LV2_Atom_Forge_Ref *ref)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ {
+ if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber
+ *ref = _props_patch_get(props, forge, frames, impl, 0);
+ }
+}
+
+static inline void
+props_stash(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ _props_impl_stash(props, impl);
+}
+
+static inline LV2_URID
+props_map(props_t *props, const char *uri)
+{
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(!strcmp(impl->def->property, uri))
+ return impl->property;
+ }
+
+ return 0;
+}
+
+static inline const char *
+props_unmap(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_get(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
+
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ // create temporary copy of value, store() may well be blocking
+ const uint32_t size = impl->stash.size;
+ memcpy(body, impl->stash.body, size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+
+ if( map_path && (impl->type == props->urid.atom_path) )
+ {
+ const char *path = strstr(body, "file://")
+ ? (char *)body + 7 // skip "file://"
+ : (char *)body;
+ char *abstract = map_path->abstract_path(map_path->handle, path);
+ if(abstract)
+ {
+ const uint32_t sz = strlen(abstract) + 1;
+ store(state, impl->property, abstract, sz, impl->type, flags);
+
+ free(abstract);
+ }
+ }
+ else // !Path
+ {
+ store(state, impl->property, body, size, impl->type, flags);
+ }
+ }
+
+ free(body);
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags __attribute__((unused)),
+ 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 *body = retrieve(state, impl->property, &size, &type, &_flags);
+
+ if( body
+ && (type == impl->type)
+ && ( (impl->def->max_size == 0) || (size <= impl->def->max_size) ) )
+ {
+ 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;
+
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ impl->stash.size = sz;
+ memcpy(impl->stash.body, absolute, sz);
+
+ _props_impl_unlock(impl, PROP_STATE_RESTORE);
+
+ free(absolute);
+ }
+ }
+ else // !Path
+ {
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ impl->stash.size = size;
+ memcpy(impl->stash.body, body, size);
+
+ _props_impl_unlock(impl, PROP_STATE_RESTORE);
+ }
+ }
+ }
+
+ _props_restoring_set(props);
+
+ return LV2_STATE_SUCCESS;
+}
+
+#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..0ecc313
--- /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 @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <props@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <props.ttl> .
diff --git a/props.lv2/test/props.c b/props.lv2/test/props.c
new file mode 100644
index 0000000..590c519
--- /dev/null
+++ b/props.lv2/test/props.c
@@ -0,0 +1,323 @@
+/*
+ * 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, int64_t frames __attribute__((unused)), props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ lv2_log_trace(&handle->logger, "SET : %s\n", impl->def->property);
+}
+
+static void
+_intercept_stat1(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ handle->state.val2 = handle->state.val1 * 2;
+
+ props_set(&handle->props, &handle->forge, frames, handle->urid.val2, &handle->ref);
+}
+
+static void
+_intercept_stat3(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ handle->state.val4 = handle->state.val3 * 2;
+
+ props_set(&handle->props, &handle->forge, frames, handle->urid.val4, &handle->ref);
+}
+
+static void
+_intercept_stat6(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ 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_cb = _intercept_stat1,
+ },
+ {
+ .property = PROPS_PREFIX"statLong",
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, val2),
+ .type = LV2_ATOM__Long,
+ .event_cb = _intercept,
+ },
+ {
+ .property = PROPS_PREFIX"statFloat",
+ .offset = offsetof(plugstate_t, val3),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_stat3,
+ },
+ {
+ .property = PROPS_PREFIX"statDouble",
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, val4),
+ .type = LV2_ATOM__Double,
+ .event_cb = _intercept,
+ },
+ {
+ .property = PROPS_PREFIX"statString",
+ .offset = offsetof(plugstate_t, val5),
+ .type = LV2_ATOM__String,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+ },
+ {
+ .property = PROPS_PREFIX"statPath",
+ .offset = offsetof(plugstate_t, val6),
+ .type = LV2_ATOM__Path,
+ .event_cb = _intercept_stat6,
+ .max_size = MAX_STRLEN // strlen
+ },
+ {
+ .property = PROPS_PREFIX"statChunk",
+ .offset = offsetof(plugstate_t, val7),
+ .type = LV2_ATOM__Chunk,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate __attribute__((unused)),
+ const char *bundle_path __attribute__((unused)),
+ 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, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ lv2_log_error(&handle->logger, "failed to initialize property structure\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 __attribute__((unused)))
+{
+ 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);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ 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);
+ }
+
+ 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 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..0ce45d6
--- /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:requiredFeature urid:map, log:log, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData state: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 <props.ttl> ;
+ props:statChunk "AQIDBAUGBw=="^^xsd:base64Binary ;
+ ] .
diff --git a/widgets.c b/widgets.c
new file mode 100644
index 0000000..78cd22f
--- /dev/null
+++ b/widgets.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018 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 <widgets.h>
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &widgets_xy_pad;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/widgets.h b/widgets.h
new file mode 100644
index 0000000..1572bca
--- /dev/null
+++ b/widgets.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018 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 _WIDGETS_LV2_H
+#define _WIDGETS_LV2_H
+
+#include <stdint.h>
+#if !defined(_WIN32)
+# include <sys/mman.h>
+#else
+# define mlock(...)
+# define munlock(...)
+#endif
+
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+#include <lv2/lv2plug.in/ns/ext/log/logger.h>
+#include <lv2/lv2plug.in/ns/ext/options/options.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+
+#define WIDGETS_URI "http://open-music-kontrollers.ch/lv2/widgets"
+#define WIDGETS_PREFIX WIDGETS_URI"#"
+
+// plugin uris
+#define WIDGETS__xy_pad WIDGETS_PREFIX"xy_pad"
+#define WIDGETS__x WIDGETS_PREFIX"x"
+#define WIDGETS__y WIDGETS_PREFIX"y"
+
+extern const LV2_Descriptor widgets_xy_pad;
+
+typedef struct _craft_t craft_t;
+
+struct _craft_t {
+ union {
+ LV2_Atom_Sequence *seq;
+ uint8_t *buf;
+ };
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+};
+
+static inline void
+_craft_init(craft_t *craft, LV2_URID_Map *map)
+{
+ lv2_atom_forge_init(&craft->forge, map);
+}
+
+static inline void
+_craft_connect(craft_t *craft, LV2_Atom_Sequence *seq)
+{
+ craft->seq = seq;
+}
+
+static inline void
+_craft_in(craft_t *craft)
+{
+ const uint32_t capacity = craft->seq->atom.size;
+ lv2_atom_forge_set_buffer(&craft->forge, craft->buf, capacity);
+ craft->ref = lv2_atom_forge_sequence_head(&craft->forge, &craft->frame, 0);
+}
+
+static inline void
+_craft_out(craft_t *craft)
+{
+ if(craft->ref)
+ lv2_atom_forge_pop(&craft->forge, &craft->frame);
+ else
+ lv2_atom_sequence_clear(craft->seq);
+}
+
+#endif // _WIDGETS_LV2_H
diff --git a/widgets.ttl b/widgets.ttl
new file mode 100644
index 0000000..df61962
--- /dev/null
+++ b/widgets.ttl
@@ -0,0 +1,168 @@
+# Copyright (c) 2018 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 xsd: <http://www.w3.org/2001/XMLSchema#> .
+@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 bufsz: <http://lv2plug.in/ns/ext/buf-size#> .
+@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> .
+@prefix opts: <http://lv2plug.in/ns/ext/options#> .
+@prefix ui: <http://lv2plug.in/ns/extentions/ui#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix widgets: <http://open-music-kontrollers.ch/lv2/widgets#> .
+@prefix canvas: <http://open-music-kontrollers.ch/lv2/canvas#> .
+
+# 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:widgets
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Router Bundle" .
+
+# Parameters
+canvas:graph
+ a lv2:Parameter ;
+ rdfs:label "Graph" ;
+ rdfs:comment "set canvas graph" ;
+ rdfs:range atom:Tuple .
+canvas:aspectRatio
+ a lv2:Parameter ;
+ rdfs:label "Aspect ratio" ;
+ rdfs:comment "set aspect ratio" ;
+ rdfs:range atom:Float ;
+ lv2:minimum 0.25 ;
+ lv2:maximum 4.0 .
+canvas:mousePositionX
+ a lv2:Parameter ;
+ rdfs:label "Mouse position X" ;
+ rdfs:comment "set mouse position X" ;
+ rdfs:range atom:Float ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 .
+canvas:mousePositionY
+ a lv2:Parameter ;
+ rdfs:label "Mouse position Y" ;
+ rdfs:comment "set mouse position Y" ;
+ rdfs:range atom:Float ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 .
+canvas:mouseButtonLeft
+ a lv2:Parameter ;
+ rdfs:label "Mouse button left" ;
+ rdfs:comment "set mouse button left" ;
+ rdfs:range atom:Bool .
+canvas:mouseButtonMiddel
+ a lv2:Parameter ;
+ rdfs:label "Mouse button middel" ;
+ rdfs:comment "set mouse button middle" ;
+ rdfs:range atom:Bool .
+canvas:mouseButtonRight
+ a lv2:Parameter ;
+ rdfs:label "Mouse button right" ;
+ rdfs:comment "set mouse button right" ;
+ rdfs:range atom:Bool .
+canvas:mouseFocus
+ a lv2:Parameter ;
+ rdfs:label "Mouse focus" ;
+ rdfs:comment "set mouse focus" ;
+ rdfs:range atom:Bool .
+
+widgets:x
+ a lv2:Parameter ;
+ rdfs:label "X" ;
+ rdfs:comment "get x" ;
+ rdfs:range atom:Float ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 .
+widgets:y
+ a lv2:Parameter ;
+ rdfs:label "Y" ;
+ rdfs:comment "get y" ;
+ rdfs:range atom:Float ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 .
+
+# xy_pad
+widgets:xy_pad
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Widgets XY Pad" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:widgets ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+ opts:requiredOption ui:updateRate ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 131072 ;
+ ] ;
+
+ patch:readable
+ canvas:graph ;
+
+ patch:writable
+ widgets:x ,
+ widgets:y ,
+ canvas:aspectRatio ,
+ canvas:mousePositionX ,
+ canvas:mousePositionY ,
+ canvas:mouseButtonLeft ,
+ canvas:mouseFocus ;
+
+ state:state [
+ widgets:x "0.5"^^xsd:float ;
+ widgets:y "0.5"^^xsd:float ;
+ canvas:aspectRatio "1.0"^^xsd:float ;
+ canvas:mousePositionX "0.5"^^xsd:float ;
+ canvas:mousePositionY "0.5"^^xsd:float ;
+ canvas:mouseButtonLeft false ;
+ canvas:mouseFocus false ;
+ ] .
diff --git a/widgets_xy_pad.c b/widgets_xy_pad.c
new file mode 100644
index 0000000..4540265
--- /dev/null
+++ b/widgets_xy_pad.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2018 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 <stdlib.h>
+#include <math.h>
+
+#include <widgets.h>
+
+#include <props.lv2/props.h>
+#include <canvas.lv2/canvas.h>
+#include <canvas.lv2/forge.h>
+
+#define MAX_SAMPLES 512
+#define MAX_GRAPH 0x20000 //FIXME actually measure this
+#define MAX_NPROPS 8
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ // writable
+ float aspect_ratio;
+ float mouse_pos_x;
+ float mouse_pos_y;
+ int32_t mouse_but_left;
+ int32_t mouse_focus;
+ float x;
+ float y;
+ // readable
+ uint8_t graph [MAX_GRAPH];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+
+ const LV2_Atom_Sequence *control;
+ craft_t notify;
+
+ LV2_Canvas_URID urid;
+ double sample_rate;
+ float update_rate;
+
+ uint32_t spf;
+ uint32_t thresh;
+
+ struct {
+ float x;
+ float y;
+ } last;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ bool dirty;
+
+ PROPS_T(props, MAX_NPROPS);
+};
+
+#define A 0.05f
+#define B (1.f - 2*A)
+
+#define SCALE(v) (B*v)
+#define MAP(v) (A + SCALE(v))
+
+static void
+_render(plughandle_t *handle)
+{
+ LV2_Atom_Forge _forge = handle->notify.forge; // clone forge object
+ LV2_Atom_Forge *forge = &_forge;
+ LV2_Canvas_URID *urid = &handle->urid;
+ LV2_Atom_Forge_Frame frame;
+
+ lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH);
+
+ const float x = MAP(handle->state.x);
+ const float y = MAP(handle->state.y);
+
+ const float hor [4] = {
+ 0.f, y,
+ 1.f, y
+ };
+
+ const float ver [4] = {
+ x, 0.f,
+ x, 1.f
+ };
+
+ if( lv2_atom_forge_tuple(forge, &frame)
+ && lv2_canvas_forge_rectangle(forge, urid, 0.f, 0.f, 1.f, 1.f)
+ && lv2_canvas_forge_style(forge, urid, 0x0000003f)
+ && lv2_canvas_forge_fill(forge, urid)
+
+ && lv2_canvas_forge_rectangle(forge, urid, MAP(0.f), MAP(0.f), SCALE(1.f), SCALE(1.f))
+ && lv2_canvas_forge_style(forge, urid, 0xffffff7f)
+ && lv2_canvas_forge_lineWidth(forge, urid, 0.01f)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_arc(forge, urid, x, y, 0.05f, 0.f, M_PI*2)
+ && lv2_canvas_forge_style(forge, urid, handle->state.mouse_but_left ? 0xff7f00ff : 0xff7f007f)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_arc(forge, urid, x, y, 0.025f, 0.f, M_PI*2)
+ && lv2_canvas_forge_style(forge, urid, 0xff7f00ff)
+ && lv2_canvas_forge_fill(forge, urid)
+
+ && lv2_canvas_forge_polyLine(forge, urid, 4, hor)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_polyLine(forge, urid, 4, ver)
+ && lv2_canvas_forge_stroke(forge, urid) )
+ {
+ lv2_atom_forge_pop(forge, &frame);
+
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->urid.Canvas_graph);
+ if(impl)
+ {
+ const LV2_Atom *value= (const LV2_Atom *)handle->state.graph;
+
+ _props_impl_set(&handle->props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+ }
+ }
+}
+
+static void
+_intercept_mouse_pos_x(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(handle->state.mouse_but_left)
+ {
+ handle->state.x += handle->state.mouse_pos_x - handle->last.x;
+ if(handle->state.x < 0.f)
+ handle->state.x = 0.f;
+ else if(handle->state.x > 1.f)
+ handle->state.x = 1.f;
+
+ const LV2_URID widgets_x = props_map(&handle->props, WIDGETS__x);
+ props_set(&handle->props, &handle->notify.forge, frames,
+ widgets_x, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+
+ handle->last.x = handle->state.mouse_pos_x;
+}
+
+static void
+_intercept_mouse_pos_y(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(handle->state.mouse_but_left)
+ {
+ handle->state.y += handle->state.mouse_pos_y - handle->last.y;
+ if(handle->state.y < 0.f)
+ handle->state.y = 0.f;
+ else if(handle->state.y > 1.f)
+ handle->state.y = 1.f;
+
+ const LV2_URID widgets_y = props_map(&handle->props, WIDGETS__y);
+ props_set(&handle->props, &handle->notify.forge, frames,
+ widgets_y, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+
+ handle->last.y = handle->state.mouse_pos_y;
+}
+
+static void
+_intercept_mouse_focus(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(!handle->state.mouse_focus)
+ {
+ handle->state.mouse_but_left = 0;
+
+ props_set(&handle->props, &handle->notify.forge, frames,
+ handle->urid.Canvas_mouseButtonLeft, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+}
+
+static void
+_intercept_dirty(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->dirty = true;
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ // readable
+ {
+ .access = LV2_PATCH__readable,
+ .property = CANVAS__graph,
+ .offset = offsetof(plugstate_t, graph),
+ .type = LV2_ATOM__Tuple,
+ .max_size = MAX_GRAPH
+ },
+ // writable
+ {
+ .property = WIDGETS__x,
+ .offset = offsetof(plugstate_t, x),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = WIDGETS__y,
+ .offset = offsetof(plugstate_t, y),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = CANVAS__aspectRatio,
+ .offset = offsetof(plugstate_t, aspect_ratio),
+ .type = LV2_ATOM__Float
+ },
+ {
+ .property = CANVAS__mousePositionX,
+ .offset = offsetof(plugstate_t, mouse_pos_x),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_mouse_pos_x
+ },
+ {
+ .property = CANVAS__mousePositionY,
+ .offset = offsetof(plugstate_t, mouse_pos_y),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_mouse_pos_y
+ },
+ {
+ .property = CANVAS__mouseButtonLeft,
+ .offset = offsetof(plugstate_t, mouse_but_left),
+ .type = LV2_ATOM__Bool,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = CANVAS__mouseFocus,
+ .offset = offsetof(plugstate_t, mouse_focus),
+ .type = LV2_ATOM__Bool,
+ .event_cb = _intercept_mouse_focus
+ }
+};
+
+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;
+ mlock(handle, sizeof(plughandle_t));
+
+ LV2_Options_Option *opts = 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_OPTIONS__options))
+ opts = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(!opts)
+ {
+ fprintf(stderr,
+ "%s: Host does not support opts:options\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ _craft_init(&handle->notify, handle->map);
+ lv2_canvas_urid_init(&handle->urid, handle->map);
+
+ const LV2_URID ui_update_rate= handle->map->map(handle->map->handle,
+ LV2_UI__updateRate);
+
+ handle->update_rate = 60.f; // fall-back
+ handle->sample_rate = rate;
+
+ for(LV2_Options_Option *opt = opts;
+ opt && (opt->key != 0) && (opt->value != NULL);
+ opt++)
+ {
+ if( (opt->key == ui_update_rate) && (opt->type == handle->notify.forge.Float) )
+ handle->update_rate = *(float*)opt->value;
+ }
+
+ handle->spf = handle->sample_rate / handle->update_rate;
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->state.x = 0.5f;
+ handle->state.y = 0.5f;
+
+ _render(handle);
+
+ 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->control = (const void *)data;
+ break;
+ case 1:
+ _craft_connect(&handle->notify, data);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+_run(plughandle_t *handle, uint32_t from, uint32_t to)
+{
+ const uint32_t num = (to - from);
+
+ for(uint32_t i = 0; i < num; i++, handle->thresh++)
+ {
+ if(handle->thresh >= handle->spf)
+ {
+ handle->thresh = 0;
+
+ if(handle->dirty)
+ {
+ _render(handle);
+ props_set(&handle->props, &handle->notify.forge, from + i,
+ handle->urid.Canvas_graph, &handle->notify.ref);
+
+ handle->dirty = false;
+ }
+ }
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ _craft_in(&handle->notify);
+
+ props_idle(&handle->props, &handle->notify.forge, 0, &handle->notify.ref);
+
+ int64_t from = 0;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(props_advance(&handle->props, &handle->notify.forge, ev->time.frames, obj, &handle->notify.ref))
+ {
+ const int64_t to = ev->time.frames;
+
+ _run(handle, from, to);
+
+ from = to;
+ }
+ }
+
+ {
+ const int64_t to = nsamples;
+
+ _run(handle, from, to);
+ }
+
+ _craft_out(&handle->notify);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ 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 = 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 = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const 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 widgets_xy_pad= {
+ .URI = WIDGETS__xy_pad,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};