aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt133
-rw-r--r--COPYING201
-rw-r--r--README.md41
-rw-r--r--VERSION1
-rw-r--r--manifest.ttl.in33
-rw-r--r--vm.c528
-rw-r--r--vm.h285
-rw-r--r--vm.ttl294
-rw-r--r--vm_ui.c591
-rw-r--r--vm_ui.ttl32
10 files changed, 2139 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..ddaeb48
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,133 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(vm.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+include_directories(${PROJECT_SOURCE_DIR}/nk_pugl)
+include_directories(${PROJECT_SOURCE_DIR}/pugl)
+
+set(CMAKE_FIND_FRAMEWORK FIRST)
+
+set(CMAKE_C_FLAGS "-fdata-sections -ffunction-sections ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-std=gnu11 -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}")
+
+if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,defs ${CMAKE_MODULE_LINKER_FLAGS}")
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}")
+elseif(WIN32)
+ set(CMAKE_C_FLAGS "-mstackrealign ${CMAKE_C_FLAGS}")
+endif()
+
+if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
+ if(APPLE)
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-dead_strip ${CMAKE_MODULE_LINKER_FLAGS}")
+ else()
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--gc-sections -Wl,-s ${CMAKE_MODULE_LINKER_FLAGS}")
+ endif()
+endif()
+
+add_definitions("-D_GNU_SOURCE=1") # asprintf
+
+if(WIN32)
+ set(VM_UI_TYPE "WindowsUI")
+elseif(APPLE)
+ set(VM_UI_TYPE "CocoaUI")
+else()
+ set(VM_UI_TYPE "X11UI")
+endif()
+
+file(STRINGS "VERSION" VM_VERSION)
+string(REPLACE "." ";" VERSION_LIST ${VM_VERSION})
+list(GET VERSION_LIST 0 VM_MAJOR_VERSION)
+list(GET VERSION_LIST 1 VM_MINOR_VERSION)
+list(GET VERSION_LIST 2 VM_MICRO_VERSION)
+add_definitions("-DVM_VERSION=\"${VM_VERSION}\"")
+
+if(NOT DEFINED PLUGIN_DEST)
+ set(PLUGIN_DEST lib/lv2/vm.lv2)
+endif()
+
+find_package(PkgConfig) # ${PKG_CONFIG_FOUND}
+
+set(LIBS m)
+set(LIBS_UI m)
+set(TAR_UI vm_ui.c)
+
+pkg_search_module(LV2 REQUIRED lv2>=1.10)
+include_directories(${LV2_INCLUDE_DIRS})
+
+find_package(OpenGL)
+if(${OPENGL_FOUND})
+ set(LIBS_UI ${LIBS_UI} ${OPENGL_LIBRARIES})
+else() # try pkg-config
+ pkg_search_module(GL REQUIRED gl)
+ if(${GL_FOUND})
+ set(LIBS_UI ${LIBS_UI} ${GL_LDFLAGS})
+ else()
+ message(FATAL_ERROR "OpenGL not found")
+ endif()
+endif()
+add_definitions("-DPUGL_HAVE_GL")
+
+if(WIN32)
+ find_library(GDI32_LIBRARY NAMES gdi32)
+ if(GDI32_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${GDI32_LIBRARY})
+ else()
+ message(FATAL_ERROR "gdi32 library not found")
+ endif()
+
+ find_library(USER32_LIBRARY NAMES user32)
+ if(USER32_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${USER32_LIBRARY})
+ else()
+ message(FATAL_ERROR "user32 library not found")
+ endif()
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_win.cpp)
+
+elseif(APPLE)
+ find_library(COCOA_LIBRARY NAMES Cocoa)
+ if(COCOA_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${COCOA_LIBRARY})
+ else()
+ message(FATAL_ERROR "Cocoa framework not found")
+ endif()
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_osx.m)
+
+else() # GNU/Linux
+ pkg_search_module(X11 REQUIRED x11>=1.6)
+ include_directories(${X11_INCLUDE_DIRS})
+ set(LIBS_UI ${LIBS_UI} ${X11_LDFLAGS})
+
+ pkg_search_module(XEXT REQUIRED xext>=1.3)
+ include_directories(${XEXT_INCLUDE_DIRS})
+ set(LIBS_UI ${LIBS_UI} ${XEXT_LDFLAGS})
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_x11.c)
+endif()
+
+add_library(vm MODULE vm.c)
+target_link_libraries(vm ${LIBS})
+set_target_properties(vm PROPERTIES PREFIX "")
+if(NOT WIN32)
+set_target_properties(vm PROPERTIES LINK_FLAGS "-Wl,-e,lv2_descriptor")
+endif()
+install(TARGETS vm DESTINATION ${PLUGIN_DEST})
+
+add_library(vm_ui MODULE ${TAR_UI})
+target_link_libraries(vm_ui ${LIBS_UI})
+set_target_properties(vm_ui PROPERTIES PREFIX "")
+if(NOT WIN32)
+set_target_properties(vm_ui PROPERTIES LINK_FLAGS "-Wl,-e,lv2ui_descriptor")
+endif()
+install(TARGETS vm_ui DESTINATION ${PLUGIN_DEST})
+
+configure_file(${PROJECT_SOURCE_DIR}/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
+install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${PLUGIN_DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/vm.ttl DESTINATION ${PLUGIN_DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/vm_ui.ttl DESTINATION ${PLUGIN_DEST})
+
+install(FILES ${PROJECT_SOURCE_DIR}/nuklear/extra_font/Cousine-Regular.ttf DESTINATION ${PLUGIN_DEST})
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/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/README.md b/README.md
new file mode 100644
index 0000000..2ad2982
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+# vm.lv2
+
+## A simple Virtual Machine for LV2
+
+
+### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/vm.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/vm.lv2/commits/master)
+
+### Dependencies
+
+* [LV2](http://lv2plug.in) (LV2 Plugin Standard)
+* [pugl](http://drobilla.net/software/pugl) (Portable API for OpenGL GUIs)
+* [nuklear](https://github.com/vurtun/nuklear) (Immediate-mode GUI)
+
+### Build / install
+
+ git clone https://github.com/OpenMusicKontrollers/vm.lv2.git
+ cd vm.lv2
+ mkdir build
+ cd build
+ cmake ..
+ make
+ sudo make install
+
+### License
+
+Copyright (c) 2017 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/VERSION b/VERSION
new file mode 100644
index 0000000..17e51c3
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.1
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..38dc17e
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,33 @@
+# Copyright (c) 2017 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 rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+
+@prefix vm: <http://open-music-kontrollers.ch/lv2/vm#> .
+
+vm:vm
+ a lv2:Plugin ;
+ lv2:minorVersion @VM_MINOR_VERSION@ ;
+ lv2:microVersion @VM_MICRO_VERSION@ ;
+ lv2:binary <vm@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:vm_ui
+ a ui:@VM_UI_TYPE@ ;
+ ui:binary <vm_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <vm_ui.ttl> .
diff --git a/vm.c b/vm.c
new file mode 100644
index 0000000..9fc96b4
--- /dev/null
+++ b/vm.c
@@ -0,0 +1,528 @@
+/*
+ * Copyright (c) 2017 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 <stdlib.h>
+#include <stdatomic.h>
+#include <math.h>
+#include <inttypes.h>
+
+#include <vm.h>
+
+#define SLOT_MAX 64
+
+typedef struct _stack_t stack_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _stack_t {
+ float slots [SLOT_MAX];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ LV2_Atom_Sequence *notify;
+ const float *in [CTRL_MAX];
+ float *out [CTRL_MAX];
+
+ float in0 [CTRL_MAX];
+ float out0 [CTRL_MAX];
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ uint32_t graph_size;
+ opcode_t opcode;
+
+ stack_t stack;
+ bool recalc;
+
+ command_t cmds [ITEMS_MAX];
+};
+
+static inline void
+_stack_clear(stack_t *stack)
+{
+ for(unsigned i = 0; i < SLOT_MAX; i++)
+ stack->slots[i] = 0.f;
+}
+
+static inline void
+_stack_push(stack_t *stack, float val)
+{
+ for(unsigned i = SLOT_MAX - 1; i > 0; i--)
+ stack->slots[i] = stack->slots[i - 1];
+
+ stack->slots[0] = val;
+}
+
+static inline float
+_stack_pop(stack_t *stack)
+{
+ const float val = stack->slots[0];
+
+ for(unsigned i = 0; i < SLOT_MAX - 1; i++)
+ stack->slots[i] = stack->slots[i + 1];
+
+ stack->slots[SLOT_MAX - 1] = 0.f;
+
+ return val;
+}
+
+static inline void
+_stack_push_num(stack_t *stack, const float *val, unsigned num)
+{
+ for(unsigned i = SLOT_MAX - num; i > 0; i--)
+ stack->slots[i] = stack->slots[i - num];
+
+ for(unsigned i = 0; i < num; i++)
+ stack->slots[i] = val[i];
+}
+
+static inline void
+_stack_pop_num(stack_t *stack, float *val, unsigned num)
+{
+ for(unsigned i = 0; i < num; i++)
+ val[i] = stack->slots[i];
+
+ for(unsigned i = 0; i < SLOT_MAX - num; i++)
+ stack->slots[i] = stack->slots[i + num];
+
+ for(unsigned i = SLOT_MAX - 1 - num; i < SLOT_MAX - 1; i++)
+ stack->slots[i] = 0.f;
+}
+
+static void
+_intercept_graph(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->graph_size = impl->value.size;
+ handle->recalc = true;
+
+ vm_deserialize(&handle->opcode, &handle->forge, handle->cmds,
+ impl->value.size, impl->value.body);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = VM__graph,
+ .offset = offsetof(plugstate_t, graph),
+ .type = LV2_ATOM__Tuple,
+ .max_size = GRAPH_SIZE,
+ .event_mask = PROP_EVENT_WRITE,
+ .event_cb = _intercept_graph,
+ }
+};
+
+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)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ vm_opcode_init(&handle->opcode, handle->map);
+
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "props_init failed\n");
+ free(handle);
+ return NULL;
+ }
+
+ if(!props_register(&handle->props, defs, MAX_NPROPS, &handle->state, &handle->stash))
+ {
+ fprintf(stderr, "props_register failed\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->recalc = true;
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = data;
+ break;
+ case 1:
+ handle->notify = data;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ handle->in[port - 2] = data;
+ break;
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ handle->out[port - 10] = data;
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ const uint32_t capacity = handle->notify->atom.size;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->notify, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ int64_t last_t = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom *atom= &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ }
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ if(handle->in0[i] != *handle->in[i])
+ {
+ handle->recalc = true;
+ handle->in0[i] = *handle->in[i];
+ }
+ }
+
+ if(handle->recalc)
+ {
+ _stack_clear(&handle->stack);
+
+ for(unsigned i = 0; i < ITEMS_MAX; i++)
+ {
+ command_t *cmd = &handle->cmds[i];
+ bool terminate = false;
+
+ switch(cmd->type)
+ {
+ case COMMAND_BOOL:
+ {
+ const float c = cmd->i32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_INT:
+ {
+ const float c = cmd->i32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_LONG:
+ {
+ const float c = cmd->i64;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_FLOAT:
+ {
+ const float c = cmd->f32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_DOUBLE:
+ {
+ const float c = cmd->f64;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_OPCODE:
+ {
+ switch(cmd->op)
+ {
+ case OP_PUSH:
+ {
+ int j = floorf(_stack_pop(&handle->stack)); //FIXME check
+ if(j < 0)
+ j = 0;
+ if(j >= CTRL_MAX)
+ j = CTRL_MAX - 1;
+
+ const float c = handle->in0[j];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ADD:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const float c = ab[0] + ab[1];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SUB:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const float c = ab[0] - ab[1];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_MUL:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const float c = ab[0] * ab[1];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_DIV:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const float c = ab[0] / ab[1];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_NEG:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = -a;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_POW:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const float c = pow(ab[0], ab[1]);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SQRT:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = sqrtf(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_EXP:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = expf(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_EXP_2:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = exp2f(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_EXP_10:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = exp10f(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LOG:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = logf(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LOG_2:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = log2f(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LOG_10:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = log10f(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SIN:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = sinf(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_COS:
+ {
+ const float a = _stack_pop(&handle->stack);
+ const float c = cosf(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SWAP:
+ {
+ float ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ _stack_push_num(&handle->stack, ab, 2);
+ } break;
+ case OP_NOP:
+ {
+ terminate = true;
+ } break;
+ case OP_MAX:
+ break;
+ }
+ } break;
+ case COMMAND_NOP:
+ {
+ terminate = true;
+ } break;
+ case COMMAND_MAX:
+ break;
+ }
+
+ if(terminate)
+ break;
+ }
+
+ _stack_pop_num(&handle->stack, handle->out0, CTRL_MAX);
+#if 0
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ printf("out %u: %f\n", i, handle->out0[i]);
+#endif
+
+ handle->recalc = false;
+ }
+
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ *handle->out[i] = handle->out0[i];;
+}
+
+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 = 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 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 vm_nuk = {
+ .URI = VM_PREFIX"vm",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &vm_nuk;
+ default:
+ return NULL;
+ }
+}
diff --git a/vm.h b/vm.h
new file mode 100644
index 0000000..0986f85
--- /dev/null
+++ b/vm.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2017 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 _VM_LV2_H
+#define _VM_LV2_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/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/log/logger.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define VM_URI "http://open-music-kontrollers.ch/lv2/vm"
+#define VM_PREFIX VM_URI"#"
+
+#define VM__vm VM_PREFIX"vm"
+#define VM__vm_ui VM_PREFIX"vm_ui"
+
+#define VM__graph VM_PREFIX"graph"
+
+#define VM__opNop VM_PREFIX"opNop"
+#define VM__opPush VM_PREFIX"opPush"
+#define VM__opAdd VM_PREFIX"opAdd"
+#define VM__opSub VM_PREFIX"opSub"
+#define VM__opMul VM_PREFIX"opMul"
+#define VM__opDiv VM_PREFIX"opDiv"
+#define VM__opNeg VM_PREFIX"opNeg"
+#define VM__opPow VM_PREFIX"opPow"
+#define VM__opSqrt VM_PREFIX"opSqrt"
+#define VM__opExp VM_PREFIX"opExp"
+#define VM__opExp2 VM_PREFIX"opExp2"
+#define VM__opExp10 VM_PREFIX"opExp10"
+#define VM__opLog VM_PREFIX"opLog"
+#define VM__opLog2 VM_PREFIX"opLog2"
+#define VM__opLog10 VM_PREFIX"opLog10"
+#define VM__opSin VM_PREFIX"opSin"
+#define VM__opCos VM_PREFIX"opCos"
+#define VM__opSwap VM_PREFIX"opSwap"
+
+#define MAX_NPROPS 1
+#define CTRL_MAX 8
+#define ITEMS_MAX 128
+#define GRAPH_SIZE (ITEMS_MAX * sizeof(LV2_Atom_Long))
+
+#include <props.lv2/props.h>
+
+typedef enum _opcode_enum_t opcode_enum_t;
+typedef enum _command_enum_t command_enum_t;
+typedef struct _opcode_t opcode_t;
+typedef struct _command_t command_t;
+typedef struct _plugstate_t plugstate_t;
+
+enum _opcode_enum_t{
+ OP_NOP = 0,
+
+ OP_PUSH,
+ OP_ADD,
+ OP_SUB,
+ OP_MUL,
+ OP_DIV,
+ OP_NEG,
+ OP_POW,
+ OP_SQRT,
+ OP_EXP,
+ OP_EXP_2,
+ OP_EXP_10,
+ OP_LOG,
+ OP_LOG_2,
+ OP_LOG_10,
+ OP_SIN,
+ OP_COS,
+ OP_SWAP,
+
+ OP_MAX
+};
+
+enum _command_enum_t {
+ COMMAND_NOP = 0,
+
+ COMMAND_OPCODE,
+ COMMAND_BOOL,
+ COMMAND_INT,
+ COMMAND_LONG,
+ COMMAND_FLOAT,
+ COMMAND_DOUBLE,
+
+ COMMAND_MAX
+};
+
+struct _opcode_t {
+ LV2_URID op [OP_MAX];
+};
+
+struct _command_t {
+ command_enum_t type;
+
+ union {
+ int32_t i32;
+ int64_t i64;
+ float f32;
+ double f64;
+ opcode_enum_t op;
+ };
+};
+
+struct _plugstate_t {
+ uint8_t graph [GRAPH_SIZE];
+};
+
+static inline void
+vm_opcode_init(opcode_t *opcode, LV2_URID_Map *map)
+{
+ opcode->op[OP_NOP] = map->map(map->handle, VM__opNop);
+ opcode->op[OP_PUSH] = map->map(map->handle, VM__opPush);
+ opcode->op[OP_ADD] = map->map(map->handle, VM__opAdd);
+ opcode->op[OP_SUB] = map->map(map->handle, VM__opSub);
+ opcode->op[OP_MUL] = map->map(map->handle, VM__opMul);
+ opcode->op[OP_DIV] = map->map(map->handle, VM__opDiv);
+ opcode->op[OP_NEG] = map->map(map->handle, VM__opNeg);
+ opcode->op[OP_POW] = map->map(map->handle, VM__opPow);
+ opcode->op[OP_SQRT] = map->map(map->handle, VM__opSqrt);
+ opcode->op[OP_EXP] = map->map(map->handle, VM__opExp);
+ opcode->op[OP_EXP_2] = map->map(map->handle, VM__opExp2);
+ opcode->op[OP_EXP_10] = map->map(map->handle, VM__opExp10);
+ opcode->op[OP_LOG] = map->map(map->handle, VM__opLog);
+ opcode->op[OP_LOG_2] = map->map(map->handle, VM__opLog2);
+ opcode->op[OP_LOG_10] = map->map(map->handle, VM__opLog10);
+ opcode->op[OP_SIN] = map->map(map->handle, VM__opSin);
+ opcode->op[OP_COS] = map->map(map->handle, VM__opCos);
+ opcode->op[OP_SWAP] = map->map(map->handle, VM__opSwap);
+}
+
+static inline opcode_enum_t
+vm_opcode_unmap(opcode_t *opcode, LV2_URID otype)
+{
+ for(unsigned i = 0; i < OP_MAX; i++)
+ {
+ if(otype == opcode->op[i])
+ return i;
+ }
+
+ return OP_NOP;
+}
+
+static inline LV2_URID
+vm_opcode_map(opcode_t *opcode, opcode_enum_t op)
+{
+ return opcode->op[op];
+}
+
+static inline LV2_Atom_Forge_Ref
+vm_serialize(opcode_t *opcode, LV2_Atom_Forge *forge, const command_t *cmds)
+{
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_tuple(forge, &frame);
+
+ for(unsigned i = 0; i < ITEMS_MAX; i++)
+ {
+ const command_t *cmd = &cmds[i];
+ bool terminate = false;
+
+ switch(cmd->type)
+ {
+ case COMMAND_BOOL:
+ {
+ if(ref)
+ ref = lv2_atom_forge_bool(forge, cmd->i32);
+ } break;
+ case COMMAND_INT:
+ {
+ if(ref)
+ ref = lv2_atom_forge_int(forge, cmd->i32);
+ } break;
+ case COMMAND_LONG:
+ {
+ if(ref)
+ ref = lv2_atom_forge_long(forge, cmd->i64);
+ } break;
+ case COMMAND_FLOAT:
+ {
+ if(ref)
+ ref = lv2_atom_forge_float(forge, cmd->f32);
+ } break;
+ case COMMAND_DOUBLE:
+ {
+ if(ref)
+ ref = lv2_atom_forge_double(forge, cmd->f64);
+ } break;
+ case COMMAND_OPCODE:
+ {
+ const LV2_URID otype = vm_opcode_map(opcode, cmd->op);
+ if(ref && otype)
+ ref = lv2_atom_forge_urid(forge, otype);
+ } break;
+ case COMMAND_NOP:
+ {
+ terminate = true;
+ } break;
+ case COMMAND_MAX:
+ break;
+ }
+
+ if(terminate)
+ break;
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+
+ return ref;
+}
+
+static inline void
+vm_deserialize(opcode_t *opcode, LV2_Atom_Forge *forge,
+ command_t *cmds, uint32_t size, const LV2_Atom *body)
+{
+ command_t *cmd = cmds;
+ memset(cmds, 0x0, sizeof(command_t)*ITEMS_MAX);
+
+ LV2_ATOM_TUPLE_BODY_FOREACH(body, size, item)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)item;
+
+ if(item->type == forge->Bool)
+ {
+ cmd->type = COMMAND_BOOL;
+ cmd->i32 = ((const LV2_Atom_Bool *)item)->body;
+ }
+ else if(item->type == forge->Int)
+ {
+ cmd->type = COMMAND_INT;
+ cmd->i32 = ((const LV2_Atom_Int *)item)->body;
+ }
+ else if(item->type == forge->Long)
+ {
+ cmd->type = COMMAND_LONG;
+ cmd->i64 = ((const LV2_Atom_Long *)item)->body;
+ }
+ else if(item->type == forge->Float)
+ {
+ cmd->type = COMMAND_FLOAT;
+ cmd->f32 = ((const LV2_Atom_Float *)item)->body;
+ }
+ else if(item->type == forge->Double)
+ {
+ cmd->type = COMMAND_DOUBLE;
+ cmd->f64 = ((const LV2_Atom_Double *)item)->body;
+ }
+ else if(item->type == forge->URID)
+ {
+ cmd->type = COMMAND_OPCODE;
+ cmd->op = vm_opcode_unmap(opcode, ((const LV2_Atom_URID *)item)->body);
+ }
+ else
+ {
+ break;
+ }
+
+ cmd += 1;
+
+ if(cmd >= cmds + ITEMS_MAX)
+ break;
+ }
+}
+
+#endif // _VM_LV2_H
diff --git a/vm.ttl b/vm.ttl
new file mode 100644
index 0000000..c109b45
--- /dev/null
+++ b/vm.ttl
@@ -0,0 +1,294 @@
+# Copyright (c) 2017 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 foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix work: <http://lv2plug.in/ns/ext/worker#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix vm: <http://open-music-kontrollers.ch/lv2/vm#> .
+
+# 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:vm
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Vm Bundle" .
+
+# Parameters
+vm:graph
+ a lv2:Parameter ;
+ rdfs:range atom:Tuple ;
+ rdfs:label "Graph" ;
+ rdfs:comment "vm graph tuple" .
+
+vm:opPush
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Push Control" .
+vm:opAdd
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Addition" .
+vm:opSub
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Subtraction" .
+vm:opMul
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Multiplication" .
+vm:opDiv
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Division" .
+vm:opNeg
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Negate" .
+vm:opPow
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Power" .
+vm:opSqrt
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Square root" .
+vm:opExp
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Exponentiation" .
+vm:opExp2
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Exponentiation base 2" .
+vm:opExp10
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Exponentiation base 10" .
+vm:opLog
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Logarithm" .
+vm:opLog2
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Logarithm base 2" .
+vm:opLog10
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Logarithm base 10" .
+vm:opSin
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Sinus" .
+vm:opCos
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Cosinus" .
+vm:opSwap
+ a rdfs:Class, owl:Class ;
+ rdfs:label "Swap" .
+
+# Plugin
+vm:vm
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "Vm" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, work:schedule, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface, work:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 65536 ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 65536 ;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 2 ;
+ lv2:symbol "control_in_0" ;
+ lv2:name "Control In 0" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.5;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 3 ;
+ lv2:symbol "control_in_1" ;
+ lv2:name "Control In 1" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 4 ;
+ lv2:symbol "control_in_2" ;
+ lv2:name "Control In 2" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 5 ;
+ lv2:symbol "control_in_3" ;
+ lv2:name "Control In 3" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 6 ;
+ lv2:symbol "control_in_4" ;
+ lv2:name "Control In 4" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 7 ;
+ lv2:symbol "control_in_5" ;
+ lv2:name "Control In 5" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 8 ;
+ lv2:symbol "control_in_6" ;
+ lv2:name "Control In 6" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 9 ;
+ lv2:symbol "control_in_7" ;
+ lv2:name "Control In 7" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 10 ;
+ lv2:symbol "control_out_0" ;
+ lv2:name "Control Out 0" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 11 ;
+ lv2:symbol "control_out_1" ;
+ lv2:name "Control Out 1" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 12 ;
+ lv2:symbol "control_out_2" ;
+ lv2:name "Control Out 2" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 13 ;
+ lv2:symbol "control_out_3" ;
+ lv2:name "Control Out 3" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 14 ;
+ lv2:symbol "control_out_4" ;
+ lv2:name "Control Out 4" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 15 ;
+ lv2:symbol "control_out_5" ;
+ lv2:name "Control Out 5" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 16 ;
+ lv2:symbol "control_out_6" ;
+ lv2:name "Control Out 6" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:ControlPort;
+ lv2:index 17 ;
+ lv2:symbol "control_out_7" ;
+ lv2:name "Control Out 7" ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opPush
+ 0.5 vm:opMul
+ 0.5 vm:opAdd
+ )
+ ]
+ ] .
diff --git a/vm_ui.c b/vm_ui.c
new file mode 100644
index 0000000..c23d3fe
--- /dev/null
+++ b/vm_ui.c
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2017 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 <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <vm.h>
+
+#define NK_PUGL_IMPLEMENTATION
+#include "nk_pugl/nk_pugl.h"
+
+#ifdef Bool
+# undef Bool
+#endif
+
+typedef struct _atom_ser_t atom_ser_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _atom_ser_t {
+ uint32_t size;
+ uint32_t offset;
+ union {
+ const LV2_Atom *atom;
+ const LV2_Atom_Event *ev;
+ uint8_t *buf;
+ };
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Atom_Forge forge;
+
+ LV2_URID atom_eventTransfer;
+ LV2_URID vm_graph;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ nk_pugl_window_t win;
+
+ LV2UI_Controller *controller;
+ LV2UI_Write_Function writer;
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ uint32_t graph_size;
+
+ float dy;
+
+ atom_ser_t ser;
+ opcode_t opcode;
+
+ float in0 [CTRL_MAX];
+ float out0 [CTRL_MAX];
+
+ command_t cmds [ITEMS_MAX];
+};
+
+static const char *op_labels [] = {
+ [OP_NOP] = "-",
+ [OP_PUSH] = "Push",
+ [OP_ADD] = "Add",
+ [OP_SUB] = "Sub",
+ [OP_MUL] = "Mul",
+ [OP_DIV] = "Div",
+ [OP_NEG] = "Neg",
+ [OP_POW] = "Pow",
+ [OP_SQRT] = "Sqrt",
+ [OP_EXP] = "Exp",
+ [OP_EXP_2] = "Exp2",
+ [OP_EXP_10] = "Exp10",
+ [OP_LOG] = "Log",
+ [OP_LOG_2] = "Log2",
+ [OP_LOG_10] = "Log10",
+ [OP_SIN] = "Sin",
+ [OP_COS] = "Cos",
+ [OP_SWAP] = "Swap"
+};
+
+static const char *cmd_labels [] = {
+ [COMMAND_NOP] = "-",
+ [COMMAND_OPCODE] = "OpCode" ,
+ [COMMAND_BOOL] = "Boolean" ,
+ [COMMAND_INT] = "Integer" ,
+ [COMMAND_LONG] = "Long" ,
+ [COMMAND_FLOAT] = "Float" ,
+ [COMMAND_DOUBLE] = "Double"
+};
+
+static void
+_intercept_graph(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->graph_size = impl->value.size;
+
+ vm_deserialize(&handle->opcode, &handle->forge, handle->cmds,
+ impl->value.size, impl->value.body);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = VM__graph,
+ .offset = offsetof(plugstate_t, graph),
+ .type = LV2_ATOM__Tuple,
+ .max_size = GRAPH_SIZE,
+ .event_mask = PROP_EVENT_WRITE,
+ .event_cb = _intercept_graph
+ }
+};
+
+static LV2_Atom_Forge_Ref
+_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size)
+{
+ atom_ser_t *ser = handle;
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+
+ const uint32_t new_offset = ser->offset + size;
+ if(new_offset > ser->size)
+ {
+ uint32_t new_size = ser->size << 1;
+ while(new_offset > new_size)
+ new_size <<= 1;
+
+ if(!(ser->buf = realloc(ser->buf, new_size)))
+ return 0; // realloc failed
+
+ ser->size = new_size;
+ }
+
+ memcpy(ser->buf + ser->offset, buf, size);
+ ser->offset = new_offset;
+
+ return ref;
+}
+
+static LV2_Atom *
+_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ atom_ser_t *ser = handle;
+
+ const uint32_t offset = ref - 1;
+
+ return (LV2_Atom *)(ser->buf + offset);
+}
+
+static void
+_set_property(plughandle_t *handle, LV2_URID property)
+{
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+
+ LV2_Atom_Forge_Ref ref = 1;
+ props_set(&handle->props, &handle->forge, 0, property, &ref);
+
+ const LV2_Atom_Event *ev = ser->ev;
+ const LV2_Atom *atom = &ev->body;
+ handle->writer(handle->controller, 0, lv2_atom_total_size(atom),
+ handle->atom_eventTransfer, atom);
+}
+
+static bool
+_tooltip_visible(struct nk_context *ctx)
+{
+ return nk_widget_has_mouse_click_down(ctx, NK_BUTTON_RIGHT, nk_true)
+ || (nk_widget_is_hovered(ctx) && nk_input_is_key_down(&ctx->input, NK_KEY_CTRL));
+}
+
+static inline void
+_draw_separator(struct nk_context *ctx, float line_width)
+{
+ const struct nk_rect b = nk_widget_bounds(ctx);
+ const float x0 = b.x;
+ const float x1 = b.x + b.w;
+ const float y = b.y + b.h;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+ nk_stroke_line(canvas, x0, y, x1, y, line_width, ctx->style.window.background);
+}
+
+static void
+_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ const float dy = handle->dy;
+
+ if(nk_begin(ctx, "Vm", wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ nk_window_set_bounds(ctx, wbounds);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float wh = wbounds.h
+ - 2*ctx->style.window.padding.y
+ - 2*ctx->style.window.border;
+ nk_layout_row_dynamic(ctx, wh, 2);
+
+ if(nk_group_begin(ctx, "Controls", NK_WINDOW_TITLE | NK_WINDOW_BORDER))
+ {
+ if(nk_tree_push(ctx, NK_TREE_NODE, "Inputs", NK_MAXIMIZED))
+ {
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ char label [16];
+ snprintf(label, 16, "Input %u:", i);
+
+ const float old_val = handle->in0[i];
+ nk_property_float(ctx, label, 0.f, &handle->in0[i], 1.f, 0.01f, 0.01f);
+ if(old_val != handle->in0[i])
+ handle->writer(handle->controller, i + 2, sizeof(float), 0, &handle->in0[i]);
+ }
+ nk_tree_pop(ctx);
+ }
+
+ nk_spacing(ctx, 1);
+
+ if(nk_tree_push(ctx, NK_TREE_NODE, "Outputs", NK_MAXIMIZED))
+ {
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ char label [16];
+ snprintf(label, 16, "Output %u", i);
+ nk_value_float(ctx, label, handle->out0[i]);
+ }
+ nk_tree_pop(ctx);
+ }
+
+ nk_group_end(ctx);
+ }
+
+ if(nk_group_begin(ctx, "Operations", NK_WINDOW_TITLE | NK_WINDOW_BORDER))
+ {
+ nk_layout_row_dynamic(ctx, dy, 2);
+ bool sync = false;
+
+ for(unsigned i = 0; i < ITEMS_MAX; i++)
+ {
+ command_t *cmd = &handle->cmds[i];
+ bool terminate = false;
+
+ const command_enum_t old_cmd_type = cmd->type;
+ int cmd_type = old_cmd_type;
+ nk_combobox(ctx, cmd_labels, COMMAND_MAX, &cmd_type, dy, nk_vec2(nk_widget_width(ctx), dy*COMMAND_MAX));
+ if(old_cmd_type != cmd_type)
+ {
+ cmd->type = cmd_type;
+ cmd->i32 = 0; // reset value
+ sync = true;
+ }
+
+ switch(cmd->type)
+ {
+ case COMMAND_BOOL:
+ {
+ /*FIXME
+ const int32_t c = ((const LV2_Atom_Bool *)item)->body;
+ nk_labelf(ctx, NK_TEXT_LEFT, "%s", c ? "true" : "false");
+ */
+ nk_spacing(ctx, 1);
+ } break;
+ case COMMAND_INT:
+ {
+ int i32 = cmd->i32;
+ nk_property_int(ctx, "#", 0, &i32, CTRL_MAX - 1, 1, 0.01f); //FIXME
+ if(i32 != cmd->i32)
+ {
+ cmd->i32 = i32;
+ sync = true;
+ }
+ } break;
+ case COMMAND_LONG:
+ {
+ int i64 = cmd->i64;
+ nk_property_int(ctx, "#", 0, &i64, CTRL_MAX - 1, 1, 0.01f); //FIXME
+ if(i64 != cmd->i64)
+ {
+ cmd->i64 = i64;
+ sync = true;
+ }
+ } break;
+ case COMMAND_FLOAT:
+ {
+ float f32 = cmd->f32;
+ nk_property_float(ctx, "#", 0.f, &f32, 1.f, 0.01f, 0.01f);
+ if(f32 != cmd->f32)
+ {
+ cmd->f32 = f32;
+ sync = true;
+ }
+ } break;
+ case COMMAND_DOUBLE:
+ {
+ double f64 = cmd->f64;
+ nk_property_double(ctx, "#", 0.f, &f64, 1.f, 0.01f, 0.01f);
+ if(f64 != cmd->f64)
+ {
+ cmd->f64 = f64;
+ sync = true;
+ }
+ } break;
+ case COMMAND_OPCODE:
+ {
+ int op = cmd->op;
+ nk_combobox(ctx, op_labels, OP_MAX, &op, dy, nk_vec2(nk_widget_width(ctx), dy*OP_MAX));
+ if(op != cmd->op)
+ {
+ cmd->op = op;
+ sync = true;
+ }
+ } break;
+ case COMMAND_NOP:
+ {
+ terminate = true;
+ } break;
+ case COMMAND_MAX:
+ break;
+ }
+
+ if(terminate)
+ {
+ for(unsigned j = i; j < ITEMS_MAX; j++)
+ handle->cmds[j].type = COMMAND_NOP;
+
+ break;
+ }
+ }
+
+ if(sync)
+ {
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+ vm_serialize(&handle->opcode, &handle->forge, handle->cmds);
+ props_impl_t *impl = _props_impl_search(&handle->props, handle->vm_graph);
+ if(impl)
+ _props_set(&handle->props, impl, ser->atom->type, ser->atom->size, LV2_ATOM_BODY_CONST(ser->atom));
+
+ _set_property(handle, handle->vm_graph);
+ }
+
+ nk_group_end(ctx);
+ }
+ }
+
+ nk_end(ctx);
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ void *parent = NULL;
+ LV2UI_Resize *host_resize = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__resize))
+ host_resize = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!parent)
+ {
+ fprintf(stderr,
+ "%s: Host does not support ui:parent\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ vm_opcode_init(&handle->opcode, handle->map);
+
+ if(!props_init(&handle->props, MAX_NPROPS, plugin_uri, handle->map, handle))
+ {
+ fprintf(stderr, "props_init failed\n");
+ free(handle);
+ return NULL;
+ }
+
+ if(!props_register(&handle->props, defs, MAX_NPROPS, &handle->state, &handle->stash))
+ {
+ fprintf(stderr, "props_register failed\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->atom_eventTransfer = handle->map->map(handle->map->handle, LV2_ATOM__eventTransfer);
+ handle->vm_graph = handle->map->map(handle->map->handle, VM__graph);
+
+ handle->controller = controller;
+ handle->writer = write_function;
+
+ const char *NK_SCALE = getenv("NK_SCALE");
+ const float scale = NK_SCALE ? atof(NK_SCALE) : 1.f;
+ handle->dy = 20.f * scale;
+
+ nk_pugl_config_t *cfg = &handle->win.cfg;
+ cfg->width = 720 * scale;
+ cfg->height = 720 * scale;
+ cfg->resizable = true;
+ cfg->ignore = false;
+ cfg->class = "vm";
+ cfg->title = "Vm";
+ cfg->parent = (intptr_t)parent;
+ cfg->data = handle;
+ cfg->expose = _expose;
+
+ char *path;
+ if(asprintf(&path, "%sCousine-Regular.ttf", bundle_path) == -1)
+ path = NULL;
+
+ cfg->font.face = path;
+ cfg->font.size = 13 * scale;
+
+ *(intptr_t *)widget = nk_pugl_init(&handle->win);
+ nk_pugl_show(&handle->win);
+
+ if(path)
+ free(path);
+
+ if(host_resize)
+ host_resize->ui_resize(host_resize->handle, cfg->width, cfg->height);
+
+ atom_ser_t *ser = &handle->ser;
+ ser->size = 1024;
+ ser->buf = malloc(ser->size);
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ nk_pugl_hide(&handle->win);
+ nk_pugl_shutdown(&handle->win);
+
+ atom_ser_t *ser = &handle->ser;
+ if(ser->buf)
+ free(ser->buf);
+
+ free(handle);
+}
+
+static void
+port_event(LV2UI_Handle instance, uint32_t index, uint32_t size,
+ uint32_t protocol, const void *buf)
+{
+ plughandle_t *handle = instance;
+
+ switch(index)
+ {
+ case 1:
+ {
+ if(protocol == handle->atom_eventTransfer)
+ {
+ const LV2_Atom_Object *obj = buf;
+
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+
+ LV2_Atom_Forge_Ref ref;
+ if(props_advance(&handle->props, &handle->forge, 0, obj, &ref))
+ {
+ nk_pugl_post_redisplay(&handle->win);
+ }
+ }
+ } break;
+
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ {
+ if(protocol == 0)
+ {
+ handle->in0[index - 2] = *(const float *)buf;
+ nk_pugl_post_redisplay(&handle->win);
+ }
+ } break;
+
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ {
+ if(protocol == 0)
+ {
+ handle->out0[index - 10] = *(const float *)buf;
+ nk_pugl_post_redisplay(&handle->win);
+ }
+ } break;
+ }
+}
+
+static int
+_idle(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ return nk_pugl_process_events(&handle->win);
+}
+
+static const LV2UI_Idle_Interface idle_ext = {
+ .idle = _idle
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ return &idle_ext;
+
+ return NULL;
+}
+
+const LV2UI_Descriptor vm_vm_ui = {
+ .URI = VM_PREFIX"vm_ui",
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &vm_vm_ui;
+ default:
+ return NULL;
+ }
+}
diff --git a/vm_ui.ttl b/vm_ui.ttl
new file mode 100644
index 0000000..d7acc7b
--- /dev/null
+++ b/vm_ui.ttl
@@ -0,0 +1,32 @@
+# Copyright (c) 2017 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 ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+
+@prefix vm: <http://open-music-kontrollers.ch/lv2/vm#> .
+
+vm:vm_ui
+ ui:portNotification [
+ ui:plugin vm:vm ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap ;
+ lv2:optionalFeature log:log ;
+ lv2:extensionData ui:idleInterface .