aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml79
-rw-r--r--ChangeLog50
-rw-r--r--README.md90
-rw-r--r--VERSION2
-rw-r--r--gitlab-ci/generic.yml106
-rw-r--r--manifest.ttl.in65
-rw-r--r--meson.build170
-rw-r--r--meson_options.txt7
-rw-r--r--png/COPYING6
-rw-r--r--png/cancel.pngbin0 -> 2642 bytes
-rw-r--r--png/plus.pngbin0 -> 2249 bytes
-rw-r--r--png/up_arrow.pngbin0 -> 2408 bytes
-rw-r--r--props.lv2/.gitlab-ci.yml2
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/VERSION1
-rw-r--r--props.lv2/gitlab-ci/generic.yml106
-rw-r--r--props.lv2/meson.build82
-rw-r--r--props.lv2/props.h1226
-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.ttl151
-rw-r--r--props.lv2/test/props_test.c647
-rw-r--r--screenshots/screenshot_1.pngbin0 -> 80779 bytes
-rw-r--r--subprojects/nk_pugl/.gitlab-ci.yml77
-rw-r--r--subprojects/nk_pugl/COPYING201
-rw-r--r--subprojects/nk_pugl/VERSION1
-rw-r--r--subprojects/nk_pugl/example/example.c (renamed from example/example.c)0
-rw-r--r--subprojects/nk_pugl/meson.build84
-rw-r--r--subprojects/nk_pugl/meson_options.txt4
-rw-r--r--subprojects/nk_pugl/nk_pugl/nk_pugl.h (renamed from nk_pugl/nk_pugl.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitattributes (renamed from nuklear/.gitattributes)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitignore (renamed from nuklear/.gitignore)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitmodules (renamed from nuklear/.gitmodules)0
-rw-r--r--subprojects/nk_pugl/nuklear/.travis.yml (renamed from nuklear/.travis.yml)0
-rw-r--r--subprojects/nk_pugl/nuklear/Readme.md (renamed from nuklear/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h (renamed from nuklear/demo/allegro5/KeyboardHandleriOS.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m (renamed from nuklear/demo/allegro5/KeyboardHandleriOS.m)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/Makefile (renamed from nuklear/demo/allegro5/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md (renamed from nuklear/demo/allegro5/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/main.c (renamed from nuklear/demo/allegro5/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h (renamed from nuklear/demo/allegro5/nuklear_allegro5.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/calculator.c (renamed from nuklear/demo/calculator.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/build.bat (renamed from nuklear/demo/d3d11/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/main.c (renamed from nuklear/demo/d3d11/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h (renamed from nuklear/demo/d3d11/nuklear_d3d11.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl (renamed from nuklear/demo/d3d11/nuklear_d3d11.hlsl)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h (renamed from nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h (renamed from nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/build.bat (renamed from nuklear/demo/d3d9/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/main.c (renamed from nuklear/demo/d3d9/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h (renamed from nuklear/demo/d3d9/nuklear_d3d9.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/build.bat (renamed from nuklear/demo/gdi/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/main.c (renamed from nuklear/demo/gdi/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h (renamed from nuklear/demo/gdi/nuklear_gdi.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/build.bat (renamed from nuklear/demo/gdip/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/main.c (renamed from nuklear/demo/gdip/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h (renamed from nuklear/demo/gdip/nuklear_gdip.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile (renamed from nuklear/demo/glfw_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c (renamed from nuklear/demo/glfw_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h (renamed from nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile (renamed from nuklear/demo/glfw_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c (renamed from nuklear/demo/glfw_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h (renamed from nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/node_editor.c (renamed from nuklear/demo/node_editor.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/overview.c (renamed from nuklear/demo/overview.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile (renamed from nuklear/demo/sdl_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c (renamed from nuklear/demo/sdl_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h (renamed from nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile (renamed from nuklear/demo/sdl_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c (renamed from nuklear/demo/sdl_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h (renamed from nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile (renamed from nuklear/demo/sdl_opengles2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c (renamed from nuklear/demo/sdl_opengles2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h (renamed from nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile (renamed from nuklear/demo/sfml_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md (renamed from nuklear/demo/sfml_opengl2/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp (renamed from nuklear/demo/sfml_opengl2/main.cpp)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h (renamed from nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile (renamed from nuklear/demo/sfml_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md (renamed from nuklear/demo/sfml_opengl3/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp (renamed from nuklear/demo/sfml_opengl3/main.cpp)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h (renamed from nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/style.c (renamed from nuklear/demo/style.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/Makefile (renamed from nuklear/demo/x11/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/main.c (renamed from nuklear/demo/x11/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h (renamed from nuklear/demo/x11/nuklear_xlib.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile (renamed from nuklear/demo/x11_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c (renamed from nuklear/demo/x11_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h (renamed from nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile (renamed from nuklear/demo/x11_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c (renamed from nuklear/demo/x11_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h (renamed from nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile (renamed from nuklear/demo/x11_rawfb/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c (renamed from nuklear/demo/x11_rawfb/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h (renamed from nuklear/demo/x11_rawfb/nuklear_rawfb.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h (renamed from nuklear/demo/x11_rawfb/nuklear_xlib.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/Makefile (renamed from nuklear/doc/Makefile)0
-rwxr-xr-xsubprojects/nk_pugl/nuklear/doc/build.sh (renamed from nuklear/doc/build.sh)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/nuklear.html (renamed from nuklear/doc/nuklear.html)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/stddoc.c (renamed from nuklear/doc/stddoc.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/Makefile (renamed from nuklear/example/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/canvas.c (renamed from nuklear/example/canvas.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/extended.c (renamed from nuklear/example/extended.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/file_browser.c (renamed from nuklear/example/file_browser.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/checked.png (renamed from nuklear/example/icon/checked.png)bin1813 -> 1813 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/cloud.png (renamed from nuklear/example/icon/cloud.png)bin7509 -> 7509 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/computer.png (renamed from nuklear/example/icon/computer.png)bin620 -> 620 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/copy.png (renamed from nuklear/example/icon/copy.png)bin655 -> 655 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/default.png (renamed from nuklear/example/icon/default.png)bin460 -> 460 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/delete.png (renamed from nuklear/example/icon/delete.png)bin11040 -> 11040 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/desktop.png (renamed from nuklear/example/icon/desktop.png)bin583 -> 583 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/directory.png (renamed from nuklear/example/icon/directory.png)bin533 -> 533 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/edit.png (renamed from nuklear/example/icon/edit.png)bin14998 -> 14998 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/export.png (renamed from nuklear/example/icon/export.png)bin13336 -> 13336 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/font.png (renamed from nuklear/example/icon/font.png)bin561 -> 561 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/home.png (renamed from nuklear/example/icon/home.png)bin819 -> 819 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/img.png (renamed from nuklear/example/icon/img.png)bin648 -> 648 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/movie.png (renamed from nuklear/example/icon/movie.png)bin626 -> 626 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/music.png (renamed from nuklear/example/icon/music.png)bin610 -> 610 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/next.png (renamed from nuklear/example/icon/next.png)bin703 -> 703 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/pause.png (renamed from nuklear/example/icon/pause.png)bin1338 -> 1338 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/pen.png (renamed from nuklear/example/icon/pen.png)bin5949 -> 5949 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/phone.png (renamed from nuklear/example/icon/phone.png)bin15778 -> 15778 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/plane.png (renamed from nuklear/example/icon/plane.png)bin13546 -> 13546 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/play.png (renamed from nuklear/example/icon/play.png)bin566 -> 566 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/prev.png (renamed from nuklear/example/icon/prev.png)bin701 -> 701 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/rocket.png (renamed from nuklear/example/icon/rocket.png)bin1121 -> 1121 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/settings.png (renamed from nuklear/example/icon/settings.png)bin15671 -> 15671 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/stop.png (renamed from nuklear/example/icon/stop.png)bin520 -> 520 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/text.png (renamed from nuklear/example/icon/text.png)bin601 -> 601 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/tools.png (renamed from nuklear/example/icon/tools.png)bin24483 -> 24483 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/unchecked.png (renamed from nuklear/example/icon/unchecked.png)bin1044 -> 1044 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/volume.png (renamed from nuklear/example/icon/volume.png)bin25438 -> 25438 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/wifi.png (renamed from nuklear/example/icon/wifi.png)bin18857 -> 18857 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image1.png (renamed from nuklear/example/images/image1.png)bin42882 -> 42882 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image2.png (renamed from nuklear/example/images/image2.png)bin5671 -> 5671 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image3.png (renamed from nuklear/example/images/image3.png)bin131502 -> 131502 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image4.png (renamed from nuklear/example/images/image4.png)bin185821 -> 185821 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image5.png (renamed from nuklear/example/images/image5.png)bin98475 -> 98475 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image6.png (renamed from nuklear/example/images/image6.png)bin35633 -> 35633 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image7.png (renamed from nuklear/example/images/image7.png)bin13960 -> 13960 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image8.png (renamed from nuklear/example/images/image8.png)bin45987 -> 45987 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image9.png (renamed from nuklear/example/images/image9.png)bin30759 -> 30759 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/skinning.c (renamed from nuklear/example/skinning.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/skins/gwen.png (renamed from nuklear/example/skins/gwen.png)bin24565 -> 24565 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/stb_image.h (renamed from nuklear/example/stb_image.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf (renamed from nuklear/extra_font/Cousine-Regular.ttf)bin43912 -> 43912 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf (renamed from nuklear/extra_font/DroidSans.ttf)bin190044 -> 190044 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf (renamed from nuklear/extra_font/Karla-Regular.ttf)bin16848 -> 16848 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf (renamed from nuklear/extra_font/ProggyClean.ttf)bin41208 -> 41208 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf (renamed from nuklear/extra_font/ProggyTiny.ttf)bin35656 -> 35656 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf (renamed from nuklear/extra_font/Raleway-Bold.ttf)bin176280 -> 176280 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf (renamed from nuklear/extra_font/Roboto-Bold.ttf)bin135820 -> 135820 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf (renamed from nuklear/extra_font/Roboto-Light.ttf)bin140276 -> 140276 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf (renamed from nuklear/extra_font/Roboto-Regular.ttf)bin145348 -> 145348 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf (renamed from nuklear/extra_font/kenvector_future.ttf)bin34136 -> 34136 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf (renamed from nuklear/extra_font/kenvector_future_thin.ttf)bin34100 -> 34100 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/nuklear.h (renamed from nuklear/nuklear.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/package.json (renamed from nuklear/package.json)0
-rw-r--r--subprojects/nk_pugl/pugl/.clang-format (renamed from pugl/.clang-format)0
-rw-r--r--subprojects/nk_pugl/pugl/.clang-tidy (renamed from pugl/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/.clant.json (renamed from pugl/.clant.json)0
-rw-r--r--subprojects/nk_pugl/pugl/.editorconfig (renamed from pugl/.editorconfig)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitattributes (renamed from pugl/.gitattributes)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitignore (renamed from pugl/.gitignore)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitlab-ci.yml (renamed from pugl/.gitlab-ci.yml)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitmodules (renamed from pugl/.gitmodules)0
-rw-r--r--subprojects/nk_pugl/pugl/.includes.imp (renamed from pugl/.includes.imp)0
-rw-r--r--subprojects/nk_pugl/pugl/AUTHORS (renamed from pugl/AUTHORS)0
-rw-r--r--subprojects/nk_pugl/pugl/COPYING (renamed from pugl/COPYING)0
-rw-r--r--subprojects/nk_pugl/pugl/README.md (renamed from pugl/README.md)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy (renamed from pugl/bindings/cxx/include/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp (renamed from pugl/bindings/cxx/include/pugl/cairo.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp (renamed from pugl/bindings/cxx/include/pugl/gl.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp (renamed from pugl/bindings/cxx/include/pugl/pugl.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp (renamed from pugl/bindings/cxx/include/pugl/stub.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp (renamed from pugl/bindings/cxx/include/pugl/vulkan.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/_static/meson.build (renamed from pugl/doc/_static/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/Doxyfile.in (renamed from pugl/doc/c/Doxyfile.in)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/api/meson.build (renamed from pugl/doc/c/api/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/event-loop.rst (renamed from pugl/doc/c/event-loop.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/events.rst (renamed from pugl/doc/c/events.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/index.rst (renamed from pugl/doc/c/index.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/meson.build (renamed from pugl/doc/c/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/overview.rst (renamed from pugl/doc/c/overview.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/shutting-down.rst (renamed from pugl/doc/c/shutting-down.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/view.rst (renamed from pugl/doc/c/view.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/world.rst (renamed from pugl/doc/c/world.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/xml/meson.build (renamed from pugl/doc/c/xml/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/conf.py.in (renamed from pugl/doc/conf.py.in)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/Doxyfile.in (renamed from pugl/doc/cpp/Doxyfile.in)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/api/meson.build (renamed from pugl/doc/cpp/api/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/event-loop.rst (renamed from pugl/doc/cpp/event-loop.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/events.rst (renamed from pugl/doc/cpp/events.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/index.rst (renamed from pugl/doc/cpp/index.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/meson.build (renamed from pugl/doc/cpp/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/overview.rst (renamed from pugl/doc/cpp/overview.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/view.rst (renamed from pugl/doc/cpp/view.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/world.rst (renamed from pugl/doc/cpp/world.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/xml/meson.build (renamed from pugl/doc/cpp/xml/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/deployment.rst (renamed from pugl/doc/deployment.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/meson.build (renamed from pugl/doc/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/summary.rst (renamed from pugl/doc/summary.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/.clang-tidy (renamed from pugl/examples/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/cube_view.h (renamed from pugl/examples/cube_view.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/demo_utils.h (renamed from pugl/examples/demo_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/file_utils.c (renamed from pugl/examples/file_utils.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/file_utils.h (renamed from pugl/examples/file_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/glad.c (renamed from pugl/examples/glad/glad.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/glad.h (renamed from pugl/examples/glad/glad.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/khrplatform.h (renamed from pugl/examples/glad/khrplatform.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/meson.build (renamed from pugl/examples/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c (renamed from pugl/examples/pugl_cairo_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c (renamed from pugl/examples/pugl_cursor_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp (renamed from pugl/examples/pugl_cxx_demo.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c (renamed from pugl/examples/pugl_embed_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_print_events.c (renamed from pugl/examples/pugl_print_events.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c (renamed from pugl/examples/pugl_shader_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp (renamed from pugl/examples/pugl_vulkan_cxx_demo.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c (renamed from pugl/examples/pugl_vulkan_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_window_demo.c (renamed from pugl/examples/pugl_window_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/rects.h (renamed from pugl/examples/rects.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shader_utils.h (renamed from pugl/examples/shader_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl (renamed from pugl/examples/shaders/header_330.glsl)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl (renamed from pugl/examples/shaders/header_420.glsl)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/meson.build (renamed from pugl/examples/shaders/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/rect.frag (renamed from pugl/examples/shaders/rect.frag)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/rect.vert (renamed from pugl/examples/shaders/rect.vert)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/sybok.hpp (renamed from pugl/examples/sybok.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/include/.clang-tidy (renamed from pugl/include/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/cairo.h (renamed from pugl/include/pugl/cairo.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/gl.h (renamed from pugl/include/pugl/gl.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/pugl.h (renamed from pugl/include/pugl/pugl.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/stub.h (renamed from pugl/include/pugl/stub.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/vulkan.h (renamed from pugl/include/pugl/vulkan.h)0
-rw-r--r--subprojects/nk_pugl/pugl/meson.build (renamed from pugl/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/meson/meson.build (renamed from pugl/meson/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/meson_options.txt (renamed from pugl/meson_options.txt)0
-rw-r--r--subprojects/nk_pugl/pugl/pugl.pc.in (renamed from pugl/pugl.pc.in)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/Info.plist.in (renamed from pugl/resources/Info.plist.in)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.ipe (renamed from pugl/resources/pugl.ipe)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.png (renamed from pugl/resources/pugl.png)bin2641 -> 2641 bytes
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.svg (renamed from pugl/resources/pugl.svg)0
-rwxr-xr-xsubprojects/nk_pugl/pugl/scripts/cat.py (renamed from pugl/scripts/cat.py)0
-rwxr-xr-xsubprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py (renamed from pugl/scripts/dox_to_sphinx.py)0
-rw-r--r--subprojects/nk_pugl/pugl/src/.clang-tidy (renamed from pugl/src/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/src/implementation.c (renamed from pugl/src/implementation.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/implementation.h (renamed from pugl/src/implementation.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac.h (renamed from pugl/src/mac.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac.m (renamed from pugl/src/mac.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_cairo.m (renamed from pugl/src/mac_cairo.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_gl.m (renamed from pugl/src/mac_gl.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_stub.m (renamed from pugl/src/mac_stub.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_vulkan.m (renamed from pugl/src/mac_vulkan.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/stub.h (renamed from pugl/src/stub.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/types.h (renamed from pugl/src/types.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win.c (renamed from pugl/src/win.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win.h (renamed from pugl/src/win.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_cairo.c (renamed from pugl/src/win_cairo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_gl.c (renamed from pugl/src/win_gl.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_stub.c (renamed from pugl/src/win_stub.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_vulkan.c (renamed from pugl/src/win_vulkan.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11.c (renamed from pugl/src/x11.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11.h (renamed from pugl/src/x11.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_cairo.c (renamed from pugl/src/x11_cairo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_gl.c (renamed from pugl/src/x11_gl.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_stub.c (renamed from pugl/src/x11_stub.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_vulkan.c (renamed from pugl/src/x11_vulkan.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/.clang-tidy (renamed from pugl/test/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/test/meson.build (renamed from pugl/test/meson.build)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_build.c (renamed from pugl/test/test_build.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_build.cpp (renamed from pugl/test/test_build.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_clipboard.c (renamed from pugl/test/test_clipboard.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_gl_hints.c (renamed from pugl/test/test_gl_hints.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_realize.c (renamed from pugl/test/test_realize.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_redisplay.c (renamed from pugl/test/test_redisplay.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_show_hide.c (renamed from pugl/test/test_show_hide.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_stub_hints.c (renamed from pugl/test/test_stub_hints.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_timer.c (renamed from pugl/test/test_timer.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_update.c (renamed from pugl/test/test_update.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_utils.h (renamed from pugl/test/test_utils.h)0
-rw-r--r--timely.lv2/.gitlab-ci.yml2
-rw-r--r--timely.lv2/COPYING201
-rw-r--r--timely.lv2/README.md20
-rw-r--r--timely.lv2/VERSION1
-rw-r--r--timely.lv2/gitlab-ci/generic.yml106
-rw-r--r--timely.lv2/meson.build67
-rw-r--r--timely.lv2/test/manifest.ttl.in28
-rw-r--r--timely.lv2/test/timely.c218
-rw-r--r--timely.lv2/test/timely.ttl65
-rw-r--r--timely.lv2/timely.h404
-rw-r--r--vm.c1781
-rw-r--r--vm.h1235
-rw-r--r--vm.ttl1470
-rw-r--r--vm_ui.c1479
-rw-r--r--vm_ui.ttl52
298 files changed, 10721 insertions, 137 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 62aa6f8..979769c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,77 +1,2 @@
-stages:
- - build
- - deploy
-
-.variables_template: &variables_definition
- variables:
- BASE_NAME: "nk_pugl"
- 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 -Dbuild-examples=true --prefix="/" --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' build/build.ninja
- - ninja -C build
- - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
-
-.analyze_template: &analyze_definition
- <<: *common_definition
- script:
- - meson -Dbuild-examples=true --prefix="/" --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' build/build.ninja
- - ninja -C build
- - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
-
- - CC=clang CXX=clang++ meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
- - ninja -C clang
-
- - scan-build --status-bugs meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
- - scan-build --status-bugs ninja -C scanbuild
-
-.universal_linux_template: &universal_linux_definition
- image: ventosus/universal-linux-gnu
- <<: *analyze_definition
-
-.arm_linux_template: &arm_linux_definition
- image: ventosus/arm-linux-gnueabihf
- <<: *build_definition
-
-# building in docker
-x86_64-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev libevdev-dev
- <<: *universal_linux_definition
-
-i686-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386
- <<: *universal_linux_definition
-
-arm-linux-gnueabihf:
- before_script:
- - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf
- <<: *arm_linux_definition
-
-aarch64-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64
- <<: *arm_linux_definition
-
-pack:
- <<: *variables_definition
- stage: deploy
- script:
- - echo 'packing up...'
- artifacts:
- name: "${BASE_NAME}-$(cat VERSION)"
- paths:
- - "${BASE_NAME}-$(cat VERSION)/"
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..1b7b3d6
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,50 @@
+# Changelog
+
+## [0.14.0] - 14 Apr 2021
+
+### Fixed
+
+* license URI in turtle manifest
+* font scaling on high DPI screens
+
+### Added
+
+* missing ui:parent feature in turtle manifest
+
+### Updated
+
+* to latest pugl
+
+## [0.12.0] - 15 Jan 2021
+
+### Added
+
+* support for host provided ui scale factor
+* support for notifying host about changed parameters
+
+## [0.10.0] - 15 Jul 2020
+
+### Added
+
+* support for GL double buffering
+
+## [0.8.0] - 13 Apr 2020
+
+### Changed
+
+* to build with pugl master
+
+## [0.6.0] - 15 Apr 2019
+
+### Added
+
+* vm:midi MIDI Conversion plugin
+* ui button icons
+
+### Changed
+
+* build system from CMake to meson
+
+### Fixed
+
+* port_event callback to handle all patch ports
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1207e71
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+## VM
+
+### A programmable virtual machine LV2 plugin
+
+This plugin bundle contains various plugins with a programmable stack-based
+virtual machine to modify up to 8 LV2 Control, CV, Audio, Atom and MIDI signals.
+
+To be used when that specific simple filter plugin you desperately need does
+not yet exist.
+
+#### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/vm.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/vm.lv2/commits/master)
+
+### Binaries
+
+For GNU/Linux (64-bit, 32-bit, armv7, arm64).
+
+To install the plugin bundle on your system, simply copy the __vm.lv2__
+folder out of the platform folder of the downloaded package into your
+[LV2 path](http://lv2plug.in/pages/filesystem-hierarchy-standard.html).
+
+#### Stable release
+
+* [vm.lv2-0.14.0.zip](https://dl.open-music-kontrollers.ch/vm.lv2/stable/vm.lv2-0.14.0.zip) ([sig](https://dl.open-music-kontrollers.ch/vm.lv2/stable/vm.lv2-0.14.0.zip.sig))
+
+#### Unstable (nightly) release
+
+* [vm.lv2-latest-unstable.zip](https://dl.open-music-kontrollers.ch/vm.lv2/unstable/vm.lv2-latest-unstable.zip) ([sig](https://dl.open-music-kontrollers.ch/vm.lv2/unstable/vm.lv2-latest-unstable.zip.sig))
+
+### Sources
+
+#### Stable release
+
+* [vm.lv2-0.14.0.tar.xz](https://git.open-music-kontrollers.ch/lv2/vm.lv2/snapshot/vm.lv2-0.14.0.tar.xz)
+([sig](https://git.open-music-kontrollers.ch/lv2/vm.lv2/snapshot/vm.lv2-0.14.0.tar.xz.asc))
+
+#### Git repository
+
+* <https://git.open-music-kontrollers.ch/lv2/vm.lv2>
+
+### Packages
+
+* [ArchLinux](https://www.archlinux.org/packages/community/x86_64/vm.lv2/)
+
+### Bugs and feature requests
+
+* [Gitlab](https://gitlab.com/OpenMusicKontrollers/vm.lv2)
+* [Github](https://github.com/OpenMusicKontrollers/vm.lv2)
+
+### Plugins
+
+![VM ](/screenshots/screenshot_1.png)
+
+#### Control VM
+
+Virtual machine for LV2 Control ports. Features 8 inputs and 8 outputs.
+
+#### CV VM
+
+Virtual machine for LV2 Control Voltage ports. Features 8 inputs and 8 outputs.
+
+#### Audio VM
+
+Virtual machine for LV2 Audio ports. Features 8 inputs and 8 outputs.
+
+#### Atom VM
+
+Virtual machine for LV2 Atom event ports. Features 8 inputs and 8 outputs.
+
+#### MIDI VM
+
+Virtual machine for LV2 MIDI event ports. Features 8 inputs and 8 outputs.
+
+### License
+
+Copyright (c) 2017-2021 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
index f4fdeb6..a803cc2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.179
+0.14.0
diff --git a/gitlab-ci/generic.yml b/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/gitlab-ci/generic.yml
@@ -0,0 +1,106 @@
+stages:
+ - build
+ - deploy
+
+variables:
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+ BUILD_OPTS : ""
+
+.native_template: &native_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+
+ - scan-build --status-bugs meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+ - scan-build --status-bugs ninja -C scanbuild test
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+.cross_template: &cross_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+# build
+.universal_linux_template_stretch: &universal_linux_definition_stretch
+ image: ventosus/universal-linux-gnu:stretch
+ <<: *cross_definition
+
+.universal_linux_template_buster: &universal_linux_definition_buster
+ image: ventosus/universal-linux-gnu:buster
+ <<: *native_definition
+
+.universal_linux_template_bullseye: &universal_linux_definition_bullseye
+ image: ventosus/universal-linux-gnu:bullseye
+ <<: *native_definition
+
+.arm_linux_template_stretch: &arm_linux_definition_stretch
+ image: ventosus/arm-linux-gnueabihf:stretch
+ <<: *cross_definition
+
+.arm_linux_template_buster: &arm_linux_definition_buster
+ image: ventosus/arm-linux-gnueabihf:buster
+ <<: *cross_definition
+
+.arm_linux_template_bullseye: &arm_linux_definition_bullseye
+ image: ventosus/arm-linux-gnueabihf:bullseye
+ <<: *cross_definition
+
+# build
+x86_64-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+x86_64-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+x86_64-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+i686-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+i686-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+i686-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+arm-linux-gnueabihf-stretch:
+ <<: *arm_linux_definition_stretch
+
+arm-linux-gnueabihf-buster:
+ <<: *arm_linux_definition_buster
+
+arm-linux-gnueabihf-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+aarch64-linux-gnu-stretch:
+ <<: *arm_linux_definition_stretch
+
+aarch64-linux-gnu-buster:
+ <<: *arm_linux_definition_buster
+
+aarch64-linux-gnu-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+pack:
+ stage: deploy
+ script:
+ - echo 'packing up'
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/"
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..5999b84
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,65 @@
+# Copyright (c) 2017-2021 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:control
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <vm@MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:cv
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <vm@MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:audio
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <vm@MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:atom
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <vm@MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:midi
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <vm@MODULE_SUFFIX@> ;
+ ui:ui vm:vm_ui ;
+ rdfs:seeAlso <vm.ttl> .
+
+vm:vm_ui
+ a ui:@UI_TYPE@ ;
+ ui:binary <vm_ui@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <vm_ui.ttl> .
diff --git a/meson.build b/meson.build
index c891785..438097c 100644
--- a/meson.build
+++ b/meson.build
@@ -1,84 +1,144 @@
-project('nk_pugl', 'c', default_options : [
+project('vm.lv2', 'c', default_options : [
'buildtype=release',
'warning_level=3',
'werror=false',
'b_lto=false',
'c_std=gnu11'])
-build_examples = get_option('build-examples')
+nk_pugl = subproject('nk_pugl')
-static_link = false #meson.is_cross_build()
+lv2libdir = get_option('lv2libdir')
+
+inst_dir = join_paths(lv2libdir, meson.project_name())
+
+nk_pugl_dep = nk_pugl.get_variable('nk_pugl_gl')
+cousine_regular_ttf = nk_pugl.get_variable('cousine_regular_ttf')
+
+source_root = meson.source_root()
+build_root = meson.build_root()
+
+static_link = meson.is_cross_build()
cc = meson.get_compiler('c')
m_dep = cc.find_library('m')
-lv2_dep = dependency('lv2',
- version : '>=1.14.0')
-glew_dep = dependency('glew',
- version : '>=2.0.0',
- static : static_link)
-glu_dep = dependency('glu',
- version : '>=9.0.0',
- static : static_link)
-
-deps = [m_dep, lv2_dep, glu_dep, glew_dep]
-links = []
-
-nk_pugl_inc = include_directories('')
-pugl_inc = include_directories(join_paths('pugl', 'include'))
-inc_dir = [nk_pugl_inc, pugl_inc]
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+
+dsp_deps = [m_dep, lv2_dep]
+ui_deps = [m_dep, lv2_dep, nk_pugl_dep]
+
+props_inc = include_directories('props.lv2')
+timely_inc = include_directories('timely.lv2')
+nk_pugl_inc = include_directories(join_paths('subprojects', 'nk_pugl'))
+inc_dir = [props_inc, timely_inc, nk_pugl_inc]
+rawvers = run_command('cat', 'VERSION').stdout().strip()
+version = rawvers.split('.')
+
+conf_data = configuration_data()
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+add_project_arguments('-DVM_VERSION="'+rawvers+'"', language : 'c')
add_project_arguments('-D_GNU_SOURCE', language : 'c')
-bin_srcs = [
- join_paths('example', 'example.c')
-]
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+
+dsp_srcs = ['vm.c']
-lib_srcs = [
- join_paths('pugl', 'src', 'implementation.c')
-]
+ui_srcs = ['vm_ui.c']
-c_args = ['-fvisibility=hidden',
- '-ffast-math']
+c_args = [
+ '-fvisibility=hidden']
if host_machine.system() == 'windows'
- deps += cc.find_library('opengl32')
- deps += cc.find_library('gdi32')
- deps += cc.find_library('ws2_32')
- lib_srcs += join_paths('pugl', 'src', 'win.c')
- lib_srcs += join_paths('pugl', 'src', 'win_gl.c')
+ conf_data.set('UI_TYPE', 'WindowsUI')
elif host_machine.system() == 'darwin'
- add_languages('objc')
- links += ['-framework', 'OpenGL']
- links += ['-framework', 'Cocoa']
- lib_srcs += join_paths('pugl', 'src', 'mac.m')
- lib_srcs += join_paths('pugl', 'src', 'mac_gl.m')
+ conf_data.set('UI_TYPE', 'CocoaUI')
else
- deps += dependency('gl')
- deps += dependency('x11', version : '>=1.6.0')
- deps += dependency('xext', version : '>=1.3.0')
- lib_srcs += join_paths('pugl', 'src', 'x11.c')
- lib_srcs += join_paths('pugl', 'src', 'x11_gl.c')
+ conf_data.set('UI_TYPE', 'X11UI')
endif
-nk_pugl_gl = declare_dependency(
- compile_args : ['-DPUGL_STATIC'],
+mod = shared_module('vm', dsp_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : dsp_deps,
+ install : true,
+ install_dir : inst_dir)
+
+ui = shared_module('vm_ui', ui_srcs,
+ c_args : c_args,
include_directories : inc_dir,
- dependencies : deps,
- link_args : links,
- sources : lib_srcs)
+ name_prefix : '',
+ dependencies : ui_deps,
+ install : true,
+ install_dir : inst_dir)
-cousine_regular_ttf = configure_file(
- input : join_paths('nuklear', 'extra_font', 'Cousine-Regular.ttf'),
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+manifest_ttl = configure_file(
+ input : 'manifest.ttl.in',
+ output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+
+dsp_ttl = configure_file(
+ input : 'vm.ttl',
+ output : 'vm.ttl',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+
+ui_ttl = configure_file(
+ input : 'vm_ui.ttl',
+ output : 'vm_ui.ttl',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+
+configure_file(
+ input : cousine_regular_ttf,
output : 'Cousine-Regular.ttf',
copy : true,
- install : false)
+ install : true,
+ install_dir : inst_dir)
+configure_file(
+ input : join_paths('png', 'cancel.png'),
+ output : 'cancel.png',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+configure_file(
+ input : join_paths('png', 'plus.png'),
+ output : 'plus.png',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+configure_file(
+ input : join_paths('png', 'up_arrow.png'),
+ output : 'up_arrow.png',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
-if build_examples
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl, ui_ttl])
+endif
- executable('nk_pugl.gl', [bin_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: nk_pugl_gl,
- install : false)
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-I', join_paths(build_root, ''),
+ '-Ewarn',
+ 'http://open-music-kontrollers.ch/lv2/vm#atom',
+ 'http://open-music-kontrollers.ch/lv2/vm#audio',
+ 'http://open-music-kontrollers.ch/lv2/vm#control',
+ 'http://open-music-kontrollers.ch/lv2/vm#cv',
+ 'http://open-music-kontrollers.ch/lv2/vm#midi'])
endif
diff --git a/meson_options.txt b/meson_options.txt
index 0a4a2a9..8a007bd 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,4 +1,3 @@
-option('build-examples',
- type : 'boolean',
- value : false,
- yield : true)
+option('lv2libdir',
+ type : 'string',
+ value : 'lib/lv2')
diff --git a/png/COPYING b/png/COPYING
new file mode 100644
index 0000000..3153202
--- /dev/null
+++ b/png/COPYING
@@ -0,0 +1,6 @@
+Icons made by http://www.flaticon.com/authors/eleonor-wang
+Released under http://creativecommons.org/licenses/by/3.0/
+Downloaded from http://www.flaticon.com/packs/flat-lines-circled
+
+Modified with ImageMagick:
+for file in *.png; do mogrify -negate -modulate 70 -resize 25% $file; done
diff --git a/png/cancel.png b/png/cancel.png
new file mode 100644
index 0000000..e226923
--- /dev/null
+++ b/png/cancel.png
Binary files differ
diff --git a/png/plus.png b/png/plus.png
new file mode 100644
index 0000000..6831e81
--- /dev/null
+++ b/png/plus.png
Binary files differ
diff --git a/png/up_arrow.png b/png/up_arrow.png
new file mode 100644
index 0000000..28a1e55
--- /dev/null
+++ b/png/up_arrow.png
Binary files differ
diff --git a/props.lv2/.gitlab-ci.yml b/props.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..979769c
--- /dev/null
+++ b/props.lv2/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: 'gitlab-ci/generic.yml'
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..5130959
--- /dev/null
+++ b/props.lv2/VERSION
@@ -0,0 +1 @@
+0.1.165
diff --git a/props.lv2/gitlab-ci/generic.yml b/props.lv2/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/props.lv2/gitlab-ci/generic.yml
@@ -0,0 +1,106 @@
+stages:
+ - build
+ - deploy
+
+variables:
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+ BUILD_OPTS : ""
+
+.native_template: &native_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+
+ - scan-build --status-bugs meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+ - scan-build --status-bugs ninja -C scanbuild test
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+.cross_template: &cross_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+# build
+.universal_linux_template_stretch: &universal_linux_definition_stretch
+ image: ventosus/universal-linux-gnu:stretch
+ <<: *cross_definition
+
+.universal_linux_template_buster: &universal_linux_definition_buster
+ image: ventosus/universal-linux-gnu:buster
+ <<: *native_definition
+
+.universal_linux_template_bullseye: &universal_linux_definition_bullseye
+ image: ventosus/universal-linux-gnu:bullseye
+ <<: *native_definition
+
+.arm_linux_template_stretch: &arm_linux_definition_stretch
+ image: ventosus/arm-linux-gnueabihf:stretch
+ <<: *cross_definition
+
+.arm_linux_template_buster: &arm_linux_definition_buster
+ image: ventosus/arm-linux-gnueabihf:buster
+ <<: *cross_definition
+
+.arm_linux_template_bullseye: &arm_linux_definition_bullseye
+ image: ventosus/arm-linux-gnueabihf:bullseye
+ <<: *cross_definition
+
+# build
+x86_64-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+x86_64-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+x86_64-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+i686-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+i686-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+i686-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+arm-linux-gnueabihf-stretch:
+ <<: *arm_linux_definition_stretch
+
+arm-linux-gnueabihf-buster:
+ <<: *arm_linux_definition_buster
+
+arm-linux-gnueabihf-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+aarch64-linux-gnu-stretch:
+ <<: *arm_linux_definition_stretch
+
+aarch64-linux-gnu-buster:
+ <<: *arm_linux_definition_buster
+
+aarch64-linux-gnu-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+pack:
+ stage: deploy
+ script:
+ - echo 'packing up'
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/"
diff --git a/props.lv2/meson.build b/props.lv2/meson.build
new file mode 100644
index 0000000..d354d89
--- /dev/null
+++ b/props.lv2/meson.build
@@ -0,0 +1,82 @@
+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')
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+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)
+
+manifest_ttl = configure_file(
+ input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+dsp_ttl = 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)
+
+props_test = executable('props_test',
+ join_paths('test', 'props_test.c'),
+ c_args : c_args,
+ install : false)
+
+test('Test', props_test,
+ timeout : 240)
+
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl])
+endif
+
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-Ewarn',
+ 'http://open-music-kontrollers.ch/lv2/props#test'])
+endif
diff --git a/props.lv2/props.h b/props.lv2/props.h
new file mode 100644
index 0000000..b3905ab
--- /dev/null
+++ b/props.lv2/props.h
@@ -0,0 +1,1226 @@
+/*
+ * 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_dyn_t props_dyn_t;
+typedef struct _props_t props_t;
+
+typedef enum _props_dyn_ev_t {
+ PROPS_DYN_EV_ADD,
+ PROPS_DYN_EV_REM,
+ PROPS_DYN_EV_SET
+} props_dyn_ev_t;
+
+// function callbacks
+typedef void (*props_event_cb_t)(
+ void *data,
+ int64_t frames,
+ props_impl_t *impl);
+
+typedef void (*props_dyn_prop_cb_t)(
+ void *data,
+ props_dyn_ev_t ev,
+ LV2_URID subj,
+ LV2_URID prop,
+ const LV2_Atom *body);
+
+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_dyn_t {
+ props_dyn_prop_cb_t prop;
+};
+
+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;
+
+ LV2_URID state_StateChanged;
+ } urid;
+
+ void *data;
+
+ bool stashing;
+ atomic_bool restoring;
+
+ uint32_t max_size;
+
+ const props_dyn_t *dyn;
+
+ 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_dyn(props_t *props, const props_dyn_t *dyn);
+
+// 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);
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.state_StateChanged);
+ 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);
+
+ props->urid.state_StateChanged = map->map(map->handle, LV2_STATE__StateChanged);
+
+ 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_dyn(props_t *props, const props_dyn_t *dyn)
+{
+ props->dyn = dyn;
+}
+
+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)
+ {
+ _props_impl_set(props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+
+ // send on (e.g. to UI)
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ 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(props->dyn && props->dyn->prop)
+ {
+ const LV2_URID subj = subject ? subject->body : 0;
+
+ props->dyn->prop(props->data, PROPS_DYN_EV_SET, subj, property->body, value);
+
+ //TODO send ack
+ }
+ 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)
+ {
+ _props_impl_set(props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+
+ // send on (e.g. to UI)
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb)
+ def->event_cb(props->data, frames, impl);
+ }
+ else if(props->dyn && props->dyn->prop)
+ {
+ const LV2_URID subj = subject ? subject->body : 0;
+
+ props->dyn->prop(props->data, PROPS_DYN_EV_SET, subj, property, value);
+
+ //TODO send ack
+ }
+ }
+
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(obj->body.otype == props->urid.patch_patch)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom_Object *add = NULL;
+ const LV2_Atom_Object *rem = NULL;
+
+ lv2_atom_object_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_add, &add,
+ props->urid.patch_remove, &rem,
+ 0);
+
+ LV2_URID subj = 0;
+ if(subject && (subject->atom.type == props->urid.atom_urid))
+ {
+ subj = subject->body;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(rem && lv2_atom_forge_is_object_type(forge, rem->atom.type))
+ {
+ LV2_ATOM_OBJECT_FOREACH(rem, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ if(props->dyn && props->dyn->prop)
+ {
+ props->dyn->prop(props->data, PROPS_DYN_EV_REM, subj, property, value);
+ }
+ }
+ }
+
+ if(add && lv2_atom_forge_is_object_type(forge, add->atom.type))
+ {
+ LV2_ATOM_OBJECT_FOREACH(add, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ if(props->dyn && props->dyn->prop)
+ {
+ props->dyn->prop(props->data, PROPS_DYN_EV_ADD, subj, property, value);
+ }
+ }
+ }
+
+ if(sequence_num && *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 int
+_copy_file(const char *to, const char *from)
+{
+ FILE *dst = NULL;
+ FILE *src = NULL;
+ int ch;
+
+ dst = fopen(to, "wb");
+ if(!dst)
+ {
+ return 1;
+ }
+
+ src = fopen(from, "rb");
+ if(!src)
+ {
+ fclose(dst);
+
+ return 1;
+ }
+
+ while( (ch = fgetc(src)) != EOF)
+ {
+ fputc(ch, dst);
+ }
+
+ fclose(dst);
+ fclose(src);
+
+ return 0;
+}
+
+static inline void
+_free_path(const LV2_State_Free_Path *free_path, char *path)
+{
+ if(free_path && free_path->free_path)
+ {
+ free_path->free_path(free_path->handle, path);
+ }
+ else
+ {
+ free(path);
+ }
+}
+
+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;
+ const LV2_State_Make_Path *make_path = NULL;
+ const LV2_State_Free_Path *free_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;
+ }
+ else if(!strcmp(features[i]->URI, LV2_STATE__makePath))
+ {
+ make_path = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_STATE__freePath))
+ {
+ free_path = features[i]->data;
+ }
+ }
+
+ 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
+
+ // always clear memory
+ memset(body, 0x0, props->max_size);
+
+ _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 && map_path->abstract_path
+ && (impl->type == props->urid.atom_path) )
+ {
+ const char *path = strstr(body, "file://") == body
+ ? (char *)body + 7 // skip "file://"
+ : (char *)body;
+
+ char *abstract = NULL;
+
+ if( make_path && make_path->path
+ && (strstr(path, "/tmp") == path) )
+ {
+ char *absolute = make_path->path(make_path->handle, basename(path));
+
+ if(absolute)
+ {
+ if(_copy_file(absolute, path) == 0)
+ {
+ abstract = map_path->abstract_path(map_path->handle, absolute);
+ }
+
+ _free_path(free_path, absolute);
+ }
+ }
+ else
+ {
+ 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_path(free_path, 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;
+ const LV2_State_Free_Path *free_path = NULL;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ {
+ map_path = features[i]->data;
+ }
+ if(!strcmp(features[i]->URI, LV2_STATE__freePath))
+ {
+ free_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 && map_path->absolute_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_path(free_path, 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..f2ce779
--- /dev/null
+++ b/props.lv2/test/props.ttl
@@ -0,0 +1,151 @@
+# 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 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 <https://spdx.org/licenses/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:statChunk "AQIDBAUGBw=="^^xsd:base64Binary ;
+ ] .
diff --git a/props.lv2/test/props_test.c b/props.lv2/test/props_test.c
new file mode 100644
index 0000000..69f3b3f
--- /dev/null
+++ b/props.lv2/test/props_test.c
@@ -0,0 +1,647 @@
+/*
+ * 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 <assert.h>
+
+#include <props.h>
+
+#define MAX_URIDS 512
+#define STR_SIZE 32
+#define CHUNK_SIZE 16
+#define VEC_SIZE 13
+
+#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#"
+#define PROPS_TEST_URI PROPS_PREFIX"test"
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _urid_t urid_t;
+typedef struct _handle_t handle_t;
+typedef void (*test_t)(handle_t *handle);
+typedef void *(*ser_atom_realloc_t)(void *data, void *buf, size_t size);
+typedef void (*ser_atom_free_t)(void *data, void *buf);
+
+typedef struct _ser_atom_t ser_atom_t;
+
+struct _plugstate_t {
+ int32_t b32;
+ int32_t i32;
+ int64_t i64;
+ float f32;
+ double f64;
+ uint32_t urid;
+ char str [STR_SIZE];
+ char uri [STR_SIZE];
+ char path [STR_SIZE];
+ uint8_t chunk [CHUNK_SIZE];
+ LV2_Atom_Literal_Body lit;
+ char lit_body [STR_SIZE];
+ LV2_Atom_Vector_Body vec;
+ int32_t vec_body [VEC_SIZE];
+ LV2_Atom_Object_Body obj; //FIXME
+ LV2_Atom_Sequence_Body seq; //FIXME
+};
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+enum {
+ PROP_b32 = 0,
+ PROP_i32,
+ PROP_i64,
+ PROP_f32,
+ PROP_f64,
+ PROP_urid,
+ PROP_str,
+ PROP_uri,
+ PROP_path,
+ PROP_chunk,
+ PROP_lit,
+ PROP_vec,
+ PROP_obj,
+ PROP_seq,
+
+ MAX_NPROPS
+};
+
+struct _handle_t {
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ LV2_URID_Map map;
+
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+struct _ser_atom_t {
+ ser_atom_realloc_t realloc;
+ ser_atom_free_t free;
+ void *data;
+
+ size_t size;
+ size_t offset;
+ union {
+ uint8_t *buf;
+ LV2_Atom *atom;
+ };
+};
+
+static LV2_Atom_Forge_Ref
+_ser_atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf,
+ uint32_t size)
+{
+ ser_atom_t *ser = handle;
+ const size_t needed = ser->offset + size;
+
+ while(needed > ser->size)
+ {
+ const size_t augmented = ser->size
+ ? ser->size << 1
+ : 1024;
+ uint8_t *grown = ser->realloc(ser->data, ser->buf, augmented);
+ if(!grown) // out-of-memory
+ {
+ return 0;
+ }
+
+ ser->buf = grown;
+ ser->size = augmented;
+ }
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+ memcpy(&ser->buf[ser->offset], buf, size);
+ ser->offset += size;
+
+ return ref;
+}
+
+static LV2_Atom *
+_ser_atom_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ ser_atom_t *ser = handle;
+
+ if(!ref) // invalid reference
+ {
+ return NULL;
+ }
+
+ const size_t offset = ref - 1;
+ return (LV2_Atom *)&ser->buf[offset];
+}
+
+static void *
+_ser_atom_realloc(void *data, void *buf, size_t size)
+{
+ (void)data;
+
+ return realloc(buf, size);
+}
+
+static void
+_ser_atom_free(void *data, void *buf)
+{
+ (void)data;
+
+ free(buf);
+}
+
+static int
+ser_atom_deinit(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return -1;
+ }
+
+ if(ser->buf)
+ {
+ ser->free(ser->data, ser->buf);
+ }
+
+ ser->size = 0;
+ ser->offset = 0;
+ ser->buf = NULL;
+
+ return 0;
+}
+
+static int
+ser_atom_funcs(ser_atom_t *ser, ser_atom_realloc_t realloc,
+ ser_atom_free_t free, void *data)
+{
+ if(!ser || !realloc || !free || ser_atom_deinit(ser))
+ {
+ return -1;
+ }
+
+ ser->realloc = realloc;
+ ser->free = free;
+ ser->data = data;
+
+ return 0;
+}
+
+static int
+ser_atom_init(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return -1;
+ }
+
+ ser->size = 0;
+ ser->offset = 0;
+ ser->buf = NULL;
+
+ return ser_atom_funcs(ser, _ser_atom_realloc, _ser_atom_free, NULL);
+}
+
+#if 0
+static int
+ser_atom_reset(ser_atom_t *ser, LV2_Atom_Forge *forge)
+{
+ if(!ser || !forge)
+ {
+ return -1;
+ }
+
+ lv2_atom_forge_set_sink(forge, _ser_atom_sink, _ser_atom_deref, ser);
+
+ ser->offset = 0;
+
+ return 0;
+}
+#endif
+
+static LV2_Atom *
+ser_atom_get(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return NULL;
+ }
+
+ return ser->atom;
+}
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(handle->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++handle->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ [PROP_b32] = {
+ .property = PROPS_PREFIX"b32",
+ .offset = offsetof(plugstate_t, b32),
+ .type = LV2_ATOM__Bool
+ },
+ [PROP_i32] = {
+ .property = PROPS_PREFIX"i32",
+ .offset = offsetof(plugstate_t, i32),
+ .type = LV2_ATOM__Int
+ },
+ [PROP_i64] = {
+ .property = PROPS_PREFIX"i64",
+ .offset = offsetof(plugstate_t, i64),
+ .type = LV2_ATOM__Long
+ },
+ [PROP_f32] = {
+ .property = PROPS_PREFIX"f32",
+ .offset = offsetof(plugstate_t, f32),
+ .type = LV2_ATOM__Float
+ },
+ [PROP_f64] = {
+ .property = PROPS_PREFIX"f64",
+ .offset = offsetof(plugstate_t, f64),
+ .type = LV2_ATOM__Double
+ },
+ [PROP_urid] = {
+ .property = PROPS_PREFIX"urid",
+ .offset = offsetof(plugstate_t, urid),
+ .type = LV2_ATOM__URID
+ },
+ [PROP_str] = {
+ .property = PROPS_PREFIX"str",
+ .offset = offsetof(plugstate_t, str),
+ .type = LV2_ATOM__String,
+ .max_size = STR_SIZE
+ },
+ [PROP_uri] = {
+ .property = PROPS_PREFIX"uri",
+ .offset = offsetof(plugstate_t, uri),
+ .type = LV2_ATOM__URI,
+ .max_size = STR_SIZE
+ },
+ [PROP_path] = {
+ .property = PROPS_PREFIX"path",
+ .offset = offsetof(plugstate_t, path),
+ .type = LV2_ATOM__Path,
+ .max_size = STR_SIZE
+ },
+ [PROP_chunk] = {
+ .property = PROPS_PREFIX"chunk",
+ .offset = offsetof(plugstate_t, chunk),
+ .type = LV2_ATOM__Chunk,
+ .max_size = CHUNK_SIZE
+ },
+ [PROP_lit] = {
+ .property = PROPS_PREFIX"lit",
+ .offset = offsetof(plugstate_t, lit),
+ .type = LV2_ATOM__Literal,
+ .max_size = sizeof(LV2_Atom_Literal_Body) + STR_SIZE
+ },
+ [PROP_vec] = {
+ .property = PROPS_PREFIX"vec",
+ .offset = offsetof(plugstate_t, vec),
+ .type = LV2_ATOM__Literal,
+ .max_size = sizeof(LV2_Atom_Vector_Body) + VEC_SIZE*sizeof(int32_t)
+ },
+ [PROP_obj] = {
+ .property = PROPS_PREFIX"obj",
+ .offset = offsetof(plugstate_t, obj),
+ .type = LV2_ATOM__Object,
+ .max_size = sizeof(LV2_Atom_Object_Body) + 0 //FIXME
+ },
+ [PROP_seq] = {
+ .property = PROPS_PREFIX"seq",
+ .offset = offsetof(plugstate_t, seq),
+ .type = LV2_ATOM__Sequence,
+ .max_size = sizeof(LV2_Atom_Sequence_Body) + 0 //FIXME
+ }
+};
+
+static void
+_test_1(handle_t *handle)
+{
+ assert(handle);
+
+ props_t *props = &handle->props;
+ plugstate_t *state = &handle->state;
+ plugstate_t *stash = &handle->stash;
+ LV2_URID_Map *map = &handle->map;
+
+ for(unsigned i = 0; i < MAX_NPROPS; i++)
+ {
+ const props_def_t *def = &defs[i];
+
+ const LV2_URID property = props_map(props, def->property);
+ assert(property != 0);
+ assert(property == map->map(map->handle, def->property));
+
+ assert(strcmp(props_unmap(props, property), def->property) == 0);
+
+ props_impl_t *impl = _props_impl_get(props, property);
+ assert(impl);
+
+ const LV2_URID type = map->map(map->handle, def->type);
+ const LV2_URID access = map->map(map->handle, def->access
+ ? def->access : LV2_PATCH__writable);
+
+ assert(impl->property == property);
+ assert(impl->type == type);
+ assert(impl->access == access);
+
+ assert(impl->def == def);
+
+ assert(atomic_load(&impl->state) == PROP_STATE_NONE);
+ assert(impl->stashing == false);
+
+ switch(i)
+ {
+ case PROP_b32:
+ {
+ assert(impl->value.size == sizeof(state->b32));
+ assert(impl->value.body == &state->b32);
+
+ assert(impl->stash.size == sizeof(stash->b32));
+ assert(impl->stash.body == &stash->b32);
+ } break;
+ case PROP_i32:
+ {
+ assert(impl->value.size == sizeof(state->i32));
+ assert(impl->value.body == &state->i32);
+
+ assert(impl->stash.size == sizeof(stash->i32));
+ assert(impl->stash.body == &stash->i32);
+ } break;
+ case PROP_i64:
+ {
+ assert(impl->value.size == sizeof(state->i64));
+ assert(impl->value.body == &state->i64);
+
+ assert(impl->stash.size == sizeof(stash->i64));
+ assert(impl->stash.body == &stash->i64);
+ } break;
+ case PROP_f32:
+ {
+ assert(impl->value.size == sizeof(state->f32));
+ assert(impl->value.body == &state->f32);
+
+ assert(impl->stash.size == sizeof(stash->f32));
+ assert(impl->stash.body == &stash->f32);
+ } break;
+ case PROP_f64:
+ {
+ assert(impl->value.size == sizeof(state->f64));
+ assert(impl->value.body == &state->f64);
+
+ assert(impl->stash.size == sizeof(stash->f64));
+ assert(impl->stash.body == &stash->f64);
+ } break;
+ case PROP_urid:
+ {
+ assert(impl->value.size == sizeof(state->urid));
+ assert(impl->value.body == &state->urid);
+
+ assert(impl->stash.size == sizeof(stash->urid));
+ assert(impl->stash.body == &stash->urid);
+ } break;
+ case PROP_str:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->str);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->str);
+ } break;
+ case PROP_uri:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->uri);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->uri);
+ } break;
+ case PROP_path:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->path);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->path);
+ } break;
+ case PROP_chunk:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->chunk);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->chunk);
+ } break;
+ case PROP_lit:
+ {
+ assert(impl->value.size == sizeof(state->lit));
+ assert(impl->value.body == &state->lit);
+
+ assert(impl->stash.size == sizeof(stash->lit));
+ assert(impl->stash.body == &stash->lit);
+ } break;
+ case PROP_vec:
+ {
+ assert(impl->value.size == sizeof(state->vec));
+ assert(impl->value.body == &state->vec);
+
+ assert(impl->stash.size == sizeof(stash->vec));
+ assert(impl->stash.body == &stash->vec);
+ } break;
+ case PROP_obj:
+ {
+ assert(impl->value.size == sizeof(state->obj));
+ assert(impl->value.body == &state->obj);
+
+ assert(impl->stash.size == sizeof(stash->obj));
+ assert(impl->stash.body == &stash->obj);
+ } break;
+ case PROP_seq:
+ {
+ assert(impl->value.size == sizeof(state->seq));
+ assert(impl->value.body == &state->seq);
+
+ assert(impl->stash.size == sizeof(stash->seq));
+ assert(impl->stash.body == &stash->seq);
+ } break;
+ default:
+ {
+ assert(false);
+ } break;
+ }
+ }
+}
+
+static void
+_test_2(handle_t *handle)
+{
+ assert(handle);
+
+ props_t *props = &handle->props;
+ plugstate_t *state = &handle->state;
+ plugstate_t *stash = &handle->stash;
+ LV2_URID_Map *map = &handle->map;
+
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+ ser_atom_t ser;
+
+ lv2_atom_forge_init(&forge, map);
+ assert(ser_atom_init(&ser) == 0);
+
+ lv2_atom_forge_set_sink(&forge, _ser_atom_sink, _ser_atom_deref, &ser);
+
+ ref = lv2_atom_forge_sequence_head(&forge, &frame, 0);
+ assert(ref);
+
+ props_idle(props, &forge, 0, &ref);
+ assert(ref);
+
+ const LV2_URID property = props_map(props, defs[0].property);
+ assert(property);
+
+ state->b32 = true;
+
+ props_set(props, &forge, 1, property, &ref);
+ assert(ref);
+
+ state->b32 = false;
+
+ lv2_atom_forge_pop(&forge, &frame);
+
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)ser_atom_get(&ser);
+ assert(seq);
+
+ unsigned nevs = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ assert(ev->time.frames == 1);
+ assert(atom->type == forge.Object);
+
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+ assert(obj->body.id == 0);
+
+ if(obj->body.otype == props->urid.state_StateChanged)
+ {
+ continue;
+ }
+
+ assert(obj->body.otype == props->urid.patch_set);
+
+ unsigned nprops = 0;
+ LV2_ATOM_OBJECT_FOREACH(obj, prop)
+ {
+ assert(prop->context == 0);
+
+ if(prop->key == props->urid.patch_subject)
+ {
+ const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value;
+
+ assert(val->atom.type == forge.URID);
+ assert(val->atom.size == sizeof(uint32_t));
+ assert(val->body == props->urid.subject);
+
+ nprops |= 0x1;
+ }
+ else if(prop->key == props->urid.patch_property)
+ {
+ const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value;
+
+ assert(val->atom.type == forge.URID);
+ assert(val->atom.size == sizeof(uint32_t));
+ assert(val->body == property);
+
+ nprops |= 0x2;
+ }
+ else if(prop->key == props->urid.patch_value)
+ {
+ const LV2_Atom_Bool *val = (const LV2_Atom_Bool *)&prop->value;
+
+ assert(val->atom.type == forge.Bool);
+ assert(val->atom.size == sizeof(int32_t));
+ assert(val->body == true);
+
+ nprops |= 0x4;
+ }
+ else
+ {
+ assert(false);
+ }
+ }
+ assert(nprops == 0x7);
+
+ assert(props_advance(props, &forge, ev->time.frames, obj, &ref) == 1);
+
+ assert(state->b32 == true);
+ assert(stash->b32 == true);
+
+ nevs |= 0x1;
+ }
+ assert(nevs == 0x1);
+
+ assert(ser_atom_deinit(&ser) == 0);
+}
+
+static const test_t tests [] = {
+ _test_1,
+ _test_2,
+ NULL
+};
+
+int
+main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
+{
+ static handle_t handle;
+
+ for(const test_t *test = tests; *test; test++)
+ {
+ memset(&handle, 0, sizeof(handle));
+
+ handle.map.handle = &handle;
+ handle.map.map = _map;
+
+ assert(props_init(&handle.props, PROPS_PREFIX"subj", defs, MAX_NPROPS,
+ &handle.state, &handle.stash, &handle.map, NULL) == 1);
+
+ (*test)(&handle);
+ }
+
+ for(urid_t *itm=handle.urids; itm->urid; itm++)
+ {
+ free(itm->uri);
+ }
+
+ return 0;
+}
diff --git a/screenshots/screenshot_1.png b/screenshots/screenshot_1.png
new file mode 100644
index 0000000..7b88178
--- /dev/null
+++ b/screenshots/screenshot_1.png
Binary files differ
diff --git a/subprojects/nk_pugl/.gitlab-ci.yml b/subprojects/nk_pugl/.gitlab-ci.yml
new file mode 100644
index 0000000..62aa6f8
--- /dev/null
+++ b/subprojects/nk_pugl/.gitlab-ci.yml
@@ -0,0 +1,77 @@
+stages:
+ - build
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "nk_pugl"
+ 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 -Dbuild-examples=true --prefix="/" --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' build/build.ninja
+ - ninja -C build
+ - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
+
+.analyze_template: &analyze_definition
+ <<: *common_definition
+ script:
+ - meson -Dbuild-examples=true --prefix="/" --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' build/build.ninja
+ - ninja -C build
+ - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
+
+ - CC=clang CXX=clang++ meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
+ - ninja -C clang
+
+ - scan-build --status-bugs meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *analyze_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev libevdev-dev
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf
+ <<: *arm_linux_definition
+
+aarch64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64
+ <<: *arm_linux_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/subprojects/nk_pugl/COPYING b/subprojects/nk_pugl/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/subprojects/nk_pugl/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/subprojects/nk_pugl/VERSION b/subprojects/nk_pugl/VERSION
new file mode 100644
index 0000000..f4fdeb6
--- /dev/null
+++ b/subprojects/nk_pugl/VERSION
@@ -0,0 +1 @@
+0.1.179
diff --git a/example/example.c b/subprojects/nk_pugl/example/example.c
index c55a2ca..c55a2ca 100644
--- a/example/example.c
+++ b/subprojects/nk_pugl/example/example.c
diff --git a/subprojects/nk_pugl/meson.build b/subprojects/nk_pugl/meson.build
new file mode 100644
index 0000000..c891785
--- /dev/null
+++ b/subprojects/nk_pugl/meson.build
@@ -0,0 +1,84 @@
+project('nk_pugl', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=gnu11'])
+
+build_examples = get_option('build-examples')
+
+static_link = false #meson.is_cross_build()
+
+cc = meson.get_compiler('c')
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2',
+ version : '>=1.14.0')
+glew_dep = dependency('glew',
+ version : '>=2.0.0',
+ static : static_link)
+glu_dep = dependency('glu',
+ version : '>=9.0.0',
+ static : static_link)
+
+deps = [m_dep, lv2_dep, glu_dep, glew_dep]
+links = []
+
+nk_pugl_inc = include_directories('')
+pugl_inc = include_directories(join_paths('pugl', 'include'))
+inc_dir = [nk_pugl_inc, pugl_inc]
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+bin_srcs = [
+ join_paths('example', 'example.c')
+]
+
+lib_srcs = [
+ join_paths('pugl', 'src', 'implementation.c')
+]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('opengl32')
+ deps += cc.find_library('gdi32')
+ deps += cc.find_library('ws2_32')
+ lib_srcs += join_paths('pugl', 'src', 'win.c')
+ lib_srcs += join_paths('pugl', 'src', 'win_gl.c')
+elif host_machine.system() == 'darwin'
+ add_languages('objc')
+ links += ['-framework', 'OpenGL']
+ links += ['-framework', 'Cocoa']
+ lib_srcs += join_paths('pugl', 'src', 'mac.m')
+ lib_srcs += join_paths('pugl', 'src', 'mac_gl.m')
+else
+ deps += dependency('gl')
+ deps += dependency('x11', version : '>=1.6.0')
+ deps += dependency('xext', version : '>=1.3.0')
+ lib_srcs += join_paths('pugl', 'src', 'x11.c')
+ lib_srcs += join_paths('pugl', 'src', 'x11_gl.c')
+endif
+
+nk_pugl_gl = declare_dependency(
+ compile_args : ['-DPUGL_STATIC'],
+ include_directories : inc_dir,
+ dependencies : deps,
+ link_args : links,
+ sources : lib_srcs)
+
+cousine_regular_ttf = configure_file(
+ input : join_paths('nuklear', 'extra_font', 'Cousine-Regular.ttf'),
+ output : 'Cousine-Regular.ttf',
+ copy : true,
+ install : false)
+
+if build_examples
+
+ executable('nk_pugl.gl', [bin_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: nk_pugl_gl,
+ install : false)
+endif
diff --git a/subprojects/nk_pugl/meson_options.txt b/subprojects/nk_pugl/meson_options.txt
new file mode 100644
index 0000000..0a4a2a9
--- /dev/null
+++ b/subprojects/nk_pugl/meson_options.txt
@@ -0,0 +1,4 @@
+option('build-examples',
+ type : 'boolean',
+ value : false,
+ yield : true)
diff --git a/nk_pugl/nk_pugl.h b/subprojects/nk_pugl/nk_pugl/nk_pugl.h
index 58fce93..58fce93 100644
--- a/nk_pugl/nk_pugl.h
+++ b/subprojects/nk_pugl/nk_pugl/nk_pugl.h
diff --git a/nuklear/.gitattributes b/subprojects/nk_pugl/nuklear/.gitattributes
index 5a5328c..5a5328c 100644
--- a/nuklear/.gitattributes
+++ b/subprojects/nk_pugl/nuklear/.gitattributes
diff --git a/nuklear/.gitignore b/subprojects/nk_pugl/nuklear/.gitignore
index 6b06b55..6b06b55 100644
--- a/nuklear/.gitignore
+++ b/subprojects/nk_pugl/nuklear/.gitignore
diff --git a/nuklear/.gitmodules b/subprojects/nk_pugl/nuklear/.gitmodules
index e69de29..e69de29 100644
--- a/nuklear/.gitmodules
+++ b/subprojects/nk_pugl/nuklear/.gitmodules
diff --git a/nuklear/.travis.yml b/subprojects/nk_pugl/nuklear/.travis.yml
index 80a5ad5..80a5ad5 100644
--- a/nuklear/.travis.yml
+++ b/subprojects/nk_pugl/nuklear/.travis.yml
diff --git a/nuklear/Readme.md b/subprojects/nk_pugl/nuklear/Readme.md
index d097e35..d097e35 100644
--- a/nuklear/Readme.md
+++ b/subprojects/nk_pugl/nuklear/Readme.md
diff --git a/nuklear/demo/allegro5/KeyboardHandleriOS.h b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h
index 2664d2e..2664d2e 100644
--- a/nuklear/demo/allegro5/KeyboardHandleriOS.h
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h
diff --git a/nuklear/demo/allegro5/KeyboardHandleriOS.m b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m
index 206f9e4..206f9e4 100644
--- a/nuklear/demo/allegro5/KeyboardHandleriOS.m
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m
diff --git a/nuklear/demo/allegro5/Makefile b/subprojects/nk_pugl/nuklear/demo/allegro5/Makefile
index 090a126..090a126 100644
--- a/nuklear/demo/allegro5/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/Makefile
diff --git a/nuklear/demo/allegro5/Readme.md b/subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md
index 71a4840..71a4840 100644
--- a/nuklear/demo/allegro5/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md
diff --git a/nuklear/demo/allegro5/main.c b/subprojects/nk_pugl/nuklear/demo/allegro5/main.c
index 624e7c9..624e7c9 100644
--- a/nuklear/demo/allegro5/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/main.c
diff --git a/nuklear/demo/allegro5/nuklear_allegro5.h b/subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h
index f396600..f396600 100644
--- a/nuklear/demo/allegro5/nuklear_allegro5.h
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h
diff --git a/nuklear/demo/calculator.c b/subprojects/nk_pugl/nuklear/demo/calculator.c
index b871301..b871301 100644
--- a/nuklear/demo/calculator.c
+++ b/subprojects/nk_pugl/nuklear/demo/calculator.c
diff --git a/nuklear/demo/d3d11/build.bat b/subprojects/nk_pugl/nuklear/demo/d3d11/build.bat
index 31bd0e0..31bd0e0 100644
--- a/nuklear/demo/d3d11/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/build.bat
diff --git a/nuklear/demo/d3d11/main.c b/subprojects/nk_pugl/nuklear/demo/d3d11/main.c
index a7abf1d..a7abf1d 100644
--- a/nuklear/demo/d3d11/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/main.c
diff --git a/nuklear/demo/d3d11/nuklear_d3d11.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h
index 7097d98..7097d98 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h
diff --git a/nuklear/demo/d3d11/nuklear_d3d11.hlsl b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl
index a932dca..a932dca 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11.hlsl
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl
diff --git a/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
index 1447559..1447559 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
diff --git a/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
index 770d2dd..770d2dd 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
diff --git a/nuklear/demo/d3d9/build.bat b/subprojects/nk_pugl/nuklear/demo/d3d9/build.bat
index 726b6f6..726b6f6 100644
--- a/nuklear/demo/d3d9/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/build.bat
diff --git a/nuklear/demo/d3d9/main.c b/subprojects/nk_pugl/nuklear/demo/d3d9/main.c
index b329e2b..b329e2b 100644
--- a/nuklear/demo/d3d9/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/main.c
diff --git a/nuklear/demo/d3d9/nuklear_d3d9.h b/subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h
index fd8d176..fd8d176 100644
--- a/nuklear/demo/d3d9/nuklear_d3d9.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h
diff --git a/nuklear/demo/gdi/build.bat b/subprojects/nk_pugl/nuklear/demo/gdi/build.bat
index 3884317..3884317 100644
--- a/nuklear/demo/gdi/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/build.bat
diff --git a/nuklear/demo/gdi/main.c b/subprojects/nk_pugl/nuklear/demo/gdi/main.c
index 1755b9c..1755b9c 100644
--- a/nuklear/demo/gdi/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/main.c
diff --git a/nuklear/demo/gdi/nuklear_gdi.h b/subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h
index c875de1..c875de1 100644
--- a/nuklear/demo/gdi/nuklear_gdi.h
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h
diff --git a/nuklear/demo/gdip/build.bat b/subprojects/nk_pugl/nuklear/demo/gdip/build.bat
index 0f24655..0f24655 100644
--- a/nuklear/demo/gdip/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/build.bat
diff --git a/nuklear/demo/gdip/main.c b/subprojects/nk_pugl/nuklear/demo/gdip/main.c
index a90a0cf..a90a0cf 100644
--- a/nuklear/demo/gdip/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/main.c
diff --git a/nuklear/demo/gdip/nuklear_gdip.h b/subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h
index 8a6c8d4..8a6c8d4 100644
--- a/nuklear/demo/gdip/nuklear_gdip.h
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h
diff --git a/nuklear/demo/glfw_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile
index c08d65f..c08d65f 100644
--- a/nuklear/demo/glfw_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile
diff --git a/nuklear/demo/glfw_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c
index 5d68bb9..5d68bb9 100644
--- a/nuklear/demo/glfw_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c
diff --git a/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
index 7724b25..7724b25 100644
--- a/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
diff --git a/nuklear/demo/glfw_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile
index da95261..da95261 100644
--- a/nuklear/demo/glfw_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile
diff --git a/nuklear/demo/glfw_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c
index 529e88c..529e88c 100644
--- a/nuklear/demo/glfw_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c
diff --git a/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
index 8fe98fc..8fe98fc 100644
--- a/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
diff --git a/nuklear/demo/node_editor.c b/subprojects/nk_pugl/nuklear/demo/node_editor.c
index d4f34c3..d4f34c3 100644
--- a/nuklear/demo/node_editor.c
+++ b/subprojects/nk_pugl/nuklear/demo/node_editor.c
diff --git a/nuklear/demo/overview.c b/subprojects/nk_pugl/nuklear/demo/overview.c
index 3c3f381..3c3f381 100644
--- a/nuklear/demo/overview.c
+++ b/subprojects/nk_pugl/nuklear/demo/overview.c
diff --git a/nuklear/demo/sdl_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile
index b73174a..b73174a 100644
--- a/nuklear/demo/sdl_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile
diff --git a/nuklear/demo/sdl_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c
index 0fbf3ff..0fbf3ff 100644
--- a/nuklear/demo/sdl_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c
diff --git a/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
index 62dc64e..62dc64e 100644
--- a/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
diff --git a/nuklear/demo/sdl_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile
index c6fcb45..c6fcb45 100644
--- a/nuklear/demo/sdl_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile
diff --git a/nuklear/demo/sdl_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c
index 9959d8a..9959d8a 100644
--- a/nuklear/demo/sdl_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c
diff --git a/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
index eb53ebb..eb53ebb 100644
--- a/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
diff --git a/nuklear/demo/sdl_opengles2/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile
index 7385305..7385305 100644
--- a/nuklear/demo/sdl_opengles2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile
diff --git a/nuklear/demo/sdl_opengles2/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c
index 16a271a..16a271a 100644
--- a/nuklear/demo/sdl_opengles2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c
diff --git a/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
index f96e610..f96e610 100644
--- a/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
diff --git a/nuklear/demo/sfml_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile
index 28848a4..28848a4 100644
--- a/nuklear/demo/sfml_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile
diff --git a/nuklear/demo/sfml_opengl2/Readme.md b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md
index e3879c0..e3879c0 100644
--- a/nuklear/demo/sfml_opengl2/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md
diff --git a/nuklear/demo/sfml_opengl2/main.cpp b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp
index 805c1f8..805c1f8 100644
--- a/nuklear/demo/sfml_opengl2/main.cpp
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp
diff --git a/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
index 9649ec0..9649ec0 100644
--- a/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
diff --git a/nuklear/demo/sfml_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile
index b30bf28..b30bf28 100644
--- a/nuklear/demo/sfml_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile
diff --git a/nuklear/demo/sfml_opengl3/Readme.md b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md
index ac03e75..ac03e75 100644
--- a/nuklear/demo/sfml_opengl3/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md
diff --git a/nuklear/demo/sfml_opengl3/main.cpp b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp
index 5dc2a6e..5dc2a6e 100644
--- a/nuklear/demo/sfml_opengl3/main.cpp
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp
diff --git a/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
index 40b390e..40b390e 100644
--- a/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
diff --git a/nuklear/demo/style.c b/subprojects/nk_pugl/nuklear/demo/style.c
index 17c48fe..17c48fe 100644
--- a/nuklear/demo/style.c
+++ b/subprojects/nk_pugl/nuklear/demo/style.c
diff --git a/nuklear/demo/x11/Makefile b/subprojects/nk_pugl/nuklear/demo/x11/Makefile
index 9057c7b..9057c7b 100644
--- a/nuklear/demo/x11/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11/Makefile
diff --git a/nuklear/demo/x11/main.c b/subprojects/nk_pugl/nuklear/demo/x11/main.c
index 791653d..791653d 100644
--- a/nuklear/demo/x11/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11/main.c
diff --git a/nuklear/demo/x11/nuklear_xlib.h b/subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h
index 8b6b4ff..8b6b4ff 100644
--- a/nuklear/demo/x11/nuklear_xlib.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h
diff --git a/nuklear/demo/x11_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile
index a3d6d32..a3d6d32 100644
--- a/nuklear/demo/x11_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile
diff --git a/nuklear/demo/x11_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c
index 12cbed1..12cbed1 100644
--- a/nuklear/demo/x11_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c
diff --git a/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
index ab36eb6..ab36eb6 100644
--- a/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
diff --git a/nuklear/demo/x11_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile
index a3d6d32..a3d6d32 100644
--- a/nuklear/demo/x11_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile
diff --git a/nuklear/demo/x11_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c
index 0191f98..0191f98 100644
--- a/nuklear/demo/x11_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c
diff --git a/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
index 487dbc7..487dbc7 100644
--- a/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
diff --git a/nuklear/demo/x11_rawfb/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile
index c1a178a..c1a178a 100644
--- a/nuklear/demo/x11_rawfb/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile
diff --git a/nuklear/demo/x11_rawfb/main.c b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c
index c70b024..c70b024 100644
--- a/nuklear/demo/x11_rawfb/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c
diff --git a/nuklear/demo/x11_rawfb/nuklear_rawfb.h b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h
index e518780..e518780 100644
--- a/nuklear/demo/x11_rawfb/nuklear_rawfb.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h
diff --git a/nuklear/demo/x11_rawfb/nuklear_xlib.h b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h
index 068112f..068112f 100644
--- a/nuklear/demo/x11_rawfb/nuklear_xlib.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h
diff --git a/nuklear/doc/Makefile b/subprojects/nk_pugl/nuklear/doc/Makefile
index 72a598b..72a598b 100644
--- a/nuklear/doc/Makefile
+++ b/subprojects/nk_pugl/nuklear/doc/Makefile
diff --git a/nuklear/doc/build.sh b/subprojects/nk_pugl/nuklear/doc/build.sh
index 560dd59..560dd59 100755
--- a/nuklear/doc/build.sh
+++ b/subprojects/nk_pugl/nuklear/doc/build.sh
diff --git a/nuklear/doc/nuklear.html b/subprojects/nk_pugl/nuklear/doc/nuklear.html
index ad0d63b..ad0d63b 100644
--- a/nuklear/doc/nuklear.html
+++ b/subprojects/nk_pugl/nuklear/doc/nuklear.html
diff --git a/nuklear/doc/stddoc.c b/subprojects/nk_pugl/nuklear/doc/stddoc.c
index 461ddc1..461ddc1 100644
--- a/nuklear/doc/stddoc.c
+++ b/subprojects/nk_pugl/nuklear/doc/stddoc.c
diff --git a/nuklear/example/Makefile b/subprojects/nk_pugl/nuklear/example/Makefile
index a3ae6f0..a3ae6f0 100644
--- a/nuklear/example/Makefile
+++ b/subprojects/nk_pugl/nuklear/example/Makefile
diff --git a/nuklear/example/canvas.c b/subprojects/nk_pugl/nuklear/example/canvas.c
index 2a7f7a2..2a7f7a2 100644
--- a/nuklear/example/canvas.c
+++ b/subprojects/nk_pugl/nuklear/example/canvas.c
diff --git a/nuklear/example/extended.c b/subprojects/nk_pugl/nuklear/example/extended.c
index 003adf3..003adf3 100644
--- a/nuklear/example/extended.c
+++ b/subprojects/nk_pugl/nuklear/example/extended.c
diff --git a/nuklear/example/file_browser.c b/subprojects/nk_pugl/nuklear/example/file_browser.c
index 1945ff5..1945ff5 100644
--- a/nuklear/example/file_browser.c
+++ b/subprojects/nk_pugl/nuklear/example/file_browser.c
diff --git a/nuklear/example/icon/checked.png b/subprojects/nk_pugl/nuklear/example/icon/checked.png
index e4e05b2..e4e05b2 100644
--- a/nuklear/example/icon/checked.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/checked.png
Binary files differ
diff --git a/nuklear/example/icon/cloud.png b/subprojects/nk_pugl/nuklear/example/icon/cloud.png
index ecc5791..ecc5791 100644
--- a/nuklear/example/icon/cloud.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/cloud.png
Binary files differ
diff --git a/nuklear/example/icon/computer.png b/subprojects/nk_pugl/nuklear/example/icon/computer.png
index 29db8fc..29db8fc 100644
--- a/nuklear/example/icon/computer.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/computer.png
Binary files differ
diff --git a/nuklear/example/icon/copy.png b/subprojects/nk_pugl/nuklear/example/icon/copy.png
index 0a6e979..0a6e979 100644
--- a/nuklear/example/icon/copy.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/copy.png
Binary files differ
diff --git a/nuklear/example/icon/default.png b/subprojects/nk_pugl/nuklear/example/icon/default.png
index c11145a..c11145a 100644
--- a/nuklear/example/icon/default.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/default.png
Binary files differ
diff --git a/nuklear/example/icon/delete.png b/subprojects/nk_pugl/nuklear/example/icon/delete.png
index 7bc6dde..7bc6dde 100644
--- a/nuklear/example/icon/delete.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/delete.png
Binary files differ
diff --git a/nuklear/example/icon/desktop.png b/subprojects/nk_pugl/nuklear/example/icon/desktop.png
index b4abcfd..b4abcfd 100644
--- a/nuklear/example/icon/desktop.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/desktop.png
Binary files differ
diff --git a/nuklear/example/icon/directory.png b/subprojects/nk_pugl/nuklear/example/icon/directory.png
index 4c73d37..4c73d37 100644
--- a/nuklear/example/icon/directory.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/directory.png
Binary files differ
diff --git a/nuklear/example/icon/edit.png b/subprojects/nk_pugl/nuklear/example/icon/edit.png
index 62ce0b4..62ce0b4 100644
--- a/nuklear/example/icon/edit.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/edit.png
Binary files differ
diff --git a/nuklear/example/icon/export.png b/subprojects/nk_pugl/nuklear/example/icon/export.png
index ff6b5aa..ff6b5aa 100644
--- a/nuklear/example/icon/export.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/export.png
Binary files differ
diff --git a/nuklear/example/icon/font.png b/subprojects/nk_pugl/nuklear/example/icon/font.png
index 918e9bf..918e9bf 100644
--- a/nuklear/example/icon/font.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/font.png
Binary files differ
diff --git a/nuklear/example/icon/home.png b/subprojects/nk_pugl/nuklear/example/icon/home.png
index 8560626..8560626 100644
--- a/nuklear/example/icon/home.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/home.png
Binary files differ
diff --git a/nuklear/example/icon/img.png b/subprojects/nk_pugl/nuklear/example/icon/img.png
index 1985957..1985957 100644
--- a/nuklear/example/icon/img.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/img.png
Binary files differ
diff --git a/nuklear/example/icon/movie.png b/subprojects/nk_pugl/nuklear/example/icon/movie.png
index 5227883..5227883 100644
--- a/nuklear/example/icon/movie.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/movie.png
Binary files differ
diff --git a/nuklear/example/icon/music.png b/subprojects/nk_pugl/nuklear/example/icon/music.png
index 0f1415c..0f1415c 100644
--- a/nuklear/example/icon/music.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/music.png
Binary files differ
diff --git a/nuklear/example/icon/next.png b/subprojects/nk_pugl/nuklear/example/icon/next.png
index af0b98d..af0b98d 100644
--- a/nuklear/example/icon/next.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/next.png
Binary files differ
diff --git a/nuklear/example/icon/pause.png b/subprojects/nk_pugl/nuklear/example/icon/pause.png
index 7d6367e..7d6367e 100644
--- a/nuklear/example/icon/pause.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/pause.png
Binary files differ
diff --git a/nuklear/example/icon/pen.png b/subprojects/nk_pugl/nuklear/example/icon/pen.png
index 10c851c..10c851c 100644
--- a/nuklear/example/icon/pen.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/pen.png
Binary files differ
diff --git a/nuklear/example/icon/phone.png b/subprojects/nk_pugl/nuklear/example/icon/phone.png
index 5e6f613..5e6f613 100644
--- a/nuklear/example/icon/phone.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/phone.png
Binary files differ
diff --git a/nuklear/example/icon/plane.png b/subprojects/nk_pugl/nuklear/example/icon/plane.png
index 3a98489..3a98489 100644
--- a/nuklear/example/icon/plane.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/plane.png
Binary files differ
diff --git a/nuklear/example/icon/play.png b/subprojects/nk_pugl/nuklear/example/icon/play.png
index 9c9e8f0..9c9e8f0 100644
--- a/nuklear/example/icon/play.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/play.png
Binary files differ
diff --git a/nuklear/example/icon/prev.png b/subprojects/nk_pugl/nuklear/example/icon/prev.png
index 0eecc2e..0eecc2e 100644
--- a/nuklear/example/icon/prev.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/prev.png
Binary files differ
diff --git a/nuklear/example/icon/rocket.png b/subprojects/nk_pugl/nuklear/example/icon/rocket.png
index ea8e187..ea8e187 100644
--- a/nuklear/example/icon/rocket.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/rocket.png
Binary files differ
diff --git a/nuklear/example/icon/settings.png b/subprojects/nk_pugl/nuklear/example/icon/settings.png
index e6e13f8..e6e13f8 100644
--- a/nuklear/example/icon/settings.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/settings.png
Binary files differ
diff --git a/nuklear/example/icon/stop.png b/subprojects/nk_pugl/nuklear/example/icon/stop.png
index 6742baf..6742baf 100644
--- a/nuklear/example/icon/stop.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/stop.png
Binary files differ
diff --git a/nuklear/example/icon/text.png b/subprojects/nk_pugl/nuklear/example/icon/text.png
index 136e534..136e534 100644
--- a/nuklear/example/icon/text.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/text.png
Binary files differ
diff --git a/nuklear/example/icon/tools.png b/subprojects/nk_pugl/nuklear/example/icon/tools.png
index 412ff85..412ff85 100644
--- a/nuklear/example/icon/tools.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/tools.png
Binary files differ
diff --git a/nuklear/example/icon/unchecked.png b/subprojects/nk_pugl/nuklear/example/icon/unchecked.png
index fca94d2..fca94d2 100644
--- a/nuklear/example/icon/unchecked.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/unchecked.png
Binary files differ
diff --git a/nuklear/example/icon/volume.png b/subprojects/nk_pugl/nuklear/example/icon/volume.png
index 8e86fa9..8e86fa9 100644
--- a/nuklear/example/icon/volume.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/volume.png
Binary files differ
diff --git a/nuklear/example/icon/wifi.png b/subprojects/nk_pugl/nuklear/example/icon/wifi.png
index 270d55d..270d55d 100644
--- a/nuklear/example/icon/wifi.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/wifi.png
Binary files differ
diff --git a/nuklear/example/images/image1.png b/subprojects/nk_pugl/nuklear/example/images/image1.png
index 66a2b63..66a2b63 100644
--- a/nuklear/example/images/image1.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image1.png
Binary files differ
diff --git a/nuklear/example/images/image2.png b/subprojects/nk_pugl/nuklear/example/images/image2.png
index 4acafe5..4acafe5 100644
--- a/nuklear/example/images/image2.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image2.png
Binary files differ
diff --git a/nuklear/example/images/image3.png b/subprojects/nk_pugl/nuklear/example/images/image3.png
index 4dfe664..4dfe664 100644
--- a/nuklear/example/images/image3.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image3.png
Binary files differ
diff --git a/nuklear/example/images/image4.png b/subprojects/nk_pugl/nuklear/example/images/image4.png
index d2f16d0..d2f16d0 100644
--- a/nuklear/example/images/image4.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image4.png
Binary files differ
diff --git a/nuklear/example/images/image5.png b/subprojects/nk_pugl/nuklear/example/images/image5.png
index 852fd70..852fd70 100644
--- a/nuklear/example/images/image5.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image5.png
Binary files differ
diff --git a/nuklear/example/images/image6.png b/subprojects/nk_pugl/nuklear/example/images/image6.png
index 0e261cb..0e261cb 100644
--- a/nuklear/example/images/image6.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image6.png
Binary files differ
diff --git a/nuklear/example/images/image7.png b/subprojects/nk_pugl/nuklear/example/images/image7.png
index f61325b..f61325b 100644
--- a/nuklear/example/images/image7.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image7.png
Binary files differ
diff --git a/nuklear/example/images/image8.png b/subprojects/nk_pugl/nuklear/example/images/image8.png
index 6b27cb8..6b27cb8 100644
--- a/nuklear/example/images/image8.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image8.png
Binary files differ
diff --git a/nuklear/example/images/image9.png b/subprojects/nk_pugl/nuklear/example/images/image9.png
index 516929e..516929e 100644
--- a/nuklear/example/images/image9.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image9.png
Binary files differ
diff --git a/nuklear/example/skinning.c b/subprojects/nk_pugl/nuklear/example/skinning.c
index 4020aec..4020aec 100644
--- a/nuklear/example/skinning.c
+++ b/subprojects/nk_pugl/nuklear/example/skinning.c
diff --git a/nuklear/example/skins/gwen.png b/subprojects/nk_pugl/nuklear/example/skins/gwen.png
index 40956c9..40956c9 100644
--- a/nuklear/example/skins/gwen.png
+++ b/subprojects/nk_pugl/nuklear/example/skins/gwen.png
Binary files differ
diff --git a/nuklear/example/stb_image.h b/subprojects/nk_pugl/nuklear/example/stb_image.h
index 0a9de39..0a9de39 100644
--- a/nuklear/example/stb_image.h
+++ b/subprojects/nk_pugl/nuklear/example/stb_image.h
diff --git a/nuklear/extra_font/Cousine-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf
index 70a0bf9..70a0bf9 100644
--- a/nuklear/extra_font/Cousine-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/DroidSans.ttf b/subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf
index 767c63a..767c63a 100644
--- a/nuklear/extra_font/DroidSans.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf
Binary files differ
diff --git a/nuklear/extra_font/Karla-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf
index 81b3de6..81b3de6 100644
--- a/nuklear/extra_font/Karla-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/ProggyClean.ttf b/subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf
index 0270cdf..0270cdf 100644
--- a/nuklear/extra_font/ProggyClean.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf
Binary files differ
diff --git a/nuklear/extra_font/ProggyTiny.ttf b/subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf
index 1c4312c..1c4312c 100644
--- a/nuklear/extra_font/ProggyTiny.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf
Binary files differ
diff --git a/nuklear/extra_font/Raleway-Bold.ttf b/subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf
index 7aa37f0..7aa37f0 100644
--- a/nuklear/extra_font/Raleway-Bold.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Bold.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf
index aaf374d..aaf374d 100644
--- a/nuklear/extra_font/Roboto-Bold.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Light.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf
index 664e1b2..664e1b2 100644
--- a/nuklear/extra_font/Roboto-Light.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf
index 3e6e2e7..3e6e2e7 100644
--- a/nuklear/extra_font/Roboto-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/kenvector_future.ttf b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf
index 39ebdfa..39ebdfa 100644
--- a/nuklear/extra_font/kenvector_future.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf
Binary files differ
diff --git a/nuklear/extra_font/kenvector_future_thin.ttf b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf
index 9f4b4fa..9f4b4fa 100644
--- a/nuklear/extra_font/kenvector_future_thin.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf
Binary files differ
diff --git a/nuklear/nuklear.h b/subprojects/nk_pugl/nuklear/nuklear.h
index dd423b9..dd423b9 100644
--- a/nuklear/nuklear.h
+++ b/subprojects/nk_pugl/nuklear/nuklear.h
diff --git a/nuklear/package.json b/subprojects/nk_pugl/nuklear/package.json
index edff924..edff924 100644
--- a/nuklear/package.json
+++ b/subprojects/nk_pugl/nuklear/package.json
diff --git a/pugl/.clang-format b/subprojects/nk_pugl/pugl/.clang-format
index 7b30bd2..7b30bd2 100644
--- a/pugl/.clang-format
+++ b/subprojects/nk_pugl/pugl/.clang-format
diff --git a/pugl/.clang-tidy b/subprojects/nk_pugl/pugl/.clang-tidy
index 1e40901..1e40901 100644
--- a/pugl/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/.clang-tidy
diff --git a/pugl/.clant.json b/subprojects/nk_pugl/pugl/.clant.json
index 6f48901..6f48901 100644
--- a/pugl/.clant.json
+++ b/subprojects/nk_pugl/pugl/.clant.json
diff --git a/pugl/.editorconfig b/subprojects/nk_pugl/pugl/.editorconfig
index c2d35dd..c2d35dd 100644
--- a/pugl/.editorconfig
+++ b/subprojects/nk_pugl/pugl/.editorconfig
diff --git a/pugl/.gitattributes b/subprojects/nk_pugl/pugl/.gitattributes
index 32967c1..32967c1 100644
--- a/pugl/.gitattributes
+++ b/subprojects/nk_pugl/pugl/.gitattributes
diff --git a/pugl/.gitignore b/subprojects/nk_pugl/pugl/.gitignore
index 41c45d2..41c45d2 100644
--- a/pugl/.gitignore
+++ b/subprojects/nk_pugl/pugl/.gitignore
diff --git a/pugl/.gitlab-ci.yml b/subprojects/nk_pugl/pugl/.gitlab-ci.yml
index dc82cf8..dc82cf8 100644
--- a/pugl/.gitlab-ci.yml
+++ b/subprojects/nk_pugl/pugl/.gitlab-ci.yml
diff --git a/pugl/.gitmodules b/subprojects/nk_pugl/pugl/.gitmodules
index e69de29..e69de29 100644
--- a/pugl/.gitmodules
+++ b/subprojects/nk_pugl/pugl/.gitmodules
diff --git a/pugl/.includes.imp b/subprojects/nk_pugl/pugl/.includes.imp
index 74a3105..74a3105 100644
--- a/pugl/.includes.imp
+++ b/subprojects/nk_pugl/pugl/.includes.imp
diff --git a/pugl/AUTHORS b/subprojects/nk_pugl/pugl/AUTHORS
index 99f6dac..99f6dac 100644
--- a/pugl/AUTHORS
+++ b/subprojects/nk_pugl/pugl/AUTHORS
diff --git a/pugl/COPYING b/subprojects/nk_pugl/pugl/COPYING
index 63e6829..63e6829 100644
--- a/pugl/COPYING
+++ b/subprojects/nk_pugl/pugl/COPYING
diff --git a/pugl/README.md b/subprojects/nk_pugl/pugl/README.md
index 5b4f82f..5b4f82f 100644
--- a/pugl/README.md
+++ b/subprojects/nk_pugl/pugl/README.md
diff --git a/pugl/bindings/cxx/include/.clang-tidy b/subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy
index 816223d..816223d 100644
--- a/pugl/bindings/cxx/include/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy
diff --git a/pugl/bindings/cxx/include/pugl/cairo.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp
index 15dc5de..15dc5de 100644
--- a/pugl/bindings/cxx/include/pugl/cairo.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp
diff --git a/pugl/bindings/cxx/include/pugl/gl.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp
index 023dd45..023dd45 100644
--- a/pugl/bindings/cxx/include/pugl/gl.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/pugl.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp
index fc3bb03..fc3bb03 100644
--- a/pugl/bindings/cxx/include/pugl/pugl.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/stub.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp
index fbafcee..fbafcee 100644
--- a/pugl/bindings/cxx/include/pugl/stub.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp
diff --git a/pugl/bindings/cxx/include/pugl/vulkan.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp
index f3dbcad..f3dbcad 100644
--- a/pugl/bindings/cxx/include/pugl/vulkan.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp
diff --git a/pugl/doc/_static/meson.build b/subprojects/nk_pugl/pugl/doc/_static/meson.build
index fc7792c..fc7792c 100644
--- a/pugl/doc/_static/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/_static/meson.build
diff --git a/pugl/doc/c/Doxyfile.in b/subprojects/nk_pugl/pugl/doc/c/Doxyfile.in
index 96bbf63..96bbf63 100644
--- a/pugl/doc/c/Doxyfile.in
+++ b/subprojects/nk_pugl/pugl/doc/c/Doxyfile.in
diff --git a/pugl/doc/c/api/meson.build b/subprojects/nk_pugl/pugl/doc/c/api/meson.build
index 5c1e30e..5c1e30e 100644
--- a/pugl/doc/c/api/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/c/api/meson.build
diff --git a/pugl/doc/c/event-loop.rst b/subprojects/nk_pugl/pugl/doc/c/event-loop.rst
index 3b9915f..3b9915f 100644
--- a/pugl/doc/c/event-loop.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/event-loop.rst
diff --git a/pugl/doc/c/events.rst b/subprojects/nk_pugl/pugl/doc/c/events.rst
index bf964db..bf964db 100644
--- a/pugl/doc/c/events.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/events.rst
diff --git a/pugl/doc/c/index.rst b/subprojects/nk_pugl/pugl/doc/c/index.rst
index 020cf32..020cf32 100644
--- a/pugl/doc/c/index.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/index.rst
diff --git a/pugl/doc/c/meson.build b/subprojects/nk_pugl/pugl/doc/c/meson.build
index df9363e..df9363e 100644
--- a/pugl/doc/c/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/c/meson.build
diff --git a/pugl/doc/c/overview.rst b/subprojects/nk_pugl/pugl/doc/c/overview.rst
index 4bd024d..4bd024d 100644
--- a/pugl/doc/c/overview.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/overview.rst
diff --git a/pugl/doc/c/shutting-down.rst b/subprojects/nk_pugl/pugl/doc/c/shutting-down.rst
index dfb56cd..dfb56cd 100644
--- a/pugl/doc/c/shutting-down.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/shutting-down.rst
diff --git a/pugl/doc/c/view.rst b/subprojects/nk_pugl/pugl/doc/c/view.rst
index 12f146d..12f146d 100644
--- a/pugl/doc/c/view.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/view.rst
diff --git a/pugl/doc/c/world.rst b/subprojects/nk_pugl/pugl/doc/c/world.rst
index 83d9dbd..83d9dbd 100644
--- a/pugl/doc/c/world.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/world.rst
diff --git a/pugl/doc/c/xml/meson.build b/subprojects/nk_pugl/pugl/doc/c/xml/meson.build
index d79d59a..d79d59a 100644
--- a/pugl/doc/c/xml/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/c/xml/meson.build
diff --git a/pugl/doc/conf.py.in b/subprojects/nk_pugl/pugl/doc/conf.py.in
index 3fa8ea2..3fa8ea2 100644
--- a/pugl/doc/conf.py.in
+++ b/subprojects/nk_pugl/pugl/doc/conf.py.in
diff --git a/pugl/doc/cpp/Doxyfile.in b/subprojects/nk_pugl/pugl/doc/cpp/Doxyfile.in
index 889ac0b..889ac0b 100644
--- a/pugl/doc/cpp/Doxyfile.in
+++ b/subprojects/nk_pugl/pugl/doc/cpp/Doxyfile.in
diff --git a/pugl/doc/cpp/api/meson.build b/subprojects/nk_pugl/pugl/doc/cpp/api/meson.build
index 4bbbec2..4bbbec2 100644
--- a/pugl/doc/cpp/api/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/cpp/api/meson.build
diff --git a/pugl/doc/cpp/event-loop.rst b/subprojects/nk_pugl/pugl/doc/cpp/event-loop.rst
index 1d2ac41..1d2ac41 100644
--- a/pugl/doc/cpp/event-loop.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/event-loop.rst
diff --git a/pugl/doc/cpp/events.rst b/subprojects/nk_pugl/pugl/doc/cpp/events.rst
index 72c396c..72c396c 100644
--- a/pugl/doc/cpp/events.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/events.rst
diff --git a/pugl/doc/cpp/index.rst b/subprojects/nk_pugl/pugl/doc/cpp/index.rst
index b11d028..b11d028 100644
--- a/pugl/doc/cpp/index.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/index.rst
diff --git a/pugl/doc/cpp/meson.build b/subprojects/nk_pugl/pugl/doc/cpp/meson.build
index d8bae11..d8bae11 100644
--- a/pugl/doc/cpp/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/cpp/meson.build
diff --git a/pugl/doc/cpp/overview.rst b/subprojects/nk_pugl/pugl/doc/cpp/overview.rst
index 1928fba..1928fba 100644
--- a/pugl/doc/cpp/overview.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/overview.rst
diff --git a/pugl/doc/cpp/view.rst b/subprojects/nk_pugl/pugl/doc/cpp/view.rst
index 3f5aee8..3f5aee8 100644
--- a/pugl/doc/cpp/view.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/view.rst
diff --git a/pugl/doc/cpp/world.rst b/subprojects/nk_pugl/pugl/doc/cpp/world.rst
index 1a3b432..1a3b432 100644
--- a/pugl/doc/cpp/world.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/world.rst
diff --git a/pugl/doc/cpp/xml/meson.build b/subprojects/nk_pugl/pugl/doc/cpp/xml/meson.build
index 3f87f2a..3f87f2a 100644
--- a/pugl/doc/cpp/xml/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/cpp/xml/meson.build
diff --git a/pugl/doc/deployment.rst b/subprojects/nk_pugl/pugl/doc/deployment.rst
index 4afc51a..4afc51a 100644
--- a/pugl/doc/deployment.rst
+++ b/subprojects/nk_pugl/pugl/doc/deployment.rst
diff --git a/pugl/doc/meson.build b/subprojects/nk_pugl/pugl/doc/meson.build
index 583f09d..583f09d 100644
--- a/pugl/doc/meson.build
+++ b/subprojects/nk_pugl/pugl/doc/meson.build
diff --git a/pugl/doc/summary.rst b/subprojects/nk_pugl/pugl/doc/summary.rst
index f05515f..f05515f 100644
--- a/pugl/doc/summary.rst
+++ b/subprojects/nk_pugl/pugl/doc/summary.rst
diff --git a/pugl/examples/.clang-tidy b/subprojects/nk_pugl/pugl/examples/.clang-tidy
index fdfa4ea..fdfa4ea 100644
--- a/pugl/examples/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/examples/.clang-tidy
diff --git a/pugl/examples/cube_view.h b/subprojects/nk_pugl/pugl/examples/cube_view.h
index 71ae88d..71ae88d 100644
--- a/pugl/examples/cube_view.h
+++ b/subprojects/nk_pugl/pugl/examples/cube_view.h
diff --git a/pugl/examples/demo_utils.h b/subprojects/nk_pugl/pugl/examples/demo_utils.h
index 2dda756..2dda756 100644
--- a/pugl/examples/demo_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/demo_utils.h
diff --git a/pugl/examples/file_utils.c b/subprojects/nk_pugl/pugl/examples/file_utils.c
index 8ecbca4..8ecbca4 100644
--- a/pugl/examples/file_utils.c
+++ b/subprojects/nk_pugl/pugl/examples/file_utils.c
diff --git a/pugl/examples/file_utils.h b/subprojects/nk_pugl/pugl/examples/file_utils.h
index 1530157..1530157 100644
--- a/pugl/examples/file_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/file_utils.h
diff --git a/pugl/examples/glad/glad.c b/subprojects/nk_pugl/pugl/examples/glad/glad.c
index 38f442c..38f442c 100644
--- a/pugl/examples/glad/glad.c
+++ b/subprojects/nk_pugl/pugl/examples/glad/glad.c
diff --git a/pugl/examples/glad/glad.h b/subprojects/nk_pugl/pugl/examples/glad/glad.h
index 9efb229..9efb229 100644
--- a/pugl/examples/glad/glad.h
+++ b/subprojects/nk_pugl/pugl/examples/glad/glad.h
diff --git a/pugl/examples/glad/khrplatform.h b/subprojects/nk_pugl/pugl/examples/glad/khrplatform.h
index 5b55ea2..5b55ea2 100644
--- a/pugl/examples/glad/khrplatform.h
+++ b/subprojects/nk_pugl/pugl/examples/glad/khrplatform.h
diff --git a/pugl/examples/meson.build b/subprojects/nk_pugl/pugl/examples/meson.build
index d455faf..d455faf 100644
--- a/pugl/examples/meson.build
+++ b/subprojects/nk_pugl/pugl/examples/meson.build
diff --git a/pugl/examples/pugl_cairo_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c
index 67bc13c..67bc13c 100644
--- a/pugl/examples/pugl_cairo_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c
diff --git a/pugl/examples/pugl_cursor_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c
index 97e3b9f..97e3b9f 100644
--- a/pugl/examples/pugl_cursor_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c
diff --git a/pugl/examples/pugl_cxx_demo.cpp b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp
index d663a3f..d663a3f 100644
--- a/pugl/examples/pugl_cxx_demo.cpp
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp
diff --git a/pugl/examples/pugl_embed_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c
index 0e12ddb..0e12ddb 100644
--- a/pugl/examples/pugl_embed_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c
diff --git a/pugl/examples/pugl_print_events.c b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c
index dfa217e..dfa217e 100644
--- a/pugl/examples/pugl_print_events.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c
diff --git a/pugl/examples/pugl_shader_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c
index aa5c38e..aa5c38e 100644
--- a/pugl/examples/pugl_shader_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c
diff --git a/pugl/examples/pugl_vulkan_cxx_demo.cpp b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp
index d92e652..d92e652 100644
--- a/pugl/examples/pugl_vulkan_cxx_demo.cpp
+++ b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp
diff --git a/pugl/examples/pugl_vulkan_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c
index 0dfbadd..0dfbadd 100644
--- a/pugl/examples/pugl_vulkan_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c
diff --git a/pugl/examples/pugl_window_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c
index f7d5b2c..f7d5b2c 100644
--- a/pugl/examples/pugl_window_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c
diff --git a/pugl/examples/rects.h b/subprojects/nk_pugl/pugl/examples/rects.h
index b99d9f0..b99d9f0 100644
--- a/pugl/examples/rects.h
+++ b/subprojects/nk_pugl/pugl/examples/rects.h
diff --git a/pugl/examples/shader_utils.h b/subprojects/nk_pugl/pugl/examples/shader_utils.h
index 2575f47..2575f47 100644
--- a/pugl/examples/shader_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/shader_utils.h
diff --git a/pugl/examples/shaders/header_330.glsl b/subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl
index 59d5f6f..59d5f6f 100644
--- a/pugl/examples/shaders/header_330.glsl
+++ b/subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl
diff --git a/pugl/examples/shaders/header_420.glsl b/subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl
index 2beaad0..2beaad0 100644
--- a/pugl/examples/shaders/header_420.glsl
+++ b/subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl
diff --git a/pugl/examples/shaders/meson.build b/subprojects/nk_pugl/pugl/examples/shaders/meson.build
index e47be9d..e47be9d 100644
--- a/pugl/examples/shaders/meson.build
+++ b/subprojects/nk_pugl/pugl/examples/shaders/meson.build
diff --git a/pugl/examples/shaders/rect.frag b/subprojects/nk_pugl/pugl/examples/shaders/rect.frag
index 33bfbb2..33bfbb2 100644
--- a/pugl/examples/shaders/rect.frag
+++ b/subprojects/nk_pugl/pugl/examples/shaders/rect.frag
diff --git a/pugl/examples/shaders/rect.vert b/subprojects/nk_pugl/pugl/examples/shaders/rect.vert
index 2c7b5f1..2c7b5f1 100644
--- a/pugl/examples/shaders/rect.vert
+++ b/subprojects/nk_pugl/pugl/examples/shaders/rect.vert
diff --git a/pugl/examples/sybok.hpp b/subprojects/nk_pugl/pugl/examples/sybok.hpp
index 7740824..7740824 100644
--- a/pugl/examples/sybok.hpp
+++ b/subprojects/nk_pugl/pugl/examples/sybok.hpp
diff --git a/pugl/include/.clang-tidy b/subprojects/nk_pugl/pugl/include/.clang-tidy
index dd2fd47..dd2fd47 100644
--- a/pugl/include/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/include/.clang-tidy
diff --git a/pugl/include/pugl/cairo.h b/subprojects/nk_pugl/pugl/include/pugl/cairo.h
index 48e868e..48e868e 100644
--- a/pugl/include/pugl/cairo.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/cairo.h
diff --git a/pugl/include/pugl/gl.h b/subprojects/nk_pugl/pugl/include/pugl/gl.h
index 51c4a7d..51c4a7d 100644
--- a/pugl/include/pugl/gl.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/gl.h
diff --git a/pugl/include/pugl/pugl.h b/subprojects/nk_pugl/pugl/include/pugl/pugl.h
index cd77334..cd77334 100644
--- a/pugl/include/pugl/pugl.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/pugl.h
diff --git a/pugl/include/pugl/stub.h b/subprojects/nk_pugl/pugl/include/pugl/stub.h
index d1a699a..d1a699a 100644
--- a/pugl/include/pugl/stub.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/stub.h
diff --git a/pugl/include/pugl/vulkan.h b/subprojects/nk_pugl/pugl/include/pugl/vulkan.h
index f12ad97..f12ad97 100644
--- a/pugl/include/pugl/vulkan.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/vulkan.h
diff --git a/pugl/meson.build b/subprojects/nk_pugl/pugl/meson.build
index 02fae17..02fae17 100644
--- a/pugl/meson.build
+++ b/subprojects/nk_pugl/pugl/meson.build
diff --git a/pugl/meson/meson.build b/subprojects/nk_pugl/pugl/meson/meson.build
index 20e0522..20e0522 100644
--- a/pugl/meson/meson.build
+++ b/subprojects/nk_pugl/pugl/meson/meson.build
diff --git a/pugl/meson_options.txt b/subprojects/nk_pugl/pugl/meson_options.txt
index dd6ea8c..dd6ea8c 100644
--- a/pugl/meson_options.txt
+++ b/subprojects/nk_pugl/pugl/meson_options.txt
diff --git a/pugl/pugl.pc.in b/subprojects/nk_pugl/pugl/pugl.pc.in
index 2cfa644..2cfa644 100644
--- a/pugl/pugl.pc.in
+++ b/subprojects/nk_pugl/pugl/pugl.pc.in
diff --git a/pugl/resources/Info.plist.in b/subprojects/nk_pugl/pugl/resources/Info.plist.in
index a08dbd0..a08dbd0 100644
--- a/pugl/resources/Info.plist.in
+++ b/subprojects/nk_pugl/pugl/resources/Info.plist.in
diff --git a/pugl/resources/pugl.ipe b/subprojects/nk_pugl/pugl/resources/pugl.ipe
index 238c09c..238c09c 100644
--- a/pugl/resources/pugl.ipe
+++ b/subprojects/nk_pugl/pugl/resources/pugl.ipe
diff --git a/pugl/resources/pugl.png b/subprojects/nk_pugl/pugl/resources/pugl.png
index 4641660..4641660 100644
--- a/pugl/resources/pugl.png
+++ b/subprojects/nk_pugl/pugl/resources/pugl.png
Binary files differ
diff --git a/pugl/resources/pugl.svg b/subprojects/nk_pugl/pugl/resources/pugl.svg
index 93189f4..93189f4 100644
--- a/pugl/resources/pugl.svg
+++ b/subprojects/nk_pugl/pugl/resources/pugl.svg
diff --git a/pugl/scripts/cat.py b/subprojects/nk_pugl/pugl/scripts/cat.py
index 5f628b6..5f628b6 100755
--- a/pugl/scripts/cat.py
+++ b/subprojects/nk_pugl/pugl/scripts/cat.py
diff --git a/pugl/scripts/dox_to_sphinx.py b/subprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py
index c3f7c44..c3f7c44 100755
--- a/pugl/scripts/dox_to_sphinx.py
+++ b/subprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py
diff --git a/pugl/src/.clang-tidy b/subprojects/nk_pugl/pugl/src/.clang-tidy
index 11b620e..11b620e 100644
--- a/pugl/src/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/src/.clang-tidy
diff --git a/pugl/src/implementation.c b/subprojects/nk_pugl/pugl/src/implementation.c
index 47b52b8..47b52b8 100644
--- a/pugl/src/implementation.c
+++ b/subprojects/nk_pugl/pugl/src/implementation.c
diff --git a/pugl/src/implementation.h b/subprojects/nk_pugl/pugl/src/implementation.h
index 8c5398c..8c5398c 100644
--- a/pugl/src/implementation.h
+++ b/subprojects/nk_pugl/pugl/src/implementation.h
diff --git a/pugl/src/mac.h b/subprojects/nk_pugl/pugl/src/mac.h
index 35e6e0d..35e6e0d 100644
--- a/pugl/src/mac.h
+++ b/subprojects/nk_pugl/pugl/src/mac.h
diff --git a/pugl/src/mac.m b/subprojects/nk_pugl/pugl/src/mac.m
index 58f16c7..58f16c7 100644
--- a/pugl/src/mac.m
+++ b/subprojects/nk_pugl/pugl/src/mac.m
diff --git a/pugl/src/mac_cairo.m b/subprojects/nk_pugl/pugl/src/mac_cairo.m
index 1c564a0..1c564a0 100644
--- a/pugl/src/mac_cairo.m
+++ b/subprojects/nk_pugl/pugl/src/mac_cairo.m
diff --git a/pugl/src/mac_gl.m b/subprojects/nk_pugl/pugl/src/mac_gl.m
index dd06cc0..dd06cc0 100644
--- a/pugl/src/mac_gl.m
+++ b/subprojects/nk_pugl/pugl/src/mac_gl.m
diff --git a/pugl/src/mac_stub.m b/subprojects/nk_pugl/pugl/src/mac_stub.m
index ac7bfcc..ac7bfcc 100644
--- a/pugl/src/mac_stub.m
+++ b/subprojects/nk_pugl/pugl/src/mac_stub.m
diff --git a/pugl/src/mac_vulkan.m b/subprojects/nk_pugl/pugl/src/mac_vulkan.m
index 22fff10..22fff10 100644
--- a/pugl/src/mac_vulkan.m
+++ b/subprojects/nk_pugl/pugl/src/mac_vulkan.m
diff --git a/pugl/src/stub.h b/subprojects/nk_pugl/pugl/src/stub.h
index c816679..c816679 100644
--- a/pugl/src/stub.h
+++ b/subprojects/nk_pugl/pugl/src/stub.h
diff --git a/pugl/src/types.h b/subprojects/nk_pugl/pugl/src/types.h
index 6fa658f..6fa658f 100644
--- a/pugl/src/types.h
+++ b/subprojects/nk_pugl/pugl/src/types.h
diff --git a/pugl/src/win.c b/subprojects/nk_pugl/pugl/src/win.c
index 1e11520..1e11520 100644
--- a/pugl/src/win.c
+++ b/subprojects/nk_pugl/pugl/src/win.c
diff --git a/pugl/src/win.h b/subprojects/nk_pugl/pugl/src/win.h
index ccab36a..ccab36a 100644
--- a/pugl/src/win.h
+++ b/subprojects/nk_pugl/pugl/src/win.h
diff --git a/pugl/src/win_cairo.c b/subprojects/nk_pugl/pugl/src/win_cairo.c
index 9dc5ce0..9dc5ce0 100644
--- a/pugl/src/win_cairo.c
+++ b/subprojects/nk_pugl/pugl/src/win_cairo.c
diff --git a/pugl/src/win_gl.c b/subprojects/nk_pugl/pugl/src/win_gl.c
index 4abd5ab..4abd5ab 100644
--- a/pugl/src/win_gl.c
+++ b/subprojects/nk_pugl/pugl/src/win_gl.c
diff --git a/pugl/src/win_stub.c b/subprojects/nk_pugl/pugl/src/win_stub.c
index cf86390..cf86390 100644
--- a/pugl/src/win_stub.c
+++ b/subprojects/nk_pugl/pugl/src/win_stub.c
diff --git a/pugl/src/win_vulkan.c b/subprojects/nk_pugl/pugl/src/win_vulkan.c
index a892a16..a892a16 100644
--- a/pugl/src/win_vulkan.c
+++ b/subprojects/nk_pugl/pugl/src/win_vulkan.c
diff --git a/pugl/src/x11.c b/subprojects/nk_pugl/pugl/src/x11.c
index 2cdb0f3..2cdb0f3 100644
--- a/pugl/src/x11.c
+++ b/subprojects/nk_pugl/pugl/src/x11.c
diff --git a/pugl/src/x11.h b/subprojects/nk_pugl/pugl/src/x11.h
index 778e5ec..778e5ec 100644
--- a/pugl/src/x11.h
+++ b/subprojects/nk_pugl/pugl/src/x11.h
diff --git a/pugl/src/x11_cairo.c b/subprojects/nk_pugl/pugl/src/x11_cairo.c
index a0e7d08..a0e7d08 100644
--- a/pugl/src/x11_cairo.c
+++ b/subprojects/nk_pugl/pugl/src/x11_cairo.c
diff --git a/pugl/src/x11_gl.c b/subprojects/nk_pugl/pugl/src/x11_gl.c
index 34152de..34152de 100644
--- a/pugl/src/x11_gl.c
+++ b/subprojects/nk_pugl/pugl/src/x11_gl.c
diff --git a/pugl/src/x11_stub.c b/subprojects/nk_pugl/pugl/src/x11_stub.c
index de89a86..de89a86 100644
--- a/pugl/src/x11_stub.c
+++ b/subprojects/nk_pugl/pugl/src/x11_stub.c
diff --git a/pugl/src/x11_vulkan.c b/subprojects/nk_pugl/pugl/src/x11_vulkan.c
index 1ff5759..1ff5759 100644
--- a/pugl/src/x11_vulkan.c
+++ b/subprojects/nk_pugl/pugl/src/x11_vulkan.c
diff --git a/pugl/test/.clang-tidy b/subprojects/nk_pugl/pugl/test/.clang-tidy
index 8014e6a..8014e6a 100644
--- a/pugl/test/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/test/.clang-tidy
diff --git a/pugl/test/meson.build b/subprojects/nk_pugl/pugl/test/meson.build
index 340a7dd..340a7dd 100644
--- a/pugl/test/meson.build
+++ b/subprojects/nk_pugl/pugl/test/meson.build
diff --git a/pugl/test/test_build.c b/subprojects/nk_pugl/pugl/test/test_build.c
index 957e0bd..957e0bd 100644
--- a/pugl/test/test_build.c
+++ b/subprojects/nk_pugl/pugl/test/test_build.c
diff --git a/pugl/test/test_build.cpp b/subprojects/nk_pugl/pugl/test/test_build.cpp
index 20235e8..20235e8 100644
--- a/pugl/test/test_build.cpp
+++ b/subprojects/nk_pugl/pugl/test/test_build.cpp
diff --git a/pugl/test/test_clipboard.c b/subprojects/nk_pugl/pugl/test/test_clipboard.c
index a458d00..a458d00 100644
--- a/pugl/test/test_clipboard.c
+++ b/subprojects/nk_pugl/pugl/test/test_clipboard.c
diff --git a/pugl/test/test_gl_hints.c b/subprojects/nk_pugl/pugl/test/test_gl_hints.c
index 3be3651..3be3651 100644
--- a/pugl/test/test_gl_hints.c
+++ b/subprojects/nk_pugl/pugl/test/test_gl_hints.c
diff --git a/pugl/test/test_realize.c b/subprojects/nk_pugl/pugl/test/test_realize.c
index 3c4e09a..3c4e09a 100644
--- a/pugl/test/test_realize.c
+++ b/subprojects/nk_pugl/pugl/test/test_realize.c
diff --git a/pugl/test/test_redisplay.c b/subprojects/nk_pugl/pugl/test/test_redisplay.c
index c5b9887..c5b9887 100644
--- a/pugl/test/test_redisplay.c
+++ b/subprojects/nk_pugl/pugl/test/test_redisplay.c
diff --git a/pugl/test/test_show_hide.c b/subprojects/nk_pugl/pugl/test/test_show_hide.c
index 584448c..584448c 100644
--- a/pugl/test/test_show_hide.c
+++ b/subprojects/nk_pugl/pugl/test/test_show_hide.c
diff --git a/pugl/test/test_stub_hints.c b/subprojects/nk_pugl/pugl/test/test_stub_hints.c
index 1cc1180..1cc1180 100644
--- a/pugl/test/test_stub_hints.c
+++ b/subprojects/nk_pugl/pugl/test/test_stub_hints.c
diff --git a/pugl/test/test_timer.c b/subprojects/nk_pugl/pugl/test/test_timer.c
index 200ddc2..200ddc2 100644
--- a/pugl/test/test_timer.c
+++ b/subprojects/nk_pugl/pugl/test/test_timer.c
diff --git a/pugl/test/test_update.c b/subprojects/nk_pugl/pugl/test/test_update.c
index 89de0b6..89de0b6 100644
--- a/pugl/test/test_update.c
+++ b/subprojects/nk_pugl/pugl/test/test_update.c
diff --git a/pugl/test/test_utils.h b/subprojects/nk_pugl/pugl/test/test_utils.h
index 2464737..2464737 100644
--- a/pugl/test/test_utils.h
+++ b/subprojects/nk_pugl/pugl/test/test_utils.h
diff --git a/timely.lv2/.gitlab-ci.yml b/timely.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..979769c
--- /dev/null
+++ b/timely.lv2/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/timely.lv2/COPYING b/timely.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/timely.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/timely.lv2/README.md b/timely.lv2/README.md
new file mode 100644
index 0000000..c422cf9
--- /dev/null
+++ b/timely.lv2/README.md
@@ -0,0 +1,20 @@
+# Timely.lv2
+
+## Utility header for time-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/timely.lv2/VERSION b/timely.lv2/VERSION
new file mode 100644
index 0000000..a2d633d
--- /dev/null
+++ b/timely.lv2/VERSION
@@ -0,0 +1 @@
+0.1.61
diff --git a/timely.lv2/gitlab-ci/generic.yml b/timely.lv2/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/timely.lv2/gitlab-ci/generic.yml
@@ -0,0 +1,106 @@
+stages:
+ - build
+ - deploy
+
+variables:
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+ BUILD_OPTS : ""
+
+.native_template: &native_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+
+ - scan-build --status-bugs meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+ - scan-build --status-bugs ninja -C scanbuild test
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+.cross_template: &cross_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+# build
+.universal_linux_template_stretch: &universal_linux_definition_stretch
+ image: ventosus/universal-linux-gnu:stretch
+ <<: *cross_definition
+
+.universal_linux_template_buster: &universal_linux_definition_buster
+ image: ventosus/universal-linux-gnu:buster
+ <<: *native_definition
+
+.universal_linux_template_bullseye: &universal_linux_definition_bullseye
+ image: ventosus/universal-linux-gnu:bullseye
+ <<: *native_definition
+
+.arm_linux_template_stretch: &arm_linux_definition_stretch
+ image: ventosus/arm-linux-gnueabihf:stretch
+ <<: *cross_definition
+
+.arm_linux_template_buster: &arm_linux_definition_buster
+ image: ventosus/arm-linux-gnueabihf:buster
+ <<: *cross_definition
+
+.arm_linux_template_bullseye: &arm_linux_definition_bullseye
+ image: ventosus/arm-linux-gnueabihf:bullseye
+ <<: *cross_definition
+
+# build
+x86_64-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+x86_64-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+x86_64-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+i686-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+i686-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+i686-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+arm-linux-gnueabihf-stretch:
+ <<: *arm_linux_definition_stretch
+
+arm-linux-gnueabihf-buster:
+ <<: *arm_linux_definition_buster
+
+arm-linux-gnueabihf-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+aarch64-linux-gnu-stretch:
+ <<: *arm_linux_definition_stretch
+
+aarch64-linux-gnu-buster:
+ <<: *arm_linux_definition_buster
+
+aarch64-linux-gnu-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+pack:
+ stage: deploy
+ script:
+ - echo 'packing up'
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/"
diff --git a/timely.lv2/meson.build b/timely.lv2/meson.build
new file mode 100644
index 0000000..df8d684
--- /dev/null
+++ b/timely.lv2/meson.build
@@ -0,0 +1,67 @@
+project('timely.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')
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+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', 'timely.c')]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+mod = shared_module('timely', 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)
+
+manifest_ttl = configure_file(input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+dsp_ttl = custom_target('timely_ttl',
+ input : join_paths('test', 'timely.ttl'),
+ output : 'timely.ttl',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl])
+endif
+
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-Ewarn',
+ 'http://open-music-kontrollers.ch/lv2/timely#test'])
+endif
diff --git a/timely.lv2/test/manifest.ttl.in b/timely.lv2/test/manifest.ttl.in
new file mode 100644
index 0000000..e55fd39
--- /dev/null
+++ b/timely.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 timely: <http://open-music-kontrollers.ch/lv2/timely#> .
+
+# Orbit Looper
+timely:test
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <timely@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <timely.ttl> .
diff --git a/timely.lv2/test/timely.c b/timely.lv2/test/timely.c
new file mode 100644
index 0000000..ba2d63a
--- /dev/null
+++ b/timely.lv2/test/timely.c
@@ -0,0 +1,218 @@
+/*
+ * 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 <stdlib.h>
+#include <inttypes.h>
+
+#include <timely.h>
+
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+#include <lv2/lv2plug.in/ns/ext/log/logger.h>
+
+#define TIMELY_PREFIX "http://open-music-kontrollers.ch/lv2/timely#"
+#define TIMELY_TEST_URI TIMELY_PREFIX"test"
+
+typedef struct _plughandle_t plughandle_t;
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+ timely_t timely;
+
+
+ const LV2_Atom_Sequence *event_in;
+};
+
+static void
+_timely_cb(timely_t *timely, int64_t frames, LV2_URID type, void *data)
+{
+ plughandle_t *handle = data;
+
+ const int64_t frame = TIMELY_FRAME(timely);
+
+ if(type == TIMELY_URI_BAR_BEAT(timely))
+ {
+ const float bar_beat = TIMELY_BAR_BEAT_RAW(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:barBeat %f\n",
+ frame, frames, bar_beat);
+ }
+ else if(type == TIMELY_URI_BAR(timely))
+ {
+ const int64_t bar = TIMELY_BAR(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:bar %"PRIi64"\n",
+ frame, frames, bar);
+ }
+ else if(type == TIMELY_URI_BEAT_UNIT(timely))
+ {
+ const int32_t beat_unit = TIMELY_BEAT_UNIT(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:beatUnit %"PRIi32"\n",
+ frame, frames, beat_unit);
+ }
+ else if(type == TIMELY_URI_BEATS_PER_BAR(timely))
+ {
+ const float bpb = TIMELY_BEATS_PER_BAR(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:beatsPerBar %f\n",
+ frame, frames, bpb);
+ }
+ else if(type == TIMELY_URI_BEATS_PER_MINUTE(timely))
+ {
+ const float bpm = TIMELY_BEATS_PER_MINUTE(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:beatsPerMinute %f\n",
+ frame, frames, bpm);
+ }
+ else if(type == TIMELY_URI_FRAME(timely))
+ {
+ /*
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:frame %"PRIi64"\n",
+ frame, frames, frame);
+ */
+ }
+ else if(type == TIMELY_URI_FRAMES_PER_SECOND(timely))
+ {
+ const float fps = TIMELY_FRAMES_PER_SECOND(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:framesPerSecond %f\n",
+ frame, frames, fps);
+ }
+ else if(type == TIMELY_URI_SPEED(timely))
+ {
+ const float speed = TIMELY_SPEED(timely);
+ lv2_log_trace(&handle->logger, "0x%08"PRIx64" %4"PRIi64" time:speed %f\n",
+ frame, frames, speed);
+ }
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ 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);
+
+ timely_mask_t mask = TIMELY_MASK_BAR_BEAT
+ | TIMELY_MASK_BAR
+ | TIMELY_MASK_BEAT_UNIT
+ | TIMELY_MASK_BEATS_PER_BAR
+ | TIMELY_MASK_BEATS_PER_MINUTE
+ //| TIMELY_MASK_FRAME
+ | TIMELY_MASK_FRAMES_PER_SECOND
+ | TIMELY_MASK_SPEED
+ | TIMELY_MASK_BAR_BEAT_WHOLE
+ | TIMELY_MASK_BAR_WHOLE;
+ timely_init(&handle->timely, handle->map, rate, mask, _timely_cb, handle);
+ timely_set_multiplier(&handle->timely, 1.f);
+
+ 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;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+ int64_t from = 0;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const int64_t to = ev->time.frames;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ const int handled = timely_advance(&handle->timely, obj, from, to);
+ (void)handled;
+ from = to;
+ }
+
+ timely_advance(&handle->timely, NULL, from, nsamples);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ free(handle);
+}
+
+const LV2_Descriptor timely_test = {
+ .URI = TIMELY_TEST_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &timely_test;
+ default:
+ return NULL;
+ }
+}
diff --git a/timely.lv2/test/timely.ttl b/timely.lv2/test/timely.ttl
new file mode 100644
index 0000000..6064c9c
--- /dev/null
+++ b/timely.lv2/test/timely.ttl
@@ -0,0 +1,65 @@
+# 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 time: <http://lv2plug.in/ns/ext/time#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix timely: <http://open-music-kontrollers.ch/lv2/timely#> .
+
+# 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:timely
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Timely Bundle" .
+
+# Looper Test
+timely:test
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Timely Test" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:timely ;
+ lv2:requiredFeature urid:map, log:log ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] .
diff --git a/timely.lv2/timely.h b/timely.lv2/timely.h
new file mode 100644
index 0000000..ed9497b
--- /dev/null
+++ b/timely.lv2/timely.h
@@ -0,0 +1,404 @@
+/*
+ * 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_TIMELY_H_
+#define _LV2_TIMELY_H_
+
+#include <math.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/time/time.h>
+
+typedef struct _timely_t timely_t;
+typedef void (*timely_cb_t)(timely_t *timely, int64_t frames, LV2_URID type,
+ void *data);
+
+typedef enum _timely_mask_t {
+ TIMELY_MASK_BAR_BEAT = (1 << 0),
+ TIMELY_MASK_BAR = (1 << 1),
+ TIMELY_MASK_BEAT_UNIT = (1 << 2),
+ TIMELY_MASK_BEATS_PER_BAR = (1 << 3),
+ TIMELY_MASK_BEATS_PER_MINUTE = (1 << 4),
+ TIMELY_MASK_FRAME = (1 << 5),
+ TIMELY_MASK_FRAMES_PER_SECOND = (1 << 6),
+ TIMELY_MASK_SPEED = (1 << 7),
+ TIMELY_MASK_BAR_BEAT_WHOLE = (1 << 8),
+ TIMELY_MASK_BAR_WHOLE = (1 << 9)
+} timely_mask_t;
+
+struct _timely_t {
+ struct {
+ LV2_URID atom_object;
+ LV2_URID atom_blank;
+ LV2_URID atom_resource;
+
+ LV2_URID time_position;
+ LV2_URID time_barBeat;
+ LV2_URID time_bar;
+ LV2_URID time_beatUnit;
+ LV2_URID time_beatsPerBar;
+ LV2_URID time_beatsPerMinute;
+ LV2_URID time_frame;
+ LV2_URID time_framesPerSecond;
+ LV2_URID time_speed;
+ } urid;
+
+ struct {
+ float bar_beat;
+ int64_t bar;
+
+ int32_t beat_unit;
+ float beats_per_bar;
+ float beats_per_minute;
+
+ int64_t frame;
+ float frames_per_second;
+
+ float speed;
+ } pos;
+
+ float multiplier;
+
+ double frames_per_beat;
+ double frames_per_bar;
+
+ struct {
+ double beat;
+ double bar;
+ } offset;
+
+ bool first;
+ timely_mask_t mask;
+ timely_cb_t cb;
+ void *data;
+};
+
+#define TIMELY_URI_BAR_BEAT(timely) ((timely)->urid.time_barBeat)
+#define TIMELY_URI_BAR(timely) ((timely)->urid.time_bar)
+#define TIMELY_URI_BEAT_UNIT(timely) ((timely)->urid.time_beatUnit)
+#define TIMELY_URI_BEATS_PER_BAR(timely) ((timely)->urid.time_beatsPerBar)
+#define TIMELY_URI_BEATS_PER_MINUTE(timely) ((timely)->urid.time_beatsPerMinute)
+#define TIMELY_URI_FRAME(timely) ((timely)->urid.time_frame)
+#define TIMELY_URI_FRAMES_PER_SECOND(timely) ((timely)->urid.time_framesPerSecond)
+#define TIMELY_URI_SPEED(timely) ((timely)->urid.time_speed)
+
+#define TIMELY_BAR_BEAT_RAW(timely) ((timely)->pos.bar_beat)
+#define TIMELY_BAR_BEAT(timely) (floor((timely)->pos.bar_beat) \
+ + (timely)->offset.beat / (timely)->frames_per_beat)
+#define TIMELY_BAR(timely) ((timely)->pos.bar)
+#define TIMELY_BEAT_UNIT(timely) ((timely)->pos.beat_unit)
+#define TIMELY_BEATS_PER_BAR(timely) ((timely)->pos.beats_per_bar)
+#define TIMELY_BEATS_PER_MINUTE(timely) ((timely)->pos.beats_per_minute)
+#define TIMELY_FRAME(timely) ((timely)->pos.frame)
+#define TIMELY_FRAMES_PER_SECOND(timely) ((timely)->pos.frames_per_second)
+#define TIMELY_SPEED(timely) ((timely)->pos.speed)
+
+#define TIMELY_FRAMES_PER_BEAT(timely) ((timely)->frames_per_beat)
+#define TIMELY_FRAMES_PER_BAR(timely) ((timely)->frames_per_bar)
+
+static inline void
+_timely_deatomize_body(timely_t *timely, int64_t frames, uint32_t size,
+ const LV2_Atom_Object_Body *body)
+{
+ const LV2_Atom_Float *bar_beat = NULL;
+ const LV2_Atom_Long *bar = NULL;
+ const LV2_Atom_Int *beat_unit = NULL;
+ const LV2_Atom_Float *beats_per_bar = NULL;
+ const LV2_Atom_Float *beats_per_minute = NULL;
+ const LV2_Atom_Long *frame = NULL;
+ const LV2_Atom_Float *frames_per_second = NULL;
+ const LV2_Atom_Float *speed = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ timely->urid.time_barBeat, &bar_beat,
+ timely->urid.time_bar, &bar,
+ timely->urid.time_beatUnit, &beat_unit,
+ timely->urid.time_beatsPerBar, &beats_per_bar,
+ timely->urid.time_beatsPerMinute, &beats_per_minute,
+ timely->urid.time_frame, &frame,
+ timely->urid.time_framesPerSecond, &frames_per_second,
+ timely->urid.time_speed, &speed,
+ 0);
+
+ // send speed first upon transport stop
+ if(speed && (speed->body != timely->pos.speed) && (speed->body == 0.f) )
+ {
+ timely->pos.speed = speed->body;
+ if(timely->mask & TIMELY_MASK_SPEED)
+ timely->cb(timely, frames, timely->urid.time_speed, timely->data);
+ }
+
+ if(beat_unit)
+ {
+ const int32_t _beat_unit = beat_unit->body * timely->multiplier;
+ if(_beat_unit != timely->pos.beat_unit)
+ {
+ timely->pos.beat_unit = _beat_unit;
+ if(timely->mask & TIMELY_MASK_BEAT_UNIT)
+ timely->cb(timely, frames, timely->urid.time_beatUnit, timely->data);
+ }
+ }
+
+ if(beats_per_bar)
+ {
+ const float _beats_per_bar = beats_per_bar->body * timely->multiplier;
+ if(_beats_per_bar != timely->pos.beats_per_bar)
+ {
+ timely->pos.beats_per_bar = _beats_per_bar;
+ if(timely->mask & TIMELY_MASK_BEATS_PER_BAR)
+ timely->cb(timely, frames, timely->urid.time_beatsPerBar, timely->data);
+ }
+ }
+
+ if(beats_per_minute && (beats_per_minute->body != timely->pos.beats_per_minute) )
+ {
+ timely->pos.beats_per_minute = beats_per_minute->body;
+ if(timely->mask & TIMELY_MASK_BEATS_PER_MINUTE)
+ timely->cb(timely, frames, timely->urid.time_beatsPerMinute, timely->data);
+ }
+
+ if(frame && (frame->body != timely->pos.frame) )
+ {
+ timely->pos.frame = frame->body;
+ if(timely->mask & TIMELY_MASK_FRAME)
+ timely->cb(timely, frames, timely->urid.time_frame, timely->data);
+ }
+
+ if(frames_per_second && (frames_per_second->body != timely->pos.frames_per_second) )
+ {
+ timely->pos.frames_per_second = frames_per_second->body;
+ if(timely->mask & TIMELY_MASK_FRAMES_PER_SECOND)
+ timely->cb(timely, frames, timely->urid.time_framesPerSecond, timely->data);
+ }
+
+ if(bar && (bar->body != timely->pos.bar) )
+ {
+ timely->pos.bar = bar->body;
+ if(timely->mask & TIMELY_MASK_BAR)
+ timely->cb(timely, frames, timely->urid.time_bar, timely->data);
+ }
+
+ if(bar_beat)
+ {
+ const float _bar_beat = bar_beat->body * timely->multiplier;
+ if(_bar_beat != timely->pos.bar_beat)
+ {
+ timely->pos.bar_beat = _bar_beat;
+ if(timely->mask & TIMELY_MASK_BAR_BEAT)
+ timely->cb(timely, frames, timely->urid.time_barBeat, timely->data);
+ }
+ }
+
+ // send speed last upon transport start
+ if(speed && (speed->body != timely->pos.speed) && (speed->body != 0.f) )
+ {
+ timely->pos.speed = speed->body;
+ if(timely->mask & TIMELY_MASK_SPEED)
+ timely->cb(timely, frames, timely->urid.time_speed, timely->data);
+ }
+}
+
+static inline void
+_timely_refresh(timely_t *timely)
+{
+ const float speed = (timely->pos.speed != 0.f)
+ ? timely->pos.speed
+ : 1.f; // prevent divisions through zero later on
+
+ timely->frames_per_beat = 240.0 * timely->pos.frames_per_second
+ / (timely->pos.beats_per_minute * timely->pos.beat_unit * speed);
+ timely->frames_per_bar = timely->frames_per_beat * timely->pos.beats_per_bar;
+
+ // bar
+ timely->offset.bar = timely->pos.bar_beat * timely->frames_per_beat;
+
+ // beat
+ double integral;
+ double beat_beat = modf(timely->pos.bar_beat, &integral);
+ (void)integral;
+ timely->offset.beat = beat_beat * timely->frames_per_beat;
+}
+
+static inline void
+timely_init(timely_t *timely, LV2_URID_Map *map, double rate,
+ timely_mask_t mask, timely_cb_t cb, void *data)
+{
+ assert(cb != NULL);
+
+ timely->mask = mask;
+ timely->cb = cb;
+ timely->data = data;
+
+ timely->urid.atom_object = map->map(map->handle, LV2_ATOM__Object);
+ timely->urid.atom_blank = map->map(map->handle, LV2_ATOM__Blank);
+ timely->urid.atom_resource = map->map(map->handle, LV2_ATOM__Resource);
+ timely->urid.time_position = map->map(map->handle, LV2_TIME__Position);
+ timely->urid.time_barBeat = map->map(map->handle, LV2_TIME__barBeat);
+ timely->urid.time_bar = map->map(map->handle, LV2_TIME__bar);
+ timely->urid.time_beatUnit = map->map(map->handle, LV2_TIME__beatUnit);
+ timely->urid.time_beatsPerBar = map->map(map->handle, LV2_TIME__beatsPerBar);
+ timely->urid.time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute);
+ timely->urid.time_frame = map->map(map->handle, LV2_TIME__frame);
+ timely->urid.time_framesPerSecond = map->map(map->handle, LV2_TIME__framesPerSecond);
+ timely->urid.time_speed = map->map(map->handle, LV2_TIME__speed);
+
+ timely->multiplier = 1.f;
+
+ timely->pos.speed = 0.f;
+ timely->pos.bar_beat = 0.f;
+ timely->pos.bar = 0;
+ timely->pos.beat_unit = 4;
+ timely->pos.beats_per_bar = 4.f;
+ timely->pos.beats_per_minute = 120.f;
+ timely->pos.frame = 0;
+ timely->pos.frames_per_second = rate;
+
+ _timely_refresh(timely);
+
+ timely->first = true;
+}
+
+static inline void
+timely_set_multiplier(timely_t *timely, float multiplier)
+{
+ const float mul = multiplier / timely->multiplier;
+
+ timely->pos.bar_beat *= mul;
+ timely->pos.beat_unit *= mul;
+ timely->pos.beats_per_bar *= mul;
+
+ timely->multiplier = multiplier;
+
+ _timely_refresh(timely);
+
+ timely->first = true;
+}
+
+static inline int
+timely_advance_body(timely_t *timely, uint32_t size, uint32_t type,
+ const LV2_Atom_Object_Body *body, uint32_t from, uint32_t to)
+{
+ if(timely->first)
+ {
+ timely->first = false;
+
+ // send initial values
+ if(timely->mask & TIMELY_MASK_SPEED)
+ timely->cb(timely, 0, timely->urid.time_speed, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BEAT_UNIT)
+ timely->cb(timely, 0, timely->urid.time_beatUnit, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BEATS_PER_BAR)
+ timely->cb(timely, 0, timely->urid.time_beatsPerBar, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BEATS_PER_MINUTE)
+ timely->cb(timely, 0, timely->urid.time_beatsPerMinute, timely->data);
+
+ if(timely->mask & TIMELY_MASK_FRAME)
+ timely->cb(timely, 0, timely->urid.time_frame, timely->data);
+
+ if(timely->mask & TIMELY_MASK_FRAMES_PER_SECOND)
+ timely->cb(timely, 0, timely->urid.time_framesPerSecond, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BAR)
+ timely->cb(timely, 0, timely->urid.time_bar, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BAR_BEAT)
+ timely->cb(timely, 0, timely->urid.time_barBeat, timely->data);
+ }
+
+ // are we rolling?
+ if(timely->pos.speed != 0.f)
+ {
+ if( (timely->offset.bar == 0) && (timely->pos.bar == 0) )
+ {
+ if(timely->mask & (TIMELY_MASK_BAR | TIMELY_MASK_BAR_WHOLE) )
+ timely->cb(timely, from, timely->urid.time_bar, timely->data);
+ }
+
+ if( (timely->offset.beat == 0) && (timely->pos.bar_beat == 0) )
+ {
+ if(timely->mask & (TIMELY_MASK_BAR_BEAT | TIMELY_MASK_BAR_BEAT_WHOLE) )
+ timely->cb(timely, from, timely->urid.time_barBeat, timely->data);
+ }
+
+ unsigned update_frame = to;
+ for(unsigned i=from; i<to; i++)
+ {
+ if(timely->offset.bar >= timely->frames_per_bar)
+ {
+ timely->pos.bar += 1;
+ timely->offset.bar -= timely->frames_per_bar;
+
+ if(timely->mask & TIMELY_MASK_FRAME)
+ timely->cb(timely, (update_frame = i), timely->urid.time_frame, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BAR_WHOLE)
+ timely->cb(timely, i, timely->urid.time_bar, timely->data);
+ }
+
+ if( (timely->offset.beat >= timely->frames_per_beat) )
+ {
+ timely->pos.bar_beat = floor(timely->pos.bar_beat) + 1;
+ timely->offset.beat -= timely->frames_per_beat;
+
+ if(timely->pos.bar_beat >= timely->pos.beats_per_bar)
+ timely->pos.bar_beat -= timely->pos.beats_per_bar;
+
+ if( (timely->mask & TIMELY_MASK_FRAME) && (update_frame != i) )
+ timely->cb(timely, (update_frame = i), timely->urid.time_frame, timely->data);
+
+ if(timely->mask & TIMELY_MASK_BAR_BEAT_WHOLE)
+ timely->cb(timely, i, timely->urid.time_barBeat, timely->data);
+ }
+
+ timely->offset.bar += 1;
+ timely->offset.beat += 1;
+ timely->pos.frame += 1;
+ }
+ }
+
+ // is this a time position event?
+ if( ( (type == timely->urid.atom_object)
+ || (type == timely->urid.atom_blank)
+ || (type == timely->urid.atom_resource) )
+ && body && (body->otype == timely->urid.time_position) )
+ {
+ _timely_deatomize_body(timely, to, size, body);
+ _timely_refresh(timely);
+
+ return 1; // handled a time position event
+ }
+
+ return 0; // did not handle a time position event
+}
+
+static inline int
+timely_advance(timely_t *timely, const LV2_Atom_Object *obj,
+ uint32_t from, uint32_t to)
+{
+ if(obj)
+ return timely_advance_body(timely, obj->atom.size, obj->atom.type, &obj->body, from, to);
+
+ return timely_advance_body(timely, 0, 0, NULL, from, to);
+}
+
+#endif // _LV2_TIMELY_H_
diff --git a/vm.c b/vm.c
new file mode 100644
index 0000000..66755e7
--- /dev/null
+++ b/vm.c
@@ -0,0 +1,1781 @@
+/*
+ * Copyright (c) 2017-2021 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 <timely.lv2/timely.h>
+
+#include <vm.h>
+
+#define SLOT_MAX 0x20
+#define SLOT_MASK (SLOT_MAX - 1)
+
+#define REG_MAX 0x20
+#define REG_MASK (REG_MAX - 1)
+
+typedef union _vm_port_t vm_port_t;
+typedef union _vm_const_port_t vm_const_port_t;
+typedef struct _vm_stack_t vm_stack_t;
+typedef struct _plughandle_t plughandle_t;
+typedef struct _forge_t forge_t;
+
+typedef double num_t;
+
+union _vm_port_t {
+ float *flt;
+ LV2_Atom_Sequence *seq;
+};
+
+union _vm_const_port_t {
+ const float *flt;
+ const LV2_Atom_Sequence *seq;
+};
+
+struct _vm_stack_t {
+ num_t slots [SLOT_MAX];
+ num_t regs [REG_MAX];
+ int ptr;
+};
+
+struct _forge_t {
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ vm_plug_enum_t vm_plug;
+
+ LV2_URID vm_graph;
+ LV2_URID midi_MidiEvent;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ LV2_Atom_Sequence *notify;
+ vm_const_port_t in [CTRL_MAX];
+ vm_port_t out [CTRL_MAX];
+
+ float in0 [CTRL_MAX];
+ num_t out0 [CTRL_MAX];
+ float inm [CTRL_MAX];
+ float outm [CTRL_MAX];
+ bool inf [CTRL_MAX];
+ bool outf [CTRL_MAX];
+ forge_t forgs [CTRL_MAX];
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ uint32_t graph_size;
+ uint32_t sourceFilter_size;
+ uint32_t destinationFilter_size;
+ vm_api_impl_t api [OP_MAX];
+ vm_filter_t sourceFilter [CTRL_MAX];
+ vm_filter_t destinationFilter [CTRL_MAX];
+ vm_filter_impl_t filt;
+
+ vm_stack_t stack;
+ bool needs_recalc;
+ vm_status_t status;
+
+ int64_t off;
+
+ vm_command_t cmds [ITEMS_MAX];
+
+ timely_t timely;
+};
+
+static inline void
+_stack_clear(vm_stack_t *stack)
+{
+ for(unsigned i = 0; i < SLOT_MAX; i++)
+ stack->slots[i] = 0;
+ stack->ptr = 0;
+}
+
+static inline void
+_stack_push(vm_stack_t *stack, num_t val)
+{
+ stack->ptr = (stack->ptr - 1) & SLOT_MASK;
+
+ stack->slots[stack->ptr] = val;
+}
+
+static inline num_t
+_stack_pop(vm_stack_t *stack)
+{
+ const num_t val = stack->slots[stack->ptr];
+
+ stack->ptr = (stack->ptr + 1) & SLOT_MASK;
+
+ return val;
+}
+
+static inline void
+_stack_push_num(vm_stack_t *stack, const num_t *val, int num)
+{
+ for(int i = 0; i < num; i++)
+ stack->slots[(stack->ptr - i - 1) & SLOT_MASK] = val[i];
+
+ stack->ptr = (stack->ptr - num) & SLOT_MASK;
+}
+
+static inline void
+_stack_pop_num(vm_stack_t *stack, num_t *val, int num)
+{
+ for(int i = 0; i < num; i++)
+ val[i] = stack->slots[(stack->ptr + i) & SLOT_MASK];
+
+ stack->ptr = (stack->ptr + num) & SLOT_MASK;
+}
+
+static inline num_t
+_stack_peek(vm_stack_t *stack)
+{
+ return stack->slots[stack->ptr];
+}
+
+static inline void
+_dirty(plughandle_t *handle)
+{
+ // sync input and output to UI
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ handle->inf[i] = true;
+ handle->outf[i] = true;
+ }
+}
+
+static void
+_intercept_graph(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->graph_size = impl->value.size;
+
+ handle->status = vm_graph_deserialize(handle->api, &handle->forge, handle->cmds,
+ impl->value.size, impl->value.body);
+
+ handle->needs_recalc = true;
+ _dirty(handle);
+}
+
+static void
+_intercept_sourceFilter(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->sourceFilter_size = impl->value.size;
+
+ const int status = vm_filter_deserialize(&handle->forge, &handle->filt,
+ handle->sourceFilter, impl->value.size, impl->value.body);
+ (void)status; //FIXME
+
+ handle->needs_recalc = true;
+ _dirty(handle);
+}
+
+static void
+_intercept_destinationFilter(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->destinationFilter_size = impl->value.size;
+
+ const int status = vm_filter_deserialize(&handle->forge, &handle->filt,
+ handle->destinationFilter, impl->value.size, impl->value.body);
+ (void)status; //FIXME
+
+ handle->needs_recalc = true;
+ _dirty(handle);
+}
+
+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_cb = _intercept_graph,
+ },
+ {
+ .property = VM__sourceFilter,
+ .offset = offsetof(plugstate_t, sourceFilter),
+ .type = LV2_ATOM__Tuple,
+ .max_size = FILTER_SIZE,
+ .event_cb = _intercept_sourceFilter,
+ },
+ {
+ .property = VM__destinationFilter,
+ .offset = offsetof(plugstate_t, destinationFilter),
+ .type = LV2_ATOM__Tuple,
+ .max_size = FILTER_SIZE,
+ .event_cb = _intercept_destinationFilter,
+ }
+};
+
+static void
+_cb(timely_t *timely __attribute__((unused)), int64_t frames __attribute__((unused)),
+ LV2_URID type __attribute__((unused)), void *data __attribute__((unused)))
+{
+ // nothing to do
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, num_t rate,
+ const char *bundle_path __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ handle->vm_plug = vm_plug_type(descriptor->URI);
+
+ 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);
+
+ handle->vm_graph = handle->map->map(handle->map->handle, VM__graph);
+ handle->midi_MidiEvent = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ handle->filt.midi_Controller = handle->map->map(handle->map->handle, LV2_MIDI__Controller);
+ handle->filt.midi_Bender = handle->map->map(handle->map->handle, LV2_MIDI__Bender);
+ handle->filt.midi_ProgramChange = handle->map->map(handle->map->handle, LV2_MIDI__ProgramChange);
+ handle->filt.midi_ChannelPressure = handle->map->map(handle->map->handle, LV2_MIDI__ChannelPressure);
+ handle->filt.midi_NotePressure = handle->map->map(handle->map->handle, LV2_MIDI__Aftertouch);
+ handle->filt.midi_NoteOn = handle->map->map(handle->map->handle, LV2_MIDI__NoteOn);
+ handle->filt.midi_channel = handle->map->map(handle->map->handle, LV2_MIDI__channel);
+ handle->filt.midi_controllerNumber = handle->map->map(handle->map->handle, LV2_MIDI__controllerNumber);
+ handle->filt.midi_noteNumber = handle->map->map(handle->map->handle, LV2_MIDI__noteNumber);
+ handle->filt.midi_velocity = handle->map->map(handle->map->handle, LV2_MIDI__velocity);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ lv2_atom_forge_init(&handle->forgs[i].forge, handle->map);
+
+ vm_api_init(handle->api, handle->map);
+ timely_init(&handle->timely, handle->map, rate, 0, _cb, handle);
+
+ const int nprops = handle->vm_plug == VM_PLUG_MIDI
+ ? MAX_NPROPS
+ : 1;
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, nprops,
+ &handle->state, &handle->stash, handle->map, handle))
+ {
+ fprintf(stderr, "props_init failed\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->needs_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].flt = data;
+ break;
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ handle->out[port - 10].flt = data;
+ break;
+ }
+}
+
+#define CLIP(a, v, b) fmin(fmax(a, v), b)
+
+static void
+run_pre(plughandle_t *handle)
+{
+ // reset maximum values and notification flags
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ handle->inm[i] = 0.f;
+ handle->outm[i] = 0.f;
+ }
+}
+
+static void
+run_post(plughandle_t *handle, uint32_t frames)
+{
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ if(handle->inf[i]) // port needs notification
+ {
+ LV2_Atom_Forge_Frame tup_frame;
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_tuple(&handle->forge, &tup_frame);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_int(&handle->forge, i + 2);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_float(&handle->forge, handle->inm[i]);
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &tup_frame);
+
+ handle->inf[i] = false;
+ }
+
+ if(handle->outf[i]) // port needs notification
+ {
+ LV2_Atom_Forge_Frame tup_frame;
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_tuple(&handle->forge, &tup_frame);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_int(&handle->forge, i + 10);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_float(&handle->forge, handle->outm[i]);
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &tup_frame);
+
+ handle->outf[i] = false;
+ }
+ }
+}
+
+static LV2_Atom_Forge_Ref
+send_chunk(LV2_Atom_Forge *forge, uint32_t frames, LV2_URID type,
+ const uint8_t *msg, uint32_t sz)
+{
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, sz, type);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, msg, sz);
+
+ return ref;
+}
+
+static void
+run_internal(plughandle_t *handle, uint32_t frames,
+ const float *in [CTRL_MAX], float *out [CTRL_MAX], forge_t forgs [CTRL_MAX])
+{
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ const float in1 = (handle->vm_plug == VM_PLUG_AUDIO)
+ ? *in[i] // don't clip audio
+ : CLIP(VM_MIN, *in[i], VM_MAX);
+
+ if(handle->in0[i] != in1)
+ {
+ handle->needs_recalc = true;
+ handle->in0[i] = in1;
+
+ if(in1 != handle->inm[i])
+ {
+ handle->inm[i] = in1;
+ handle->inf[i] = true; // notify in run_post
+ }
+ }
+ }
+
+ if(handle->status != VM_STATUS_STATIC)
+ handle->needs_recalc = true;
+
+ if(handle->needs_recalc)
+ {
+ _stack_clear(&handle->stack);
+
+ for(unsigned i = 0; i < ITEMS_MAX; i++)
+loop: {
+ vm_command_t *cmd = &handle->cmds[i];
+ bool terminate = false;
+
+ switch(cmd->type)
+ {
+ case COMMAND_BOOL:
+ {
+ const num_t c = cmd->i32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_INT:
+ {
+ const num_t c = cmd->i32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_FLOAT:
+ {
+ const num_t c = cmd->f32;
+ _stack_push(&handle->stack, c);
+ } break;
+ case COMMAND_OPCODE:
+ {
+ switch(cmd->op)
+ {
+ case OP_CTRL:
+ {
+ const int idx = floor(_stack_pop(&handle->stack));
+ const num_t c = handle->in0[idx & CTRL_MASK];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_PUSH:
+ {
+ const num_t c = _stack_peek(&handle->stack);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_POP:
+ {
+ const num_t c = _stack_pop(&handle->stack);
+ (void)c;
+ } break;
+ case OP_SWAP:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ _stack_push_num(&handle->stack, ab, 2);
+ } break;
+ case OP_STORE:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const int idx = floorf(ab[0]);
+ handle->stack.regs[idx & REG_MASK] = ab[1];
+ } break;
+ case OP_LOAD:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const int idx = floorf(a);
+ const num_t c = handle->stack.regs[idx & REG_MASK];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BREAK:
+ {
+ const bool a = _stack_pop(&handle->stack);
+ if(a)
+ terminate = true;
+ } break;
+ case OP_GOTO:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ if(ab[0])
+ {
+ const int idx = ab[1];
+ i = idx & ITEMS_MASK;
+ goto loop;
+ }
+ } break;
+
+ case OP_RAND:
+ {
+ const num_t c = (num_t)rand() / RAND_MAX;
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_ADD:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ab[1] + ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SUB:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ab[1] - ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_MUL:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ab[1] * ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_DIV:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ab[0] == 0.0
+ ? 0.0
+ : ab[1] / ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_MOD:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ab[0] == 0.0
+ ? 0.0
+ : fmod(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_POW:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = pow(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_NEG:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = -a;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ABS:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = fabs(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SQRT:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = sqrt(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_CBRT:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = cbrt(a);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_FLOOR:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = floor(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_CEIL:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = ceil(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ROUND:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = round(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_RINT:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = rint(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_TRUNC:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = trunc(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_MODF:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ num_t d;
+ const num_t c = modf(a, &d);
+ _stack_push(&handle->stack, c);
+ _stack_push(&handle->stack, d);
+ } break;
+
+ case OP_EXP:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = exp(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_EXP_2:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = exp2(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LD_EXP:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = ldexp(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_FR_EXP:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ int d;
+ const num_t c = frexp(a, &d);
+ _stack_push(&handle->stack, c);
+ _stack_push(&handle->stack, d);
+ } break;
+ case OP_LOG:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = log(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LOG_2:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = log2(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LOG_10:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = log10(a);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_PI:
+ {
+ num_t c = M_PI;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SIN:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = sin(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_COS:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = cos(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_TAN:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = tan(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ASIN:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = asin(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ACOS:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = acos(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ATAN:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = atan(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ATAN2:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = atan2(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SINH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = sinh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_COSH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = cosh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_TANH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = tanh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ASINH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = asinh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ACOSH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = acosh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_ATANH:
+ {
+ const num_t a = _stack_pop(&handle->stack);
+ const num_t c = atanh(a);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_EQ:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] == ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LT:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] < ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_GT:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] > ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LE:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] <= ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_GE:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] >= ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_TER:
+ {
+ num_t ab [3];
+ _stack_pop_num(&handle->stack, ab, 3);
+ const bool c = ab[0];
+ _stack_push(&handle->stack, c ? ab[2] : ab[1]);
+ } break;
+ case OP_MINI:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = fmin(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_MAXI:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const num_t c = fmax(ab[1], ab[0]);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_AND:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] && ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_OR:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const bool c = ab[1] || ab[0];
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_NOT:
+ {
+ const int a = _stack_pop(&handle->stack);
+ const bool c = !a;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BAND:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const unsigned a = ab[1];
+ const unsigned b = ab[0];
+ const unsigned c = a & b;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BOR:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const unsigned a = ab[1];
+ const unsigned b = ab[0];
+ const unsigned c = a | b;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BNOT:
+ {
+ const unsigned a = _stack_pop(&handle->stack);
+ const unsigned c = ~a;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_LSHIFT:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const unsigned a = ab[1];
+ const unsigned b = ab[0];
+ const unsigned c = a << b;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_RSHIFT:
+ {
+ num_t ab [2];
+ _stack_pop_num(&handle->stack, ab, 2);
+ const unsigned a = ab[1];
+ const unsigned b = ab[0];
+ const unsigned c = a >> b;
+ _stack_push(&handle->stack, c);
+ } break;
+
+ // time
+ case OP_BAR_BEAT:
+ {
+ const num_t c = TIMELY_BAR_BEAT(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BAR:
+ {
+ const num_t c = TIMELY_BAR(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BEAT:
+ {
+ const num_t bar = TIMELY_BAR(&handle->timely);
+ const num_t beats_per_bar = TIMELY_BEATS_PER_BAR(&handle->timely);
+ const num_t bar_beat = TIMELY_BAR_BEAT(&handle->timely);
+ const num_t c = bar*beats_per_bar + bar_beat;
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BEAT_UNIT:
+ {
+ const num_t c = TIMELY_BEAT_UNIT(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BPB:
+ {
+ const num_t c = TIMELY_BEATS_PER_BAR(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_BPM:
+ {
+ const num_t c = TIMELY_BEATS_PER_MINUTE(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_FRAME:
+ {
+ const num_t c = TIMELY_FRAME(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_FPS:
+ {
+ const num_t c = TIMELY_FRAMES_PER_SECOND(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+ case OP_SPEED:
+ {
+ const num_t c = TIMELY_SPEED(&handle->timely);
+ _stack_push(&handle->stack, c);
+ } break;
+
+ case OP_NOP:
+ {
+ // no operation
+ } 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);
+ handle->needs_recalc = false;
+ }
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ const float out1 = (handle->vm_plug == VM_PLUG_AUDIO)
+ ? handle->out0[i] // don't clip audio
+ : CLIP(VM_MIN, handle->out0[i], VM_MAX);
+
+ if(*out[i] != out1)
+ {
+ if(forgs)
+ {
+ if(handle->vm_plug == VM_PLUG_ATOM)
+ {
+ // send changes on atom output ports
+ if(forgs[i].ref)
+ forgs[i].ref = lv2_atom_forge_frame_time(&forgs[i].forge, frames);
+ if(handle->ref)
+ forgs[i].ref = lv2_atom_forge_float(&forgs[i].forge, out1);
+ }
+ else if(handle->vm_plug == VM_PLUG_MIDI)
+ {
+ const vm_filter_t *filter = &handle->destinationFilter[i];
+
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const uint8_t msg [3] = {
+ [0] = LV2_MIDI_MSG_CONTROLLER | filter->channel,
+ [1] = filter->value,
+ [2] = value
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ } break;
+ case FILTER_BENDER:
+ {
+ const int16_t value = floor(out1*0x2000 + 0x1fff);
+ const uint8_t msg [3] = {
+ [0] = LV2_MIDI_MSG_BENDER | filter->channel,
+ [1] = value & 0x7f,
+ [2] = value >> 7
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ } break;
+ case FILTER_PROGRAM_CHANGE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const uint8_t msg [2] = {
+ [0] = LV2_MIDI_MSG_PGM_CHANGE | filter->channel,
+ [1] = value
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ } break;
+ case FILTER_CHANNEL_PRESSURE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const uint8_t msg [2] = {
+ [0] = LV2_MIDI_MSG_CHANNEL_PRESSURE | filter->channel,
+ [1] = value
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ } break;
+ case FILTER_NOTE_ON:
+ {
+ if(floor(*out[i] * 0x7f) > 0x0)
+ {
+ const uint8_t value = floor(*out[i] * 0x7f);
+ const uint8_t msg [3] = {
+ [0] = LV2_MIDI_MSG_NOTE_OFF | filter->channel,
+ [1] = value,
+ [2] = 0x0
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ }
+ if(floor(out1 * 0x7f) > 0x0)
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const uint8_t msg [3] = {
+ [0] = LV2_MIDI_MSG_NOTE_ON | filter->channel,
+ [1] = value,
+ [2] = filter->value
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ }
+ } break;
+ case FILTER_NOTE_PRESSURE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const uint8_t msg [3] = {
+ [0] = LV2_MIDI_MSG_NOTE_PRESSURE | filter->channel,
+ [1] = filter->value,
+ [2] = value
+ };
+
+ if(forgs[i].ref)
+ forgs[i].ref = send_chunk(&forgs[i].forge, frames, handle->midi_MidiEvent, msg, sizeof(msg));
+ } break;
+ //FIXME handle more types
+
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+ }
+ }
+
+ *out[i] = out1;
+
+ if(out1 != handle->outm[i])
+ {
+ handle->outm[i] = out1;
+ handle->outf[i] = true; // notify out run_post
+ }
+ }
+ }
+}
+
+static void
+run_control(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);
+
+ run_pre(handle);
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ int64_t last_t = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ timely_advance(&handle->timely, obj, last_t, ev->time.frames);
+
+ last_t = ev->time.frames;
+ }
+ timely_advance(&handle->timely, NULL, last_t, nsamples);
+
+ // run once at end of period for controls
+ {
+ const float *in [CTRL_MAX ] = {
+ handle->in[0].flt,
+ handle->in[1].flt,
+ handle->in[2].flt,
+ handle->in[3].flt,
+ handle->in[4].flt,
+ handle->in[5].flt,
+ handle->in[6].flt,
+ handle->in[7].flt
+ };
+
+ float *out [CTRL_MAX ] = {
+ handle->out[0].flt,
+ handle->out[1].flt,
+ handle->out[2].flt,
+ handle->out[3].flt,
+ handle->out[4].flt,
+ handle->out[5].flt,
+ handle->out[6].flt,
+ handle->out[7].flt
+ };
+
+ run_internal(handle, nsamples -1, in, out, NULL);
+ }
+
+ run_post(handle, nsamples - 1);
+
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, nsamples - 1);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_long(&handle->forge, handle->off);
+
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->off += nsamples;
+}
+
+static void
+run_cv_audio_advance(plughandle_t *handle, const LV2_Atom_Object *obj,
+ uint32_t from, uint32_t to)
+{
+ if(from == to) // just run timely_advance for void range
+ {
+ timely_advance(&handle->timely, obj, from, to);
+ }
+ else
+ {
+ for(unsigned i = from; i < to; i++)
+ {
+ if(timely_advance(&handle->timely, obj, i, i + 1))
+ obj = NULL; // invalidate obj for further steps if handled
+
+ // make it inplace-safe
+ const float tmp [CTRL_MAX] = {
+ handle->in[0].flt[i],
+ handle->in[1].flt[i],
+ handle->in[2].flt[i],
+ handle->in[3].flt[i],
+ handle->in[4].flt[i],
+ handle->in[5].flt[i],
+ handle->in[6].flt[i],
+ handle->in[7].flt[i]
+ };
+
+ const float *in [CTRL_MAX ] = {
+ &tmp[0],
+ &tmp[1],
+ &tmp[2],
+ &tmp[3],
+ &tmp[4],
+ &tmp[5],
+ &tmp[6],
+ &tmp[7]
+ };
+
+ float *out [CTRL_MAX ] = {
+ &handle->out[0].flt[i],
+ &handle->out[1].flt[i],
+ &handle->out[2].flt[i],
+ &handle->out[3].flt[i],
+ &handle->out[4].flt[i],
+ &handle->out[5].flt[i],
+ &handle->out[6].flt[i],
+ &handle->out[7].flt[i]
+ };
+
+ run_internal(handle, i, in, out, NULL);
+ }
+ }
+}
+
+static void
+run_cv_audio(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);
+
+ run_pre(handle);
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ int64_t last_t = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ run_cv_audio_advance(handle, obj, last_t, ev->time.frames);
+
+ last_t = ev->time.frames;
+ }
+ run_cv_audio_advance(handle, NULL, last_t, nsamples);
+
+ run_post(handle, nsamples - 1);
+
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, nsamples - 1);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_long(&handle->forge, handle->off);
+
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->off += nsamples;
+}
+
+static void
+run_atom_advance(plughandle_t *handle, const LV2_Atom_Object *obj,
+ uint32_t from, uint32_t to, const float *in [CTRL_MAX], float *out [CTRL_MAX],
+ forge_t forgs [CTRL_MAX])
+{
+ if(from == to) // just run timely_advance for void range
+ {
+ timely_advance(&handle->timely, obj, from, to);
+ }
+ else
+ {
+ for(unsigned i = from; i < to; i++)
+ {
+ if(timely_advance(&handle->timely, obj, i, i + 1))
+ obj = NULL; // invalidate obj for further steps if handled
+
+ run_internal(handle, i, in, out, forgs);
+ }
+ }
+}
+
+static void
+run_atom(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);
+
+ run_pre(handle);
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ forge_t *forgs = handle->forgs;
+ float pin [CTRL_MAX];
+ float pout [CTRL_MAX];
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ lv2_atom_forge_set_buffer(&forgs[i].forge, (uint8_t *)handle->out[i].seq, handle->out[i].seq->atom.size);
+ forgs[i].ref = lv2_atom_forge_sequence_head(&forgs[i].forge, &forgs[i].frame, 0);
+
+ pin[i] = handle->in0[i];
+ pout[i] = handle->out0[i];
+ }
+
+ const float *in [CTRL_MAX ] = {
+ &pin[0],
+ &pin[1],
+ &pin[2],
+ &pin[3],
+ &pin[4],
+ &pin[5],
+ &pin[6],
+ &pin[7]
+ };
+
+ float *out [CTRL_MAX ] = {
+ &pout[0],
+ &pout[1],
+ &pout[2],
+ &pout[3],
+ &pout[4],
+ &pout[5],
+ &pout[6],
+ &pout[7]
+ };
+
+ const unsigned nseqs = CTRL_MAX + 1;
+ const LV2_Atom_Sequence *seqs [nseqs];
+ const LV2_Atom_Event *evs [nseqs];
+
+ for(unsigned i = 0; i < nseqs; i++)
+ {
+ seqs[i] = (i == 0)
+ ? handle->control
+ : handle->in[i-1].seq;
+
+ evs[i] = lv2_atom_sequence_begin(&seqs[i]->body);
+ }
+
+ int64_t last_t = 0;
+ while(true)
+ {
+ int nxt = -1;
+ int64_t frames = nsamples;
+
+ // search next event
+ for(unsigned i = 0; i < nseqs; i++)
+ {
+ if(!evs[i] || lv2_atom_sequence_is_end(&seqs[i]->body, seqs[i]->atom.size, evs[i]))
+ {
+ evs[i] = NULL; // invalidate, sequence has been drained
+ continue;
+ }
+
+ if(evs[i]->time.frames < frames)
+ {
+ frames = evs[i]->time.frames;
+ nxt = i;
+ }
+ }
+
+ if(nxt == -1)
+ break; // no events anymore, exit loop
+
+ // handle event
+ {
+ const bool is_control = (nxt == 0); // is event from control port?
+ const LV2_Atom_Event *ev = evs[nxt];
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const LV2_Atom_Float *f32 = (const LV2_Atom_Float *)&ev->body;
+
+ if(is_control)
+ {
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ }
+ else if(f32->atom.type == handle->forge.Float)
+ {
+ pin[nxt-1] = f32->body;
+ }
+
+ run_atom_advance(handle, is_control ? obj : NULL, last_t, ev->time.frames, in, out, forgs);
+
+ last_t = ev->time.frames;
+ }
+
+ // advance event iterator on active sequence
+ evs[nxt] = lv2_atom_sequence_next(evs[nxt]);
+ }
+ run_atom_advance(handle, NULL, last_t, nsamples, in, out, forgs);
+
+ run_post(handle, nsamples - 1);
+
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, nsamples - 1);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_long(&handle->forge, handle->off);
+
+ 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++)
+ {
+ if(forgs[i].ref)
+ lv2_atom_forge_pop(&forgs[i].forge, &forgs[i].frame);
+ else
+ lv2_atom_sequence_clear(handle->out[i].seq);
+ }
+
+ handle->off += nsamples;
+}
+
+static void
+run_midi_advance(plughandle_t *handle, const LV2_Atom_Object *obj,
+ uint32_t from, uint32_t to, const float *in [CTRL_MAX], float *out [CTRL_MAX],
+ forge_t forgs [CTRL_MAX])
+{
+ if(from == to) // just run timely_advance for void range
+ {
+ timely_advance(&handle->timely, obj, from, to);
+ }
+ else
+ {
+ for(unsigned i = from; i < to; i++)
+ {
+ if(timely_advance(&handle->timely, obj, i, i + 1))
+ obj = NULL; // invalidate obj for further steps if handled
+
+ run_internal(handle, i, in, out, forgs);
+ }
+ }
+}
+
+static bool
+filter_midi(vm_filter_t *filter, const uint8_t *msg, float *f32)
+{
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ {
+ if( (msg[0] == (LV2_MIDI_MSG_CONTROLLER | filter->channel) )
+ && (msg[1] == filter->value) )
+ {
+ const uint8_t value = msg[2];
+ *f32 = (float)value / 0x7f;
+
+ return true;
+ }
+ } break;
+ case FILTER_BENDER:
+ {
+ if(msg[0] == (LV2_MIDI_MSG_BENDER | filter->channel) )
+ {
+ const int64_t value = msg[2] | (msg[1] << 7);
+ *f32 = (float)(value - 0x1fff) / 0x2000;
+
+ return true;
+ }
+ } break;
+ case FILTER_PROGRAM_CHANGE:
+ {
+ if(msg[0] == (LV2_MIDI_MSG_PGM_CHANGE | filter->channel) )
+ {
+ const uint8_t value = msg[1];
+ *f32 = (float)value / 0x7f;
+
+ return true;
+ }
+ } break;
+ case FILTER_CHANNEL_PRESSURE:
+ {
+ if(msg[0] == (LV2_MIDI_MSG_CHANNEL_PRESSURE | filter->channel) )
+ {
+ const uint8_t value = msg[1];
+ *f32 = (float)value / 0x7f;
+
+ return true;
+ }
+ } break;
+ case FILTER_NOTE_ON:
+ {
+ if(msg[0] == (LV2_MIDI_MSG_NOTE_ON | filter->channel) )
+ {
+ const uint8_t value = msg[1];
+ *f32 = (float)value / 0x7f;
+
+ return true;
+ }
+ else if(msg[0] == (LV2_MIDI_MSG_NOTE_OFF | filter->channel) )
+ {
+ *f32 = 0.f;
+
+ return true;
+ }
+ } break;
+ case FILTER_NOTE_PRESSURE:
+ {
+ if( (msg[0] == (LV2_MIDI_MSG_NOTE_PRESSURE | filter->channel) )
+ && (msg[1] == filter->value) )
+ {
+ const uint8_t value = msg[2];
+ *f32 = (float)value / 0x7f;
+
+ return true;
+ }
+ } break;
+ //FIXME handle more filter types
+
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+
+ return false;
+}
+
+static void
+run_midi(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);
+
+ run_pre(handle);
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ forge_t *forgs = handle->forgs;
+ float pin [CTRL_MAX];
+ float pout [CTRL_MAX];
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ lv2_atom_forge_set_buffer(&forgs[i].forge, (uint8_t *)handle->out[i].seq, handle->out[i].seq->atom.size);
+ forgs[i].ref = lv2_atom_forge_sequence_head(&forgs[i].forge, &forgs[i].frame, 0);
+
+ pin[i] = handle->in0[i];
+ pout[i] = handle->out0[i];
+ }
+
+ const float *in [CTRL_MAX ] = {
+ &pin[0],
+ &pin[1],
+ &pin[2],
+ &pin[3],
+ &pin[4],
+ &pin[5],
+ &pin[6],
+ &pin[7]
+ };
+
+ float *out [CTRL_MAX ] = {
+ &pout[0],
+ &pout[1],
+ &pout[2],
+ &pout[3],
+ &pout[4],
+ &pout[5],
+ &pout[6],
+ &pout[7]
+ };
+
+ const unsigned nseqs = CTRL_MAX + 1;
+ const LV2_Atom_Sequence *seqs [nseqs];
+ const LV2_Atom_Event *evs [nseqs];
+
+ for(unsigned i = 0; i < nseqs; i++)
+ {
+ seqs[i] = (i == 0)
+ ? handle->control
+ : handle->in[i-1].seq;
+
+ evs[i] = lv2_atom_sequence_begin(&seqs[i]->body);
+ }
+
+ int64_t last_t = 0;
+ while(true)
+ {
+ int nxt = -1;
+ int64_t frames = nsamples;
+
+ // search next event
+ for(unsigned i = 0; i < nseqs; i++)
+ {
+ if(!evs[i] || lv2_atom_sequence_is_end(&seqs[i]->body, seqs[i]->atom.size, evs[i]))
+ {
+ evs[i] = NULL; // invalidate, sequence has been drained
+ continue;
+ }
+
+ if(evs[i]->time.frames < frames)
+ {
+ frames = evs[i]->time.frames;
+ nxt = i;
+ }
+ }
+
+ if(nxt == -1)
+ break; // no events anymore, exit loop
+
+ // handle event
+ {
+ const bool is_control = (nxt == 0); // is event from control port?
+ const LV2_Atom_Event *ev = evs[nxt];
+ const LV2_Atom *atom= &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(atom);
+ float f32 = 0;
+
+ if(is_control)
+ {
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ }
+ else if( (atom->type == handle->midi_MidiEvent)
+ && filter_midi(&handle->sourceFilter[nxt-1], msg, &f32) )
+ {
+ pin[nxt-1] = f32;
+ }
+
+
+ run_midi_advance(handle, is_control ? obj : NULL, last_t, ev->time.frames, in, out, forgs);
+
+ last_t = ev->time.frames;
+ }
+
+ // advance event iterator on active sequence
+ evs[nxt] = lv2_atom_sequence_next(evs[nxt]);
+ }
+ run_midi_advance(handle, NULL, last_t, nsamples, in, out, forgs);
+
+ run_post(handle, nsamples - 1);
+
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_frame_time(&handle->forge, nsamples - 1);
+ if(handle->ref)
+ handle->ref = lv2_atom_forge_long(&handle->forge, handle->off);
+
+ 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++)
+ {
+ if(forgs[i].ref)
+ lv2_atom_forge_pop(&forgs[i].forge, &forgs[i].frame);
+ else
+ lv2_atom_sequence_clear(handle->out[i].seq);
+ }
+
+ handle->off += nsamples;
+}
+
+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 const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+static const LV2_Descriptor vm_control = {
+ .URI = VM_PREFIX"control",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run_control,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor vm_cv = {
+ .URI = VM_PREFIX"cv",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run_cv_audio,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor vm_audio = {
+ .URI = VM_PREFIX"audio",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run_cv_audio,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor vm_atom = {
+ .URI = VM_PREFIX"atom",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run_atom,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor vm_midi = {
+ .URI = VM_PREFIX"midi",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run_midi,
+ .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_control;
+ case 1:
+ return &vm_cv;
+ case 2:
+ return &vm_audio;
+ case 3:
+ return &vm_atom;
+ case 4:
+ return &vm_midi;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/vm.h b/vm.h
new file mode 100644
index 0000000..3498fac
--- /dev/null
+++ b/vm.h
@@ -0,0 +1,1235 @@
+/*
+ * Copyright (c) 2017-2021 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/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/options/options.h"
+#include "lv2/lv2plug.in/ns/ext/parameters/parameters.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_ui VM_PREFIX"vm_ui"
+
+#define VM__graph VM_PREFIX"graph"
+#define VM__sourceFilter VM_PREFIX"sourceFilter"
+#define VM__destinationFilter VM_PREFIX"destinationFilter"
+
+#define MAX_NPROPS 3
+
+#define CTRL_MAX 0x8
+#define CTRL_MASK (CTRL_MAX - 1)
+
+#define ITEMS_MAX 128
+#define ITEMS_MASK (ITEMS_MAX - 1)
+#define GRAPH_SIZE (ITEMS_MAX * sizeof(LV2_Atom_Long))
+#define FILTER_SIZE 0x1000 // 4K
+
+#define VM_MIN -1.f
+#define VM_MAX 1.f
+#define VM_STP 0.01f
+#define VM_RNG (VM_MAX - VM_MIN)
+#define VM_VIS (VM_RNG * 1.1f)
+
+#include <props.lv2/props.h>
+
+typedef enum _vm_plug_enum_t {
+ VM_PLUG_CONTROL = 0,
+ VM_PLUG_CV,
+ VM_PLUG_AUDIO,
+ VM_PLUG_ATOM,
+ VM_PLUG_MIDI
+} vm_plug_enum_t;
+
+typedef enum _vm_status_t {
+ VM_STATUS_STATIC = (0 << 0),
+ VM_STATUS_HAS_TIME = (1 << 1),
+ VM_STATUS_HAS_RAND = (1 << 2),
+} vm_status_t;
+
+typedef enum _vm_opcode_enum_t {
+ OP_NOP = 0,
+
+ OP_CTRL,
+ OP_PUSH,
+ OP_POP,
+ OP_SWAP,
+ OP_STORE,
+ OP_LOAD,
+ OP_BREAK,
+ OP_GOTO,
+
+ OP_RAND,
+
+ OP_ADD,
+ OP_SUB,
+ OP_MUL,
+ OP_DIV,
+ OP_MOD,
+ OP_POW,
+
+ OP_NEG,
+ OP_ABS,
+ OP_SQRT,
+ OP_CBRT,
+
+ OP_FLOOR,
+ OP_CEIL,
+ OP_ROUND,
+ OP_RINT,
+ OP_TRUNC,
+ OP_MODF,
+
+ OP_EXP,
+ OP_EXP_2,
+ OP_LD_EXP,
+ OP_FR_EXP,
+ OP_LOG,
+ OP_LOG_2,
+ OP_LOG_10,
+
+ OP_PI,
+ OP_SIN,
+ OP_COS,
+ OP_TAN,
+ OP_ASIN,
+ OP_ACOS,
+ OP_ATAN,
+ OP_ATAN2,
+ OP_SINH,
+ OP_COSH,
+ OP_TANH,
+ OP_ASINH,
+ OP_ACOSH,
+ OP_ATANH,
+
+ OP_EQ,
+ OP_LT,
+ OP_GT,
+ OP_LE,
+ OP_GE,
+ OP_TER,
+ OP_MINI,
+ OP_MAXI,
+
+ OP_AND,
+ OP_OR,
+ OP_NOT,
+
+ OP_BAND,
+ OP_BOR,
+ OP_BNOT,
+ OP_LSHIFT,
+ OP_RSHIFT,
+
+ OP_BAR_BEAT,
+ OP_BAR,
+ OP_BEAT,
+ OP_BEAT_UNIT,
+ OP_BPB,
+ OP_BPM,
+ OP_FRAME,
+ OP_FPS,
+ OP_SPEED,
+
+ OP_MAX,
+} vm_opcode_enum_t ;
+
+typedef enum _vm_command_enum_t {
+ COMMAND_NOP = 0,
+
+ COMMAND_OPCODE,
+ COMMAND_BOOL,
+ COMMAND_INT,
+ COMMAND_FLOAT,
+
+ COMMAND_MAX,
+} vm_command_enum_t;
+
+typedef enum _vm_filter_enum_t {
+ FILTER_CONTROLLER = 0,
+ FILTER_BENDER,
+ FILTER_PROGRAM_CHANGE,
+ FILTER_CHANNEL_PRESSURE,
+ FILTER_NOTE_ON,
+ FILTER_NOTE_PRESSURE,
+
+ FILTER_MAX,
+} vm_filter_enum_t;
+
+typedef struct _vm_command_t vm_command_t;
+typedef struct _vm_api_def_t vm_api_def_t;
+typedef struct _vm_api_impl_t vm_api_impl_t;
+typedef struct _vm_filter_impl_t vm_filter_impl_t;
+typedef struct _vm_filter_t vm_filter_t;
+typedef struct _plugstate_t plugstate_t;
+
+struct _vm_command_t {
+ vm_command_enum_t type;
+
+ union {
+ int32_t i32;
+ float f32;
+ vm_opcode_enum_t op;
+ };
+};
+
+struct _vm_api_def_t {
+ const char *uri;
+ const char *label;
+ const char *mnemo;
+ char key;
+ unsigned npops;
+ unsigned npushs;
+};
+
+struct _vm_api_impl_t {
+ LV2_URID urid;
+};
+
+struct _vm_filter_t {
+ vm_filter_enum_t type;
+ uint8_t channel;
+ uint8_t value;
+};
+
+struct _vm_filter_impl_t {
+ LV2_URID midi_Controller;
+ LV2_URID midi_Bender;
+ LV2_URID midi_ProgramChange;
+ LV2_URID midi_ChannelPressure;
+ LV2_URID midi_NotePressure;
+ LV2_URID midi_NoteOn;
+ LV2_URID midi_channel;
+ LV2_URID midi_controllerNumber;
+ LV2_URID midi_noteNumber;
+ LV2_URID midi_velocity;
+};
+
+struct _plugstate_t {
+ uint8_t graph [GRAPH_SIZE];
+ uint8_t sourceFilter [FILTER_SIZE];
+ uint8_t destinationFilter [FILTER_SIZE];
+};
+
+static const vm_api_def_t vm_api_def [OP_MAX] = {
+ [OP_NOP] = {
+ .uri = VM_PREFIX"opNop",
+ .label = "",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 0
+ },
+
+ [OP_CTRL] = {
+ .uri = VM_PREFIX"opInput",
+ .label = "input",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_PUSH] = {
+ .uri = VM_PREFIX"opPush",
+ .label = "Push topmost value",
+ .mnemo = "push",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 2
+ },
+ [OP_POP] = {
+ .uri = VM_PREFIX"opPop",
+ .label = "Pop topmost value",
+ .mnemo = "pop",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 0
+ },
+ [OP_SWAP] = {
+ .uri = VM_PREFIX"opSwap",
+ .label = "Swap 2 topmost values",
+ .mnemo = "swap",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 2
+ },
+ [OP_STORE] = {
+ .uri = VM_PREFIX"opStore",
+ .label = "Store in register",
+ .mnemo = "store",
+ .key = '[',
+ .npops = 2,
+ .npushs = 0
+ },
+ [OP_LOAD] = {
+ .uri = VM_PREFIX"opLoad",
+ .label = "Load from register",
+ .mnemo = "load",
+ .key = ']',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_BREAK] = {
+ .uri = VM_PREFIX"opBreak",
+ .label = "Break program execution if top of stack is true",
+ .mnemo = "break",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 0
+ },
+ [OP_GOTO] = {
+ .uri = VM_PREFIX"opGoto",
+ .label = "Goto given operation",
+ .mnemo = "goto",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 0
+ },
+
+ [OP_RAND] = {
+ .uri = VM_PREFIX"opRand",
+ .label = "Generate random number",
+ .mnemo = "rand",
+ .key = 'r',
+ .npops = 0,
+ .npushs = 1
+ },
+
+ [OP_ADD] = {
+ .uri = VM_PREFIX"opAdd",
+ .label = "Add",
+ .mnemo = "+",
+ .key = '+',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_SUB] = {
+ .uri = VM_PREFIX"opSub",
+ .label = "Subtract",
+ .mnemo = "-",
+ .key = '-',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_MUL] = {
+ .uri = VM_PREFIX"opMul",
+ .label = "Multiply",
+ .mnemo = "*",
+ .key = '*',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_DIV] = {
+ .uri = VM_PREFIX"opDiv",
+ .label = "Divide",
+ .mnemo = "/",
+ .key = '/',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_MOD] = {
+ .uri = VM_PREFIX"opMod",
+ .label = "Modulo",
+ .mnemo = "%",
+ .key = '%',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_POW] = {
+ .uri = VM_PREFIX"opPow",
+ .label = "Power",
+ .mnemo = "^",
+ .key = '^',
+ .npops = 2,
+ .npushs = 1
+ },
+
+ [OP_NEG] = {
+ .uri = VM_PREFIX"opNeg",
+ .label = "Negate",
+ .mnemo = "neg",
+ .key = 'n',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ABS] = {
+ .uri = VM_PREFIX"opAbs",
+ .label = "Absolute",
+ .mnemo = "abs",
+ .key = 'a',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_SQRT] = {
+ .uri = VM_PREFIX"opSqrt",
+ .label = "Square root",
+ .mnemo = "sqrt",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_CBRT] = {
+ .uri = VM_PREFIX"opCbrt",
+ .label = "Cubic root",
+ .mnemo = "cbrt",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+
+ [OP_FLOOR] = {
+ .uri = VM_PREFIX"opFloor",
+ .label = "Floor",
+ .mnemo = "floor",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_CEIL] = {
+ .uri = VM_PREFIX"opCeil",
+ .label = "Ceiling",
+ .mnemo = "ceil",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ROUND] = {
+ .uri = VM_PREFIX"opRound",
+ .label = "Round",
+ .mnemo = "round",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_RINT] = {
+ .uri = VM_PREFIX"opRint",
+ .label = "Rint",
+ .mnemo = "rint",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_TRUNC] = {
+ .uri = VM_PREFIX"opTrunc",
+ .label = "Truncate",
+ .mnemo = "trunc",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_MODF] = {
+ .uri = VM_PREFIX"opModF",
+ .label = "Break number into integer and fractional parts",
+ .mnemo = "modf",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 2
+ },
+
+ [OP_EXP] = {
+ .uri = VM_PREFIX"opExp",
+ .label = "Exponential",
+ .mnemo = "exp",
+ .key = 'e',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_EXP_2] = {
+ .uri = VM_PREFIX"opExp2",
+ .label = "Exponential base 2",
+ .mnemo = "exp2",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_LD_EXP] = {
+ .uri = VM_PREFIX"opLDExp",
+ .label = "Multiply number by 2 raised to a power",
+ .mnemo = "ldexp",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_FR_EXP] = {
+ .uri = VM_PREFIX"opFRExp",
+ .label = "Break number into significand and power of 2",
+ .mnemo = "frexp",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 2
+ },
+ [OP_LOG] = {
+ .uri = VM_PREFIX"opLog",
+ .label = "Logarithm",
+ .mnemo = "log",
+ .key = 'l',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_LOG_2] = {
+ .uri = VM_PREFIX"opLog2",
+ .label = "Logarithm base 2",
+ .mnemo = "log2",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_LOG_10] = {
+ .uri = VM_PREFIX"opLog10",
+ .label = "Logarithm base 10",
+ .mnemo = "log10",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+
+ [OP_PI] = {
+ .uri = VM_PREFIX"opPi",
+ .label = "Pi",
+ .mnemo = "pi",
+ .key = 'p',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_SIN] = {
+ .uri = VM_PREFIX"opSin",
+ .label = "Sinus",
+ .mnemo = "sin",
+ .key = 's',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_COS] = {
+ .uri = VM_PREFIX"opCos",
+ .label = "Cosinus",
+ .mnemo = "cos",
+ .key = 'c',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_TAN] = {
+ .uri = VM_PREFIX"opTan",
+ .label = "Tangens",
+ .mnemo = "tan",
+ .key = 't',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ASIN] = {
+ .uri = VM_PREFIX"opASin",
+ .label = "Arcus Sinus",
+ .mnemo = "asin",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ACOS] = {
+ .uri = VM_PREFIX"opACos",
+ .label = "Arcus Cosinus",
+ .mnemo = "acos",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ATAN] = {
+ .uri = VM_PREFIX"opATan",
+ .label = "Arcus Tangens",
+ .mnemo = "atan",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ATAN2] = {
+ .uri = VM_PREFIX"opATan2",
+ .label = "Arcus Tangens using quadrants",
+ .mnemo = "atan2",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_SINH] = {
+ .uri = VM_PREFIX"opSinH",
+ .label = "Sinus Hyperbolicus",
+ .mnemo = "sinh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_COSH] = {
+ .uri = VM_PREFIX"opCosH",
+ .label = "Cosinus Hyperbolicus",
+ .mnemo = "cosh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_TANH] = {
+ .uri = VM_PREFIX"opTanH",
+ .label = "Tangens Hyperbolicus",
+ .mnemo = "tanh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ASINH] = {
+ .uri = VM_PREFIX"opASinH",
+ .label = "Arcus Sinus Hyperbolicus",
+ .mnemo = "asinh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ACOSH] = {
+ .uri = VM_PREFIX"opACosH",
+ .label = "Arcus Cosinus Hyperbolicus",
+ .mnemo = "acosh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_ATANH] = {
+ .uri = VM_PREFIX"opATanH",
+ .label = "Arcus Tangens Hyperbolicus",
+ .mnemo = "atanh",
+ .key = '\0',
+ .npops = 1,
+ .npushs = 1
+ },
+
+ [OP_EQ] = {
+ .uri = VM_PREFIX"opEq",
+ .label = "Equal",
+ .mnemo = "==",
+ .key = '=',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_LT] = {
+ .uri = VM_PREFIX"opLt",
+ .label = "Less than",
+ .mnemo = "<",
+ .key = '<',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_GT] = {
+ .uri = VM_PREFIX"opGt",
+ .label = "Greater than",
+ .mnemo = ">",
+ .key = '>',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_LE] = {
+ .uri = VM_PREFIX"opLe",
+ .label = "Less or equal",
+ .mnemo = "<=",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_GE] = {
+ .uri = VM_PREFIX"opGe",
+ .label = "Greater or equal",
+ .mnemo = ">=",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_TER] = {
+ .uri = VM_PREFIX"opTernary",
+ .label = "Ternary operator",
+ .mnemo = "?",
+ .key = '?',
+ .npops = 3,
+ .npushs = 1
+ },
+ [OP_MINI] = {
+ .uri = VM_PREFIX"opMin",
+ .label = "Minimum",
+ .mnemo = "min",
+ .key = '{',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_MAXI] = {
+ .uri = VM_PREFIX"opMax",
+ .label = "Maximum",
+ .mnemo = "max",
+ .key = '}',
+ .npops = 2,
+ .npushs = 1
+ },
+
+ [OP_AND] = {
+ .uri = VM_PREFIX"opAnd",
+ .label = "And",
+ .mnemo = "&&",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_OR] = {
+ .uri = VM_PREFIX"opOr",
+ .label = "Or",
+ .mnemo = "||",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_NOT] = {
+ .uri = VM_PREFIX"opNot",
+ .label = "Not",
+ .mnemo = "!",
+ .key = '!',
+ .npops = 1,
+ .npushs = 1
+ },
+
+ [OP_BAND] = {
+ .uri = VM_PREFIX"opBAnd",
+ .label = "Bitwise and",
+ .mnemo = "&",
+ .key = '&',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_BOR] = {
+ .uri = VM_PREFIX"opBOr",
+ .label = "Bitwise or",
+ .mnemo = "|",
+ .key = '|',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_BNOT] = {
+ .uri = VM_PREFIX"opBNot",
+ .label = "Bitwise not",
+ .mnemo = "~",
+ .key = '~',
+ .npops = 1,
+ .npushs = 1
+ },
+ [OP_LSHIFT] = {
+ .uri = VM_PREFIX"opLShift",
+ .label = "Left shift",
+ .mnemo = "<<",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+ [OP_RSHIFT] = {
+ .uri = VM_PREFIX"opRShift",
+ .label = "Right shift",
+ .mnemo = ">>",
+ .key = '\0',
+ .npops = 2,
+ .npushs = 1
+ },
+
+ [OP_BAR_BEAT] = {
+ .uri = LV2_TIME__barBeat,
+ .label = "time:barBeat",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_BAR] = {
+ .uri = LV2_TIME__bar,
+ .label = "time:bar",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_BEAT] = {
+ .uri = LV2_TIME__beat,
+ .label = "time:beat",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_BEAT_UNIT] = {
+ .uri = LV2_TIME__beatUnit,
+ .label = "time:beatUnit",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_BPB] = {
+ .uri = LV2_TIME__beatsPerBar,
+ .label = "time:beatsPerBar",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_BPM] = {
+ .uri = LV2_TIME__beatsPerMinute,
+ .label = "time:beatsPerMinute",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_FRAME] = {
+ .uri = LV2_TIME__frame,
+ .label = "time:frame",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_FPS] = {
+ .uri = LV2_TIME__framesPerSecond,
+ .label = "time:framesPerSecond",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+ [OP_SPEED] = {
+ .uri = LV2_TIME__speed,
+ .label = "time:speed",
+ .mnemo = NULL,
+ .key = '\0',
+ .npops = 0,
+ .npushs = 1
+ },
+};
+
+static vm_plug_enum_t
+vm_plug_type(const char *plugin_uri)
+{
+ if(!strcmp(plugin_uri, VM_PREFIX"control"))
+ return VM_PLUG_CONTROL;
+ else if(!strcmp(plugin_uri, VM_PREFIX"cv"))
+ return VM_PLUG_CV;
+ else if(!strcmp(plugin_uri, VM_PREFIX"audio"))
+ return VM_PLUG_AUDIO;
+ else if(!strcmp(plugin_uri, VM_PREFIX"atom"))
+ return VM_PLUG_ATOM;
+ else if(!strcmp(plugin_uri, VM_PREFIX"midi"))
+ return VM_PLUG_MIDI;
+
+ return VM_PLUG_CONTROL;
+}
+
+static inline void
+vm_api_init(vm_api_impl_t *impl, LV2_URID_Map *map)
+{
+ for(unsigned op = 0; op < OP_MAX; op++)
+ {
+ impl[op].urid = map->map(map->handle, vm_api_def[op].uri);
+ }
+}
+
+static inline vm_opcode_enum_t
+vm_api_unmap(vm_api_impl_t *impl, LV2_URID urid)
+{
+ for(unsigned op = 0; op < OP_MAX; op++)
+ {
+ if(impl[op].urid == urid)
+ return op;
+ }
+
+ return OP_NOP;
+}
+
+static inline LV2_URID
+vm_api_map(vm_api_impl_t *impl, vm_opcode_enum_t op)
+{
+ return impl[op].urid;
+}
+
+static inline LV2_Atom_Forge_Ref
+vm_graph_serialize(vm_api_impl_t *impl, LV2_Atom_Forge *forge, const vm_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 vm_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_FLOAT:
+ {
+ if(ref)
+ ref = lv2_atom_forge_float(forge, cmd->f32);
+ } break;
+ case COMMAND_OPCODE:
+ {
+ const LV2_URID otype = vm_api_map(impl, 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 vm_status_t
+vm_graph_deserialize(vm_api_impl_t *impl, LV2_Atom_Forge *forge,
+ vm_command_t *cmds, uint32_t size, const LV2_Atom *body)
+{
+ vm_command_t *cmd = cmds;
+ memset(cmds, 0x0, sizeof(vm_command_t)*ITEMS_MAX);
+
+ vm_status_t state = VM_STATUS_STATIC;
+
+ LV2_ATOM_TUPLE_BODY_FOREACH(body, size, 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_INT;
+ cmd->i32 = ((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_FLOAT;
+ cmd->f32 = ((const LV2_Atom_Double *)item)->body;
+ }
+ else if(item->type == forge->URID)
+ {
+ cmd->type = COMMAND_OPCODE;
+ cmd->op = vm_api_unmap(impl, ((const LV2_Atom_URID *)item)->body);
+
+ if( (cmd->op == OP_BAR_BEAT)
+ || (cmd->op == OP_BAR)
+ || (cmd->op == OP_BEAT)
+ || (cmd->op == OP_BEAT_UNIT)
+ || (cmd->op == OP_BPB)
+ || (cmd->op == OP_BPM)
+ || (cmd->op == OP_FRAME)
+ //|| (cmd->op == OP_FPS) // is constant
+ || (cmd->op == OP_SPEED) )
+ {
+ state |= VM_STATUS_HAS_TIME;
+ }
+ else if(cmd->op == OP_RAND)
+ {
+ state |= VM_STATUS_HAS_RAND;
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ if(++cmd >= cmds + ITEMS_MAX)
+ break;
+ }
+
+ return state;
+}
+
+static inline LV2_Atom_Forge_Ref
+vm_filter_serialize(LV2_Atom_Forge *forge, const vm_filter_impl_t *impl,
+ const vm_filter_t *filters)
+{
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_tuple(forge, &frame[0]);
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ const vm_filter_t *filter = &filters[i];
+
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_Controller);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_controllerNumber);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, filter->value);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ case FILTER_BENDER:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_Bender);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ case FILTER_PROGRAM_CHANGE:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_ProgramChange);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ case FILTER_CHANNEL_PRESSURE:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_ChannelPressure);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ case FILTER_NOTE_ON:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_NoteOn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_velocity);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->value);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ case FILTER_NOTE_PRESSURE:
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame[1], 0, impl->midi_NotePressure);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_channel);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->channel);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->midi_noteNumber);
+ if(ref)
+ ref =lv2_atom_forge_int(forge, filter->value);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ } break;
+ //FIXME handle more types
+
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[0]);
+
+ return ref;
+}
+
+static inline int
+vm_filter_deserialize(LV2_Atom_Forge *forge, const vm_filter_impl_t *impl,
+ vm_filter_t *filters, uint32_t size, const LV2_Atom *body)
+{
+ memset(filters, 0x0, sizeof(vm_filter_t)*CTRL_MAX);
+
+ unsigned i = 0;
+ LV2_ATOM_TUPLE_BODY_FOREACH(body, size, atom)
+ {
+ vm_filter_t *filter = &filters[i];
+
+ if(lv2_atom_forge_is_object_type(forge, atom->type))
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(obj->body.otype == impl->midi_Controller)
+ {
+ const LV2_Atom_Int *channel = NULL;
+ const LV2_Atom_Int *value = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ impl->midi_controllerNumber, &value,
+ 0);
+
+ filter->type = FILTER_CONTROLLER;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+
+ if(value && (value->atom.type == forge->Int) )
+ filter->value = value->body;
+ }
+ else if(obj->body.otype == impl->midi_Bender)
+ {
+ const LV2_Atom_Int *channel = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ 0);
+
+ filter->type = FILTER_BENDER;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+ }
+ else if(obj->body.otype == impl->midi_ProgramChange)
+ {
+ const LV2_Atom_Int *channel = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ 0);
+
+ filter->type = FILTER_PROGRAM_CHANGE;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+ }
+ else if(obj->body.otype == impl->midi_ChannelPressure)
+ {
+ const LV2_Atom_Int *channel = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ 0);
+
+ filter->type = FILTER_CHANNEL_PRESSURE;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+ }
+ else if(obj->body.otype == impl->midi_NoteOn)
+ {
+ const LV2_Atom_Int *channel = NULL;
+ const LV2_Atom_Int *value = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ impl->midi_velocity, &value,
+ 0);
+
+ filter->type = FILTER_NOTE_ON;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+
+ if(value && (value->atom.type == forge->Int) )
+ filter->value = value->body;
+ }
+ else if(obj->body.otype == impl->midi_NotePressure)
+ {
+ const LV2_Atom_Int *channel = NULL;
+ const LV2_Atom_Int *value = NULL;
+
+ lv2_atom_object_get(obj,
+ impl->midi_channel, &channel,
+ impl->midi_noteNumber, &value,
+ 0);
+
+ filter->type = FILTER_NOTE_PRESSURE;
+
+ if(channel && (channel->atom.type == forge->Int) )
+ filter->channel = channel->body;
+
+ if(value && (value->atom.type == forge->Int) )
+ filter->value = value->body;
+ }
+ //FIXME handle more types
+ }
+
+ i += 1;
+ }
+
+ return 0;
+}
+
+#endif // _VM_LV2_H
diff --git a/vm.ttl b/vm.ttl
new file mode 100644
index 0000000..05005af
--- /dev/null
+++ b/vm.ttl
@@ -0,0 +1,1470 @@
+# Copyright (c) 2017-2021 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 pset: <http://lv2plug.in/ns/ext/presets#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@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:sourceFilter
+ a lv2:Parameter ;
+ rdfs:range atom:Tuple ;
+ rdfs:label "Source Filter" ;
+ rdfs:comment "vm source filter tuple" .
+vm:destinationFilter
+ a lv2:Parameter ;
+ rdfs:range atom:Tuple ;
+ rdfs:label "Destination Filter" ;
+ rdfs:comment "vm destination filter tuple" .
+
+vm:opNop
+ a rdfs:Datatype .
+vm:opInput
+ a rdfs:Datatype .
+vm:opPush
+ a rdfs:Datatype .
+vm:opPop
+ a rdfs:Datatype .
+vm:opSwap
+ a rdfs:Datatype .
+vm:opStore
+ a rdfs:Datatype .
+vm:opLoad
+ a rdfs:Datatype .
+vm:opBreak
+ a rdfs:Datatype .
+vm:opGoto
+ a rdfs:Datatype .
+
+vm:opRand
+ a rdfs:Datatype .
+
+vm:opAdd
+ a rdfs:Datatype .
+vm:opSub
+ a rdfs:Datatype .
+vm:opMul
+ a rdfs:Datatype .
+vm:opDiv
+ a rdfs:Datatype .
+vm:opMod
+ a rdfs:Datatype .
+vm:opPow
+ a rdfs:Datatype .
+
+vm:opNeg
+ a rdfs:Datatype .
+vm:opAbs
+ a rdfs:Datatype .
+vm:opSqrt
+ a rdfs:Datatype .
+vm:opCbrt
+ a rdfs:Datatype .
+
+vm:opFloor
+ a rdfs:Datatype .
+vm:opCeil
+ a rdfs:Datatype .
+vm:opRound
+ a rdfs:Datatype .
+vm:opRint
+ a rdfs:Datatype .
+vm:opTrunc
+ a rdfs:Datatype .
+vm:opModF
+ a rdfs:Datatype .
+
+vm:opExp
+ a rdfs:Datatype .
+vm:opExp2
+ a rdfs:Datatype .
+vm:opLDExp
+ a rdfs:Datatype .
+vm:opFRExp
+ a rdfs:Datatype .
+vm:opLog
+ a rdfs:Datatype .
+vm:opLog2
+ a rdfs:Datatype .
+vm:opLog10
+ a rdfs:Datatype .
+
+vm:opPi
+ a rdfs:Datatype .
+vm:opSin
+ a rdfs:Datatype .
+vm:opCos
+ a rdfs:Datatype .
+vm:opTan
+ a rdfs:Datatype .
+vm:opASin
+ a rdfs:Datatype .
+vm:opACos
+ a rdfs:Datatype .
+vm:opATan
+ a rdfs:Datatype .
+vm:opATan2
+ a rdfs:Datatype .
+vm:opSinH
+ a rdfs:Datatype .
+vm:opCosH
+ a rdfs:Datatype .
+vm:opTanH
+ a rdfs:Datatype .
+vm:opASinH
+ a rdfs:Datatype .
+vm:opACosH
+ a rdfs:Datatype .
+vm:opATanH
+ a rdfs:Datatype .
+
+vm:opEq
+ a rdfs:Datatype .
+vm:opLt
+ a rdfs:Datatype .
+vm:opGt
+ a rdfs:Datatype .
+vm:opLe
+ a rdfs:Datatype .
+vm:opGe
+ a rdfs:Datatype .
+vm:opTernary
+ a rdfs:Datatype .
+vm:opMin
+ a rdfs:Datatype .
+vm:opMax
+ a rdfs:Datatype .
+
+vm:opAnd
+ a rdfs:Datatype .
+vm:opOr
+ a rdfs:Datatype .
+vm:opNot
+ a rdfs:Datatype .
+
+vm:opBAnd
+ a rdfs:Datatype .
+vm:opBOr
+ a rdfs:Datatype .
+vm:opBNot
+ a rdfs:Datatype .
+vm:opLShift
+ a rdfs:Datatype .
+vm:opRShift
+ a rdfs:Datatype .
+
+# Plugin
+vm:control
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "VM Control" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 8192 ;
+ ] , [
+ 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 8192 ;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 2 ;
+ lv2:symbol "control_in_0" ;
+ lv2:name "Control In 0" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:ControlPort;
+ lv2:index 3 ;
+ lv2:symbol "control_in_1" ;
+ lv2:name "Control In 1" ;
+ lv2:minimum -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.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 -1.0;
+ lv2:maximum 1.0;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 7 vm:opInput
+ 6 vm:opInput
+ 5 vm:opInput
+ 4 vm:opInput
+ 3 vm:opInput
+ 2 vm:opInput
+ 1 vm:opInput
+ 0 vm:opInput
+ )
+ ]
+ ] .
+
+# Plugin
+vm:cv
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "VM CV" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 8192 ;
+ ] , [
+ 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 8192 ;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 2 ;
+ lv2:symbol "cv_in_0" ;
+ lv2:name "CV In 0" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 3 ;
+ lv2:symbol "cv_in_1" ;
+ lv2:name "CV In 1" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 4 ;
+ lv2:symbol "cv_in_2" ;
+ lv2:name "CV In 2" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 5 ;
+ lv2:symbol "cv_in_3" ;
+ lv2:name "CV In 3" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 6 ;
+ lv2:symbol "cv_in_4" ;
+ lv2:name "CV In 4" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 7 ;
+ lv2:symbol "cv_in_5" ;
+ lv2:name "CV In 5" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 8 ;
+ lv2:symbol "cv_in_6" ;
+ lv2:name "CV In 6" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ lv2:CVPort;
+ lv2:index 9 ;
+ lv2:symbol "cv_in_7" ;
+ lv2:name "CV In 7" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 10 ;
+ lv2:symbol "cv_out_0" ;
+ lv2:name "CV Out 0" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 11 ;
+ lv2:symbol "cv_out_1" ;
+ lv2:name "CV Out 1" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 12 ;
+ lv2:symbol "cv_out_2" ;
+ lv2:name "CV Out 2" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 13 ;
+ lv2:symbol "cv_out_3" ;
+ lv2:name "CV Out 3" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 14 ;
+ lv2:symbol "cv_out_4" ;
+ lv2:name "CV Out 4" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 15 ;
+ lv2:symbol "cv_out_5" ;
+ lv2:name "CV Out 5" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 16 ;
+ lv2:symbol "cv_out_6" ;
+ lv2:name "CV Out 6" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 17 ;
+ lv2:symbol "cv_out_7" ;
+ lv2:name "CV Out 7" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 7 vm:opInput
+ 6 vm:opInput
+ 5 vm:opInput
+ 4 vm:opInput
+ 3 vm:opInput
+ 2 vm:opInput
+ 1 vm:opInput
+ 0 vm:opInput
+ )
+ ]
+ ] .
+
+# Plugin
+vm:audio
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "VM Audio" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 8192 ;
+ ] , [
+ 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 8192 ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 2 ;
+ lv2:symbol "audio_in_0" ;
+ lv2:name "Audio In 0" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 3 ;
+ lv2:symbol "audio_in_1" ;
+ lv2:name "Audio In 1" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 4 ;
+ lv2:symbol "audio_in_2" ;
+ lv2:name "Audio In 2" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 5 ;
+ lv2:symbol "audio_in_3" ;
+ lv2:name "Audio In 3" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 6 ;
+ lv2:symbol "audio_in_4" ;
+ lv2:name "Audio In 4" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 7 ;
+ lv2:symbol "audio_in_5" ;
+ lv2:name "Audio In 5" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 8 ;
+ lv2:symbol "audio_in_6" ;
+ lv2:name "Audio In 6" ;
+ ] , [
+ a lv2:InputPort,
+ lv2:AudioPort;
+ lv2:index 9 ;
+ lv2:symbol "audio_in_7" ;
+ lv2:name "Audio In 7" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 10 ;
+ lv2:symbol "audio_out_0" ;
+ lv2:name "Audio Out 0" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 11 ;
+ lv2:symbol "audio_out_1" ;
+ lv2:name "Audio Out 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 12 ;
+ lv2:symbol "audio_out_2" ;
+ lv2:name "Audio Out 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 13 ;
+ lv2:symbol "audio_out_3" ;
+ lv2:name "Audio Out 3" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 14 ;
+ lv2:symbol "audio_out_4" ;
+ lv2:name "Audio Out 4" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 15 ;
+ lv2:symbol "audio_out_5" ;
+ lv2:name "Audio Out 5" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 16 ;
+ lv2:symbol "audio_out_6" ;
+ lv2:name "Audio Out 6" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 17 ;
+ lv2:symbol "audio_out_7" ;
+ lv2:name "Audio Out 7" ;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 7 vm:opInput
+ 6 vm:opInput
+ 5 vm:opInput
+ 4 vm:opInput
+ 3 vm:opInput
+ 2 vm:opInput
+ 1 vm:opInput
+ 0 vm:opInput
+ )
+ ]
+ ] .
+
+# Plugin
+vm:atom
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "VM Atom" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 8192 ;
+ ] , [
+ 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 8192 ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 2 ;
+ lv2:symbol "event_in_0" ;
+ lv2:name "Event In 0" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 3 ;
+ lv2:symbol "event_in_1" ;
+ lv2:name "Event In 1" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 4 ;
+ lv2:symbol "event_in_2" ;
+ lv2:name "Event In 2" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 5 ;
+ lv2:symbol "event_in_3" ;
+ lv2:name "Event In 3" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 6 ;
+ lv2:symbol "event_in_4" ;
+ lv2:name "Event In 4" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 7 ;
+ lv2:symbol "event_in_5" ;
+ lv2:name "Event In 5" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 8 ;
+ lv2:symbol "event_in_6" ;
+ lv2:name "Event In 6" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 9 ;
+ lv2:symbol "event_in_7" ;
+ lv2:name "Event In 7" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ lv2:default 0.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 10 ;
+ lv2:symbol "event_out_0" ;
+ lv2:name "Event Out 0" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 11 ;
+ lv2:symbol "event_out_1" ;
+ lv2:name "Event Out 1" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 12 ;
+ lv2:symbol "event_out_2" ;
+ lv2:name "Event Out 2" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 13 ;
+ lv2:symbol "event_out_3" ;
+ lv2:name "Event Out 3" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 14 ;
+ lv2:symbol "event_out_4" ;
+ lv2:name "Event Out 4" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 15 ;
+ lv2:symbol "event_out_5" ;
+ lv2:name "Event Out 5" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 16 ;
+ lv2:symbol "event_out_6" ;
+ lv2:name "Event Out 6" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Float ;
+ lv2:index 17 ;
+ lv2:symbol "event_out_7" ;
+ lv2:name "Event Out 7" ;
+ lv2:minimum -1.0;
+ lv2:maximum 1.0;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 7 vm:opInput
+ 6 vm:opInput
+ 5 vm:opInput
+ 4 vm:opInput
+ 3 vm:opInput
+ 2 vm:opInput
+ 1 vm:opInput
+ 0 vm:opInput
+ )
+ ]
+ ] .
+
+# Plugin
+vm:midi
+ a lv2:Plugin,
+ lv2:ConverterPlugin ;
+ doap:name "VM MIDI" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:vm ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, log:log, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ rsz:minimumSize 8192 ;
+ ] , [
+ 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 8192 ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 2 ;
+ lv2:symbol "event_in_0" ;
+ lv2:name "Event In 0" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 3 ;
+ lv2:symbol "event_in_1" ;
+ lv2:name "Event In 1" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 4 ;
+ lv2:symbol "event_in_2" ;
+ lv2:name "Event In 2" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 5 ;
+ lv2:symbol "event_in_3" ;
+ lv2:name "Event In 3" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 6 ;
+ lv2:symbol "event_in_4" ;
+ lv2:name "Event In 4" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 7 ;
+ lv2:symbol "event_in_5" ;
+ lv2:name "Event In 5" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 8 ;
+ lv2:symbol "event_in_6" ;
+ lv2:name "Event In 6" ;
+ ] , [
+ a lv2:InputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 9 ;
+ lv2:symbol "event_in_7" ;
+ lv2:name "Event In 7" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 10 ;
+ lv2:symbol "event_out_0" ;
+ lv2:name "Event Out 0" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 11 ;
+ lv2:symbol "event_out_1" ;
+ lv2:name "Event Out 1" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 12 ;
+ lv2:symbol "event_out_2" ;
+ lv2:name "Event Out 2" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 13 ;
+ lv2:symbol "event_out_3" ;
+ lv2:name "Event Out 3" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 14 ;
+ lv2:symbol "event_out_4" ;
+ lv2:name "Event Out 4" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 15 ;
+ lv2:symbol "event_out_5" ;
+ lv2:name "Event Out 5" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 16 ;
+ lv2:symbol "event_out_6" ;
+ lv2:name "Event Out 6" ;
+ ] , [
+ a lv2:OutputPort,
+ atom:AtomPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 17 ;
+ lv2:symbol "event_out_7" ;
+ lv2:name "Event Out 7" ;
+ ] ;
+
+ #patch:writable
+ # vm:graph ;
+
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 7 vm:opInput
+ 6 vm:opInput
+ 5 vm:opInput
+ 4 vm:opInput
+ 3 vm:opInput
+ 2 vm:opInput
+ 1 vm:opInput
+ 0 vm:opInput
+ )
+ ] ;
+ vm:sourceFilter [
+ a atom:Tuple ;
+ rdf:value (
+ [ a midi:Controller ; midi:channel 0 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 1 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 2 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 3 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 4 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 5 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 6 ; midi:controllerNumber 1 ]
+ [ a midi:Controller ; midi:channel 7 ; midi:controllerNumber 1 ]
+ )
+ ] ;
+ vm:destinationFilter [
+ a atom:Tuple ;
+ rdf:value (
+ [ a midi:Bender ; midi:channel 0 ]
+ [ a midi:Bender ; midi:channel 1 ]
+ [ a midi:Bender ; midi:channel 2 ]
+ [ a midi:Bender ; midi:channel 3 ]
+ [ a midi:Bender ; midi:channel 4 ]
+ [ a midi:Bender ; midi:channel 5 ]
+ [ a midi:Bender ; midi:channel 6 ]
+ [ a midi:Bender ; midi:channel 7 ]
+ )
+ ]
+ ] .
+
+
+vm:add
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Add" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opAdd
+ )
+ ]
+ ] .
+
+vm:sumLoop
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Sum in a loop" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ # Acc := 0.0
+ # for(Store0 := 7; Store0 >= 0; Store0 := Store0 - 1)
+ # Acc := Acc + Input(Store0)
+ 0.0
+ 7
+ 0 vm:opStore
+ 0 vm:opLoad # goto target
+ vm:opInput
+ vm:opAdd
+ 0 vm:opLoad
+ 1 vm:opSub
+ 0 vm:opStore
+ 4
+ 0 vm:opLoad
+ 0 vm:opGe
+ vm:opGoto
+ )
+ ]
+ ] .
+
+vm:sumLinear
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Sum" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opAdd
+ 2 vm:opInput
+ vm:opAdd
+ 3 vm:opInput
+ vm:opAdd
+ 4 vm:opInput
+ vm:opAdd
+ 5 vm:opInput
+ vm:opAdd
+ 6 vm:opInput
+ vm:opAdd
+ 7 vm:opInput
+ vm:opAdd
+ )
+ ]
+ ] .
+
+vm:sub
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Subtract" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opSub
+ )
+ ]
+ ] .
+
+vm:mul
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Multiply" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opMul
+ )
+ ]
+ ] .
+
+vm:div
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Divide" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opDiv
+ )
+ ]
+ ] .
+
+vm:mod
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Modulo" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opMod
+ )
+ ]
+ ] .
+
+vm:pow
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Power" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ 0 vm:opInput
+ 1 vm:opInput
+ vm:opPow
+ )
+ ]
+ ] .
+
+vm:dBFS
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "dBFS" ;
+ lv2:port [
+ lv2:symbol "control_in_0" ;
+ pset:value 0.5
+ ] ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ # dBFS := Log10( |Control0| ) * 20.0
+ 0 vm:opInput
+ vm:opAbs
+ vm:opLog10
+ 20.0 vm:opMul
+ 70.0 vm:opDiv
+ )
+ ]
+ ] .
+
+vm:rand
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Random" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ vm:opRand
+ )
+ ]
+ ] .
+
+vm:sampleAndHold
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "Sample and hold" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ # Reg0 := (Beat % 1 == 0) ? Rand : Reg0
+ vm:opRand
+ 0 vm:opLoad
+ time:barBeat vm:opModF vm:opPop
+ 0 vm:opEq vm:opTernary
+ vm:opPush
+ 0 vm:opStore
+ )
+ ]
+ ] .
+
+vm:lfo
+ a pset:Preset ;
+ lv2:appliesTo vm:control ,
+ vm:cv ,
+ vm:atom ,
+ vm:midi ,
+ vm:audio ;
+ rdfs:label "LFO" ;
+ state:state [
+ vm:graph [
+ a atom:Tuple ;
+ rdf:value (
+ # Phase := (Beat % Cycles) / Control0
+ 2.0 # Cycles per beat
+ vm:opPush
+ time:beat
+ vm:opSwap
+ vm:opMod
+ vm:opSwap
+ vm:opDiv
+ 0 vm:opStore
+
+ # SquareLFO := (Phase > 0.25) && (Phase < 0.75)
+ 0 vm:opLoad
+ 0.25 vm:opGt
+ 0 vm:opLoad
+ 0.75 vm:opLt
+ vm:opAnd
+ 2.0 vm:opMul
+ 1.0 vm:opSub
+
+ # TriangleLFO := (Phase > 0.5 ? (1.0 - Phase) : Phase) * 2.0
+ 1.0
+ 0 vm:opLoad
+ vm:opSub
+ 0 vm:opLoad
+ vm:opPush
+ 0.5 vm:opGt
+ vm:opTernary
+ 2.0 vm:opMul
+ 2.0 vm:opMul
+ 1.0 vm:opSub
+
+ # SineLFO := Sin( (Phase - 0.25) * 2.0 * PI) * 0.5 + 0.5
+ 0 vm:opLoad
+ 0.25 vm:opSub
+ 2.0 vm:opMul
+ vm:opPi vm:opMul
+ vm:opSin
+ 0.5 vm:opMul
+ 0.5 vm:opAdd
+ 2.0 vm:opMul
+ 1.0 vm:opSub
+
+ # push phase
+ 0 vm:opLoad
+ 2.0 vm:opMul
+ 1.0 vm:opSub
+ )
+ ]
+ ] .
diff --git a/vm_ui.c b/vm_ui.c
new file mode 100644
index 0000000..5693720
--- /dev/null
+++ b/vm_ui.c
@@ -0,0 +1,1479 @@
+/*
+ * Copyright (c) 2017-2021 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
+
+#define PLOT_MAX 256
+#define PLOT_MASK (PLOT_MAX - 1)
+
+typedef struct _lv2_atom_midi_t lv2_atom_midi_t;
+typedef struct _atom_ser_t atom_ser_t;
+typedef struct _plot_t plot_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _lv2_atom_midi_t {
+ LV2_Atom atom;
+ uint8_t body [3];
+};
+
+struct _atom_ser_t {
+ uint32_t size;
+ uint32_t offset;
+ union {
+ const LV2_Atom *atom;
+ const LV2_Atom_Event *ev;
+ uint8_t *buf;
+ };
+};
+
+struct _plot_t {
+ float vals [PLOT_MAX];
+ int window;
+ double pre;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Atom_Forge forge;
+
+ vm_plug_enum_t vm_plug;
+
+ float scale;
+
+ LV2_URID atom_eventTransfer;
+ LV2_URID vm_graph;
+ LV2_URID vm_sourceFilter;
+ LV2_URID vm_destinationFilter;
+ LV2_URID midi_MidiEvent;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ nk_pugl_window_t win;
+ struct {
+ struct nk_image cancel;
+ struct nk_image up_arrow;
+ struct nk_image plus;
+ } icons;
+
+ LV2UI_Controller *controller;
+ LV2UI_Write_Function writer;
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ uint32_t graph_size;
+ uint32_t sourceFilter_size;
+ uint32_t destinationFilter_size;
+
+ float dy;
+
+ atom_ser_t ser;
+ vm_api_impl_t api [OP_MAX];
+ vm_filter_t sourceFilter [CTRL_MAX];
+ vm_filter_t destinationFilter [CTRL_MAX];
+ vm_filter_impl_t filt;
+
+ float in0 [CTRL_MAX];
+ float out0 [CTRL_MAX];
+
+ float in1 [CTRL_MAX];
+ float out1 [CTRL_MAX];
+
+ float in2 [CTRL_MAX];
+ float out2 [CTRL_MAX];
+
+ int64_t off;
+ plot_t inp [CTRL_MAX];
+ plot_t outp [CTRL_MAX];
+
+ float sample_rate;
+
+ vm_command_t cmds [ITEMS_MAX];
+};
+
+static const char *command_labels [COMMAND_MAX] = {
+ [COMMAND_NOP] = "",
+ [COMMAND_OPCODE] = "Operation",
+ [COMMAND_BOOL] = "Boolean",
+ [COMMAND_INT] = "Integer",
+ [COMMAND_FLOAT] = "Float",
+};
+
+static const char *filter_labels [FILTER_MAX] = {
+ [FILTER_CONTROLLER] = "Controller",
+ [FILTER_BENDER] = "Bender",
+ [FILTER_PROGRAM_CHANGE] = "Program Change",
+ [FILTER_CHANNEL_PRESSURE] = "Channel Pressure",
+ [FILTER_NOTE_ON] = "Note On",
+ [FILTER_NOTE_PRESSURE] = "Note Pressure",
+};
+
+static const char *input_labels [CTRL_MAX] = {
+ "In 0:",
+ "In 1:",
+ "In 2:",
+ "In 3:",
+ "In 4:",
+ "In 5:",
+ "In 6:",
+ "In 7:",
+};
+
+static const struct nk_color plot_bg_color = {
+ .r = 0x22, .g = 0x22, .b = 0x22, .a = 0xff
+};
+
+static const struct nk_color plot_fg_color = {
+ .r = 0xbb, .g = 0x66, .b = 0x00, .a = 0xff
+};
+
+static const struct nk_color plot_fg2_color = {
+ .r = 0xbb, .g = 0x66, .b = 0x00, .a = 0x7f
+};
+
+static const char *ms_label = "#ms:";
+static const char *nil_label = "#";
+static const char *chn_label = "#chn:";
+static const char *val_label = "#val:";
+
+static void
+_intercept_graph(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->graph_size = impl->value.size;
+
+ vm_graph_deserialize(handle->api, &handle->forge, handle->cmds,
+ impl->value.size, impl->value.body);
+}
+
+static void
+_intercept_sourceFilter(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->sourceFilter_size = impl->value.size;
+
+ const int status = vm_filter_deserialize(&handle->forge, &handle->filt,
+ handle->sourceFilter, impl->value.size, impl->value.body);
+ (void)status; //FIXME
+}
+
+static void
+_intercept_destinationFilter(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->destinationFilter_size = impl->value.size;
+
+ const int status = vm_filter_deserialize(&handle->forge, &handle->filt,
+ handle->destinationFilter, impl->value.size, impl->value.body);
+ (void)status; //FIXME
+}
+
+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_cb = _intercept_graph
+ },
+ {
+ .property = VM__sourceFilter,
+ .offset = offsetof(plugstate_t, sourceFilter),
+ .type = LV2_ATOM__Tuple,
+ .max_size = FILTER_SIZE,
+ .event_cb = _intercept_sourceFilter,
+ },
+ {
+ .property = VM__destinationFilter,
+ .offset = offsetof(plugstate_t, destinationFilter),
+ .type = LV2_ATOM__Tuple,
+ .max_size = FILTER_SIZE,
+ .event_cb = _intercept_destinationFilter,
+ }
+};
+
+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
+_restore_state(plughandle_t *handle)
+{
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_get);
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ const LV2_Atom *atom = ser->atom;
+ handle->writer(handle->controller, 0, lv2_atom_total_size(atom),
+ handle->atom_eventTransfer, atom);
+}
+
+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 inline void
+_draw_plot(struct nk_context *ctx, const float *vals, vm_plug_enum_t vm_plug)
+{
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect bounds;
+ const nk_flags states = nk_widget(&bounds, ctx);
+ if(states != NK_WIDGET_INVALID)
+ {
+ const struct nk_rect old_clip = canvas->clip;
+ const struct nk_rect outer = nk_pad_rect(bounds, nk_vec2(-2.f, -2.f));
+ struct nk_rect new_clip;
+ nk_unify(&new_clip, &old_clip, outer.x, outer.y,
+ outer.x + outer.w, outer.y + outer.h);
+
+ nk_push_scissor(canvas, new_clip);
+ nk_fill_rect(canvas, bounds, 0.f, plot_bg_color);
+ nk_stroke_rect(canvas, bounds, 0.f, 1.f, ctx->style.window.border_color);
+
+ float mem [PLOT_MAX*4];
+ float x0 = -1.f;
+ unsigned J = 0;
+
+ const float yh = bounds.y + 0.5f*bounds.h;
+
+ for(unsigned i = 0; i < PLOT_MAX; i++)
+ {
+ const float sx = (float)i / PLOT_MAX;
+ const float x1 = bounds.x + sx*bounds.w;
+ if(x1 - x0 < 1.f)
+ continue;
+
+ const float sy = vals[i] / VM_VIS;
+
+ if(vm_plug == VM_PLUG_AUDIO)
+ {
+ mem[J++] = x1;
+ mem[J++] = fabsf(sy);
+ x0 = x1;
+ }
+ else
+ {
+ const float dy = sy*bounds.h;
+
+ mem[J++] = x1;
+ mem[J++] = yh - dy;
+ x0 = x1;
+ }
+ }
+
+ nk_stroke_line(canvas, bounds.x, yh, bounds.x + bounds.w, yh, 1.f,
+ ctx->style.window.border_color);
+
+ if(vm_plug == VM_PLUG_AUDIO)
+ {
+ for(unsigned j = 0, k = 2*J; j < J; j += 2, k -= 2)
+ {
+ const float x = mem[j];
+ const float sy = mem[j + 1];
+ const float dy = sy*bounds.h;
+ const float y1 = yh - dy;
+ const float y2 = yh + dy;
+
+ mem[j + 1] = y1;
+
+ mem[k - 1] = y2;
+ mem[k - 2] = x;
+ }
+
+ nk_stroke_polyline(canvas, &mem[J], J/2, 1.f, plot_fg2_color);
+ }
+
+ nk_stroke_polyline(canvas, mem, J/2, 1.f, plot_fg_color);
+
+ nk_push_scissor(canvas, old_clip);
+ }
+}
+
+#define dBFS6_min -54
+#define dBFS6_max 6
+#define dBFS6_div 2
+#define dBFS6_rng (dBFS6_max - dBFS6_min)
+
+static const float mx1 = (float)(dBFS6_rng - 2*dBFS6_max) / dBFS6_rng;
+static const float mx2 = (float)(2*dBFS6_max) / dBFS6_rng;
+
+static inline float
+dBFS6(float peak)
+{
+ const float d = dBFS6_max + 20.f * log10f(fabsf(peak) / dBFS6_div);
+ const float e = (d - dBFS6_min) / dBFS6_rng;
+ return NK_CLAMP(0.f, e, 1.f);
+}
+
+static inline void
+_draw_mixer(struct nk_context *ctx, float peak)
+{
+ struct nk_rect bounds;
+ const enum nk_widget_layout_states states = nk_widget(&bounds, ctx);
+ if(states != NK_WIDGET_INVALID)
+ {
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const struct nk_color bg = ctx->style.property.normal.data.color;
+ nk_fill_rect(canvas, bounds, ctx->style.property.rounding, bg);
+ nk_stroke_rect(canvas, bounds, ctx->style.property.rounding, ctx->style.property.border, bg);
+
+ const struct nk_rect orig = bounds;
+ struct nk_rect outline;
+ const uint8_t alph = 0x7f;
+
+ const float ox = ctx->style.font->height/2 + ctx->style.property.border + ctx->style.property.padding.x;
+ const float oy = ctx->style.property.border + ctx->style.property.padding.y;
+ bounds.x += ox;
+ bounds.y += oy;
+ bounds.w -= 2*ox;
+ bounds.h -= 2*oy;
+ outline = bounds;
+
+ if(peak > 0.f) //FIXME use threshold > 0.f
+ {
+ // <= -6dBFS
+ {
+ const float dbfs = NK_MIN(peak, mx1);
+ const uint8_t dcol = 0xff * dbfs / mx1;
+ const struct nk_color left = nk_rgba(0x00, 0xff, 0xff, alph);
+ const struct nk_color bottom = left;
+ const struct nk_color right = nk_rgba(dcol, 0xff, 0xff-dcol, alph);
+ const struct nk_color top = right;
+
+ bounds.w *= dbfs;
+
+ nk_fill_rect_multi_color(canvas, bounds, left, top, right, bottom);
+ }
+
+ // > 6dBFS
+ if(peak > mx1)
+ {
+ const float dbfs = peak - mx1;
+ const uint8_t dcol = 0xff * dbfs / mx2;
+ const struct nk_color left = nk_rgba(0xff, 0xff, 0x00, alph);
+ const struct nk_color bottom = left;
+ const struct nk_color right = nk_rgba(0xff, 0xff - dcol, 0x00, alph);
+ const struct nk_color top = right;
+
+ bounds = outline;
+ bounds.x += bounds.w * mx1;
+ bounds.w *= dbfs;
+ nk_fill_rect_multi_color(canvas, bounds, left, top, right, bottom);
+ }
+ }
+
+ // draw 6dBFS lines from -60 to +6
+ for(unsigned i = 0; i <= dBFS6_rng; i += dBFS6_max)
+ {
+ const bool is_zero = (i == dBFS6_rng - dBFS6_max);
+ const float dx = outline.w * i / dBFS6_rng;
+
+ const float x0 = outline.x + dx;
+ const float y0 = is_zero ? orig.y + 2.f : outline.y;
+
+ const float border = (is_zero ? 2.f : 1.f) * ctx->style.window.group_border;
+
+ const float x1 = x0;
+ const float y1 = is_zero ? orig.y + orig.h - 2.f : outline.y + outline.h;
+
+ nk_stroke_line(canvas, x0, y0, x1, y1, border, ctx->style.window.group_border_color);
+ }
+
+ nk_stroke_rect(canvas, outline, 0.f, ctx->style.window.group_border, ctx->style.window.group_border_color);
+ }
+}
+
+static void
+_wheel_float(struct nk_context *ctx, float *value, float stp)
+{
+ const struct nk_rect bounds = nk_widget_bounds(ctx);
+
+ if(nk_input_is_mouse_hovering_rect(&ctx->input, bounds))
+ {
+ if(ctx->input.mouse.scroll_delta.y > 0.f)
+ {
+ *value += stp;
+ ctx->input.mouse.scroll_delta.y = 0.f;
+ }
+ else if(ctx->input.mouse.scroll_delta.y < 0.f)
+ {
+ *value -= stp;
+ ctx->input.mouse.scroll_delta.y = 0.f;
+ }
+ }
+}
+
+static void
+_wheel_int(struct nk_context *ctx, int *value)
+{
+ const struct nk_rect bounds = nk_widget_bounds(ctx);
+
+ if(nk_input_is_mouse_hovering_rect(&ctx->input, bounds))
+ {
+ if(ctx->input.mouse.scroll_delta.y > 0.f)
+ {
+ *value += 1;
+ ctx->input.mouse.scroll_delta.y = 0.f;
+ }
+ else if(ctx->input.mouse.scroll_delta.y < 0.f)
+ {
+ *value -= 1;
+ ctx->input.mouse.scroll_delta.y = 0.f;
+ }
+ }
+}
+
+static void
+_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ handle->dy = 20.f * handle->scale;
+ const float dy = handle->dy;
+
+ // mouse sensitivity for dragable property widgets
+ const bool has_control = nk_input_is_key_down(&ctx->input, NK_KEY_CTRL);
+ const bool has_shift = nk_input_is_key_down(&ctx->input, NK_KEY_SHIFT);
+ float scl = 1.f;
+ if(has_control)
+ scl /= 4;
+ if(has_shift)
+ scl /= 4;
+
+ const char *window_name = "Vm";
+ if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ const float wh = wbounds.h
+ - 2*ctx->style.window.padding.y
+ - 2*ctx->style.window.border;
+
+ const float ratio [3] = {0.25f, 0.5f, 0.25f};
+ nk_layout_row(ctx, NK_DYNAMIC, wh, 3, ratio);
+
+ if(nk_group_begin(ctx, "Inputs", NK_WINDOW_TITLE | NK_WINDOW_BORDER))
+ {
+ float stp;
+ float fpp;
+
+ bool sync = false;
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ nk_layout_row_dynamic(ctx, dy*4, 1);
+ _draw_plot(ctx, handle->inp[i].vals, handle->vm_plug);
+
+ nk_layout_row_dynamic(ctx, dy, 2);
+ if( (handle->vm_plug == VM_PLUG_CONTROL)
+ || (handle->vm_plug == VM_PLUG_CV)
+ || (handle->vm_plug == VM_PLUG_ATOM)
+ || (handle->vm_plug == VM_PLUG_MIDI) )
+ {
+ if(i == 0) // calculate only once
+ {
+ stp = scl * VM_STP;
+ fpp = scl * VM_RNG / nk_widget_width(ctx);
+ }
+ const float old_val = handle->in0[i];
+ _wheel_float(ctx, &handle->in0[i], stp);
+ nk_property_float(ctx, input_labels[i], VM_MIN, &handle->in0[i], VM_MAX, stp, fpp);
+
+ if(old_val != handle->in0[i])
+ {
+ const float out1 = handle->in0[i];
+
+ if(handle->vm_plug == VM_PLUG_ATOM)
+ {
+ const LV2_Atom_Float flt = {
+ .atom = {
+ .size = sizeof(float),
+ .type = handle->forge.Float
+ },
+ .body = out1
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&flt.atom), handle->atom_eventTransfer, &flt);
+ }
+ else if(handle->vm_plug == VM_PLUG_MIDI)
+ {
+ vm_filter_t *filter = &handle->sourceFilter[i];
+
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 3,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_CONTROLLER | filter->channel,
+ [1] = filter->value,
+ [2] = value
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ case FILTER_BENDER:
+ {
+ const int16_t value = floor(out1*0x2000 + 0x1fff);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 3,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_BENDER | filter->channel,
+ [1] = value & 0x7f,
+ [2] = value >> 7
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ case FILTER_PROGRAM_CHANGE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 2,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_PGM_CHANGE | filter->channel,
+ [1] = value
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ case FILTER_CHANNEL_PRESSURE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 2,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_CHANNEL_PRESSURE | filter->channel,
+ [1] = value
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ case FILTER_NOTE_ON:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 3,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_NOTE_ON | filter->channel,
+ [1] = value,
+ [2] = filter->value
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ case FILTER_NOTE_PRESSURE:
+ {
+ const uint8_t value = floor(out1 * 0x7f);
+ const lv2_atom_midi_t msg = {
+ .atom = {
+ .size = 3,
+ .type = handle->midi_MidiEvent
+ },
+ .body = {
+ [0] = LV2_MIDI_MSG_NOTE_PRESSURE | filter->channel,
+ [1] = filter->value,
+ [2] = value
+ }
+ };
+
+ handle->writer(handle->controller, i + 2,
+ lv2_atom_total_size(&msg.atom), handle->atom_eventTransfer, &msg);
+ } break;
+ //FIXME handle more types
+
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+ }
+ else // CONTROL, CV
+ {
+ handle->writer(handle->controller, i + 2,
+ sizeof(float), 0, &out1);
+ }
+ }
+ }
+ else if(handle->vm_plug == VM_PLUG_AUDIO)
+ {
+ _draw_mixer(ctx, handle->in1[i]);
+
+ if(handle->in2[i] > handle->in1[i])
+ handle->in1[i] = handle->in2[i];
+ else
+ handle->in1[i] -= (handle->in1[i] - handle->in2[i]) * 0.1; //FIXME make dependent on ui:updateRate
+ }
+
+ const int old_window = handle->inp[i].window;
+ _wheel_int(ctx, &handle->inp[i].window);
+ nk_property_int(ctx, ms_label, 10, &handle->inp[i].window, 100000, 1, 1.f);
+ if(old_window != handle->inp[i].window)
+ memset(handle->inp[i].vals, 0x0, sizeof(float)*PLOT_MAX);
+
+ if(handle->vm_plug == VM_PLUG_MIDI)
+ {
+ vm_filter_t *filter = &handle->sourceFilter[i];
+
+ nk_layout_row_dynamic(ctx, dy, 3);
+
+ int filter_type = filter->type;
+ nk_combobox(ctx, filter_labels, FILTER_MAX, &filter_type,
+ dy, nk_vec2(nk_widget_width(ctx), dy*14)); //FIXME
+ if(filter->type != (vm_filter_enum_t)filter_type)
+ {
+ filter->type = filter_type;
+ sync = true;
+ }
+
+ const int old_channel = filter->channel;
+ filter->channel = nk_propertyi(ctx, chn_label, 0x0, old_channel, 0x7, 1, 1.f);
+ if(old_channel != filter->channel)
+ sync = true;
+
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ case FILTER_NOTE_PRESSURE:
+ {
+ const int old_value = filter->value;
+ filter->value = nk_propertyi(ctx, val_label, 0x0, old_value, 0x7f, 1, 1.f);
+ if(old_value != filter->value)
+ sync = true;
+ } break;
+
+ case FILTER_BENDER:
+ case FILTER_PROGRAM_CHANGE:
+ case FILTER_CHANNEL_PRESSURE:
+ case FILTER_NOTE_ON:
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+ }
+ }
+
+ if(sync)
+ {
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+ vm_filter_serialize(&handle->forge, &handle->filt, handle->sourceFilter);
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->vm_sourceFilter);
+ if(impl)
+ _props_impl_set(&handle->props, impl, ser->atom->type, ser->atom->size, LV2_ATOM_BODY_CONST(ser->atom));
+
+ _set_property(handle, handle->vm_sourceFilter);
+ }
+
+ nk_group_end(ctx);
+ }
+
+ if(nk_group_begin(ctx, "Program", NK_WINDOW_TITLE | NK_WINDOW_BORDER))
+ {
+ const float ratio2 [6] = {
+ 0.1, 0.05, 0.05, 0.05, 0.3, 0.45
+ };
+ nk_layout_row(ctx, NK_DYNAMIC, dy, 6, ratio2);
+
+ bool sync = false;
+
+ const float stp = scl * VM_STP;
+ const float fpp = scl * VM_RNG / nk_widget_width(ctx);
+
+ for(unsigned i = 0; i < ITEMS_MAX; i++)
+ {
+ vm_command_t *cmd = &handle->cmds[i];
+ bool terminate = false;
+
+ nk_labelf(ctx, NK_TEXT_CENTERED, "%03u", i);
+ if(cmd->type == COMMAND_NOP)
+ {
+ nk_spacing(ctx, 3);
+ }
+ else
+ {
+ if(nk_button_image_label(ctx, handle->icons.plus, "", NK_TEXT_RIGHT)) // insert cmd
+ {
+ for(unsigned j = ITEMS_MAX - 1; j > i; j--)
+ handle->cmds[j] = handle->cmds[j - 1];
+
+ cmd->type = COMMAND_OPCODE;
+ cmd->i32 = 0;
+ sync = true;
+ }
+
+ if(nk_button_image_label(ctx, handle->icons.cancel, "", NK_TEXT_RIGHT)) // remove cmd
+ {
+ for(unsigned j = i; j < ITEMS_MAX - 1; j++)
+ handle->cmds[j] = handle->cmds[j + 1];
+
+ sync = true;
+ }
+
+ if(i == 0)
+ {
+ nk_spacing(ctx, 1);
+ }
+ else if(nk_button_image_label(ctx, handle->icons.up_arrow, "", NK_TEXT_RIGHT)) // swap cmd with one above
+ {
+ const vm_command_t tmp = handle->cmds[i];
+ handle->cmds[i] = handle->cmds[i - 1];
+ handle->cmds[i - 1] = tmp;
+
+ sync = true;
+ }
+ }
+
+ const vm_command_enum_t old_cmd_type = cmd->type;
+ int cmd_type = old_cmd_type;
+ nk_combobox(ctx, command_labels, COMMAND_MAX, &cmd_type,
+ dy, nk_vec2(nk_widget_width(ctx), dy*14));
+ if(old_cmd_type != (vm_command_enum_t)cmd_type)
+ {
+ cmd->type = cmd_type;
+ cmd->i32 = 0; // reset value
+ sync = true;
+ }
+
+ switch(cmd->type)
+ {
+ case COMMAND_BOOL:
+ {
+ if(nk_button_symbol_label(ctx,
+ cmd->i32 ? NK_SYMBOL_CIRCLE_SOLID : NK_SYMBOL_CIRCLE_OUTLINE,
+ cmd->i32 ? "true" : "false", NK_TEXT_LEFT))
+ {
+ cmd->i32 = !cmd->i32;
+ sync = true;
+ }
+ } break;
+ case COMMAND_INT:
+ {
+ int i32 = cmd->i32;
+ _wheel_int(ctx, &i32);
+ nk_property_int(ctx, nil_label, INT32_MIN, &i32, INT32_MAX, 1, 1.f);
+ if(i32 != cmd->i32)
+ {
+ cmd->i32 = i32;
+ sync = true;
+ }
+ } break;
+ case COMMAND_FLOAT:
+ {
+ float f32 = cmd->f32;
+ _wheel_float(ctx, &f32, stp);
+ nk_property_float(ctx, nil_label, -HUGE_VAL, &f32, HUGE_VAL, stp, fpp);
+ if(f32 != cmd->f32)
+ {
+ cmd->f32 = f32;
+ sync = true;
+ }
+ } break;
+ case COMMAND_OPCODE:
+ {
+ const bool show_mnemo = true; //FIXME
+ const char *desc = show_mnemo && vm_api_def[cmd->op].mnemo
+ ? vm_api_def[cmd->op].mnemo
+ : vm_api_def[cmd->op].label;
+ if(nk_combo_begin_label(ctx, desc, nk_vec2(nk_widget_width(ctx), dy*14)))
+ {
+ nk_layout_row_dynamic(ctx, dy, 1);
+ for(unsigned op = 0; op < OP_MAX; op++)
+ {
+ desc = show_mnemo && vm_api_def[op].mnemo
+ ? vm_api_def[op].mnemo
+ : vm_api_def[op].label;
+ if(nk_combo_item_label(ctx, desc, NK_TEXT_LEFT))
+ {
+ cmd->op = op;
+ sync = true;
+ }
+ }
+ nk_combo_end(ctx);
+ }
+ } break;
+ case COMMAND_NOP:
+ {
+ struct nk_keyboard *keybd = &ctx->input.keyboard;
+
+ nk_spacing(ctx, 1);
+
+ if(keybd->text_len == 1)
+ {
+ const char key = keybd->text[0];
+
+ switch(key)
+ {
+ case 'o':
+ {
+ cmd->type = COMMAND_OPCODE;
+ cmd->op = OP_NOP;
+ } break;
+ case 'b':
+ {
+ cmd->type = COMMAND_BOOL;
+ cmd->i32 = 0;
+ } break;
+ case 'i':
+ {
+ cmd->type = COMMAND_INT;
+ cmd->i32 = 0;
+ } break;
+ case 'f':
+ {
+ cmd->type = COMMAND_FLOAT;
+ cmd->f32= 0;
+ } break;
+
+ default:
+ {
+ terminate = true;
+
+ for(unsigned op = 0; op < OP_MAX; op++)
+ {
+ if(vm_api_def[op].key == key)
+ {
+ cmd->type = COMMAND_OPCODE;
+ cmd->op = op;
+ terminate = false;
+ break;
+ }
+ }
+ } break;
+ }
+
+ if(!terminate)
+ {
+ keybd->text_len = 0;
+ sync = true;
+ }
+ }
+ else
+ terminate = true;
+ } break;
+ case COMMAND_MAX:
+ break;
+ }
+
+ if(terminate)
+ {
+ for(unsigned j = i; j < ITEMS_MAX; j++)
+ handle->cmds[j].type = COMMAND_NOP;
+
+ break;
+ }
+ }
+
+ // add spacing so we can see the whole combobox of last elements
+ nk_layout_row_dynamic(ctx, dy, 1);
+ nk_spacing(ctx, 10);
+
+ if(sync)
+ {
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+ vm_graph_serialize(handle->api, &handle->forge, handle->cmds);
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->vm_graph);
+ if(impl)
+ _props_impl_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);
+ }
+
+ if(nk_group_begin(ctx, "Outputs", NK_WINDOW_TITLE | NK_WINDOW_BORDER))
+ {
+ bool sync = false;
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ nk_layout_row_dynamic(ctx, dy*4, 1);
+ _draw_plot(ctx, handle->outp[i].vals, handle->vm_plug);
+
+ nk_layout_row_dynamic(ctx, dy, 2);
+ if( (handle->vm_plug == VM_PLUG_CONTROL)
+ || (handle->vm_plug == VM_PLUG_CV)
+ || (handle->vm_plug == VM_PLUG_ATOM)
+ || (handle->vm_plug == VM_PLUG_MIDI) )
+ {
+ nk_labelf(ctx, NK_TEXT_LEFT, "Out %u: %+f", i, handle->out0[i]);
+ }
+ else if(handle->vm_plug == VM_PLUG_AUDIO)
+ {
+ _draw_mixer(ctx, handle->out1[i]);
+
+ if(handle->out2[i] > handle->out1[i])
+ handle->out1[i] = handle->out2[i];
+ else
+ handle->out1[i] -= (handle->out1[i] - handle->out2[i]) * 0.1; //FIXME make dependent on ui:updateRate
+ }
+
+ const int old_window = handle->outp[i].window;
+ _wheel_int(ctx, &handle->outp[i].window);
+ nk_property_int(ctx, ms_label, 10, &handle->outp[i].window, 100000, 1, 1.f);
+ if(old_window != handle->outp[i].window)
+ memset(handle->outp[i].vals, 0x0, sizeof(float)*PLOT_MAX);
+
+ if(handle->vm_plug == VM_PLUG_MIDI)
+ {
+ vm_filter_t *filter = &handle->destinationFilter[i];
+
+ nk_layout_row_dynamic(ctx, dy, 3);
+
+ int filter_type = filter->type;
+ nk_combobox(ctx, filter_labels, FILTER_MAX, &filter_type,
+ dy, nk_vec2(nk_widget_width(ctx), dy*14)); //FIXME
+ if(filter->type != (vm_filter_enum_t)filter_type)
+ {
+ filter->type = filter_type;
+ sync = true;
+ }
+
+ const int old_channel = filter->channel;
+ filter->channel = nk_propertyi(ctx, chn_label, 0x0, old_channel, 0x7, 1, 1.f);
+ if(old_channel != filter->channel)
+ sync = true;
+
+ switch(filter->type)
+ {
+ case FILTER_CONTROLLER:
+ case FILTER_NOTE_ON:
+ case FILTER_NOTE_PRESSURE:
+ {
+ const int old_value = filter->value;
+ filter->value = nk_propertyi(ctx, val_label, 0x0, old_value, 0x7f, 1, 1.f);
+ if(old_value != filter->value)
+ sync = true;
+ } break;
+
+ case FILTER_BENDER:
+ case FILTER_PROGRAM_CHANGE:
+ case FILTER_CHANNEL_PRESSURE:
+ case FILTER_MAX:
+ {
+ // nothing
+ } break;
+ }
+ }
+ }
+
+ if(sync)
+ {
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+ vm_filter_serialize(&handle->forge, &handle->filt, handle->destinationFilter);
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->vm_destinationFilter);
+ if(impl)
+ _props_impl_set(&handle->props, impl, ser->atom->type, ser->atom->size, LV2_ATOM_BODY_CONST(ser->atom));
+
+ _set_property(handle, handle->vm_destinationFilter);
+ }
+
+ 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;
+
+ handle->vm_plug = vm_plug_type(plugin_uri);
+
+ void *parent = NULL;
+ LV2UI_Resize *host_resize = NULL;
+ LV2_Options_Option *opts = 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;
+ }
+ else if(!strcmp(features[i]->URI, LV2_OPTIONS__options))
+ {
+ opts = 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);
+
+ const LV2_URID param_sampleRate = handle->map->map(handle->map->handle,
+ LV2_PARAMETERS__sampleRate);
+ const LV2_URID ui_scaleFactor = handle->map->map(handle->map->handle,
+ LV2_UI__scaleFactor);
+
+ for(LV2_Options_Option *opt = opts;
+ (opt->key != 0) && (opt->value != NULL);
+ opt++)
+ {
+ if( (opt->key == param_sampleRate) && (opt->type == handle->forge.Float) )
+ {
+ handle->sample_rate = *(const float *)opt->value;
+ }
+ else if( (opt->key == ui_scaleFactor) && (opt->type == handle->forge.Float) )
+ {
+ handle->scale = *(const float*)opt->value;
+ }
+ }
+
+ if(!handle->sample_rate)
+ {
+ handle->sample_rate = 48000.f; // fall-back
+ }
+
+ if(handle->scale == 0.f)
+ {
+ handle->scale = nk_pugl_get_scale();
+ }
+
+ vm_api_init(handle->api, handle->map);
+
+ const int nprops = handle->vm_plug == VM_PLUG_MIDI
+ ? MAX_NPROPS
+ : 1;
+
+ if(!props_init(&handle->props, plugin_uri,
+ defs, nprops,
+ &handle->state, &handle->stash, handle->map, handle))
+ {
+ fprintf(stderr, "props_init 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->vm_sourceFilter = handle->map->map(handle->map->handle, VM__sourceFilter);
+ handle->vm_destinationFilter = handle->map->map(handle->map->handle, VM__destinationFilter);
+ handle->midi_MidiEvent = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ handle->filt.midi_Controller = handle->map->map(handle->map->handle, LV2_MIDI__Controller);
+ handle->filt.midi_Bender = handle->map->map(handle->map->handle, LV2_MIDI__Bender);
+ handle->filt.midi_ProgramChange = handle->map->map(handle->map->handle, LV2_MIDI__ProgramChange);
+ handle->filt.midi_ChannelPressure = handle->map->map(handle->map->handle, LV2_MIDI__ChannelPressure);
+ handle->filt.midi_NotePressure = handle->map->map(handle->map->handle, LV2_MIDI__Aftertouch);
+ handle->filt.midi_NoteOn = handle->map->map(handle->map->handle, LV2_MIDI__NoteOn);
+ handle->filt.midi_channel = handle->map->map(handle->map->handle, LV2_MIDI__channel);
+ handle->filt.midi_controllerNumber = handle->map->map(handle->map->handle, LV2_MIDI__controllerNumber);
+ handle->filt.midi_noteNumber = handle->map->map(handle->map->handle, LV2_MIDI__noteNumber);
+ handle->filt.midi_velocity = handle->map->map(handle->map->handle, LV2_MIDI__velocity);
+
+ handle->controller = controller;
+ handle->writer = write_function;
+
+ nk_pugl_config_t *cfg = &handle->win.cfg;
+ cfg->width = 1280 * handle->scale;
+ cfg->height = 720 * handle->scale;
+ cfg->resizable = true;
+ cfg->ignore = false;
+ cfg->class = "vm";
+ cfg->title = "Vm";
+ cfg->parent = (intptr_t)parent;
+ cfg->host_resize = host_resize;
+ cfg->data = handle;
+ cfg->expose = _expose;
+
+ if(asprintf(&cfg->font.face, "%sCousine-Regular.ttf", bundle_path) == -1)
+ cfg->font.face = NULL;
+ cfg->font.size = 13 * handle->scale;
+
+ *(intptr_t *)widget = nk_pugl_init(&handle->win);
+
+ nk_pugl_show(&handle->win);
+
+ char *icon_path;
+ if(asprintf(&icon_path, "%scancel.png", bundle_path) != -1)
+ {
+ handle->icons.cancel = nk_pugl_icon_load(&handle->win, icon_path);
+ free(icon_path);
+ }
+ if(asprintf(&icon_path, "%sup_arrow.png", bundle_path) != -1)
+ {
+ handle->icons.up_arrow= nk_pugl_icon_load(&handle->win, icon_path);
+ free(icon_path);
+ }
+ if(asprintf(&icon_path, "%splus.png", bundle_path) != -1)
+ {
+ handle->icons.plus= nk_pugl_icon_load(&handle->win, icon_path);
+ free(icon_path);
+ }
+
+ atom_ser_t *ser = &handle->ser;
+ ser->size = 1024;
+ ser->buf = malloc(ser->size);
+
+ _restore_state(handle);
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ handle->inp[i].window = 1000; // 1 second window
+ handle->outp[i].window = 1000; // 1 second window
+ }
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ if(handle->win.cfg.font.face)
+ free(handle->win.cfg.font.face);
+ 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 __attribute__((unused)), uint32_t protocol, const void *buf)
+{
+ plughandle_t *handle = instance;
+
+ switch(index)
+ {
+ case 0:
+ case 1:
+ {
+ if(protocol == handle->atom_eventTransfer)
+ {
+ const LV2_Atom *atom = buf;
+
+ if(atom->type == handle->forge.Long)
+ {
+ const LV2_Atom_Long *off = (const LV2_Atom_Long *)atom;
+
+ const int64_t dt = off->body - handle->off;
+ handle->off = off->body;
+ const float dts = 1000.f * dt / handle->sample_rate; // in seconds
+
+ float mem [PLOT_MAX];
+ bool needs_refresh = false;
+
+ //FIXME can get out of sync
+
+ for(unsigned i = 0; i < CTRL_MAX; i++)
+ {
+ // inputs
+ {
+ handle->inp[i].pre += PLOT_MAX * dts / handle->inp[i].window;
+ double intp;
+ const double frac = modf(handle->inp[i].pre, &intp);
+
+ if(intp > 0)
+ {
+ handle->inp[i].pre = frac;
+
+ unsigned pre = floorf(intp);
+ pre &= PLOT_MASK;
+ const unsigned post = PLOT_MAX - pre;
+
+ memcpy(mem, &handle->inp[i].vals[pre], sizeof(float)*post);
+ for(unsigned j = post; j < PLOT_MAX; j++)
+ mem[j] = handle->in0[i];
+
+ //FIXME can be made more efficient
+ if(memcmp(handle->inp[i].vals, mem, sizeof(float)*PLOT_MAX))
+ needs_refresh = true;
+
+ memcpy(handle->inp[i].vals, mem, sizeof(float)*PLOT_MAX);
+ }
+ }
+
+ // outputs
+ {
+ handle->outp[i].pre += PLOT_MAX * dts / handle->outp[i].window;
+ double intp;
+ const double frac = modf(handle->outp[i].pre, &intp);
+
+ if(intp > 0)
+ {
+ handle->outp[i].pre = frac;
+
+ unsigned pre = floorf(intp);
+ pre &= PLOT_MASK;
+ const unsigned post = PLOT_MAX - pre;
+
+ memcpy(mem, &handle->outp[i].vals[pre], sizeof(float)*post);
+ for(unsigned j = post; j < PLOT_MAX; j++)
+ mem[j] = handle->out0[i];
+
+ //FIXME can be made more efficient
+ if(memcmp(handle->outp[i].vals, mem, sizeof(float)*PLOT_MAX))
+ needs_refresh = true;
+
+ memcpy(handle->outp[i].vals, mem, sizeof(float)*PLOT_MAX);
+ }
+ }
+ }
+
+ if(needs_refresh)
+ nk_pugl_post_redisplay(&handle->win);
+ }
+ else if(atom->type == handle->forge.Tuple)
+ {
+ const LV2_Atom_Tuple *tup = (const LV2_Atom_Tuple *)atom;
+
+ const LV2_Atom *itr = lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *idx = (const LV2_Atom_Int *)itr;
+
+ itr = lv2_atom_tuple_next(itr);
+ const LV2_Atom_Float *val = (const LV2_Atom_Float *)itr;
+
+ if(idx->body < 10)
+ {
+ const unsigned j = idx->body - 2;
+ handle->in0[j] = val->body;
+
+ if(handle->vm_plug == VM_PLUG_AUDIO)
+ handle->in2[j] = dBFS6(val->body);
+ }
+ else
+ {
+ const unsigned j = idx->body - 10;
+ handle->out0[j] = val->body;
+
+ if(handle->vm_plug == VM_PLUG_AUDIO)
+ handle->out2[j] = dBFS6(val->body);
+ }
+ }
+ else // !tuple
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ atom_ser_t *ser = &handle->ser;
+ ser->offset = 0;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, ser);
+
+ LV2_Atom_Forge_Ref ref = 0;
+ if(props_advance(&handle->props, &handle->forge, 0, obj, &ref))
+ {
+ 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 int
+_resize(LV2UI_Handle instance, int width, int height)
+{
+ plughandle_t *handle = instance;
+
+ return nk_pugl_resize(&handle->win, width, height);
+}
+
+static const LV2UI_Resize resize_ext = {
+ .ui_resize = _resize
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ return &idle_ext;
+ else if(!strcmp(uri, LV2_UI__resize))
+ return &resize_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..c5a4c78
--- /dev/null
+++ b/vm_ui.ttl
@@ -0,0 +1,52 @@
+# Copyright (c) 2017-2021 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 opts: <http://lv2plug.in/ns/ext/options#> .
+@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
+@prefix opts: <http://lv2plug.in/ns/ext/options#> .
+
+@prefix vm: <http://open-music-kontrollers.ch/lv2/vm#> .
+
+vm:vm_ui
+ ui:portNotification [
+ ui:plugin vm:control ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ ui:portNotification [
+ ui:plugin vm:cv ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ ui:portNotification [
+ ui:plugin vm:audio ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ ui:portNotification [
+ ui:plugin vm:atom ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap, opts:options, ui:parent ;
+ lv2:optionalFeature log:log, ui:resize, opts:options ;
+ opts:supportedOption ui:scaleFactor ;
+ lv2:extensionData ui:idleInterface, ui:resize ;
+ opts:supportedOption param:sampleRate .