aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml140
-rw-r--r--ChangeLog78
-rw-r--r--README.md244
-rw-r--r--VERSION2
-rw-r--r--bank-analyzer_vu-meter.dsp5
-rw-r--r--bank-filter_gain.dsp8
-rw-r--r--bank-filter_through.dsp5
-rw-r--r--bank-instrument_osc.dsp19
-rw-r--r--bank-time_lfo.dsp23
-rw-r--r--gitlab-ci/generic.yml106
-rw-r--r--manifest.ttl.in167
-rw-r--r--mephisto.c2711
-rw-r--r--mephisto.h245
-rw-r--r--mephisto.ttl.in1283
-rw-r--r--mephisto_ui.c1586
-rw-r--r--mephisto_ui.ttl65
-rw-r--r--meson.build583
-rw-r--r--meson_options.txt42
-rw-r--r--png/alert-triangle.pngbin0 -> 944 bytes
-rw-r--r--png/clipboard.pngbin0 -> 623 bytes
-rw-r--r--png/copy.pngbin0 -> 687 bytes
-rw-r--r--png/delete.pngbin0 -> 741 bytes
-rw-r--r--png/eye-off.pngbin0 -> 1196 bytes
-rw-r--r--png/eye.pngbin0 -> 939 bytes
-rw-r--r--png/save.pngbin0 -> 633 bytes
-rw-r--r--presets.ttl.in141
-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.pngbin149120 -> 104254 bytes
-rw-r--r--ser_atom.lv2/.gitlab-ci.yml60
-rw-r--r--ser_atom.lv2/COPYING201
-rw-r--r--ser_atom.lv2/README.md33
-rw-r--r--ser_atom.lv2/VERSION1
-rw-r--r--ser_atom.lv2/meson.build26
-rw-r--r--ser_atom.lv2/ser_atom.lv2/ser_atom.h215
-rw-r--r--ser_atom.lv2/test/ser_atom_test.c136
-rw-r--r--subprojects/d2tk/.gitignore (renamed from .gitignore)0
-rw-r--r--subprojects/d2tk/.gitlab-ci.yml108
-rw-r--r--subprojects/d2tk/COPYING201
-rw-r--r--subprojects/d2tk/Doxyfile (renamed from Doxyfile)0
-rw-r--r--subprojects/d2tk/README.md62
-rw-r--r--subprojects/d2tk/VERSION1
-rwxr-xr-xsubprojects/d2tk/check_for_font (renamed from check_for_font)0
-rw-r--r--subprojects/d2tk/d2tk/backend.h (renamed from d2tk/backend.h)0
-rw-r--r--subprojects/d2tk/d2tk/base.h (renamed from d2tk/base.h)0
-rw-r--r--subprojects/d2tk/d2tk/config.h.in (renamed from d2tk/config.h.in)0
-rw-r--r--subprojects/d2tk/d2tk/core.h (renamed from d2tk/core.h)0
-rw-r--r--subprojects/d2tk/d2tk/d2tk.h (renamed from d2tk/d2tk.h)0
-rw-r--r--subprojects/d2tk/d2tk/frontend.h (renamed from d2tk/frontend.h)0
-rw-r--r--subprojects/d2tk/d2tk/frontend_fbdev.h (renamed from d2tk/frontend_fbdev.h)0
-rw-r--r--subprojects/d2tk/d2tk/frontend_glfw.h (renamed from d2tk/frontend_glfw.h)0
-rw-r--r--subprojects/d2tk/d2tk/frontend_pugl.h (renamed from d2tk/frontend_pugl.h)0
-rw-r--r--subprojects/d2tk/d2tk/hash.h (renamed from d2tk/hash.h)0
-rw-r--r--subprojects/d2tk/d2tk/util.h (renamed from d2tk/util.h)0
-rw-r--r--subprojects/d2tk/example/custom_cairo.c (renamed from example/custom_cairo.c)0
-rw-r--r--subprojects/d2tk/example/custom_nanovg.c (renamed from example/custom_nanovg.c)0
-rw-r--r--subprojects/d2tk/example/d2tk_fbdev.c (renamed from example/d2tk_fbdev.c)0
-rw-r--r--subprojects/d2tk/example/d2tk_glfw.c (renamed from example/d2tk_glfw.c)0
-rw-r--r--subprojects/d2tk/example/d2tk_pugl.c (renamed from example/d2tk_pugl.c)0
-rw-r--r--subprojects/d2tk/example/example.c (renamed from example/example.c)0
-rw-r--r--subprojects/d2tk/example/example.h (renamed from example/example.h)0
-rw-r--r--subprojects/d2tk/example/libre-arrow-circle-right.png (renamed from example/libre-arrow-circle-right.png)bin2133 -> 2133 bytes
-rw-r--r--subprojects/d2tk/example/libre-gui-file.png (renamed from example/libre-gui-file.png)bin1034 -> 1034 bytes
-rw-r--r--subprojects/d2tk/example/libre-gui-folder.png (renamed from example/libre-gui-folder.png)bin710 -> 710 bytes
-rw-r--r--subprojects/d2tk/linenoise/.gitignore (renamed from linenoise/.gitignore)0
-rw-r--r--subprojects/d2tk/linenoise/LICENSE (renamed from linenoise/LICENSE)0
-rw-r--r--subprojects/d2tk/linenoise/Makefile (renamed from linenoise/Makefile)0
-rw-r--r--subprojects/d2tk/linenoise/README.markdown (renamed from linenoise/README.markdown)0
-rw-r--r--subprojects/d2tk/linenoise/encodings/tools/EastAsianWidth.txt (renamed from linenoise/encodings/tools/EastAsianWidth.txt)0
-rw-r--r--subprojects/d2tk/linenoise/encodings/tools/UnicodeData.txt (renamed from linenoise/encodings/tools/UnicodeData.txt)0
-rw-r--r--subprojects/d2tk/linenoise/encodings/tools/generate_combining_char_table.rb (renamed from linenoise/encodings/tools/generate_combining_char_table.rb)0
-rw-r--r--subprojects/d2tk/linenoise/encodings/tools/generate_wide_char_table.rb (renamed from linenoise/encodings/tools/generate_wide_char_table.rb)0
-rwxr-xr-xsubprojects/d2tk/linenoise/encodings/utf8.c (renamed from linenoise/encodings/utf8.c)0
-rwxr-xr-xsubprojects/d2tk/linenoise/encodings/utf8.h (renamed from linenoise/encodings/utf8.h)0
-rwxr-xr-xsubprojects/d2tk/linenoise/example.c (renamed from linenoise/example.c)0
-rwxr-xr-xsubprojects/d2tk/linenoise/linenoise.c (renamed from linenoise/linenoise.c)0
-rwxr-xr-xsubprojects/d2tk/linenoise/linenoise.h (renamed from linenoise/linenoise.h)0
-rw-r--r--subprojects/d2tk/meson.build433
-rw-r--r--subprojects/d2tk/meson_options.txt47
-rw-r--r--subprojects/d2tk/nanovg/.gitignore (renamed from nanovg/.gitignore)0
-rw-r--r--subprojects/d2tk/nanovg/LICENSE.txt (renamed from nanovg/LICENSE.txt)0
-rw-r--r--subprojects/d2tk/nanovg/README.md (renamed from nanovg/README.md)0
-rw-r--r--subprojects/d2tk/nanovg/example/LICENSE_OFL.txt (renamed from nanovg/example/LICENSE_OFL.txt)0
-rw-r--r--subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf (renamed from nanovg/example/NotoEmoji-Regular.ttf)bin418804 -> 418804 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Bold.ttf (renamed from nanovg/example/Roboto-Bold.ttf)bin135820 -> 135820 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Light.ttf (renamed from nanovg/example/Roboto-Light.ttf)bin140276 -> 140276 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Regular.ttf (renamed from nanovg/example/Roboto-Regular.ttf)bin145348 -> 145348 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/demo.c (renamed from nanovg/example/demo.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/demo.h (renamed from nanovg/example/demo.h)0
-rw-r--r--subprojects/d2tk/nanovg/example/entypo.ttf (renamed from nanovg/example/entypo.ttf)bin35392 -> 35392 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/example_fbo.c (renamed from nanovg/example/example_fbo.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/example_gl2.c (renamed from nanovg/example/example_gl2.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/example_gl3.c (renamed from nanovg/example/example_gl3.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/example_gles2.c (renamed from nanovg/example/example_gles2.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/example_gles3.c (renamed from nanovg/example/example_gles3.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/images.txt (renamed from nanovg/example/images.txt)0
-rw-r--r--subprojects/d2tk/nanovg/example/images/image1.jpg (renamed from nanovg/example/images/image1.jpg)bin25760 -> 25760 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image10.jpg (renamed from nanovg/example/images/image10.jpg)bin3439 -> 3439 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image11.jpg (renamed from nanovg/example/images/image11.jpg)bin3818 -> 3818 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image12.jpg (renamed from nanovg/example/images/image12.jpg)bin5452 -> 5452 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image2.jpg (renamed from nanovg/example/images/image2.jpg)bin24091 -> 24091 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image3.jpg (renamed from nanovg/example/images/image3.jpg)bin29282 -> 29282 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image4.jpg (renamed from nanovg/example/images/image4.jpg)bin23830 -> 23830 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image5.jpg (renamed from nanovg/example/images/image5.jpg)bin27131 -> 27131 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image6.jpg (renamed from nanovg/example/images/image6.jpg)bin25116 -> 25116 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image7.jpg (renamed from nanovg/example/images/image7.jpg)bin25590 -> 25590 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image8.jpg (renamed from nanovg/example/images/image8.jpg)bin24607 -> 24607 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image9.jpg (renamed from nanovg/example/images/image9.jpg)bin4035 -> 4035 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/perf.c (renamed from nanovg/example/perf.c)0
-rw-r--r--subprojects/d2tk/nanovg/example/perf.h (renamed from nanovg/example/perf.h)0
-rw-r--r--subprojects/d2tk/nanovg/example/screenshot-01.png (renamed from nanovg/example/screenshot-01.png)bin989424 -> 989424 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/screenshot-02.png (renamed from nanovg/example/screenshot-02.png)bin229443 -> 229443 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/stb_image_write.h (renamed from nanovg/example/stb_image_write.h)0
-rw-r--r--subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h (renamed from nanovg/obsolete/nanovg_gl2.h)0
-rw-r--r--subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h (renamed from nanovg/obsolete/nanovg_gl3.h)0
-rw-r--r--subprojects/d2tk/nanovg/obsolete/obsolete.md (renamed from nanovg/obsolete/obsolete.md)0
-rw-r--r--subprojects/d2tk/nanovg/premake4.lua (renamed from nanovg/premake4.lua)0
-rw-r--r--subprojects/d2tk/nanovg/src/fontstash.h (renamed from nanovg/src/fontstash.h)0
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg.c (renamed from nanovg/src/nanovg.c)0
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg.h (renamed from nanovg/src/nanovg.h)0
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg_gl.h (renamed from nanovg/src/nanovg_gl.h)0
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg_gl_utils.h (renamed from nanovg/src/nanovg_gl_utils.h)0
-rw-r--r--subprojects/d2tk/nanovg/src/stb_image.h (renamed from nanovg/src/stb_image.h)0
-rw-r--r--subprojects/d2tk/nanovg/src/stb_truetype.h (renamed from nanovg/src/stb_truetype.h)0
-rw-r--r--subprojects/d2tk/pugl/.clang-format (renamed from pugl/.clang-format)0
-rw-r--r--subprojects/d2tk/pugl/.clang-tidy (renamed from pugl/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/.clant.json (renamed from pugl/.clant.json)0
-rw-r--r--subprojects/d2tk/pugl/.editorconfig (renamed from pugl/.editorconfig)0
-rw-r--r--subprojects/d2tk/pugl/.gitattributes (renamed from pugl/.gitattributes)0
-rw-r--r--subprojects/d2tk/pugl/.gitignore (renamed from pugl/.gitignore)0
-rw-r--r--subprojects/d2tk/pugl/.gitlab-ci.yml (renamed from pugl/.gitlab-ci.yml)0
-rw-r--r--subprojects/d2tk/pugl/.gitmodules (renamed from pugl/.gitmodules)0
-rw-r--r--subprojects/d2tk/pugl/.includes.imp (renamed from pugl/.includes.imp)0
-rw-r--r--subprojects/d2tk/pugl/AUTHORS (renamed from pugl/AUTHORS)0
-rw-r--r--subprojects/d2tk/pugl/COPYING (renamed from pugl/COPYING)0
-rw-r--r--subprojects/d2tk/pugl/README.md (renamed from pugl/README.md)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/.clang-tidy (renamed from pugl/bindings/cxx/include/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/pugl/cairo.hpp (renamed from pugl/bindings/cxx/include/pugl/cairo.hpp)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/pugl/gl.hpp (renamed from pugl/bindings/cxx/include/pugl/gl.hpp)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/pugl/pugl.hpp (renamed from pugl/bindings/cxx/include/pugl/pugl.hpp)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/pugl/stub.hpp (renamed from pugl/bindings/cxx/include/pugl/stub.hpp)0
-rw-r--r--subprojects/d2tk/pugl/bindings/cxx/include/pugl/vulkan.hpp (renamed from pugl/bindings/cxx/include/pugl/vulkan.hpp)0
-rw-r--r--subprojects/d2tk/pugl/doc/_static/meson.build (renamed from pugl/doc/_static/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/Doxyfile.in (renamed from pugl/doc/c/Doxyfile.in)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/api/meson.build (renamed from pugl/doc/c/api/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/event-loop.rst (renamed from pugl/doc/c/event-loop.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/events.rst (renamed from pugl/doc/c/events.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/index.rst (renamed from pugl/doc/c/index.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/meson.build (renamed from pugl/doc/c/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/overview.rst (renamed from pugl/doc/c/overview.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/shutting-down.rst (renamed from pugl/doc/c/shutting-down.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/view.rst (renamed from pugl/doc/c/view.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/world.rst (renamed from pugl/doc/c/world.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/c/xml/meson.build (renamed from pugl/doc/c/xml/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/conf.py.in (renamed from pugl/doc/conf.py.in)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/Doxyfile.in (renamed from pugl/doc/cpp/Doxyfile.in)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/api/meson.build (renamed from pugl/doc/cpp/api/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/event-loop.rst (renamed from pugl/doc/cpp/event-loop.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/events.rst (renamed from pugl/doc/cpp/events.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/index.rst (renamed from pugl/doc/cpp/index.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/meson.build (renamed from pugl/doc/cpp/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/overview.rst (renamed from pugl/doc/cpp/overview.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/view.rst (renamed from pugl/doc/cpp/view.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/world.rst (renamed from pugl/doc/cpp/world.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/cpp/xml/meson.build (renamed from pugl/doc/cpp/xml/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/deployment.rst (renamed from pugl/doc/deployment.rst)0
-rw-r--r--subprojects/d2tk/pugl/doc/meson.build (renamed from pugl/doc/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/doc/summary.rst (renamed from pugl/doc/summary.rst)0
-rw-r--r--subprojects/d2tk/pugl/examples/.clang-tidy (renamed from pugl/examples/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/examples/cube_view.h (renamed from pugl/examples/cube_view.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/demo_utils.h (renamed from pugl/examples/demo_utils.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/file_utils.c (renamed from pugl/examples/file_utils.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/file_utils.h (renamed from pugl/examples/file_utils.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/glad/glad.c (renamed from pugl/examples/glad/glad.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/glad/glad.h (renamed from pugl/examples/glad/glad.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/glad/khrplatform.h (renamed from pugl/examples/glad/khrplatform.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/meson.build (renamed from pugl/examples/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_cairo_demo.c (renamed from pugl/examples/pugl_cairo_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_cursor_demo.c (renamed from pugl/examples/pugl_cursor_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_cxx_demo.cpp (renamed from pugl/examples/pugl_cxx_demo.cpp)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_embed_demo.c (renamed from pugl/examples/pugl_embed_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_print_events.c (renamed from pugl/examples/pugl_print_events.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_shader_demo.c (renamed from pugl/examples/pugl_shader_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_vulkan_cxx_demo.cpp (renamed from pugl/examples/pugl_vulkan_cxx_demo.cpp)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_vulkan_demo.c (renamed from pugl/examples/pugl_vulkan_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/pugl_window_demo.c (renamed from pugl/examples/pugl_window_demo.c)0
-rw-r--r--subprojects/d2tk/pugl/examples/rects.h (renamed from pugl/examples/rects.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/shader_utils.h (renamed from pugl/examples/shader_utils.h)0
-rw-r--r--subprojects/d2tk/pugl/examples/shaders/header_330.glsl (renamed from pugl/examples/shaders/header_330.glsl)0
-rw-r--r--subprojects/d2tk/pugl/examples/shaders/header_420.glsl (renamed from pugl/examples/shaders/header_420.glsl)0
-rw-r--r--subprojects/d2tk/pugl/examples/shaders/meson.build (renamed from pugl/examples/shaders/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/examples/shaders/rect.frag (renamed from pugl/examples/shaders/rect.frag)0
-rw-r--r--subprojects/d2tk/pugl/examples/shaders/rect.vert (renamed from pugl/examples/shaders/rect.vert)0
-rw-r--r--subprojects/d2tk/pugl/examples/sybok.hpp (renamed from pugl/examples/sybok.hpp)0
-rw-r--r--subprojects/d2tk/pugl/include/.clang-tidy (renamed from pugl/include/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/include/pugl/cairo.h (renamed from pugl/include/pugl/cairo.h)0
-rw-r--r--subprojects/d2tk/pugl/include/pugl/gl.h (renamed from pugl/include/pugl/gl.h)0
-rw-r--r--subprojects/d2tk/pugl/include/pugl/pugl.h (renamed from pugl/include/pugl/pugl.h)0
-rw-r--r--subprojects/d2tk/pugl/include/pugl/stub.h (renamed from pugl/include/pugl/stub.h)0
-rw-r--r--subprojects/d2tk/pugl/include/pugl/vulkan.h (renamed from pugl/include/pugl/vulkan.h)0
-rw-r--r--subprojects/d2tk/pugl/meson.build (renamed from pugl/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/meson/meson.build (renamed from pugl/meson/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/meson_options.txt (renamed from pugl/meson_options.txt)0
-rw-r--r--subprojects/d2tk/pugl/pugl.pc.in (renamed from pugl/pugl.pc.in)0
-rw-r--r--subprojects/d2tk/pugl/resources/Info.plist.in (renamed from pugl/resources/Info.plist.in)0
-rw-r--r--subprojects/d2tk/pugl/resources/pugl.ipe (renamed from pugl/resources/pugl.ipe)0
-rw-r--r--subprojects/d2tk/pugl/resources/pugl.png (renamed from pugl/resources/pugl.png)bin2641 -> 2641 bytes
-rw-r--r--subprojects/d2tk/pugl/resources/pugl.svg (renamed from pugl/resources/pugl.svg)0
-rwxr-xr-xsubprojects/d2tk/pugl/scripts/cat.py (renamed from pugl/scripts/cat.py)0
-rwxr-xr-xsubprojects/d2tk/pugl/scripts/dox_to_sphinx.py (renamed from pugl/scripts/dox_to_sphinx.py)0
-rw-r--r--subprojects/d2tk/pugl/src/.clang-tidy (renamed from pugl/src/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/src/implementation.c (renamed from pugl/src/implementation.c)0
-rw-r--r--subprojects/d2tk/pugl/src/implementation.h (renamed from pugl/src/implementation.h)0
-rw-r--r--subprojects/d2tk/pugl/src/mac.h (renamed from pugl/src/mac.h)0
-rw-r--r--subprojects/d2tk/pugl/src/mac.m (renamed from pugl/src/mac.m)0
-rw-r--r--subprojects/d2tk/pugl/src/mac_cairo.m (renamed from pugl/src/mac_cairo.m)0
-rw-r--r--subprojects/d2tk/pugl/src/mac_gl.m (renamed from pugl/src/mac_gl.m)0
-rw-r--r--subprojects/d2tk/pugl/src/mac_stub.m (renamed from pugl/src/mac_stub.m)0
-rw-r--r--subprojects/d2tk/pugl/src/mac_vulkan.m (renamed from pugl/src/mac_vulkan.m)0
-rw-r--r--subprojects/d2tk/pugl/src/stub.h (renamed from pugl/src/stub.h)0
-rw-r--r--subprojects/d2tk/pugl/src/types.h (renamed from pugl/src/types.h)0
-rw-r--r--subprojects/d2tk/pugl/src/win.c (renamed from pugl/src/win.c)0
-rw-r--r--subprojects/d2tk/pugl/src/win.h (renamed from pugl/src/win.h)0
-rw-r--r--subprojects/d2tk/pugl/src/win_cairo.c (renamed from pugl/src/win_cairo.c)0
-rw-r--r--subprojects/d2tk/pugl/src/win_gl.c (renamed from pugl/src/win_gl.c)0
-rw-r--r--subprojects/d2tk/pugl/src/win_stub.c (renamed from pugl/src/win_stub.c)0
-rw-r--r--subprojects/d2tk/pugl/src/win_vulkan.c (renamed from pugl/src/win_vulkan.c)0
-rw-r--r--subprojects/d2tk/pugl/src/x11.c (renamed from pugl/src/x11.c)0
-rw-r--r--subprojects/d2tk/pugl/src/x11.h (renamed from pugl/src/x11.h)0
-rw-r--r--subprojects/d2tk/pugl/src/x11_cairo.c (renamed from pugl/src/x11_cairo.c)0
-rw-r--r--subprojects/d2tk/pugl/src/x11_gl.c (renamed from pugl/src/x11_gl.c)0
-rw-r--r--subprojects/d2tk/pugl/src/x11_stub.c (renamed from pugl/src/x11_stub.c)0
-rw-r--r--subprojects/d2tk/pugl/src/x11_vulkan.c (renamed from pugl/src/x11_vulkan.c)0
-rw-r--r--subprojects/d2tk/pugl/test/.clang-tidy (renamed from pugl/test/.clang-tidy)0
-rw-r--r--subprojects/d2tk/pugl/test/meson.build (renamed from pugl/test/meson.build)0
-rw-r--r--subprojects/d2tk/pugl/test/test_build.c (renamed from pugl/test/test_build.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_build.cpp (renamed from pugl/test/test_build.cpp)0
-rw-r--r--subprojects/d2tk/pugl/test/test_clipboard.c (renamed from pugl/test/test_clipboard.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_gl_hints.c (renamed from pugl/test/test_gl_hints.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_realize.c (renamed from pugl/test/test_realize.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_redisplay.c (renamed from pugl/test/test_redisplay.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_show_hide.c (renamed from pugl/test/test_show_hide.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_stub_hints.c (renamed from pugl/test/test_stub_hints.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_timer.c (renamed from pugl/test/test_timer.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_update.c (renamed from pugl/test/test_update.c)0
-rw-r--r--subprojects/d2tk/pugl/test/test_utils.h (renamed from pugl/test/test_utils.h)0
-rw-r--r--subprojects/d2tk/screenshots/screenshot_1.pngbin0 -> 149120 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_2.png (renamed from screenshots/screenshot_2.png)bin37532 -> 37532 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_3.png (renamed from screenshots/screenshot_3.png)bin128486 -> 128486 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_4.png (renamed from screenshots/screenshot_4.png)bin35888 -> 35888 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_5.png (renamed from screenshots/screenshot_5.png)bin51888 -> 51888 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_6.png (renamed from screenshots/screenshot_6.png)bin84697 -> 84697 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_7.png (renamed from screenshots/screenshot_7.png)bin42476 -> 42476 bytes
-rw-r--r--subprojects/d2tk/screenshots/screenshot_8.png (renamed from screenshots/screenshot_8.png)bin73108 -> 73108 bytes
-rw-r--r--subprojects/d2tk/src/backend_cairo.c (renamed from src/backend_cairo.c)0
-rw-r--r--subprojects/d2tk/src/backend_nanovg.c (renamed from src/backend_nanovg.c)0
-rw-r--r--subprojects/d2tk/src/base.c (renamed from src/base.c)0
-rw-r--r--subprojects/d2tk/src/base_bar.c (renamed from src/base_bar.c)0
-rw-r--r--subprojects/d2tk/src/base_bitmap.c (renamed from src/base_bitmap.c)0
-rw-r--r--subprojects/d2tk/src/base_button.c (renamed from src/base_button.c)0
-rw-r--r--subprojects/d2tk/src/base_combo.c (renamed from src/base_combo.c)0
-rw-r--r--subprojects/d2tk/src/base_cursor.c (renamed from src/base_cursor.c)0
-rw-r--r--subprojects/d2tk/src/base_custom.c (renamed from src/base_custom.c)0
-rw-r--r--subprojects/d2tk/src/base_dial.c (renamed from src/base_dial.c)0
-rw-r--r--subprojects/d2tk/src/base_flowmatrix.c (renamed from src/base_flowmatrix.c)0
-rw-r--r--subprojects/d2tk/src/base_frame.c (renamed from src/base_frame.c)0
-rw-r--r--subprojects/d2tk/src/base_image.c (renamed from src/base_image.c)0
-rw-r--r--subprojects/d2tk/src/base_internal.h (renamed from src/base_internal.h)0
-rw-r--r--subprojects/d2tk/src/base_label.c (renamed from src/base_label.c)0
-rw-r--r--subprojects/d2tk/src/base_layout.c (renamed from src/base_layout.c)0
-rw-r--r--subprojects/d2tk/src/base_lineedit.c (renamed from src/base_lineedit.c)0
-rw-r--r--subprojects/d2tk/src/base_link.c (renamed from src/base_link.c)0
-rw-r--r--subprojects/d2tk/src/base_meter.c (renamed from src/base_meter.c)0
-rw-r--r--subprojects/d2tk/src/base_pane.c (renamed from src/base_pane.c)0
-rw-r--r--subprojects/d2tk/src/base_pty.c (renamed from src/base_pty.c)0
-rw-r--r--subprojects/d2tk/src/base_scrollbar.c (renamed from src/base_scrollbar.c)0
-rw-r--r--subprojects/d2tk/src/base_separator.c (renamed from src/base_separator.c)0
-rw-r--r--subprojects/d2tk/src/base_spinner.c (renamed from src/base_spinner.c)0
-rw-r--r--subprojects/d2tk/src/base_table.c (renamed from src/base_table.c)0
-rw-r--r--subprojects/d2tk/src/base_textfield.c (renamed from src/base_textfield.c)0
-rw-r--r--subprojects/d2tk/src/base_tooltip.c (renamed from src/base_tooltip.c)0
-rw-r--r--subprojects/d2tk/src/base_vkb.c (renamed from src/base_vkb.c)0
-rw-r--r--subprojects/d2tk/src/base_wave.c (renamed from src/base_wave.c)0
-rw-r--r--subprojects/d2tk/src/core.c (renamed from src/core.c)0
-rw-r--r--subprojects/d2tk/src/core_internal.h (renamed from src/core_internal.h)0
-rw-r--r--subprojects/d2tk/src/frontend_fbdev.c (renamed from src/frontend_fbdev.c)0
-rw-r--r--subprojects/d2tk/src/frontend_glfw.c (renamed from src/frontend_glfw.c)0
-rw-r--r--subprojects/d2tk/src/frontend_pugl.c (renamed from src/frontend_pugl.c)0
-rw-r--r--subprojects/d2tk/src/hash.c (renamed from src/hash.c)0
-rw-r--r--subprojects/d2tk/src/mum.h (renamed from src/mum.h)0
-rw-r--r--subprojects/d2tk/src/util_spawn.c (renamed from src/util_spawn.c)0
-rw-r--r--subprojects/d2tk/test/base.c (renamed from test/base.c)0
-rw-r--r--subprojects/d2tk/test/core.c (renamed from test/core.c)0
-rw-r--r--subprojects/d2tk/test/mock.c (renamed from test/mock.c)0
-rw-r--r--subprojects/d2tk/test/mock.h (renamed from test/mock.h)0
-rw-r--r--subprojects/d2tk/ttf/FiraCode-Bold.ttf (renamed from ttf/FiraCode-Bold.ttf)bin315784 -> 315784 bytes
-rw-r--r--subprojects/d2tk/ttf/FiraCode-Light.ttf (renamed from ttf/FiraCode-Light.ttf)bin276684 -> 276684 bytes
-rw-r--r--subprojects/d2tk/ttf/FiraCode-Medium.ttf (renamed from ttf/FiraCode-Medium.ttf)bin286232 -> 286232 bytes
-rw-r--r--subprojects/d2tk/ttf/FiraCode-Regular.ttf (renamed from ttf/FiraCode-Regular.ttf)bin290360 -> 290360 bytes
-rw-r--r--subprojects/d2tk/ttf/FiraSans-Bold.ttf (renamed from ttf/FiraSans-Bold.ttf)bin521312 -> 521312 bytes
-rw-r--r--subprojects/d2tk/ttf/LICENSE (renamed from ttf/LICENSE)0
-rw-r--r--subprojects/d2tk/utf8.h/.clang-format (renamed from utf8.h/.clang-format)0
-rw-r--r--subprojects/d2tk/utf8.h/.gitignore (renamed from utf8.h/.gitignore)0
-rw-r--r--subprojects/d2tk/utf8.h/.travis.yml (renamed from utf8.h/.travis.yml)0
-rw-r--r--subprojects/d2tk/utf8.h/LICENSE (renamed from utf8.h/LICENSE)0
-rw-r--r--subprojects/d2tk/utf8.h/README.md (renamed from utf8.h/README.md)0
-rw-r--r--subprojects/d2tk/utf8.h/appveyor.yml (renamed from utf8.h/appveyor.yml)0
-rw-r--r--subprojects/d2tk/utf8.h/test/CMakeLists.txt (renamed from utf8.h/test/CMakeLists.txt)0
-rw-r--r--subprojects/d2tk/utf8.h/test/main.c (renamed from utf8.h/test/main.c)0
-rw-r--r--subprojects/d2tk/utf8.h/test/test.c (renamed from utf8.h/test/test.c)0
-rw-r--r--subprojects/d2tk/utf8.h/test/test.cpp (renamed from utf8.h/test/test.cpp)0
-rw-r--r--subprojects/d2tk/utf8.h/test/utest.h (renamed from utf8.h/test/utest.h)0
-rw-r--r--subprojects/d2tk/utf8.h/utf8.h (renamed from utf8.h/utf8.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--varchunk/.gitlab-ci.yml2
-rw-r--r--varchunk/COPYING201
-rw-r--r--varchunk/README.md112
-rw-r--r--varchunk/VERSION1
-rw-r--r--varchunk/gitlab-ci/generic.yml106
-rw-r--r--varchunk/meson.build30
-rw-r--r--varchunk/test_varchunk.c254
-rw-r--r--varchunk/varchunk.h384
341 files changed, 13437 insertions, 529 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4847758..f9765ae 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,108 +1,62 @@
-stages:
- - build
- - deploy
+include:
+ - local: 'gitlab-ci/generic.yml'
-# templates
-.variables_template: &variables_definition
- variables:
- BASE_NAME: "d2tk"
- PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+variables:
+ BUILD_OPTS: "-Duse-fontconfig=disabled"
-.common_template: &common_definition
- <<: *variables_definition
- stage: build
- artifacts:
- name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
- paths:
- - "${BASE_NAME}-$(cat VERSION)/"
+# build
+x86_64-linux-gnu-stretch:
+ except:
+ - /^.*$/i
-.build_template: &build_definition
- <<: *common_definition
- stage: build
- script:
- - meson --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
+x86_64-linux-gnu-buster:
+ except:
+ - /^.*$/i
-.test_template: &test_definition
- <<: *common_definition
- stage: build
- script:
- - meson --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
-
- - meson test -C build
-
-.analyze_template: &analyze_definition
- <<: *common_definition
- stage: build
- script:
- - meson --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
-
- - meson test -C build
- - meson test -C build --wrap=valgrind
-
- - CC=clang CXX=clang++ meson --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
- - ninja -C clang
- - ninja -C clang test
-
- - scan-build --status-bugs meson --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
- - scan-build --status-bugs ninja -C scanbuild
- - scan-build --status-bugs ninja -C scanbuild test
+x86_64-linux-gnu-bullseye:
+ before_script:
+ - apt-get update
+ - apt-get install -y -q faust-common libfaust2
+ - ln -s /usr/lib/x86_64-linux-gnu/libfaust.so.2 /usr/lib/x86_64-linux-gnu/libfaust.so
-.universal_linux_template: &universal_linux_definition
- image: ventosus/universal-linux-gnu:buster
- <<: *analyze_definition
+i686-linux-gnu-stretch:
+ except:
+ - /^.*$/i
-.arm_linux_template: &arm_linux_definition
- image: ventosus/arm-linux-gnueabihf:buster
- <<: *test_definition
+i686-linux-gnu-buster:
+ except:
+ - /^.*$/i
-# targets
-x86_64-linux-gnu:
+i686-linux-gnu-bullseye:
before_script:
- - apt-get install -y libglu1-mesa-dev libevdev-dev libvterm-dev
- <<: *universal_linux_definition
+ - apt-get update
+ - apt-get install -y -q faust-common:i386 libfaust2:i386
+ - ln -s /usr/lib/i386-linux-gnu/libfaust.so.2 /usr/lib/i386-linux-gnu/libfaust.so
-i686-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386 libvterm-dev:i386
- <<: *universal_linux_definition
+arm-linux-gnueabihf-stretch:
+ except:
+ - /^.*$/i
-arm-linux-gnueabihf:
- before_script:
- - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf libvterm-dev:armhf
- <<: *arm_linux_definition
+arm-linux-gnueabihf-buster:
+ except:
+ - /^.*$/i
-aarch64-linux-gnu:
+arm-linux-gnueabihf-bullseye:
before_script:
- - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64 libvterm-dev:arm64
- <<: *arm_linux_definition
+ - apt-get update
+ - apt-get install -y -q faust-common:armhf libfaust2:armhf
+ - ln -s /usr/lib/arm-linux-gnueabihf/libfaust.so.2 /usr/lib/arm-linux-gnueabihf/libfaust.so
+
+aarch64-linux-gnu-stretch:
+ except:
+ - /^.*$/i
-pack:
- <<: *variables_definition
- stage: deploy
- script:
- - echo 'packing up...'
- artifacts:
- name: "${BASE_NAME}-$(cat VERSION)"
- paths:
- - "${BASE_NAME}-$(cat VERSION)/"
+aarch64-linux-gnu-buster:
+ except:
+ - /^.*$/i
-pages:
- stage: deploy
+aarch64-linux-gnu-bullseye:
before_script:
- - apt-get update -y
- - apt-get install -y doxygen
- script:
- - doxygen
- - cp -r doc/html public
- artifacts:
- paths:
- - public/
+ - apt-get update
+ - apt-get install -y -q faust-common:arm64 libfaust2:arm64
+ - ln -s /usr/lib/aarch64-linux-gnu/libfaust.so.2 /usr/lib/aarch64-linux-gnu/libfaust.so
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..fb21edd
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,78 @@
+# Changelog
+
+## [0.14.0] - 15 Jan 2021
+
+### Added
+
+* support for host provided file selector
+* support for clipboard copy/paste
+* sending of for state:StateChanged
+* usage of state:freePath
+* support for bargraph properties with acoompanying wave widget
+
+### Changed
+
+* general ui beautification
+* show empty parameter slots
+
+## [0.12.0] - 15 Jul 2020
+
+### Added
+
+* support for bargraphs
+* preset for simple VU meter
+* momentary button ui-widget
+* support for ui:scaleFactor option
+* support for GL double buffering
+
+### Changed
+
+* all ui-widgets to show labels and correct value ranges
+
+## [0.8.0] - 13 Apr 2020
+
+### Fixed
+
+* wrong note frequency at noteOn when pitch bending
+
+### Deprecated
+
+* release duration paramter (voices are always on now)
+
+### Changed
+
+* to build with pugl master
+* to use spinners instead of dials
+* to automagically adapt color theme to terminal colors
+* to count slots from 0 in ui
+
+## [0.6.0] - 15 Jan 2020
+
+### Deprecated
+
+* external UI
+
+### Added
+
+* native UI based on D2TK with embedded terminal emulator
+* retrigger of gate signal over 1 sample at least at NoteOn
+
+### Fixed
+
+* too short error buffer size
+* handling of AllNotesOff and AllSoundsOff MIDI messages
+* compilaton errors (FAUST 2.20.2)
+* slot ordering of parameter slots (FAUST 2.20.2)
+* lv2lint warnings
+
+### Removed
+
+* build dependency on FAUST binary
+
+### Changed
+
+* parameter labels in presets
+
+## [0.2.0] - 13 Oct 2019
+
+### Initial release
diff --git a/README.md b/README.md
index 51c9529..3ac690e 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,246 @@
-# d2tk
+## Mephisto
-## Data Driven Tool Kit
+### a Just-in-Time FAUST compiler embedded in an LV2 plugin
-A performant, dyamic, immediate-mode GUI tool kit in C which partially renders
-on-change only by massively hashing-and-cashing of vector drawing instructions
-and on-demand rendered sprites.
+Write LV2 audio/cv instruments/filters directly in your host in FAUST
+DSP language without any need to restart/reload host or plugin upon code changes.
-### Build / test
+Use it for one-off instruments/filters, prototyping, experimenting or
+glueing stuff together.
- git clone https://git.open-music-kontrollers.ch/lad/d2tk
- cd d2tk
+*Note: This will need a fairly recent libFAUST and/or bleeding edge GNU/Linux distribution.*
+
+*Note: libFAUST 2.20.2 has a broken LLVM C-API and thus will be non-functional
+with this plugin.*
+
+#### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/mephisto.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/mephisto.lv2/commits/master)
+
+### Binaries
+
+For GNU/Linux (64-bit, 32-bit).
+
+To install the plugin bundle on your system, simply copy the __mephisto.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
+
+* [mephisto.lv2-0.14.0.zip](https://dl.open-music-kontrollers.ch/mephisto.lv2/stable/mephisto.lv2-0.14.0.zip) ([sig](https://dl.open-music-kontrollers.ch/mephisto.lv2/stable/mephisto.lv2-0.14.0.zip.sig))
+
+#### Unstable (nightly) release
+
+* [mephisto.lv2-latest-unstable.zip](https://dl.open-music-kontrollers.ch/mephisto.lv2/unstable/mephisto.lv2-latest-unstable.zip) ([sig](https://dl.open-music-kontrollers.ch/mephisto.lv2/unstable/mephisto.lv2-latest-unstable.zip.sig))
+
+### Sources
+
+#### Stable release
+
+* [mephisto.lv2-0.14.0.tar.xz](https://git.open-music-kontrollers.ch/lv2/mephisto.lv2/snapshot/mephisto.lv2-0.14.0.tar.xz)([sig](https://git.open-music-kontrollers.ch/lv2/mephisto.lv2/snapshot/mephisto.lv2-0.14.0.tar.xz.asc))
+
+#### Git repository
+
+* <https://git.open-music-kontrollers.ch/lv2/mephisto.lv2>
+
+### Packages
+
+* [ArchLinux](https://www.archlinux.org/packages/community/x86_64/mephisto.lv2/)
+
+### Bugs and feature requests
+
+* [Gitlab](https://gitlab.com/OpenMusicKontrollers/mephisto.lv2)
+* [Github](https://github.com/OpenMusicKontrollers/mephisto.lv2)
+
+#### Plugins
+
+![Screenshot](/screenshots/screenshot_1.png)
+
+##### Audio 1x1
+
+1x1 Audio version of the plugin.
+
+Prototype new audio filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### Audio 2x2
+
+2x2 Audio version of the plugin.
+
+Prototype new audio filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### Audio 4x4
+
+4x4 Audio version of the plugin.
+
+Prototype new audio filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### Audio 8x8
+
+8x8 Audio version of the plugin.
+
+Prototype new audio filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### CV 1x1
+
+1x1 CV version of the plugin.
+
+Prototype new CV filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### CV 2x2
+
+2x2 CV version of the plugin.
+
+Prototype new CV filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### CV 4x4
+
+4x4 CV version of the plugin.
+
+Prototype new CV filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+##### CV 8x8
+
+8x8 CV version of the plugin.
+
+Prototype new CV filters and instruments in [FAUST](https://faust.grame.fr)
+directly in your favorite running host, without the need to restart the latter
+after code changes.
+
+#### Dependencies
+
+* [LV2](http://lv2plug.in) (LV2 Plugin Standard)
+* [FAUST](https://faust.grame.fr/) (Faust Programming Language >=2.14.4)
+* [OpenGl](https://www.opengl.org) (OpenGl)
+* [GLEW](http://glew.sourceforge.net) (GLEW)
+* [VTERM](http://www.leonerd.org.uk/code/libvterm) (Virtual terminal emulator)
+
+#### Build / install
+
+ git clone https://git.open-music-kontrollers.ch/lv2/mephisto.lv2
+ cd mephisto.lv2
meson build
cd build
ninja -j4
+ ninja test
+ sudo ninja install
+
+#### UI
+
+This plugin features a native LV2 plugin UI which embeds a terminal emulator
+which can run your favorite terminal editor to edit the plugin's FAUST source.
+
+Currently, the editor has to be defined via the environment variable *EDITOR*:
+
+ export EDITOR='vim'
+ export EDITOR='emacs'
+
+If no environment variable is defined, the default fallback editor is 'vi', as
+it must be part of every POSIX system.
+
+Whenever you save the FAUST source, the plugin will try to just-in-time compile and
+inject it. Potential warnings and errors are reported in the plugin host's log
+and the UI itself.
+
+On hi-DPI displays, the UI scales automatically if you have set the correct DPI
+in your ~/.Xresources.
+
+ Xft.dpi: 200
+
+If not, you can manually set your DPI via environmental variable *D2TK_SCALE*:
+
+ export D2TK_SCALE=200
+
+#### Controls
+
+The plugin supports up to 16 controls implemented as LV2
+[Parameters](http://lv2plug.in/ns/lv2core/lv2core.html#Parameter). To have
+access to them, simply use one of FAUST's active control structures with
+[ordering indeces](https://faust.grame.fr/doc/manual/index.html#ordering-ui-elements)
+(monitonically rising starting from 0) in their labels in your DSP code:
+
+ cntrl1 = hslider("[0]Control 0", 500.0, 10.0, 1000.0, 1.0);
+ cntrl2 = hslider("[1]Control 1", 5.0, 1.0, 10.0, 1.0);
+ cntrl3 = hslider("[2]Control 2", 0.5, 0.0, 1.0, 0.1);
+
+Read-only controls are also supported and will be shown as waveforms in the UI:
+
+ _ : hbargraph("[3]Probe 3", 0, 1);
+
+#### MIDI and polyphony
-#### Pugl/NanoVG backend
+The plugin supports building instruments with
+[MIDI polyphony](https://faust.grame.fr/doc/manual/index.html#midi-polyphony-support).
+For this to work you have to enable the MIDI option and declare amount of polyphony
+(maximum polyphony is 64).
- ./d2tk.nanovg
+The plugin automatically derives the 4 control signals:
-#### Pugl/Cairo backend
+* gate (NoteOn vs NoteOff)
+* freq (NoteOn-note + PitchBend), honouring PitchBend range RPN 0/0
+* gain (NoteOn-velocity)
+* pressure (NotePressure aka polyphonic aftertouch)
- ./d2tk.cairo
+Additionally, the following MIDI ControlChanges are supported:
-#### FBdev/Cairo backend
+* SustainPedal
+* AllNotesOff
+* AllSoundsOff
- ./d2tk.fbdev
+Other MIDI events are not supported as of today and thus should be
+automated via the plugin host to one of the 16 control slots.
-### Screenshots
+ declare options("[midi:on][nvoices:16]");
-![Screenshot 1](/screenshots/screenshot_1.png)
+ freq = hslider("freq", 0, 0, 1, 0.1);
+ gain = hslider("gain", 0, 0, 1, 0.1);
+ gate = button("gate");
+ pressure = button("gate");
+ pressure = hslider("pressure", 0.0, 0.0, 1.0, 0.1);
-![Screenshot 2](/screenshots/screenshot_2.png)
+ cntrl1 = hslider("[0]Control 0", 500.0, 10.0, 1000.0, 1.0);
+ cntrl2 = hslider("[1]Control 1", 5.0, 1.0, 10.0, 1.0);
+ cntrl3 = hslider("[2]Control 2", 0.5, 0.0, 1.0, 0.1);
-![Screenshot 3](/screenshots/screenshot_3.png)
+#### OSC
-![Screenshot 4](/screenshots/screenshot_4.png)
+OSC events are not supported as of today and thus should be automated via
+the plugin host to one of the 16 control slots.
-![Screenshot 5](/screenshots/screenshot_5.png)
+#### Time
-![Screenshot 6](/screenshots/screenshot_6.png)
+The plugin supports LV2 [time position](http://lv2plug.in/ns/ext/time/time.html#Position)
+events. To have access to them, simply use one of FAUST's active control
+structures with the corresponding time metadata in their labels in your DPS code
+and additionally enable the time option:
-![Screenshot 7](/screenshots/screenshot_7.png)
+ declare options("[time:on]");
-![Screenshot 8](/screenshots/screenshot_8.png)
+ barBeat = hslider("barBeat[time:barBeat]", 0.0, 0.0, 32.0, 1.0);
+ bar = hslider("bar[time:bar]", 0.0, 0.0, 400.0, 1.0);
+ beatUnit = hslider("beatUnit[time:beatUnit]", 1.0, 1.0, 32.0, 1.0);
+ beatsPerBar = hslider("beatsPerBar[time:beatsPerBar]", 1.0, 1.0, 32.0, 1.0);
+ beatsPerMinute = hslider("beatsPerMinute[time:beatsPerMinute]", 1.0, 1.0, 400.0, 1.0);
+ frame = hslider("frame[time:frame]", 1.0, 1.0, 400.0, 1.0);
+ framesPerSecond = hslider("framesPerSecond[time:framesPerSecond]", 1.0, 1.0, 96000.0, 1.0);
+ speed = button("speed[time:speed]");
-### License
+#### License
-Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+Copyright (c) 2019-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
diff --git a/VERSION b/VERSION
index f25c9b0..6c6f557 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.1253
+0.15.27
diff --git a/bank-analyzer_vu-meter.dsp b/bank-analyzer_vu-meter.dsp
new file mode 100644
index 0000000..e300362
--- /dev/null
+++ b/bank-analyzer_vu-meter.dsp
@@ -0,0 +1,5 @@
+import("stdfaust.lib");
+
+process = _ <: attach(_,abs : ba.linear2db : hbargraph("Level [0]", -60, 0));
+
+// vim: set syntax=faust:
diff --git a/bank-filter_gain.dsp b/bank-filter_gain.dsp
new file mode 100644
index 0000000..311d0c0
--- /dev/null
+++ b/bank-filter_gain.dsp
@@ -0,0 +1,8 @@
+import("stdfaust.lib");
+
+gain_l = hslider("gain left[0]", 0, 0, 1, 0.01);
+gain_r = hslider("gain right[1]", 0, 0, 1, 0.01);
+
+process = _ * gain_l, _ * gain_r;
+
+// vim: set syntax=faust:
diff --git a/bank-filter_through.dsp b/bank-filter_through.dsp
new file mode 100644
index 0000000..7fe32b2
--- /dev/null
+++ b/bank-filter_through.dsp
@@ -0,0 +1,5 @@
+import("stdfaust.lib");
+
+process = _, _;
+
+// vim: set syntax=faust:
diff --git a/bank-instrument_osc.dsp b/bank-instrument_osc.dsp
new file mode 100644
index 0000000..d779d60
--- /dev/null
+++ b/bank-instrument_osc.dsp
@@ -0,0 +1,19 @@
+declare options "[nvoices:16][midi:on]";
+
+import("stdfaust.lib");
+
+freq = hslider("freq", 20, 20, 20000, 1);
+gain = hslider("gain", 0, 0, 1, 0.01);
+gate = button("gate");
+
+lfo_f = hslider("LFO frequency[0]", 0, 0, 100, 1);
+lfo_a = hslider("LFO amplitude[1]", 0, 0, 1, 0.01);
+
+env = en.adsr(0.01, 1.0, 0.8, 0.1, gate) * gain;
+lfo = 1 + os.lf_triangle(lfo_f) * lfo_a;
+
+inst = os.triangle(freq * lfo) * env / 4;
+
+process = inst, inst;
+
+// vim: set syntax=faust:
diff --git a/bank-time_lfo.dsp b/bank-time_lfo.dsp
new file mode 100644
index 0000000..99a0fbb
--- /dev/null
+++ b/bank-time_lfo.dsp
@@ -0,0 +1,23 @@
+declare options "[time:on]";
+
+import("stdfaust.lib");
+
+barBeat = hslider("bar beat[time:barBeat]", 0, 0, 32, 1);
+beatsPerBar = hslider("beats per bar[time:beatsPerBar]", 1, 1, 32, 1);
+gate = button("speed[time:speed]");
+
+mul = hslider("mul[0]", 0, 0, 1000, 1);
+add = hslider("add[1]", 0, 0, 1000, 1);
+
+frac = barBeat / beatsPerBar
+ : hbargraph("bar pos %[2]", 0, 1);
+
+freq = sin(frac * ma.PI) * mul + add;
+
+env = en.adsr(0.01, 1.0, 0.8, 0.1, gate);
+
+instr = os.triangle(freq) * env;
+
+process = instr, instr;
+
+// vim: set syntax=faust:
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..1f1a41f
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,167 @@
+# Copyright (c) 2019-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 owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix pset: <http://lv2plug.in/ns/ext/presets#> .
+
+@prefix mephisto: <http://open-music-kontrollers.ch/lv2/mephisto#> .
+
+# Mephisto Audio 1x1
+mephisto:audio_1x1
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto Audio 2x2
+mephisto:audio_2x2
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto Audio 4x4
+mephisto:audio_4x4
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto Audio 8x8
+mephisto:audio_8x8
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto CV 1x1
+mephisto:cv_1x1
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto CV 2x2
+mephisto:cv_2x2
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto CV 4x4
+mephisto:cv_4x4
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# Mephisto CV 8x8
+mephisto:cv_8x8
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ ui:ui mephisto:ui ;
+ lv2:binary <mephisto@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto.ttl> .
+
+# UIs
+mephisto:ui
+ a ui:X11UI ;
+ ui:binary <mephisto_ui@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <mephisto_ui.ttl> .
+
+# Preset banks
+mephisto:bank-filter
+ a pset:Bank ;
+ rdfs:label "Filter bank" .
+mephisto:bank-instrument
+ a pset:Bank ;
+ rdfs:label "Instrument bank" .
+mephisto:bank-time
+ a pset:Bank ;
+ rdfs:label "Time bank" .
+mephisto:bank-analyzer
+ a pset:Bank ;
+ rdfs:label "Analyzer bank" .
+
+# Filter bank
+mephisto:bank-filter_through
+ a pset:Preset ;
+ lv2:appliesTo mephisto:audio_1x1 ,
+ mephisto:audio_2x2 ,
+ mephisto:audio_4x4 ,
+ mephisto:audio_8x8 ;
+ pset:bank mephisto:bank-filter ;
+ rdfs:label "[Filter] Through" ;
+ rdfs:seeAlso <presets.ttl> .
+mephisto:bank-filter_gain
+ a pset:Preset ;
+ lv2:appliesTo mephisto:audio_1x1 ,
+ mephisto:audio_2x2 ,
+ mephisto:audio_4x4 ,
+ mephisto:audio_8x8 ;
+ pset:bank mephisto:bank-filter ;
+ rdfs:label "[Filter] Gain" ;
+ rdfs:seeAlso <presets.ttl> .
+
+# Time bank
+mephisto:bank-time_lfo
+ a pset:Preset ;
+ lv2:appliesTo mephisto:audio_1x1 ,
+ mephisto:audio_2x2 ,
+ mephisto:audio_4x4 ,
+ mephisto:audio_8x8 ;
+ pset:bank mephisto:bank-time ;
+ rdfs:label "[Time] Simple LFO" ;
+ rdfs:seeAlso <presets.ttl> .
+
+# Instrument bank
+mephisto:bank-instrument_osc
+ a pset:Preset ;
+ lv2:appliesTo mephisto:audio_1x1 ,
+ mephisto:audio_2x2 ,
+ mephisto:audio_4x4 ,
+ mephisto:audio_8x8 ;
+ pset:bank mephisto:bank-instrument ;
+ rdfs:label "[Instrument] Simple Oscillator" ;
+ rdfs:seeAlso <presets.ttl> .
+
+# Analyzer bank
+mephisto:bank-analyzer_vu-meter
+ a pset:Preset ;
+ lv2:appliesTo mephisto:audio_1x1 ,
+ mephisto:audio_2x2 ,
+ mephisto:audio_4x4 ,
+ mephisto:audio_8x8 ;
+ pset:bank mephisto:bank-analyzer ;
+ rdfs:label "[Analyzer] VU Meter" ;
+ rdfs:seeAlso <presets.ttl> .
diff --git a/mephisto.c b/mephisto.c
new file mode 100644
index 0000000..47e7af0
--- /dev/null
+++ b/mephisto.c
@@ -0,0 +1,2711 @@
+/*
+ * Copyright (c) 2019-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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <math.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pthread.h>
+#include <inttypes.h>
+
+#include <mephisto.h>
+#include <props.h>
+#include <timely.h>
+#include <varchunk.h>
+
+#include <faust/dsp/llvm-c-dsp.h>
+
+#define MAX_CHANNEL 8
+#define MAX_VOICES 64
+
+//#define MDI_MPE
+
+typedef union _hash_t hash_t;
+typedef struct _voice_t voice_t;
+typedef struct _dsp_t dsp_t;
+typedef struct _job_t job_t;
+typedef struct _pos_t pos_t;
+typedef struct _plughandle_t plughandle_t;
+
+typedef struct _cntrl_vertical_slider_t cntrl_vertical_slider_t;
+typedef struct _cntrl_horizontal_slider_t cntrl_horizontal_slider_t;
+typedef struct _cntrl_num_entry_t cntrl_num_entry_t;
+typedef struct _cntrl_horizontal_bargraph_t cntrl_horizontal_bargraph_t;
+typedef struct _cntrl_vertical_bargraph_t cntrl_vertical_bargraph_t;
+typedef struct _cntrl_sound_file_t cntrl_sound_file_t;
+
+typedef struct _cntrl_t cntrl_t;
+
+struct _cntrl_vertical_slider_t {
+ float init;
+ float min;
+ float max;
+ float ran;
+ float step;
+};
+
+struct _cntrl_horizontal_slider_t {
+ float init;
+ float min;
+ float max;
+ float ran;
+ float step;
+};
+
+struct _cntrl_num_entry_t {
+ float init;
+ float min;
+ float max;
+ float ran;
+ float step;
+};
+
+struct _cntrl_horizontal_bargraph_t {
+ float min;
+ float max;
+ float ran_1;
+};
+
+struct _cntrl_vertical_bargraph_t {
+ float min;
+ float max;
+ float ran_1;
+};
+
+struct _cntrl_sound_file_t {
+ uint32_t dummy; //FIXME
+};
+
+struct _cntrl_t {
+ char label [LABEL_SIZE];
+ cntrl_type_t type;
+ bool readonly;
+ float *zone;
+ union {
+ cntrl_vertical_slider_t vertical_slider;
+ cntrl_horizontal_slider_t horizontal_slider;
+ cntrl_num_entry_t num_entry;
+ cntrl_horizontal_bargraph_t horizontal_bargraph;
+ cntrl_vertical_bargraph_t vertical_bargraph;
+ cntrl_sound_file_t sound_file;
+ };
+};
+
+typedef enum _voice_state_t {
+ VOICE_STATE_INACTIVE = 0,
+ VOICE_STATE_ACTIVE = (1 << 0),
+ VOICE_STATE_SUSTAIN = (1 << 1)
+} voice_state_t;
+
+union _hash_t {
+ struct {
+ uint8_t chn;
+ uint8_t key;
+ };
+ uint16_t id;
+};
+
+struct _pos_t {
+ cntrl_t bar_beat;
+ cntrl_t bar;
+ cntrl_t beat_unit;
+ cntrl_t beats_per_bar;
+ cntrl_t beats_per_minute;
+ cntrl_t frame;
+ cntrl_t frames_per_second;
+ cntrl_t speed;
+};
+
+struct _voice_t {
+ llvm_dsp *instance;
+
+ cntrl_t gate;
+ cntrl_t gain;
+ cntrl_t freq;
+ cntrl_t pressure;
+ cntrl_t timbre;
+ cntrl_t d_freq;
+ cntrl_t d_pressure;
+ cntrl_t d_timbre;
+
+ cntrl_t cntrls [NCONTROLS];
+
+ pos_t pos;
+
+ voice_state_t state;
+ hash_t hash;
+ bool retrigger;
+};
+
+struct _dsp_t {
+ plughandle_t *handle;
+ llvm_dsp_factory *factory;
+ UIGlue ui_glue;
+ MetaGlue meta_glue;
+ uint32_t nins;
+ uint32_t nouts;
+ uint32_t nvoices;
+ uint32_t cvoices;
+ voice_t voices [MAX_VOICES];
+ bool midi_on;
+ bool time_on;
+ bool is_instrument;
+ timely_mask_t timely_mask;
+ int32_t idx;
+ uint32_t ivoice;
+};
+
+typedef enum _job_type_t {
+ JOB_TYPE_INIT,
+ JOB_TYPE_DEINIT,
+ JOB_TYPE_ERROR_CLEAR,
+ JOB_TYPE_ERROR_APPEND,
+ JOB_TYPE_ERROR_FREE
+} job_type_t;
+
+struct _job_t {
+ job_type_t type;
+ union {
+ dsp_t *dsp;
+ char *error;
+ };
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Worker_Schedule *sched;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ LV2_URID midi_MidiEvent;
+
+ char dsp_dir [FILENAME_MAX];
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ const LV2_Atom_Sequence *control;
+ LV2_Atom_Sequence *notify;
+ const float *audio_in [MAX_CHANNEL];
+ float *audio_out [MAX_CHANNEL];
+ unsigned nchannel;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ varchunk_t *to_worker;
+
+ uint32_t xfade_max;
+ uint32_t xfade_cur;
+
+ uint32_t srate;
+ char bundle_path [PATH_MAX];
+
+ LV2_URID mephisto_error;
+ LV2_URID mephisto_timestamp;
+ LV2_URID mephisto_control [NCONTROLS];
+ LV2_URID mephisto_controlMin [NCONTROLS];
+ LV2_URID mephisto_controlMax [NCONTROLS];
+ LV2_URID mephisto_controlStep [NCONTROLS];
+ LV2_URID mephisto_controlType [NCONTROLS];
+ LV2_URID mephisto_controlLabel [NCONTROLS];
+
+ struct {
+ bool error;
+ bool attributes;
+ } dirty;
+
+ bool play;
+ dsp_t *dsp [2];
+
+ uint16_t rpn_lsb [0x10];
+ uint16_t rpn_msb [0x10];
+ uint16_t data_lsb [0x10];
+ uint16_t pressure [0x10];
+ uint16_t timbre [0x10];
+ float bend [0x10];
+ float range [0x10];
+ bool sustain [0x10];
+
+ timely_t timely;
+
+ FAUSTFLOAT *faudio_in [MAX_CHANNEL];
+ FAUSTFLOAT *faudio_out [MAX_CHANNEL];
+};
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+#if 0
+# define DBG(HANDLE, FMT, ...) \
+ if((HANDLE)->log) \
+ { \
+ lv2_log_note(&((HANDLE)->logger), (FMT), __VA_ARGS__); \
+ }
+#else
+# define DBG(HANDLE, FMT, ...) \
+ if(!(HANDLE)) \
+ { \
+ lv2_log_note(&((HANDLE)->logger), (FMT), __VA_ARGS__); \
+ }
+#endif
+
+static inline voice_t *
+_voice_begin(dsp_t *dsp)
+{
+ return dsp->voices;
+}
+
+static inline bool
+_voice_not_end(dsp_t *dsp, voice_t *voice)
+{
+ const uint32_t voice_offset = voice - dsp->voices;
+
+ return voice_offset < dsp->nvoices;
+}
+
+static inline voice_t *
+_voice_next(voice_t *voice)
+{
+ return voice + 1;
+}
+
+#define VOICE_FOREACH(DSP, VOICE) \
+ for(voice_t *(VOICE) = _voice_begin((DSP)); \
+ _voice_not_end((DSP), (VOICE)); \
+ (VOICE) = _voice_next((VOICE)))
+
+static void
+_intercept_code(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ char *code;
+ if( (code = varchunk_write_request(handle->to_worker, impl->value.size)) )
+ {
+ memcpy(code, handle->state.code, impl->value.size);
+
+ varchunk_write_advance(handle->to_worker, impl->value.size);
+
+ const job_t job = {
+ .type = JOB_TYPE_INIT
+ };
+ handle->sched->schedule_work(handle->sched->handle, sizeof(job), &job);
+ }
+ else if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "[%s] ringbuffer overflow\n", __func__);
+ }
+}
+
+static void
+_cntrl_refresh_value_abs(cntrl_t *cntrl, float val)
+{
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+}
+
+static float
+_cntrl_get_value_abs(cntrl_t *cntrl)
+{
+ if(cntrl->zone)
+ {
+ return *cntrl->zone;
+ }
+
+ return 0.f;
+}
+
+static float
+_cntrl_get_value_rel(cntrl_t *cntrl)
+{
+ switch(cntrl->type)
+ {
+ case CNTRL_VERTICAL_BARGRAPH:
+ {
+ if(cntrl->zone)
+ {
+ return (*cntrl->zone - cntrl->vertical_bargraph.min) * cntrl->vertical_bargraph.ran_1;
+ }
+ } break;
+ case CNTRL_HORIZONTAL_BARGRAPH:
+ {
+ if(cntrl->zone)
+ {
+ return (*cntrl->zone - cntrl->horizontal_bargraph.min) * cntrl->horizontal_bargraph.ran_1;
+ }
+ } break;
+
+ case CNTRL_NONE:
+ // fall-through
+ case CNTRL_BUTTON:
+ // fall-through
+ case CNTRL_CHECK_BUTTON:
+ // fall-through
+ case CNTRL_VERTICAL_SLIDER:
+ // fall-through
+ case CNTRL_HORIZONTAL_SLIDER:
+ // fall-through
+ case CNTRL_NUM_ENTRY:
+ // fall-through
+ case CNTRL_SOUND_FILE:
+ // fall-through
+ {
+ // nothing to do
+ } break;
+ }
+
+ return 0.f;
+}
+
+static void
+_cntrl_refresh_value_rel(cntrl_t *cntrl, float val)
+{
+ switch(cntrl->type)
+ {
+ case CNTRL_NONE:
+ {
+ // do nothing
+ } break;
+ case CNTRL_BUTTON:
+ {
+ val = val > 0.5f
+ ? 1.f
+ : 0.0;
+
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+ } break;
+ case CNTRL_CHECK_BUTTON:
+ {
+ val = val > 0.5f
+ ? 1.f
+ : 0.0;
+
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+ } break;
+ case CNTRL_VERTICAL_SLIDER:
+ {
+ val = val * cntrl->vertical_slider.ran
+ + cntrl->vertical_slider.min;
+
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+ } break;
+ case CNTRL_HORIZONTAL_SLIDER:
+ {
+ val = val * cntrl->horizontal_slider.ran
+ + cntrl->horizontal_slider.min;
+
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+ } break;
+ case CNTRL_NUM_ENTRY:
+ {
+ val = val * cntrl->num_entry.ran
+ + cntrl->num_entry.min;
+
+ if(cntrl->zone)
+ {
+ *cntrl->zone = val;
+ }
+ } break;
+ case CNTRL_SOUND_FILE:
+ {
+ //FIXME
+ } break;
+
+ case CNTRL_HORIZONTAL_BARGRAPH:
+ // fall-through
+ case CNTRL_VERTICAL_BARGRAPH:
+ {
+ // nothing to do
+ } break;
+ }
+}
+
+static void
+_cntrl_refresh_attributes(cntrl_t *cntrl, float *min, float *max, float *step,
+ int32_t *type, char *label)
+{
+ *type = cntrl->type;
+ strncpy(label, cntrl->label, LABEL_SIZE - 1);
+
+ switch(cntrl->type)
+ {
+ case CNTRL_BUTTON:
+ // fall-through
+ case CNTRL_CHECK_BUTTON:
+ {
+ *min = 0.f;
+ *max = 1.f;
+ *step = 1.f;
+ } break;
+
+ case CNTRL_VERTICAL_SLIDER:
+ {
+ *min = cntrl->vertical_slider.min;
+ *max = cntrl->vertical_slider.max;
+ *step = cntrl->vertical_slider.step;
+ } break;
+ case CNTRL_HORIZONTAL_SLIDER:
+ {
+ *min = cntrl->horizontal_slider.min;
+ *max = cntrl->horizontal_slider.max;
+ *step = cntrl->horizontal_slider.step;
+ } break;
+ case CNTRL_NUM_ENTRY:
+ {
+ *min = cntrl->num_entry.min;
+ *max = cntrl->num_entry.max;
+ *step = cntrl->num_entry.step;
+ } break;
+ case CNTRL_HORIZONTAL_BARGRAPH:
+ {
+ *min = cntrl->horizontal_bargraph.min;
+ *max = cntrl->horizontal_bargraph.max;
+ *step = 1.f;
+ } break;
+ case CNTRL_VERTICAL_BARGRAPH:
+ {
+ *min = cntrl->vertical_bargraph.min;
+ *max = cntrl->vertical_bargraph.max;
+ *step = 1.f;
+ } break;
+
+ case CNTRL_NONE:
+ // fall-through
+ case CNTRL_SOUND_FILE:
+ {
+ // do nothing
+ } break;
+ }
+}
+
+static void
+_refresh_attributes(plughandle_t *handle, uint32_t idx)
+{
+ float min = 0.f;
+ float max = 0.f;
+ float step = 0.f;
+ int32_t type = CNTRL_NONE;
+ char label [LABEL_SIZE] = "";
+
+ dsp_t *dsp = handle->dsp[!handle->play];
+
+ if(!dsp)
+ {
+ return;
+ }
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ cntrl_t *cntrl = &voice->cntrls[idx];
+
+ if(cntrl->type == CNTRL_NONE)
+ {
+ continue;
+ }
+
+ _cntrl_refresh_attributes(cntrl, &min, &max, &step, &type, label);
+ }
+
+
+ props_impl_t *impl = NULL;
+
+ impl = _props_impl_get(&handle->props, handle->mephisto_controlMin[idx]);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.Float, sizeof(float), &min);
+ }
+
+ impl = _props_impl_get(&handle->props, handle->mephisto_controlMax[idx]);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.Float, sizeof(float), &max);
+ }
+
+ impl = _props_impl_get(&handle->props, handle->mephisto_controlStep[idx]);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.Float, sizeof(float), &step);
+ }
+
+ impl = _props_impl_get(&handle->props, handle->mephisto_controlType[idx]);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.Int, sizeof(int32_t), &type);
+ }
+
+ impl = _props_impl_get(&handle->props, handle->mephisto_controlLabel[idx]);
+ if(impl)
+ {
+ _props_impl_set(&handle->props, impl, handle->forge.String, strlen(label) + 1, label);
+ }
+}
+
+static void
+_refresh_value(plughandle_t *handle, uint32_t idx)
+{
+ const bool off [2] = {
+ handle->play,
+ !handle->play
+ };
+
+ const float val = handle->state.control[idx];
+
+ for(uint32_t d = 0; d < 2; d++)
+ {
+ dsp_t *dsp = handle->dsp[off[d]];
+
+ if(!dsp)
+ {
+ continue;
+ }
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ cntrl_t *cntrl = &voice->cntrls[idx];
+
+ if(cntrl->type == CNTRL_NONE)
+ {
+ continue;
+ }
+
+ _cntrl_refresh_value_rel(cntrl, val);
+ }
+ }
+}
+
+static void
+_refresh_time_position(plughandle_t *handle)
+{
+ const bool off [2] = {
+ handle->play,
+ !handle->play
+ };
+
+ for(uint32_t d = 0; d < 2; d++)
+ {
+ dsp_t *dsp = handle->dsp[off[d]];
+
+ if(!dsp || !dsp->time_on)
+ {
+ continue;
+ }
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ _cntrl_refresh_value_abs(&voice->pos.bar_beat,
+ TIMELY_BAR_BEAT(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.bar,
+ TIMELY_BAR(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.beat_unit,
+ TIMELY_BEAT_UNIT(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.beats_per_bar,
+ TIMELY_BEATS_PER_BAR(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.beats_per_minute,
+ TIMELY_BEATS_PER_MINUTE(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.frame,
+ TIMELY_FRAME(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.frames_per_second,
+ TIMELY_FRAMES_PER_SECOND(&handle->timely));
+ _cntrl_refresh_value_abs(&voice->pos.speed,
+ TIMELY_SPEED(&handle->timely));
+ }
+ }
+}
+
+static void
+_intercept_xfade_duration(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ handle->xfade_max = handle->srate * handle->state.xfade_dur / 1000;
+}
+
+static void
+_intercept_control(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+ const uint32_t idx = (float *)impl->value.body - handle->state.control;
+
+ _refresh_value(handle, idx);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = MEPHISTO__code,
+ .offset = offsetof(plugstate_t, code),
+ .type = LV2_ATOM__String,
+ .event_cb = _intercept_code,
+ .max_size = CODE_SIZE
+ },
+ {
+ .property = MEPHISTO__error,
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, error),
+ .type = LV2_ATOM__String,
+ .max_size = ERROR_SIZE
+ },
+ {
+ .property = MEPHISTO__xfadeDuration,
+ .offset = offsetof(plugstate_t, xfade_dur),
+ .type = LV2_ATOM__Int,
+ .event_cb = _intercept_xfade_duration
+ },
+ {
+ .property = MEPHISTO__fontHeight,
+ .offset = offsetof(plugstate_t, font_height),
+ .type = LV2_ATOM__Int
+ },
+ {
+ .property = MEPHISTO__timestamp,
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, timestamp),
+ .type = LV2_ATOM__Long
+ },
+ CONTROL(1),
+ CONTROL(2),
+ CONTROL(3),
+ CONTROL(4),
+ CONTROL(5),
+ CONTROL(6),
+ CONTROL(7),
+ CONTROL(1),
+ CONTROL(9),
+ CONTROL(10),
+ CONTROL(11),
+ CONTROL(12),
+ CONTROL(13),
+ CONTROL(14),
+ CONTROL(15),
+ CONTROL(16)
+};
+
+static inline void
+_play(plughandle_t *handle, int64_t from, int64_t to)
+{
+ const uint32_t nsamples = to - from;
+
+ FAUSTFLOAT *audio_in [32];
+ FAUSTFLOAT *audio_out [32];
+
+ // libFAUST does not check for valid audio buffer pointers,
+ // so we must provide a plethora (32) of them in case the user makes a patch
+ // with more than 2 ins and outs
+ for(uint32_t i = 0; i < 32; i++)
+ {
+ const uint32_t idx = i % handle->nchannel;
+
+ audio_in[i] = handle->faudio_in[idx];
+ audio_out[i] = handle->faudio_out[idx];
+ }
+
+ // fill audio in, clear audio out
+ for(uint32_t n = 0; n < handle->nchannel; n++)
+ {
+ for(uint32_t i = 0; i < nsamples; i++)
+ {
+ audio_in[n][i] = handle->audio_in[n][from + i];
+ handle->audio_out[n][from + i] = 0.f;
+ }
+ }
+
+ const bool off [2] = {
+ handle->play,
+ !handle->play
+ };
+
+ for(uint32_t d = 0; d < 2; d++)
+ {
+ dsp_t *dsp = handle->dsp[off[d]];
+
+ if(!dsp)
+ {
+ continue;
+ }
+
+ float gain = 1.f;
+
+ if(handle->xfade_cur > 0)
+ {
+ const float t = 2.f * handle->xfade_cur / handle->xfade_max - 1.f;
+
+ if(d == 0) // fade-out
+ {
+ gain = sqrtf(0.5f * (1.f + t) );
+ }
+ else // fade-in
+ {
+ gain = sqrtf(0.5f * (1.f - t) );
+ }
+ }
+ else
+ {
+ if(d == 1)
+ {
+ continue; // skip this
+ }
+ }
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ {
+ if(voice->retrigger)
+ {
+ _cntrl_refresh_value_abs(&voice->gate, 0.f);
+ computeCDSPInstance(voice->instance, 1, audio_in, audio_out);
+ _cntrl_refresh_value_abs(&voice->gate, 1.f);
+
+ voice->retrigger = false;
+ }
+
+ computeCDSPInstance(voice->instance, nsamples, audio_in, audio_out);
+
+ // add to master out
+ for(uint32_t n = 0; n < handle->nchannel; n++)
+ {
+ for(uint32_t i = 0; i < nsamples; i++)
+ {
+ handle->audio_out[n][from + i] += gain * audio_out[n][i];
+ }
+ }
+ }
+ }
+ }
+
+ if(handle->xfade_cur > 0)
+ {
+ if(nsamples >= handle->xfade_cur)
+ {
+ handle->xfade_cur = 0;
+
+ // switch dsps
+ handle->play = !handle->play;
+ }
+ else
+ {
+ handle->xfade_cur -= nsamples;
+ }
+ }
+}
+
+static void
+_timely_cb(timely_t *timely __attribute__((unused)),
+ int64_t frames __attribute__((unused)), LV2_URID type __attribute__((unused)),
+ void *data __attribute__((unused)))
+{
+ // nothing to do
+}
+
+static int
+_dsp_dir(char *buf, size_t len)
+{
+ FILE *fin = popen("faust -dspdir", "r");
+ if(!fin)
+ {
+ return -1;
+ }
+
+ const size_t sz = fread(buf, 1, len, fin);
+ pclose(fin);
+
+ if(sz == 0)
+ {
+ return 1;
+ }
+
+ buf[sz] = '\0';
+
+ char *esc = strpbrk(buf, "\n\r");
+ if(esc)
+ {
+ *esc = '\0';
+ }
+
+ return 0;
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ {
+ return NULL;
+ }
+ mlock(handle, sizeof(plughandle_t));
+
+ handle->nchannel = 1;
+ if( !strcmp(descriptor->URI, MEPHISTO__audio_2x2)
+ || !strcmp(descriptor->URI, MEPHISTO__cv_2x2) )
+ {
+ handle->nchannel = 2;
+ }
+ else if(!strcmp(descriptor->URI, MEPHISTO__audio_4x4)
+ || !strcmp(descriptor->URI, MEPHISTO__cv_4x4) )
+ {
+ handle->nchannel = 4;
+ }
+ else if(!strcmp(descriptor->URI, MEPHISTO__audio_8x8)
+ || !strcmp(descriptor->URI, MEPHISTO__cv_8x8) )
+ {
+ handle->nchannel = 8;
+ }
+
+ strncpy(handle->bundle_path, bundle_path, sizeof(handle->bundle_path) - 1);
+
+ LV2_Options_Option *opts = NULL;
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ {
+ handle->map = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_WORKER__schedule))
+ {
+ handle->sched= 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(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(!handle->sched)
+ {
+ fprintf(stderr,
+ "%s: Host does not support work:sched\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(_dsp_dir(handle->dsp_dir, sizeof(handle->dsp_dir)) != 0)
+ {
+ fprintf(stderr,
+ "%s: failed to get FAUST DSP directory\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);
+
+ handle->midi_MidiEvent = handle->map->map(handle->map->handle,
+ LV2_MIDI__MidiEvent);
+ const LV2_URID bufsz_maxBlockLength = handle->map->map(handle->map->handle,
+ LV2_BUF_SIZE__maxBlockLength);
+
+ int32_t max_block_length = 0;
+ for(LV2_Options_Option *opt = opts;
+ (opt->key != 0) && (opt->value != NULL);
+ opt++)
+ {
+ if( (opt->key == bufsz_maxBlockLength)
+ && (opt->size == sizeof(int32_t))
+ && (opt->type == handle->forge.Int) )
+ {
+ max_block_length = *(const int32_t *)opt->value;
+
+ break;
+ }
+ }
+
+ if(max_block_length == 0)
+ {
+ fprintf(stderr,
+ "%s: Host does not provide bufsz:maxBlockLength\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ const size_t buflen = sizeof(FAUSTFLOAT) * max_block_length;
+ for(uint32_t n = 0; n < handle->nchannel; n++)
+ {
+ handle->faudio_in[n] = malloc(buflen);
+ handle->faudio_out[n] = malloc(buflen);
+
+ if(!handle->faudio_in[n] || !handle->faudio_out[n])
+ {
+ for(n = 0; n < handle->nchannel; n++)
+ {
+ free(handle->faudio_in[n]);
+ free(handle->faudio_out[n]);
+ }
+
+ free(handle);
+ return NULL;
+ }
+ }
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->mephisto_error = props_map(&handle->props, MEPHISTO__error);
+ handle->mephisto_timestamp = props_map(&handle->props, MEPHISTO__timestamp);
+
+ handle->mephisto_control[0] = props_map(&handle->props, MEPHISTO__control_1);
+ handle->mephisto_control[1] = props_map(&handle->props, MEPHISTO__control_2);
+ handle->mephisto_control[2] = props_map(&handle->props, MEPHISTO__control_3);
+ handle->mephisto_control[3] = props_map(&handle->props, MEPHISTO__control_4);
+ handle->mephisto_control[4] = props_map(&handle->props, MEPHISTO__control_5);
+ handle->mephisto_control[5] = props_map(&handle->props, MEPHISTO__control_6);
+ handle->mephisto_control[6] = props_map(&handle->props, MEPHISTO__control_7);
+ handle->mephisto_control[7] = props_map(&handle->props, MEPHISTO__control_8);
+ handle->mephisto_control[8] = props_map(&handle->props, MEPHISTO__control_9);
+ handle->mephisto_control[9] = props_map(&handle->props, MEPHISTO__control_10);
+ handle->mephisto_control[10] = props_map(&handle->props, MEPHISTO__control_11);
+ handle->mephisto_control[11] = props_map(&handle->props, MEPHISTO__control_12);
+ handle->mephisto_control[12] = props_map(&handle->props, MEPHISTO__control_13);
+ handle->mephisto_control[13] = props_map(&handle->props, MEPHISTO__control_14);
+ handle->mephisto_control[14] = props_map(&handle->props, MEPHISTO__control_15);
+ handle->mephisto_control[15] = props_map(&handle->props, MEPHISTO__control_16);
+
+ handle->mephisto_controlMin[0] = props_map(&handle->props, MEPHISTO__controlMin_1);
+ handle->mephisto_controlMin[1] = props_map(&handle->props, MEPHISTO__controlMin_2);
+ handle->mephisto_controlMin[2] = props_map(&handle->props, MEPHISTO__controlMin_3);
+ handle->mephisto_controlMin[3] = props_map(&handle->props, MEPHISTO__controlMin_4);
+ handle->mephisto_controlMin[4] = props_map(&handle->props, MEPHISTO__controlMin_5);
+ handle->mephisto_controlMin[5] = props_map(&handle->props, MEPHISTO__controlMin_6);
+ handle->mephisto_controlMin[6] = props_map(&handle->props, MEPHISTO__controlMin_7);
+ handle->mephisto_controlMin[7] = props_map(&handle->props, MEPHISTO__controlMin_8);
+ handle->mephisto_controlMin[8] = props_map(&handle->props, MEPHISTO__controlMin_9);
+ handle->mephisto_controlMin[9] = props_map(&handle->props, MEPHISTO__controlMin_10);
+ handle->mephisto_controlMin[10] = props_map(&handle->props, MEPHISTO__controlMin_11);
+ handle->mephisto_controlMin[11] = props_map(&handle->props, MEPHISTO__controlMin_12);
+ handle->mephisto_controlMin[12] = props_map(&handle->props, MEPHISTO__controlMin_13);
+ handle->mephisto_controlMin[13] = props_map(&handle->props, MEPHISTO__controlMin_14);
+ handle->mephisto_controlMin[14] = props_map(&handle->props, MEPHISTO__controlMin_15);
+ handle->mephisto_controlMin[15] = props_map(&handle->props, MEPHISTO__controlMin_16);
+
+ handle->mephisto_controlMax[0] = props_map(&handle->props, MEPHISTO__controlMax_1);
+ handle->mephisto_controlMax[1] = props_map(&handle->props, MEPHISTO__controlMax_2);
+ handle->mephisto_controlMax[2] = props_map(&handle->props, MEPHISTO__controlMax_3);
+ handle->mephisto_controlMax[3] = props_map(&handle->props, MEPHISTO__controlMax_4);
+ handle->mephisto_controlMax[4] = props_map(&handle->props, MEPHISTO__controlMax_5);
+ handle->mephisto_controlMax[5] = props_map(&handle->props, MEPHISTO__controlMax_6);
+ handle->mephisto_controlMax[6] = props_map(&handle->props, MEPHISTO__controlMax_7);
+ handle->mephisto_controlMax[7] = props_map(&handle->props, MEPHISTO__controlMax_8);
+ handle->mephisto_controlMax[8] = props_map(&handle->props, MEPHISTO__controlMax_9);
+ handle->mephisto_controlMax[9] = props_map(&handle->props, MEPHISTO__controlMax_10);
+ handle->mephisto_controlMax[10] = props_map(&handle->props, MEPHISTO__controlMax_11);
+ handle->mephisto_controlMax[11] = props_map(&handle->props, MEPHISTO__controlMax_12);
+ handle->mephisto_controlMax[12] = props_map(&handle->props, MEPHISTO__controlMax_13);
+ handle->mephisto_controlMax[13] = props_map(&handle->props, MEPHISTO__controlMax_14);
+ handle->mephisto_controlMax[14] = props_map(&handle->props, MEPHISTO__controlMax_15);
+ handle->mephisto_controlMax[15] = props_map(&handle->props, MEPHISTO__controlMax_16);
+
+ handle->mephisto_controlStep[0] = props_map(&handle->props, MEPHISTO__controlStep_1);
+ handle->mephisto_controlStep[1] = props_map(&handle->props, MEPHISTO__controlStep_2);
+ handle->mephisto_controlStep[2] = props_map(&handle->props, MEPHISTO__controlStep_3);
+ handle->mephisto_controlStep[3] = props_map(&handle->props, MEPHISTO__controlStep_4);
+ handle->mephisto_controlStep[4] = props_map(&handle->props, MEPHISTO__controlStep_5);
+ handle->mephisto_controlStep[5] = props_map(&handle->props, MEPHISTO__controlStep_6);
+ handle->mephisto_controlStep[6] = props_map(&handle->props, MEPHISTO__controlStep_7);
+ handle->mephisto_controlStep[7] = props_map(&handle->props, MEPHISTO__controlStep_8);
+ handle->mephisto_controlStep[8] = props_map(&handle->props, MEPHISTO__controlStep_9);
+ handle->mephisto_controlStep[9] = props_map(&handle->props, MEPHISTO__controlStep_10);
+ handle->mephisto_controlStep[10] = props_map(&handle->props, MEPHISTO__controlStep_11);
+ handle->mephisto_controlStep[11] = props_map(&handle->props, MEPHISTO__controlStep_12);
+ handle->mephisto_controlStep[12] = props_map(&handle->props, MEPHISTO__controlStep_13);
+ handle->mephisto_controlStep[13] = props_map(&handle->props, MEPHISTO__controlStep_14);
+ handle->mephisto_controlStep[14] = props_map(&handle->props, MEPHISTO__controlStep_15);
+ handle->mephisto_controlStep[15] = props_map(&handle->props, MEPHISTO__controlStep_16);
+
+ handle->mephisto_controlType[0] = props_map(&handle->props, MEPHISTO__controlType_1);
+ handle->mephisto_controlType[1] = props_map(&handle->props, MEPHISTO__controlType_2);
+ handle->mephisto_controlType[2] = props_map(&handle->props, MEPHISTO__controlType_3);
+ handle->mephisto_controlType[3] = props_map(&handle->props, MEPHISTO__controlType_4);
+ handle->mephisto_controlType[4] = props_map(&handle->props, MEPHISTO__controlType_5);
+ handle->mephisto_controlType[5] = props_map(&handle->props, MEPHISTO__controlType_6);
+ handle->mephisto_controlType[6] = props_map(&handle->props, MEPHISTO__controlType_7);
+ handle->mephisto_controlType[7] = props_map(&handle->props, MEPHISTO__controlType_8);
+ handle->mephisto_controlType[8] = props_map(&handle->props, MEPHISTO__controlType_9);
+ handle->mephisto_controlType[9] = props_map(&handle->props, MEPHISTO__controlType_10);
+ handle->mephisto_controlType[10] = props_map(&handle->props, MEPHISTO__controlType_11);
+ handle->mephisto_controlType[11] = props_map(&handle->props, MEPHISTO__controlType_12);
+ handle->mephisto_controlType[12] = props_map(&handle->props, MEPHISTO__controlType_13);
+ handle->mephisto_controlType[13] = props_map(&handle->props, MEPHISTO__controlType_14);
+ handle->mephisto_controlType[14] = props_map(&handle->props, MEPHISTO__controlType_15);
+ handle->mephisto_controlType[15] = props_map(&handle->props, MEPHISTO__controlType_16);
+
+ handle->mephisto_controlLabel[0] = props_map(&handle->props, MEPHISTO__controlLabel_1);
+ handle->mephisto_controlLabel[1] = props_map(&handle->props, MEPHISTO__controlLabel_2);
+ handle->mephisto_controlLabel[2] = props_map(&handle->props, MEPHISTO__controlLabel_3);
+ handle->mephisto_controlLabel[3] = props_map(&handle->props, MEPHISTO__controlLabel_4);
+ handle->mephisto_controlLabel[4] = props_map(&handle->props, MEPHISTO__controlLabel_5);
+ handle->mephisto_controlLabel[5] = props_map(&handle->props, MEPHISTO__controlLabel_6);
+ handle->mephisto_controlLabel[6] = props_map(&handle->props, MEPHISTO__controlLabel_7);
+ handle->mephisto_controlLabel[7] = props_map(&handle->props, MEPHISTO__controlLabel_8);
+ handle->mephisto_controlLabel[8] = props_map(&handle->props, MEPHISTO__controlLabel_9);
+ handle->mephisto_controlLabel[9] = props_map(&handle->props, MEPHISTO__controlLabel_10);
+ handle->mephisto_controlLabel[10] = props_map(&handle->props, MEPHISTO__controlLabel_11);
+ handle->mephisto_controlLabel[11] = props_map(&handle->props, MEPHISTO__controlLabel_12);
+ handle->mephisto_controlLabel[12] = props_map(&handle->props, MEPHISTO__controlLabel_13);
+ handle->mephisto_controlLabel[13] = props_map(&handle->props, MEPHISTO__controlLabel_14);
+ handle->mephisto_controlLabel[14] = props_map(&handle->props, MEPHISTO__controlLabel_15);
+ handle->mephisto_controlLabel[15] = props_map(&handle->props, MEPHISTO__controlLabel_16);
+
+ handle->to_worker = varchunk_new(BUF_SIZE, true);
+ handle->srate = rate;
+
+ for(uint32_t chn = 0; chn < 0x10; chn++)
+ {
+ handle->range[chn] = 48.f; // semitones
+ }
+
+ const timely_mask_t mask = 0;
+ 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->control = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+
+ case 2:
+ handle->audio_in[0] = (const float *)data;
+ break;
+ case 3:
+ handle->audio_out[0] = (float *)data;
+ break;
+
+ case 4:
+ handle->audio_in[1] = (const float *)data;
+ break;
+ case 5:
+ handle->audio_out[1] = (float *)data;
+ break;
+
+ case 6:
+ handle->audio_in[2] = (const float *)data;
+ break;
+ case 7:
+ handle->audio_out[2] = (float *)data;
+ break;
+
+ case 8:
+ handle->audio_in[3] = (const float *)data;
+ break;
+ case 9:
+ handle->audio_out[3] = (float *)data;
+ break;
+
+ case 10:
+ handle->audio_in[4] = (const float *)data;
+ break;
+ case 11:
+ handle->audio_out[4] = (float *)data;
+ break;
+
+ case 12:
+ handle->audio_in[5] = (const float *)data;
+ break;
+ case 13:
+ handle->audio_out[5] = (float *)data;
+ break;
+
+ case 14:
+ handle->audio_in[6] = (const float *)data;
+ break;
+ case 15:
+ handle->audio_out[6] = (float *)data;
+ break;
+
+ case 16:
+ handle->audio_in[7] = (const float *)data;
+ break;
+ case 17:
+ handle->audio_out[7] = (float *)data;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static inline float
+_midi2cps(float pitch)
+{
+ return exp2f( (pitch - 69.f) / 12.f) * 440.f;
+}
+
+static inline voice_t *
+_next_available_voice(dsp_t *dsp)
+{
+ uint32_t i;
+
+ for(i = 0, dsp->ivoice = (dsp->ivoice + i + 1) % dsp->nvoices;
+ i < dsp->nvoices;
+ i++, dsp->ivoice = (dsp->ivoice + i) % dsp->nvoices)
+ {
+ voice_t *voice = &dsp->voices[dsp->ivoice];
+
+ if(voice->state == VOICE_STATE_INACTIVE)
+ {
+ return voice;
+ }
+ }
+
+ for(i = 0, dsp->ivoice = (dsp->ivoice + i) % dsp->nvoices;
+ i < dsp->nvoices;
+ i++, dsp->ivoice = (dsp->ivoice + i) % dsp->nvoices)
+ {
+ voice_t *voice = &dsp->voices[dsp->ivoice];
+
+ if(voice->state & VOICE_STATE_SUSTAIN)
+ {
+ return voice;
+ }
+ }
+
+ for(i = 0, dsp->ivoice = (dsp->ivoice + i) % dsp->nvoices;
+ i < dsp->nvoices;
+ i++, dsp->ivoice = (dsp->ivoice + i) % dsp->nvoices)
+ {
+ voice_t *voice = &dsp->voices[dsp->ivoice];
+
+ if(voice->state & VOICE_STATE_ACTIVE)
+ {
+ return voice;
+ }
+ }
+
+ return NULL;
+}
+
+static inline voice_t *
+_find_active_voice(dsp_t *dsp, const hash_t *hash)
+{
+ VOICE_FOREACH(dsp, voice)
+ {
+ if( (voice->state == VOICE_STATE_ACTIVE)
+ && (voice->hash.id == hash->id) )
+ {
+ return voice;
+ }
+ }
+
+ return NULL;
+}
+
+static inline void
+_update_frequency(plughandle_t *handle, dsp_t *dsp, uint8_t chn)
+{
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice->state & VOICE_STATE_ACTIVE)
+ {
+ if(voice->hash.chn == chn)
+ {
+ const float freq = _midi2cps(voice->hash.key
+ + handle->bend[chn]*handle->range[chn]);
+
+ _cntrl_refresh_value_abs(&voice->freq, freq);
+ }
+ }
+ }
+}
+
+static inline void
+_update_pressure(plughandle_t *handle, dsp_t *dsp, uint8_t chn)
+{
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice->state & VOICE_STATE_ACTIVE)
+ {
+ if(voice->hash.chn == chn)
+ {
+ const float pressure = handle->pressure[chn] * 0x1p-14;
+
+ _cntrl_refresh_value_abs(&voice->pressure, pressure);
+ }
+ }
+ }
+}
+
+static inline void
+_update_timbre(plughandle_t *handle, dsp_t *dsp, uint8_t chn)
+{
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice->state & VOICE_STATE_ACTIVE)
+ {
+ if(voice->hash.chn == chn)
+ {
+ const float timbre = handle->timbre[chn] * 0x1p-14;
+
+ _cntrl_refresh_value_abs(&voice->timbre, timbre);
+ }
+ }
+ }
+}
+
+static inline void
+_voice_off(plughandle_t *handle, voice_t *voice)
+{
+ if(handle->sustain[voice->hash.chn])
+ {
+ voice->state |= VOICE_STATE_SUSTAIN;
+ }
+ else
+ {
+ _cntrl_refresh_value_abs(&voice->gate, 0.f);
+
+ voice->state = VOICE_STATE_INACTIVE;
+ }
+}
+
+static inline void
+_voice_off_panic(voice_t *voice)
+{
+ _cntrl_refresh_value_abs(&voice->gate, 0.f);
+
+ voice->state = VOICE_STATE_INACTIVE;
+}
+
+static inline void
+_voice_off_force(voice_t *voice)
+{
+ _cntrl_refresh_value_abs(&voice->gate, 0.f);
+
+ voice->state = VOICE_STATE_INACTIVE;
+}
+
+static void
+_handle_midi_2(dsp_t *dsp,
+ int64_t frames __attribute__((unused)), const uint8_t *msg)
+{
+ const uint8_t cmd = msg[0] & 0xf0;
+
+ if(!dsp || !dsp->is_instrument || !dsp->midi_on)
+ {
+ return;
+ }
+
+ switch(cmd)
+ {
+ case LV2_MIDI_MSG_CONTROLLER:
+ {
+ const uint8_t ctr = msg[1];
+
+ switch(ctr)
+ {
+ case LV2_MIDI_CTL_ALL_NOTES_OFF:
+ {
+ VOICE_FOREACH(dsp, voice)
+ {
+ _voice_off_panic(voice);
+ }
+ } break;
+ case LV2_MIDI_CTL_ALL_SOUNDS_OFF:
+ {
+ VOICE_FOREACH(dsp, voice)
+ {
+ _voice_off_force(voice);
+ }
+ } break;
+ }
+ } break;
+ }
+}
+
+static void
+_handle_midi_3(plughandle_t *handle, dsp_t *dsp,
+ int64_t frames __attribute__((unused)), const uint8_t *msg)
+{
+ const uint8_t cmd = msg[0] & 0xf0;
+ const uint8_t chn = msg[0] & 0x0f;
+
+ if(!dsp || !dsp->is_instrument || !dsp->midi_on)
+ {
+ return;
+ }
+
+ switch(cmd)
+ {
+ case LV2_MIDI_MSG_NOTE_ON:
+ {
+ const uint8_t key = msg[1];
+ const uint8_t vel = msg[2];
+
+ voice_t *voice = _next_available_voice(dsp);
+ if(voice)
+ {
+ const float freq = _midi2cps((float)key
+ + handle->bend[chn]*handle->range[chn]);
+
+ _cntrl_refresh_value_abs(&voice->freq, freq);
+ _cntrl_refresh_value_abs(&voice->gain, vel * 0x1p-7);
+ _cntrl_refresh_value_abs(&voice->gate, 0.f);
+
+ voice->hash.key = key;
+ voice->hash.chn = chn;
+ voice->state = VOICE_STATE_ACTIVE;
+ voice->retrigger = true;
+ }
+ } break;
+ case LV2_MIDI_MSG_NOTE_OFF:
+ {
+ const uint8_t key = msg[1];
+
+ const hash_t hash = {
+ .key= key,
+ .chn = chn
+ };
+
+ voice_t *voice = _find_active_voice(dsp, &hash);
+
+ if(voice)
+ {
+ _voice_off(handle, voice);
+ }
+ } break;
+ case LV2_MIDI_MSG_NOTE_PRESSURE:
+ {
+ const uint8_t key = msg[1];
+ const uint8_t pre = msg[2];
+
+ const hash_t hash = {
+ .key= key,
+ .chn = chn
+ };
+
+ voice_t *voice = _find_active_voice(dsp, &hash);
+
+ if(voice)
+ {
+ _cntrl_refresh_value_abs(&voice->pressure, pre * 0x1p-7);
+ }
+ } break;
+ case LV2_MIDI_MSG_BENDER:
+ {
+ const uint8_t lsb = msg[1];
+ const uint8_t msb = msg[2];
+ const int16_t bend = (msb << 7) | lsb;
+
+ handle->bend[chn] = (bend - 0x2000) * 0x1p-13;
+ _update_frequency(handle, dsp, chn);
+ } break;
+ case LV2_MIDI_MSG_CONTROLLER:
+ {
+ const uint8_t ctr = msg[1];
+ const uint8_t val = msg[2];
+
+ switch(ctr)
+ {
+ case LV2_MIDI_CTL_SUSTAIN:
+ {
+ handle->sustain[chn] = (val > 0x3f)
+ ? true
+ : false;
+
+ if(handle->sustain[chn] == false)
+ {
+ VOICE_FOREACH(dsp, voice)
+ {
+ if( (voice->hash.chn == chn)
+ && (voice->state & VOICE_STATE_SUSTAIN) )
+ {
+ _voice_off(handle, voice);
+ }
+ }
+ }
+ } break;
+ case LV2_MIDI_CTL_RPN_LSB:
+ {
+ handle->rpn_lsb[chn] = val;
+ } break;
+ case LV2_MIDI_CTL_RPN_MSB:
+ {
+ handle->rpn_msb[chn] = val;
+ } break;
+ case LV2_MIDI_CTL_LSB_DATA_ENTRY:
+ {
+ handle->data_lsb[chn] = val;
+ } break;
+ case LV2_MIDI_CTL_MSB_DATA_ENTRY:
+ {
+ // pitch-bend range
+ if( (handle->rpn_msb[chn] == 0x0) && (handle->rpn_lsb[chn] == 0x0) )
+ {
+ const uint8_t semi = val;
+ const uint8_t cent = handle->data_lsb[chn];
+
+ handle->range[chn] = (float)semi + cent*0.01f;
+ _update_frequency(handle, dsp, chn);
+ }
+ } break;
+
+ case LV2_MIDI_CTL_SC1_SOUND_VARIATION | 0x20:
+ {
+ handle->pressure[chn] &= 0x7f;
+ handle->pressure[chn] |= val;
+ } break;
+ case LV2_MIDI_CTL_SC1_SOUND_VARIATION:
+ {
+ handle->pressure[chn] &= 0x3f80;
+ handle->pressure[chn] |= val << 7;
+
+ _update_pressure(handle, dsp, chn);
+ } break;
+ case LV2_MIDI_CTL_SC5_BRIGHTNESS | 0x20:
+ {
+ handle->timbre[chn] &= 0x7f;
+ handle->timbre[chn] |= val;
+ } break;
+ case LV2_MIDI_CTL_SC5_BRIGHTNESS:
+ {
+ handle->timbre[chn] &= 0x3f80;
+ handle->timbre[chn] |= val << 7;
+
+ _update_timbre(handle, dsp, chn);
+ } break;
+ }
+ } break;
+ }
+}
+
+static void
+_handle_midi(plughandle_t *handle, dsp_t *dsp,
+ int64_t frames, const uint8_t *msg, uint32_t len)
+{
+ switch(len)
+ {
+ case 2:
+ {
+ _handle_midi_2(dsp, frames, msg);
+ } break;
+ case 3:
+ {
+ _handle_midi_3(handle, dsp, frames, msg);
+ } break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ const uint32_t capacity = handle->notify->atom.size;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->notify, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ int64_t from = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const int64_t to = ev->time.frames;
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(atom->type == handle->midi_MidiEvent)
+ {
+ const bool off [2] = {
+ handle->play,
+ !handle->play
+ };
+
+ for(uint32_t d = 0; d < 2; d++)
+ {
+ dsp_t *dsp = handle->dsp[off[d]];
+
+ if(!dsp)
+ {
+ continue;
+ }
+
+ _handle_midi(handle, dsp, to, LV2_ATOM_BODY_CONST(atom),
+ atom->size);
+ }
+ }
+ else
+ {
+ props_advance(&handle->props, &handle->forge, to, obj,
+ &handle->ref);
+ }
+
+ timely_advance(&handle->timely, obj, from, to);
+ _refresh_time_position(handle);
+ _play(handle, from, to);
+
+ from = to;
+ }
+
+ timely_advance(&handle->timely, NULL, from, nsamples);
+ _refresh_time_position(handle);
+ _play(handle, from, nsamples);
+
+ // send error if applicable
+ if(handle->dirty.error)
+ {
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_error,
+ &handle->ref);
+
+ handle->dirty.error = false;
+ }
+
+ if(handle->dirty.attributes)
+ {
+ for(unsigned i = 0; i < NCONTROLS; i++)
+ {
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_controlMin[i],
+ &handle->ref);
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_controlMax[i],
+ &handle->ref);
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_controlStep[i],
+ &handle->ref);
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_controlType[i],
+ &handle->ref);
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_controlLabel[i],
+ &handle->ref);
+ }
+
+ handle->dirty.attributes = false;
+ }
+
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_timestamp,
+ &handle->ref);
+
+ for(unsigned i = 0; i < NCONTROLS; i++)
+ {
+ dsp_t *dsp = handle->dsp[handle->play];
+
+ if(!dsp)
+ {
+ continue;
+ }
+
+ voice_t *voice = &dsp->voices[0];
+ cntrl_t *cntrl = &voice->cntrls[i];
+
+ if(!cntrl->readonly)
+ {
+ continue;
+ }
+
+ handle->state.control[i] = _cntrl_get_value_rel(&voice->cntrls[i]);
+
+ props_set(&handle->props, &handle->forge, nsamples-1, handle->mephisto_control[i],
+ &handle->ref);
+ }
+
+ handle->state.timestamp += nsamples;
+
+ if(handle->ref)
+ {
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ }
+ else
+ {
+ lv2_atom_sequence_clear(handle->notify);
+
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "forge buffer overflow\n");
+ }
+ }
+}
+
+static void
+_meta_declare(void *iface, const char *key, const char *val)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %s", __func__, key, val);
+
+ if(!strcmp(key, "options"))
+ {
+ // iterate over options values
+ for(const char *ptr = strchr(val, '['); ptr; ptr = strchr(++ptr, '['))
+ {
+ if(sscanf(ptr, "[nvoices:%"SCNu32"]", &dsp->nvoices) == 1)
+ {
+ if( (dsp->nvoices == 0) || (dsp->nvoices > MAX_VOICES) )
+ {
+ dsp->nvoices = MAX_VOICES;
+ }
+ }
+ else if(strcasestr(ptr, "[midi:on]") == ptr)
+ {
+ dsp->midi_on = true;
+ }
+ else if(strcasestr(ptr, "[time:on]") == ptr)
+ {
+ dsp->time_on = true;
+ }
+ }
+ }
+}
+
+static const char *
+_strendswith(const char *haystack, const char *needle)
+{
+ const char *match = strcasestr(haystack, needle);
+
+ if(match)
+ {
+ const size_t needle_len = strlen(needle);
+
+ if(match[needle_len] == '\0')
+ {
+ return match;
+ }
+ }
+
+ return NULL;
+}
+
+static voice_t *
+_current_voice(dsp_t *dsp)
+{
+ if(dsp->cvoices < dsp->nvoices)
+ {
+ return &dsp->voices[dsp->cvoices];
+ }
+
+ return NULL;
+}
+
+static cntrl_t *
+_ui_next_cntrl(dsp_t *dsp, cntrl_type_t type, const char *label)
+{
+ cntrl_t *cntrl = NULL;
+ voice_t *voice = _current_voice(dsp);
+
+ if(!voice)
+ {
+ return NULL;
+ }
+
+ if(dsp->is_instrument && _strendswith(label, "gain"))
+ {
+ cntrl = &voice->gain;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "gate"))
+ {
+ cntrl = &voice->gate;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "dfreq"))
+ {
+ cntrl = &voice->d_freq;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "dpressure"))
+ {
+ cntrl = &voice->d_pressure;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "dtimbre"))
+ {
+ cntrl = &voice->d_timbre;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "freq"))
+ {
+ cntrl = &voice->freq;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "pressure"))
+ {
+ cntrl = &voice->pressure;
+ }
+ else if(dsp->is_instrument && _strendswith(label, "timbre"))
+ {
+ cntrl = &voice->timbre;
+ }
+ else if(dsp->timely_mask)
+ {
+ switch(dsp->timely_mask)
+ {
+ case TIMELY_MASK_BAR_BEAT:
+ {
+ cntrl = &voice->pos.bar_beat;
+ } break;
+ case TIMELY_MASK_BAR:
+ {
+ cntrl = &voice->pos.bar;
+ } break;
+ case TIMELY_MASK_BEAT_UNIT:
+ {
+ cntrl = &voice->pos.beat_unit;
+ } break;
+ case TIMELY_MASK_BEATS_PER_BAR:
+ {
+ cntrl = &voice->pos.beats_per_bar;
+ } break;
+ case TIMELY_MASK_BEATS_PER_MINUTE:
+ {
+ cntrl = &voice->pos.beats_per_minute;
+ } break;
+ case TIMELY_MASK_FRAME:
+ {
+ cntrl = &voice->pos.frame;
+ } break;
+ case TIMELY_MASK_FRAMES_PER_SECOND:
+ {
+ cntrl = &voice->pos.frames_per_second;
+ } break;
+ case TIMELY_MASK_SPEED:
+ {
+ cntrl = &voice->pos.speed;
+ } break;
+ case TIMELY_MASK_BAR_BEAT_WHOLE:
+ {
+ // not used
+ } break;
+ case TIMELY_MASK_BAR_WHOLE:
+ {
+ // not used
+ } break;
+ }
+
+ dsp->timely_mask = 0; // reset flag
+ }
+ else if( (dsp->idx >= 0) && (dsp->idx < NCONTROLS) )
+ {
+ cntrl = &voice->cntrls[dsp->idx];
+
+ dsp->idx = -1;
+ }
+
+ if(!cntrl)
+ {
+ return NULL;
+ }
+
+ cntrl->type = type;
+ strncpy(cntrl->label, label, sizeof(cntrl->label) - 1);
+
+ return cntrl;
+}
+
+static void
+_ui_open_tab_box(void* iface, const char* label)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s", __func__, label);
+}
+
+static void
+_ui_open_horizontal_box(void* iface, const char* label)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s", __func__, label);
+}
+
+static void
+_ui_open_vertical_box(void* iface, const char* label)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s", __func__, label);
+}
+
+static void
+_ui_close_box(void* iface)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s]", __func__);
+}
+
+static void
+_ui_add_button(void* iface, const char* label, FAUSTFLOAT* zone)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f", __func__, label, *zone);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_BUTTON, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+}
+
+static void
+_ui_add_check_button(void* iface, const char* label, FAUSTFLOAT* zone)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f", __func__, label, *zone);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_CHECK_BUTTON, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+}
+
+static void
+_ui_add_vertical_slider(void* iface, const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f %f %f %f %f", __func__,
+ label, *zone, init, min, max, step);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_VERTICAL_SLIDER, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+ cntrl->vertical_slider.init = init;
+ cntrl->vertical_slider.min = min;
+ cntrl->vertical_slider.max = max;
+ cntrl->vertical_slider.ran = max - min;
+ cntrl->vertical_slider.step = step;
+}
+
+static void
+_ui_add_horizontal_slider(void* iface, const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f %f %f %f %f", __func__,
+ label, *zone, init, min, max, step);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_HORIZONTAL_SLIDER, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+ cntrl->horizontal_slider.init = init;
+ cntrl->horizontal_slider.min = min;
+ cntrl->horizontal_slider.max = max;
+ cntrl->horizontal_slider.ran = max - min;
+ cntrl->horizontal_slider.step = step;
+}
+
+static void
+_ui_add_num_entry(void* iface, const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f %f %f %f %f", __func__,
+ label, *zone, init, min, max, step);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_NUM_ENTRY, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+ cntrl->num_entry.init = init;
+ cntrl->num_entry.min = min;
+ cntrl->num_entry.max = max;
+ cntrl->num_entry.ran = max - min;
+ cntrl->num_entry.step = step;
+}
+
+static void
+_ui_add_horizontal_bargraph(void* iface, const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT min, FAUSTFLOAT max)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f %f %f", __func__,
+ label, *zone, min, max);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_HORIZONTAL_BARGRAPH, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+ cntrl->readonly = true;
+ cntrl->horizontal_bargraph.min = min;
+ cntrl->horizontal_bargraph.max = max;
+ cntrl->horizontal_bargraph.ran_1 = 1.f / (max - min);
+}
+
+static void
+_ui_add_vertical_bargraph(void* iface, const char* label, FAUSTFLOAT* zone,
+ FAUSTFLOAT min, FAUSTFLOAT max)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %f %f %f", __func__,
+ label, *zone, min, max);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_VERTICAL_BARGRAPH, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ cntrl->zone = zone;
+ cntrl->readonly = true;
+ cntrl->vertical_bargraph.min = min;
+ cntrl->vertical_bargraph.max = max;
+ cntrl->vertical_bargraph.ran_1 = 1.f / (max - min);
+}
+
+static void
+_ui_add_sound_file(void* iface, const char* label, const char* filename,
+ struct Soundfile** sf_zone __attribute__((unused)))
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %s", __func__,
+ label, filename);
+
+ cntrl_t *cntrl = _ui_next_cntrl(dsp, CNTRL_SOUND_FILE, label);
+ if(!cntrl)
+ {
+ return;
+ }
+
+ //FIXME
+}
+
+static void
+_ui_declare(void* iface, FAUSTFLOAT* zone __attribute__((unused)),
+ const char* key, const char* value)
+{
+ dsp_t *dsp = iface;
+ plughandle_t *handle = dsp->handle;
+
+ DBG(handle, "[%s] %s %s", __func__,
+ key, value);
+
+ if(!strcasecmp(value, ""))
+ {
+ char *endptr = NULL;
+ const long int idx = strtol(key, &endptr, 10);
+
+ if(endptr != key)
+ {
+ dsp->idx = idx;
+ }
+ else if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] invalid index key %s",
+ __func__, key);
+ }
+ }
+ else if(!strcasecmp(key, "time"))
+ {
+ if(!strcasecmp(value, "barBeat"))
+ {
+ dsp->timely_mask = TIMELY_MASK_BAR_BEAT;
+ }
+ else if(!strcasecmp(value, "bar"))
+ {
+ dsp->timely_mask = TIMELY_MASK_BAR;
+ }
+ else if(!strcasecmp(value, "beatUnit"))
+ {
+ dsp->timely_mask = TIMELY_MASK_BEAT_UNIT;
+ }
+ else if(!strcasecmp(value, "beatsPerBar"))
+ {
+ dsp->timely_mask = TIMELY_MASK_BEATS_PER_BAR;
+ }
+ else if(!strcasecmp(value, "beatsPerMinute"))
+ {
+ dsp->timely_mask = TIMELY_MASK_BEATS_PER_MINUTE;
+ }
+ else if(!strcasecmp(value, "frame"))
+ {
+ dsp->timely_mask = TIMELY_MASK_FRAME;
+ }
+ else if(!strcasecmp(value, "framesPerSecond"))
+ {
+ dsp->timely_mask = TIMELY_MASK_FRAMES_PER_SECOND;
+ }
+ else if(!strcasecmp(value, "speed"))
+ {
+ dsp->timely_mask = TIMELY_MASK_SPEED;
+ }
+ else if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] invalid time value %s",
+ __func__, value);
+ }
+ }
+ else if(handle->log)
+ {
+ lv2_log_note(&handle->logger, "[%s] unknown key:value pair %s:%s",
+ __func__, key, value);
+ }
+}
+
+static int
+_meta_init(dsp_t *dsp, voice_t *base_voice)
+{
+ MetaGlue *glue = &dsp->meta_glue;
+
+ glue->metaInterface = dsp;
+
+ glue->declare = _meta_declare;
+
+ dsp->nvoices = 1; // assume we're a filter by default
+ dsp->timely_mask = 0;
+ dsp->idx = -1;
+
+ metadataCDSPInstance(base_voice->instance, glue);
+
+ return 0;
+}
+
+static int
+_ui_init(dsp_t *dsp)
+{
+ UIGlue *glue = &dsp->ui_glue;
+
+ glue->uiInterface = dsp;
+
+ glue->openTabBox = _ui_open_tab_box;
+ glue->openHorizontalBox = _ui_open_horizontal_box;
+ glue->openVerticalBox = _ui_open_vertical_box;
+ glue->closeBox = _ui_close_box;
+ glue->addButton = _ui_add_button;
+ glue->addCheckButton = _ui_add_check_button;
+ glue->addVerticalSlider = _ui_add_vertical_slider;
+ glue->addHorizontalSlider = _ui_add_horizontal_slider;
+ glue->addNumEntry = _ui_add_num_entry;
+ glue->addHorizontalBargraph = _ui_add_horizontal_bargraph;
+ glue->addVerticalBargraph = _ui_add_vertical_bargraph;
+ glue->FAUST_ADDSOUNDFILE= _ui_add_sound_file;
+ glue->declare = _ui_declare;
+
+ dsp->cvoices = 0;
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice->instance)
+ {
+ buildUserInterfaceCDSPInstance(voice->instance, glue);
+ }
+
+ dsp->cvoices++;
+ }
+
+ return 0;
+}
+
+static int
+_dsp_init(plughandle_t *handle, dsp_t *dsp, const char *code,
+ LV2_Worker_Respond_Function respond, LV2_Worker_Respond_Handle target)
+{
+#define ARGC 5
+ char err [4096];
+
+ const char *argv [ARGC] = {
+ "-I", handle->dsp_dir,
+ "-vec",
+ "-lv", "1"
+ };
+
+ {
+ const job_t job = {
+ .type = JOB_TYPE_ERROR_CLEAR,
+ .error = NULL
+ };
+
+ respond(target, sizeof(job), &job);
+ }
+
+ dsp->handle = handle;
+ memset(err, 0x0, sizeof(err));
+
+ pthread_mutex_lock(&lock);
+
+ dsp->factory = createCDSPFactoryFromString("mephisto", code, ARGC, argv, "", err, -1);
+ if(!dsp->factory)
+ {
+ if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] %s", __func__, err);
+
+ const job_t job = {
+ .type = JOB_TYPE_ERROR_APPEND,
+ .error = strdup(err)
+ };
+
+ respond(target, sizeof(job), &job);
+ }
+
+ goto fail;
+ }
+
+ voice_t *base_voice = _voice_begin(dsp);
+ base_voice->instance = createCDSPInstance(dsp->factory);
+ if(!base_voice->instance)
+ {
+ if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] instance creation failed", __func__);
+ }
+
+ deleteCDSPFactory(dsp->factory);
+ goto fail;
+ }
+
+ instanceInitCDSPInstance(base_voice->instance, handle->srate);
+
+ dsp->nins = getNumInputsCDSPInstance(base_voice->instance);
+ dsp->nouts = getNumInputsCDSPInstance(base_voice->instance);
+
+ if(_meta_init(dsp, base_voice) != 0)
+ {
+ if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] meta creation failed", __func__);
+ }
+
+ deleteCDSPFactory(dsp->factory);
+ goto fail;
+ }
+
+ dsp->is_instrument = (dsp->nvoices > 1);
+
+ if(dsp->is_instrument)
+ {
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice == base_voice) // skip base voice
+ {
+ continue;
+ }
+
+ voice->instance = cloneCDSPInstance(base_voice->instance);
+ if(!voice->instance)
+ {
+ if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] instance creation failed", __func__);
+ }
+
+ break;
+ }
+
+ instanceInitCDSPInstance(voice->instance, handle->srate);
+ }
+ }
+ else
+ {
+ base_voice->state = VOICE_STATE_ACTIVE;
+ }
+
+ if(_ui_init(dsp) != 0)
+ {
+ if(handle->log)
+ {
+ lv2_log_error(&handle->logger, "[%s] ui creation failed", __func__);
+ }
+
+ deleteCDSPFactory(dsp->factory);
+ goto fail;
+ }
+
+ if(handle->log)
+ {
+ lv2_log_note(&handle->logger,
+ "[%s] compilation succeeded (ins: %u, outs: %u, type: %s)", __func__,
+ dsp->nins, dsp->nouts, dsp->is_instrument ? "instrument" : "filter");
+ }
+
+ pthread_mutex_unlock(&lock);
+ return 0;
+
+fail:
+ pthread_mutex_unlock(&lock);
+ return 1;
+#undef ARGC
+}
+
+static void
+_dsp_deinit(plughandle_t *handle __attribute__((unused)), dsp_t *dsp)
+{
+ if(dsp)
+ {
+ pthread_mutex_lock(&lock);
+
+ VOICE_FOREACH(dsp, voice)
+ {
+ if(voice->instance)
+ {
+ instanceClearCDSPInstance(voice->instance);
+ deleteCDSPInstance(voice->instance);
+ }
+ }
+
+ if(dsp->factory)
+ {
+ deleteCDSPFactory(dsp->factory);
+ }
+
+ pthread_mutex_unlock(&lock);
+ }
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ varchunk_free(handle->to_worker);
+ _dsp_deinit(handle, handle->dsp[0]);
+ _dsp_deinit(handle, handle->dsp[1]);
+ 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
+};
+
+// non-rt thread
+static LV2_Worker_Status
+_work(LV2_Handle instance,
+ LV2_Worker_Respond_Function respond,
+ LV2_Worker_Respond_Handle target,
+ uint32_t body_size,
+ const void *body)
+{
+ plughandle_t *handle = instance;
+
+ if(body_size != sizeof(job_t))
+ {
+ return LV2_WORKER_ERR_UNKNOWN;
+ }
+
+ const job_t *job = body;
+ switch(job->type)
+ {
+ case JOB_TYPE_INIT:
+ {
+ size_t size;
+ const char *code;
+ while( (code= varchunk_read_request(handle->to_worker, &size)) )
+ {
+ dsp_t *dsp = calloc(1, sizeof(dsp_t));
+ if(dsp && (_dsp_init(handle, dsp, code, respond, target) == 0) )
+ {
+ const job_t job2 = {
+ .type = JOB_TYPE_INIT,
+ .dsp = dsp
+ };
+
+ respond(target, sizeof(job2), &job2);
+ }
+
+ varchunk_read_advance(handle->to_worker);
+ }
+ } break;
+ case JOB_TYPE_DEINIT:
+ {
+ _dsp_deinit(handle, job->dsp);
+ } break;
+
+ case JOB_TYPE_ERROR_CLEAR:
+ {
+ // never reached
+ } break;
+ case JOB_TYPE_ERROR_APPEND:
+ {
+ // never reached
+ } break;
+ case JOB_TYPE_ERROR_FREE:
+ {
+ if(job->error)
+ {
+ free(job->error);
+ }
+ } break;
+ default:
+ {
+ // never reached
+ } return LV2_WORKER_ERR_UNKNOWN;
+ }
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_work_response(LV2_Handle instance, uint32_t size, const void *body)
+{
+ plughandle_t *handle = instance;
+
+ if(size != sizeof(job_t))
+ {
+ return LV2_WORKER_ERR_UNKNOWN;
+ }
+
+ const job_t *job = body;
+ switch(job->type)
+ {
+ case JOB_TYPE_INIT:
+ {
+ const job_t job2 = {
+ .type = JOB_TYPE_DEINIT,
+ .dsp = handle->dsp[!handle->play]
+ };
+ handle->sched->schedule_work(handle->sched->handle, sizeof(job2), &job2);
+
+ handle->dsp[!handle->play] = job->dsp;
+ handle->xfade_cur = handle->xfade_max;
+
+ for(uint32_t i = 0; i < NCONTROLS; i++)
+ {
+ _refresh_value(handle, i);
+ _refresh_attributes(handle, i);
+ }
+
+ dsp_t *cur_dsp = handle->dsp[handle->play];
+ dsp_t *new_dsp = handle->dsp[!handle->play];
+
+ if(cur_dsp && new_dsp)
+ {
+ voice_t *cur_voice = _voice_begin(cur_dsp);
+
+ VOICE_FOREACH(new_dsp, new_voice)
+ {
+ _cntrl_refresh_value_abs(&new_voice->freq,
+ _cntrl_get_value_abs(&cur_voice->freq));
+ _cntrl_refresh_value_abs(&new_voice->pressure,
+ _cntrl_get_value_abs(&cur_voice->pressure));
+ _cntrl_refresh_value_abs(&new_voice->timbre,
+ _cntrl_get_value_abs(&cur_voice->timbre));
+ _cntrl_refresh_value_abs(&new_voice->d_freq,
+ _cntrl_get_value_abs(&cur_voice->d_freq));
+ _cntrl_refresh_value_abs(&new_voice->d_pressure,
+ _cntrl_get_value_abs(&cur_voice->d_pressure));
+ _cntrl_refresh_value_abs(&new_voice->d_timbre,
+ _cntrl_get_value_abs(&cur_voice->d_timbre));
+ _cntrl_refresh_value_abs(&new_voice->gate,
+ _cntrl_get_value_abs(&cur_voice->gate));
+ _cntrl_refresh_value_abs(&new_voice->gain,
+ _cntrl_get_value_abs(&cur_voice->gain));
+
+ new_voice->state = cur_voice->state;
+ new_voice->hash = cur_voice->hash;
+
+ if(!_voice_not_end(cur_dsp, cur_voice))
+ {
+ break;
+ }
+
+ cur_voice = _voice_next(cur_voice);
+ }
+ }
+
+ handle->dirty.attributes = true;
+ } break;
+ case JOB_TYPE_DEINIT:
+ {
+ // never reached
+ } break;
+ case JOB_TYPE_ERROR_CLEAR:
+ {
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->mephisto_error);
+
+ if(impl)
+ {
+ static const char *empty = "";
+
+ strncpy(handle->state.error, empty, impl->def->max_size);
+ impl->value.size = strlen(handle->state.error) + 1;
+
+ handle->dirty.error = true;
+ }
+ } break;
+ case JOB_TYPE_ERROR_APPEND:
+ {
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->mephisto_error);
+
+ if(impl)
+ {
+ strncat(handle->state.error, job->error, impl->def->max_size - strlen(job->error) - 1);
+ impl->value.size = strlen(handle->state.error) + 1;
+
+ handle->dirty.error = true;
+
+ const job_t job2 = {
+ .type = JOB_TYPE_ERROR_FREE,
+ .error = job->error
+ };
+
+ handle->sched->schedule_work(handle->sched->handle, sizeof(job2), &job2);
+ }
+ } break;
+ case JOB_TYPE_ERROR_FREE:
+ {
+ // never reached
+ } break;
+ default:
+ {
+ // never reached
+ } return LV2_WORKER_ERR_UNKNOWN;
+ }
+
+ return LV2_WORKER_SUCCESS;
+}
+
+// rt-thread
+static LV2_Worker_Status
+_end_run(LV2_Handle instance __attribute__((unused)))
+{
+ // do nothing
+
+ return LV2_WORKER_SUCCESS;
+}
+
+static const LV2_Worker_Interface work_iface = {
+ .work = _work,
+ .work_response = _work_response,
+ .end_run = _end_run
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ {
+ return &state_iface;
+ }
+ else if(!strcmp(uri, LV2_WORKER__interface))
+ {
+ return &work_iface;
+ }
+
+ return NULL;
+}
+
+static const LV2_Descriptor mephisto_audio_1x1 = {
+ .URI = MEPHISTO__audio_1x1,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_audio_2x2 = {
+ .URI = MEPHISTO__audio_2x2,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_audio_4x4 = {
+ .URI = MEPHISTO__audio_4x4,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_audio_8x8 = {
+ .URI = MEPHISTO__audio_8x8,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_cv_1x1 = {
+ .URI = MEPHISTO__cv_1x1,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_cv_2x2 = {
+ .URI = MEPHISTO__cv_2x2,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_cv_4x4 = {
+ .URI = MEPHISTO__cv_4x4,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+static const LV2_Descriptor mephisto_cv_8x8 = {
+ .URI = MEPHISTO__cv_8x8,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &mephisto_audio_1x1;
+ case 1:
+ return &mephisto_audio_2x2;
+ case 2:
+ return &mephisto_audio_4x4;
+ case 3:
+ return &mephisto_audio_8x8;
+
+ case 4:
+ return &mephisto_cv_1x1;
+ case 5:
+ return &mephisto_cv_2x2;
+ case 6:
+ return &mephisto_cv_4x4;
+ case 7:
+ return &mephisto_cv_8x8;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/mephisto.h b/mephisto.h
new file mode 100644
index 0000000..0a7c9e0
--- /dev/null
+++ b/mephisto.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2019-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 _MEPHISTO_LV2_H
+#define _MEPHISTO_LV2_H
+
+#include <stdint.h>
+#if !defined(_WIN32)
+# include <sys/mman.h>
+#else
+# define mlock(...)
+# define munlock(...)
+#endif
+
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/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/worker/worker.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/options/options.h>
+#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.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 MEPHISTO_URI "http://open-music-kontrollers.ch/lv2/mephisto"
+#define MEPHISTO_PREFIX MEPHISTO_URI "#"
+
+// plugin uris
+#define MEPHISTO__audio_1x1 MEPHISTO_PREFIX "audio_1x1"
+#define MEPHISTO__audio_2x2 MEPHISTO_PREFIX "audio_2x2"
+#define MEPHISTO__audio_4x4 MEPHISTO_PREFIX "audio_4x4"
+#define MEPHISTO__audio_8x8 MEPHISTO_PREFIX "audio_8x8"
+#define MEPHISTO__cv_1x1 MEPHISTO_PREFIX "cv_1x1"
+#define MEPHISTO__cv_2x2 MEPHISTO_PREFIX "cv_2x2"
+#define MEPHISTO__cv_4x4 MEPHISTO_PREFIX "cv_4x4"
+#define MEPHISTO__cv_8x8 MEPHISTO_PREFIX "cv_8x8"
+
+// plugin UI uris
+#define MEPHISTO__ui MEPHISTO_PREFIX "ui"
+
+// param uris
+#define MEPHISTO__code MEPHISTO_PREFIX "code"
+#define MEPHISTO__error MEPHISTO_PREFIX "error"
+#define MEPHISTO__xfadeDuration MEPHISTO_PREFIX "xfadeDuration"
+#define MEPHISTO__fontHeight MEPHISTO_PREFIX "fontHeight"
+
+#define MEPHISTO__timestamp MEPHISTO_PREFIX "timestamp"
+
+#define MEPHISTO__control_1 MEPHISTO_PREFIX "control_1"
+#define MEPHISTO__control_2 MEPHISTO_PREFIX "control_2"
+#define MEPHISTO__control_3 MEPHISTO_PREFIX "control_3"
+#define MEPHISTO__control_4 MEPHISTO_PREFIX "control_4"
+#define MEPHISTO__control_5 MEPHISTO_PREFIX "control_5"
+#define MEPHISTO__control_6 MEPHISTO_PREFIX "control_6"
+#define MEPHISTO__control_7 MEPHISTO_PREFIX "control_7"
+#define MEPHISTO__control_8 MEPHISTO_PREFIX "control_8"
+#define MEPHISTO__control_9 MEPHISTO_PREFIX "control_9"
+#define MEPHISTO__control_10 MEPHISTO_PREFIX "control_10"
+#define MEPHISTO__control_11 MEPHISTO_PREFIX "control_11"
+#define MEPHISTO__control_12 MEPHISTO_PREFIX "control_12"
+#define MEPHISTO__control_13 MEPHISTO_PREFIX "control_13"
+#define MEPHISTO__control_14 MEPHISTO_PREFIX "control_14"
+#define MEPHISTO__control_15 MEPHISTO_PREFIX "control_15"
+#define MEPHISTO__control_16 MEPHISTO_PREFIX "control_16"
+
+#define MEPHISTO__controlMin_1 MEPHISTO_PREFIX "controlMin_1"
+#define MEPHISTO__controlMin_2 MEPHISTO_PREFIX "controlMin_2"
+#define MEPHISTO__controlMin_3 MEPHISTO_PREFIX "controlMin_3"
+#define MEPHISTO__controlMin_4 MEPHISTO_PREFIX "controlMin_4"
+#define MEPHISTO__controlMin_5 MEPHISTO_PREFIX "controlMin_5"
+#define MEPHISTO__controlMin_6 MEPHISTO_PREFIX "controlMin_6"
+#define MEPHISTO__controlMin_7 MEPHISTO_PREFIX "controlMin_7"
+#define MEPHISTO__controlMin_8 MEPHISTO_PREFIX "controlMin_8"
+#define MEPHISTO__controlMin_9 MEPHISTO_PREFIX "controlMin_9"
+#define MEPHISTO__controlMin_10 MEPHISTO_PREFIX "controlMin_10"
+#define MEPHISTO__controlMin_11 MEPHISTO_PREFIX "controlMin_11"
+#define MEPHISTO__controlMin_12 MEPHISTO_PREFIX "controlMin_12"
+#define MEPHISTO__controlMin_13 MEPHISTO_PREFIX "controlMin_13"
+#define MEPHISTO__controlMin_14 MEPHISTO_PREFIX "controlMin_14"
+#define MEPHISTO__controlMin_15 MEPHISTO_PREFIX "controlMin_15"
+#define MEPHISTO__controlMin_16 MEPHISTO_PREFIX "controlMin_16"
+
+#define MEPHISTO__controlMax_1 MEPHISTO_PREFIX "controlMax_1"
+#define MEPHISTO__controlMax_2 MEPHISTO_PREFIX "controlMax_2"
+#define MEPHISTO__controlMax_3 MEPHISTO_PREFIX "controlMax_3"
+#define MEPHISTO__controlMax_4 MEPHISTO_PREFIX "controlMax_4"
+#define MEPHISTO__controlMax_5 MEPHISTO_PREFIX "controlMax_5"
+#define MEPHISTO__controlMax_6 MEPHISTO_PREFIX "controlMax_6"
+#define MEPHISTO__controlMax_7 MEPHISTO_PREFIX "controlMax_7"
+#define MEPHISTO__controlMax_8 MEPHISTO_PREFIX "controlMax_8"
+#define MEPHISTO__controlMax_9 MEPHISTO_PREFIX "controlMax_9"
+#define MEPHISTO__controlMax_10 MEPHISTO_PREFIX "controlMax_10"
+#define MEPHISTO__controlMax_11 MEPHISTO_PREFIX "controlMax_11"
+#define MEPHISTO__controlMax_12 MEPHISTO_PREFIX "controlMax_12"
+#define MEPHISTO__controlMax_13 MEPHISTO_PREFIX "controlMax_13"
+#define MEPHISTO__controlMax_14 MEPHISTO_PREFIX "controlMax_14"
+#define MEPHISTO__controlMax_15 MEPHISTO_PREFIX "controlMax_15"
+#define MEPHISTO__controlMax_16 MEPHISTO_PREFIX "controlMax_16"
+
+#define MEPHISTO__controlStep_1 MEPHISTO_PREFIX "controlStep_1"
+#define MEPHISTO__controlStep_2 MEPHISTO_PREFIX "controlStep_2"
+#define MEPHISTO__controlStep_3 MEPHISTO_PREFIX "controlStep_3"
+#define MEPHISTO__controlStep_4 MEPHISTO_PREFIX "controlStep_4"
+#define MEPHISTO__controlStep_5 MEPHISTO_PREFIX "controlStep_5"
+#define MEPHISTO__controlStep_6 MEPHISTO_PREFIX "controlStep_6"
+#define MEPHISTO__controlStep_7 MEPHISTO_PREFIX "controlStep_7"
+#define MEPHISTO__controlStep_8 MEPHISTO_PREFIX "controlStep_8"
+#define MEPHISTO__controlStep_9 MEPHISTO_PREFIX "controlStep_9"
+#define MEPHISTO__controlStep_10 MEPHISTO_PREFIX "controlStep_10"
+#define MEPHISTO__controlStep_11 MEPHISTO_PREFIX "controlStep_11"
+#define MEPHISTO__controlStep_12 MEPHISTO_PREFIX "controlStep_12"
+#define MEPHISTO__controlStep_13 MEPHISTO_PREFIX "controlStep_13"
+#define MEPHISTO__controlStep_14 MEPHISTO_PREFIX "controlStep_14"
+#define MEPHISTO__controlStep_15 MEPHISTO_PREFIX "controlStep_15"
+#define MEPHISTO__controlStep_16 MEPHISTO_PREFIX "controlStep_16"
+
+#define MEPHISTO__controlType_1 MEPHISTO_PREFIX "controlType_1"
+#define MEPHISTO__controlType_2 MEPHISTO_PREFIX "controlType_2"
+#define MEPHISTO__controlType_3 MEPHISTO_PREFIX "controlType_3"
+#define MEPHISTO__controlType_4 MEPHISTO_PREFIX "controlType_4"
+#define MEPHISTO__controlType_5 MEPHISTO_PREFIX "controlType_5"
+#define MEPHISTO__controlType_6 MEPHISTO_PREFIX "controlType_6"
+#define MEPHISTO__controlType_7 MEPHISTO_PREFIX "controlType_7"
+#define MEPHISTO__controlType_8 MEPHISTO_PREFIX "controlType_8"
+#define MEPHISTO__controlType_9 MEPHISTO_PREFIX "controlType_9"
+#define MEPHISTO__controlType_10 MEPHISTO_PREFIX "controlType_10"
+#define MEPHISTO__controlType_11 MEPHISTO_PREFIX "controlType_11"
+#define MEPHISTO__controlType_12 MEPHISTO_PREFIX "controlType_12"
+#define MEPHISTO__controlType_13 MEPHISTO_PREFIX "controlType_13"
+#define MEPHISTO__controlType_14 MEPHISTO_PREFIX "controlType_14"
+#define MEPHISTO__controlType_15 MEPHISTO_PREFIX "controlType_15"
+#define MEPHISTO__controlType_16 MEPHISTO_PREFIX "controlType_16"
+
+#define MEPHISTO__controlLabel_1 MEPHISTO_PREFIX "controlLabel_1"
+#define MEPHISTO__controlLabel_2 MEPHISTO_PREFIX "controlLabel_2"
+#define MEPHISTO__controlLabel_3 MEPHISTO_PREFIX "controlLabel_3"
+#define MEPHISTO__controlLabel_4 MEPHISTO_PREFIX "controlLabel_4"
+#define MEPHISTO__controlLabel_5 MEPHISTO_PREFIX "controlLabel_5"
+#define MEPHISTO__controlLabel_6 MEPHISTO_PREFIX "controlLabel_6"
+#define MEPHISTO__controlLabel_7 MEPHISTO_PREFIX "controlLabel_7"
+#define MEPHISTO__controlLabel_8 MEPHISTO_PREFIX "controlLabel_8"
+#define MEPHISTO__controlLabel_9 MEPHISTO_PREFIX "controlLabel_9"
+#define MEPHISTO__controlLabel_10 MEPHISTO_PREFIX "controlLabel_10"
+#define MEPHISTO__controlLabel_11 MEPHISTO_PREFIX "controlLabel_11"
+#define MEPHISTO__controlLabel_12 MEPHISTO_PREFIX "controlLabel_12"
+#define MEPHISTO__controlLabel_13 MEPHISTO_PREFIX "controlLabel_13"
+#define MEPHISTO__controlLabel_14 MEPHISTO_PREFIX "controlLabel_14"
+#define MEPHISTO__controlLabel_15 MEPHISTO_PREFIX "controlLabel_15"
+#define MEPHISTO__controlLabel_16 MEPHISTO_PREFIX "controlLabel_16"
+
+#define NCONTROLS 16
+#define MAX_NPROPS (5 + 6*NCONTROLS)
+#define CODE_SIZE 0x10000 // 64 K
+#define ERROR_SIZE 0x2000 // 8 K
+#define BUF_SIZE (CODE_SIZE * 4)
+#define LABEL_SIZE 0x80 // 128
+
+#define CONTROL(NUM) \
+{ \
+ .property = MEPHISTO_PREFIX"control_"#NUM, \
+ .offset = offsetof(plugstate_t, control) + (NUM-1)*sizeof(float), \
+ .type = LV2_ATOM__Float, \
+ .event_cb = _intercept_control \
+}, \
+{ \
+ .access = LV2_PATCH__readable, \
+ .property = MEPHISTO_PREFIX"controlMin_"#NUM, \
+ .offset = offsetof(plugstate_t, control_min) + (NUM-1)*sizeof(float), \
+ .type = LV2_ATOM__Float \
+}, \
+{ \
+ .access = LV2_PATCH__readable, \
+ .property = MEPHISTO_PREFIX"controlMax_"#NUM, \
+ .offset = offsetof(plugstate_t, control_max) + (NUM-1)*sizeof(float), \
+ .type = LV2_ATOM__Float \
+}, \
+{ \
+ .access = LV2_PATCH__readable, \
+ .property = MEPHISTO_PREFIX"controlStep_"#NUM, \
+ .offset = offsetof(plugstate_t, control_step) + (NUM-1)*sizeof(float), \
+ .type = LV2_ATOM__Float \
+}, \
+{ \
+ .access = LV2_PATCH__readable, \
+ .property = MEPHISTO_PREFIX"controlType_"#NUM, \
+ .offset = offsetof(plugstate_t, control_type) + (NUM-1)*sizeof(int32_t), \
+ .type = LV2_ATOM__Int \
+}, \
+{ \
+ .access = LV2_PATCH__readable, \
+ .property = MEPHISTO_PREFIX"controlLabel_"#NUM, \
+ .offset = offsetof(plugstate_t, control_label) + (NUM-1)*LABEL_SIZE, \
+ .type = LV2_ATOM__String, \
+ .max_size = LABEL_SIZE \
+}
+
+typedef enum _cntrl_type_t {
+ CNTRL_NONE = 0,
+ CNTRL_BUTTON,
+ CNTRL_CHECK_BUTTON,
+ CNTRL_VERTICAL_SLIDER,
+ CNTRL_HORIZONTAL_SLIDER,
+ CNTRL_NUM_ENTRY,
+ CNTRL_HORIZONTAL_BARGRAPH,
+ CNTRL_VERTICAL_BARGRAPH,
+ CNTRL_SOUND_FILE
+} cntrl_type_t;
+
+typedef struct _plugstate_t plugstate_t;
+
+struct _plugstate_t {
+ char code [CODE_SIZE];
+ char error [ERROR_SIZE];
+ float control [NCONTROLS];
+ float control_min [NCONTROLS];
+ float control_max [NCONTROLS];
+ float control_step [NCONTROLS];
+ int32_t control_type [NCONTROLS];
+ char control_label [NCONTROLS][LABEL_SIZE];
+ int32_t xfade_dur;
+ int32_t font_height;
+ int64_t timestamp;
+};
+
+#endif // _MEPHISTO_LV2_H
diff --git a/mephisto.ttl.in b/mephisto.ttl.in
new file mode 100644
index 0000000..2511e02
--- /dev/null
+++ b/mephisto.ttl.in
@@ -0,0 +1,1283 @@
+# Copyright (c) 2019-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 owl: <http://www.w3.org/2002/07/owl#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix work: <http://lv2plug.in/ns/ext/worker#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix rsz: <http://lv2plug.in/ns/ext/resize-port#> .
+@prefix bufsz: <http://lv2plug.in/ns/ext/buf-size#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix opts: <http://lv2plug.in/ns/ext/options#> .
+
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix mephisto: <http://open-music-kontrollers.ch/lv2/mephisto#> .
+
+# 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:mephisto
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Mephisto Bundle" .
+
+# Units
+mephisto:px
+ a units:Unit ;
+ rdfs:label "pixels" ;
+ units:render "%f px" ;
+ units:symbol "px" .
+
+# Params
+mephisto:code
+ a lv2:Parameter ;
+ rdfs:range atom:String ;
+ rdfs:label "Code" ;
+ rdfs:comment "DSP code" .
+mephisto:error
+ a lv2:Parameter ;
+ rdfs:range atom:String ;
+ rdfs:label "Error" ;
+ rdfs:comment "Compilation error" .
+mephisto:xfadeDuration
+ a lv2:Parameter ;
+ rdfs:range atom:Int ;
+ rdfs:label "Crossfade duration" ;
+ rdfs:comment "get/set crossfade duration in ms" ;
+ lv2:minimum 10 ;
+ lv2:maximum 1000 ;
+ units:unit units:ms .
+mephisto:fontHeight
+ a lv2:Parameter ;
+ rdfs:range atom:Int ;
+ rdfs:label "Font height" ;
+ rdfs:comment "get/set font height in px" ;
+ lv2:minimum 10 ;
+ lv2:maximum 25 ;
+ units:unit mephisto:px .
+mephisto:control_1
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 1" ;
+ rdfs:comment "get/set control 1" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_2
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 2" ;
+ rdfs:comment "get/set control 2" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_3
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 3" ;
+ rdfs:comment "get/set control 3" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_4
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 4" ;
+ rdfs:comment "get/set control 4" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_5
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 5" ;
+ rdfs:comment "get/set control 5" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_6
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 6" ;
+ rdfs:comment "get/set control 6" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_7
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 7" ;
+ rdfs:comment "get/set control 7" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_8
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 8" ;
+ rdfs:comment "get/set control 8" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_9
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 9" ;
+ rdfs:comment "get/set control 9" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_10
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 10" ;
+ rdfs:comment "get/set control 10" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_11
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 11" ;
+ rdfs:comment "get/set control 11" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_12
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 12" ;
+ rdfs:comment "get/set control 12" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_13
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 13" ;
+ rdfs:comment "get/set control 13" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_14
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 14" ;
+ rdfs:comment "get/set control 14" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_15
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 15" ;
+ rdfs:comment "get/set control 15" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+mephisto:control_16
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "Control 16" ;
+ rdfs:comment "get/set control 16" ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:default 0.0 .
+
+mephisto:audio_1x1
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto Audio 1x1" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 2 ;
+ lv2:symbol "audio_in_1" ;
+ lv2:name "Audio In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 3 ;
+ lv2:symbol "audio_out_1" ;
+ lv2:name "Audio Out 1" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:audio_2x2
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto Audio 2x2" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 2 ;
+ lv2:symbol "audio_in_1" ;
+ lv2:name "Audio In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 3 ;
+ lv2:symbol "audio_out_1" ;
+ lv2:name "Audio Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 4 ;
+ lv2:symbol "audio_in_2" ;
+ lv2:name "Audio In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 5 ;
+ lv2:symbol "audio_out_2" ;
+ lv2:name "Audio Out 2" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:audio_4x4
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto Audio 4x4" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 2 ;
+ lv2:symbol "audio_in_1" ;
+ lv2:name "Audio In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 3 ;
+ lv2:symbol "audio_out_1" ;
+ lv2:name "Audio Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 4 ;
+ lv2:symbol "audio_in_2" ;
+ lv2:name "Audio In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 5 ;
+ lv2:symbol "audio_out_2" ;
+ lv2:name "Audio Out 2" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 6 ;
+ lv2:symbol "audio_in_3" ;
+ lv2:name "Audio In 3" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 7 ;
+ lv2:symbol "audio_out_3" ;
+ lv2:name "Audio Out 3" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 8 ;
+ lv2:symbol "audio_in_4" ;
+ lv2:name "Audio In 4" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 9 ;
+ lv2:symbol "audio_out_4" ;
+ lv2:name "Audio Out 4" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:audio_8x8
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto Audio 8x8" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 2 ;
+ lv2:symbol "audio_in_1" ;
+ lv2:name "Audio In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 3 ;
+ lv2:symbol "audio_out_1" ;
+ lv2:name "Audio Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 4 ;
+ lv2:symbol "audio_in_2" ;
+ lv2:name "Audio In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 5 ;
+ lv2:symbol "audio_out_2" ;
+ lv2:name "Audio Out 2" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 6 ;
+ lv2:symbol "audio_in_3" ;
+ lv2:name "Audio In 3" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 7 ;
+ lv2:symbol "audio_out_3" ;
+ lv2:name "Audio Out 3" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 8 ;
+ lv2:symbol "audio_in_4" ;
+ lv2:name "Audio In 4" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 9 ;
+ lv2:symbol "audio_out_4" ;
+ lv2:name "Audio Out 4" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 10 ;
+ lv2:symbol "audio_in_5" ;
+ lv2:name "Audio In 5" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 11 ;
+ lv2:symbol "audio_out_5" ;
+ lv2:name "Audio Out 5" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 12 ;
+ lv2:symbol "audio_in_6" ;
+ lv2:name "Audio In 6" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 13 ;
+ lv2:symbol "audio_out_6" ;
+ lv2:name "Audio Out 6" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 14 ;
+ lv2:symbol "audio_in_7" ;
+ lv2:name "Audio In 7" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 15 ;
+ lv2:symbol "audio_out_7" ;
+ lv2:name "Audio Out 7" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:AudioPort;
+ lv2:index 16 ;
+ lv2:symbol "audio_in_8" ;
+ lv2:name "Audio In 8" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:AudioPort;
+ lv2:index 17 ;
+ lv2:symbol "audio_out_8" ;
+ lv2:name "Audio Out 8" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:cv_1x1
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto CV 1x1" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 2 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_1" ;
+ lv2:name "CV In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 3 ;
+ lv2:symbol "cv_out_1" ;
+ lv2:name "CV Out 1" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:cv_2x2
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto CV 2x2" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 2 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_1" ;
+ lv2:name "CV In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 3 ;
+ lv2:symbol "cv_out_1" ;
+ lv2:name "CV Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 4 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_2" ;
+ lv2:name "CV In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 5 ;
+ lv2:symbol "cv_out_2" ;
+ lv2:name "CV Out 2" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:cv_4x4
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto CV 4x4" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 2 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_1" ;
+ lv2:name "CV In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 3 ;
+ lv2:symbol "cv_out_1" ;
+ lv2:name "CV Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 4 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_2" ;
+ lv2:name "CV In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 5 ;
+ lv2:symbol "cv_out_2" ;
+ lv2:name "CV Out 2" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 6 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_3" ;
+ lv2:name "CV In 3" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 7 ;
+ lv2:symbol "cv_out_3" ;
+ lv2:name "CV Out 3" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 8 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_4" ;
+ lv2:name "CV In 4" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 9 ;
+ lv2:symbol "cv_out_4" ;
+ lv2:name "CV Out 4" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:cv_8x8
+ a lv2:Plugin ,
+ #lv2:FilterPlugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Mephisto CV 8x8" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:mephisto ;
+ lv2:requiredFeature urid:map, state:loadDefaultState, work:schedule, opts:options ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:extensionData state:interface, work:interface ;
+ opts:requiredOption bufsz:maxBlockLength ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports time:Position ,
+ midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ rsz:minimumSize 262144 ;
+ lv2:designation lv2:control ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 2 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_1" ;
+ lv2:name "CV In 1" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 3 ;
+ lv2:symbol "cv_out_1" ;
+ lv2:name "CV Out 1" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 4 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_2" ;
+ lv2:name "CV In 2" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 5 ;
+ lv2:symbol "cv_out_2" ;
+ lv2:name "CV Out 2" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 6 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_3" ;
+ lv2:name "CV In 3" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 7 ;
+ lv2:symbol "cv_out_3" ;
+ lv2:name "CV Out 3" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 8 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_4" ;
+ lv2:name "CV In 4" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 9 ;
+ lv2:symbol "cv_out_4" ;
+ lv2:name "CV Out 4" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 10 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_5" ;
+ lv2:name "CV In 5" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 11 ;
+ lv2:symbol "cv_out_5" ;
+ lv2:name "CV Out 5" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 12 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_6" ;
+ lv2:name "CV In 6" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 13 ;
+ lv2:symbol "cv_out_6" ;
+ lv2:name "CV Out 6" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 14 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_7" ;
+ lv2:name "CV In 7" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 15 ;
+ lv2:symbol "cv_out_7" ;
+ lv2:name "CV Out 7" ;
+ ] , [
+ a lv2:InputPort ,
+ lv2:CVPort;
+ lv2:index 16 ;
+ lv2:default 0.0 ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 ;
+ lv2:symbol "cv_in_8" ;
+ lv2:name "CV In 8" ;
+ ] , [
+ a lv2:OutputPort,
+ lv2:CVPort;
+ lv2:index 17 ;
+ lv2:symbol "cv_out_8" ;
+ lv2:name "CV Out 8" ;
+ ] ;
+
+ patch:readable
+ mephisto:error ;
+
+ patch:writable
+ mephisto:code ,
+ mephisto:xfadeDuration ,
+ mephisto:fontHeight ,
+ mephisto:control_1 ,
+ mephisto:control_2 ,
+ mephisto:control_3 ,
+ mephisto:control_4 ,
+ mephisto:control_5 ,
+ mephisto:control_6 ,
+ mephisto:control_7 ,
+ mephisto:control_8 ,
+ mephisto:control_9 ,
+ mephisto:control_10 ,
+ mephisto:control_11 ,
+ mephisto:control_12 ,
+ mephisto:control_13 ,
+ mephisto:control_14 ,
+ mephisto:control_15 ,
+ mephisto:control_16 ;
+
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:xfadeDuration "100"^^xsd:int ;
+ mephisto:fontHeight "16"^^xsd:int ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
diff --git a/mephisto_ui.c b/mephisto_ui.c
new file mode 100644
index 0000000..f24c518
--- /dev/null
+++ b/mephisto_ui.c
@@ -0,0 +1,1586 @@
+/*
+ * Copyright (c) 2019-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 <inttypes.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
+#include <limits.h>
+#include <wordexp.h>
+
+#include <mephisto.h>
+#include <props.h>
+
+#define SER_ATOM_IMPLEMENTATION
+#include <ser_atom.lv2/ser_atom.h>
+
+#include <d2tk/hash.h>
+#include <d2tk/util.h>
+#include <d2tk/frontend_pugl.h>
+
+#define GLYPH_W 7
+#define GLYPH_H (GLYPH_W * 2)
+
+#define FPS 25
+
+#define DEFAULT_FG 0xddddddff
+#define DEFAULT_BG 0x222222ff
+
+#define NROWS_MAX 512
+#define NCOLS_MAX 512
+
+#define MAX(x, y) (x > y ? y : x)
+#define WAV_MAX 512
+
+typedef struct _wav_t wav_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _wav_t {
+ float vals [WAV_MAX];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+#ifdef _LV2_HAS_REQUEST_VALUE
+ LV2UI_Request_Value *request_code;
+#endif
+
+ d2tk_pugl_config_t config;
+ d2tk_frontend_t *dpugl;
+
+ LV2UI_Controller *controller;
+ LV2UI_Write_Function writer;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ int64_t ts;
+ wav_t wavs [NCONTROLS];
+
+ uint64_t hash;
+
+ LV2_URID atom_eventTransfer;
+ LV2_URID midi_MidiEvent;
+ LV2_URID urid_code;
+ LV2_URID urid_error;
+ LV2_URID urid_xfadeDuration;
+ LV2_URID urid_fontHeight;
+ LV2_URID urid_control [NCONTROLS];
+
+ bool reinit;
+ char template [24];
+ int fd;
+ time_t modtime;
+
+ float scale;
+ float sample_rate;
+ double frac;
+ d2tk_coord_t header_height;
+ d2tk_coord_t footer_height;
+ d2tk_coord_t tip_height;
+ d2tk_coord_t sidebar_width;
+ d2tk_coord_t item_height;
+ d2tk_coord_t font_height;
+
+ uint32_t max_red;
+
+ int done;
+ int kid;
+
+ wordexp_t wordexp;
+};
+
+static inline void
+_update_font_height(plughandle_t *handle)
+{
+ handle->font_height = handle->state.font_height * handle->scale;
+}
+
+static void
+_intercept_code(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ const ssize_t len = strlen(handle->state.code);
+ const uint64_t hash = d2tk_hash(handle->state.code, len);
+
+ if(handle->hash == hash)
+ {
+ return;
+ }
+
+ handle->hash = hash;
+
+ // save code to file
+ if(lseek(handle->fd, 0, SEEK_SET) == -1)
+ {
+ lv2_log_error(&handle->logger, "lseek: %s\n", strerror(errno));
+ }
+ if(ftruncate(handle->fd, 0) == -1)
+ {
+ lv2_log_error(&handle->logger, "ftruncate: %s\n", strerror(errno));
+ }
+ if(fsync(handle->fd) == -1)
+ {
+ lv2_log_error(&handle->logger, "fsync: %s\n", strerror(errno));
+ }
+ if(write(handle->fd, handle->state.code, len) == -1)
+ {
+ lv2_log_error(&handle->logger, "write: %s\n", strerror(errno));
+ }
+ if(fsync(handle->fd) == -1)
+ {
+ lv2_log_error(&handle->logger, "fsync: %s\n", strerror(errno));
+ }
+
+ // change modification timestamp of file
+ struct stat st;
+ if(stat(handle->template, &st) == -1)
+ {
+ lv2_log_error(&handle->logger, "stat: %s\n", strerror(errno));
+ }
+
+ handle->modtime = time(NULL);
+
+ const struct utimbuf btime = {
+ .actime = st.st_atime,
+ .modtime = handle->modtime
+ };
+
+ if(utime(handle->template, &btime) == -1)
+ {
+ lv2_log_error(&handle->logger, "utime: %s\n", strerror(errno));
+ }
+
+ handle->reinit = true;
+}
+
+static void
+_intercept_font_height(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ _update_font_height(handle);
+}
+
+static void
+_reset_wavs(plughandle_t *handle)
+{
+ for(unsigned c = 0; c < NCONTROLS; c++)
+ {
+ for(unsigned i = 0; i < WAV_MAX; i++)
+ {
+ handle->wavs[c].vals[i] = HUGE_VAL;
+ }
+ }
+}
+
+static void
+_update_wavs(plughandle_t *handle, unsigned nframes)
+{
+ const double nsecs = 2.f; //FIXME make this configurable
+ const double npixels = handle->frac + nframes * WAV_MAX / (handle->sample_rate * nsecs);
+ double fpixels = 0.f;
+ handle->frac = modf(npixels, &fpixels);
+ const unsigned ipixels = fpixels;
+
+ for(unsigned c = 0; c < NCONTROLS; c++)
+ {
+ for(unsigned i = 0; i < WAV_MAX-ipixels; i++)
+ {
+ handle->wavs[c].vals[i] = handle->wavs[c].vals[i + ipixels];
+ }
+
+ for(unsigned i = WAV_MAX-ipixels; i < WAV_MAX; i++)
+ {
+ handle->wavs[c].vals[i] = HUGE_VAL;
+ }
+
+ handle->wavs[c].vals[WAV_MAX-1] = handle->state.control[c];
+ }
+}
+
+static void
+_intercept_timestamp(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ if(handle->ts && (handle->state.timestamp > handle->ts) )
+ {
+ const unsigned nframes = handle->state.timestamp - handle->ts;
+
+ _update_wavs(handle, nframes);
+ }
+
+ handle->ts = handle->state.timestamp;
+}
+
+static void
+_intercept_control(void *data, int64_t frames __attribute__((unused)),
+ props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+ const uint32_t k = (float *)impl->value.body - handle->state.control;
+
+ const cntrl_type_t type = handle->state.control_type[k];
+ switch(type)
+ {
+ case CNTRL_VERTICAL_BARGRAPH:
+ // fall-through
+ case CNTRL_HORIZONTAL_BARGRAPH:
+ {
+ //FIXME
+ } break;
+
+ case CNTRL_BUTTON:
+ // fall-through
+ case CNTRL_CHECK_BUTTON:
+ // fall-through
+ case CNTRL_VERTICAL_SLIDER:
+ // fall-through
+ case CNTRL_HORIZONTAL_SLIDER:
+ // fall-through
+ case CNTRL_NUM_ENTRY:
+ // fall-through
+ case CNTRL_NONE:
+ // fall-through
+ case CNTRL_SOUND_FILE:
+ {
+ // nothing to do
+ } break;
+ }
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = MEPHISTO__code,
+ .offset = offsetof(plugstate_t, code),
+ .type = LV2_ATOM__String,
+ .event_cb = _intercept_code,
+ .max_size = CODE_SIZE
+ },
+ {
+ .property = MEPHISTO__error,
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, error),
+ .type = LV2_ATOM__String,
+ .max_size = ERROR_SIZE
+ },
+ {
+ .property = MEPHISTO__xfadeDuration,
+ .offset = offsetof(plugstate_t, xfade_dur),
+ .type = LV2_ATOM__Int
+ },
+ {
+ .property = MEPHISTO__fontHeight,
+ .offset = offsetof(plugstate_t, font_height),
+ .type = LV2_ATOM__Int,
+ .event_cb = _intercept_font_height
+ },
+ {
+ .property = MEPHISTO__timestamp,
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, timestamp),
+ .type = LV2_ATOM__Long,
+ .event_cb = _intercept_timestamp
+ },
+ CONTROL(1),
+ CONTROL(2),
+ CONTROL(3),
+ CONTROL(4),
+ CONTROL(5),
+ CONTROL(6),
+ CONTROL(7),
+ CONTROL(1),
+ CONTROL(9),
+ CONTROL(10),
+ CONTROL(11),
+ CONTROL(12),
+ CONTROL(13),
+ CONTROL(14),
+ CONTROL(15),
+ CONTROL(16)
+};
+
+static void
+_message_midi_note(plughandle_t *handle, uint8_t chan, uint8_t msg, uint8_t note,
+ uint8_t vel)
+{
+ const struct {
+ LV2_Atom atom;
+ uint8_t body [8];
+ } midi = {
+ .atom = {
+ .type = handle->midi_MidiEvent,
+ .size = 3
+ },
+ .body = {
+ [0] = chan | msg,
+ [1] = note,
+ [2] = vel
+ }
+ };
+
+ handle->writer(handle->controller, 0, lv2_atom_total_size(&midi.atom),
+ handle->atom_eventTransfer, &midi);
+}
+
+static void
+_message_midi_allnotesoff(plughandle_t *handle)
+{
+ for(uint8_t chan = 0x0; chan < 0x10; chan++)
+ {
+ const struct {
+ LV2_Atom atom;
+ uint8_t body [8];
+ } midi = {
+ .atom = {
+ .type = handle->midi_MidiEvent,
+ .size = 2
+ },
+ .body = {
+ [0] = chan | LV2_MIDI_MSG_CONTROLLER,
+ [1] = LV2_MIDI_CTL_ALL_NOTES_OFF
+ }
+ };
+
+ handle->writer(handle->controller, 0, lv2_atom_total_size(&midi.atom),
+ handle->atom_eventTransfer, &midi);
+ }
+}
+
+static void
+_message_set_code(plughandle_t *handle, size_t len)
+{
+ ser_atom_t ser;
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->urid_code);
+ if(!impl)
+ {
+ return;
+ }
+
+ impl->value.size = len;
+
+ ser_atom_init(&ser);
+ ser_atom_reset(&ser, &handle->forge);
+
+ LV2_Atom_Forge_Ref ref = 1;
+
+ props_set(&handle->props, &handle->forge, 0, handle->urid_code, &ref);
+
+ const LV2_Atom_Event *ev = (const LV2_Atom_Event *)ser_atom_get(&ser);
+ const LV2_Atom *atom = &ev->body;
+ handle->writer(handle->controller, 0, lv2_atom_total_size(atom),
+ handle->atom_eventTransfer, atom);
+
+ ser_atom_deinit(&ser);
+}
+
+static void
+_message_set_key(plughandle_t *handle, LV2_URID key)
+{
+ ser_atom_t ser;
+ props_impl_t *impl = _props_impl_get(&handle->props, key);
+ if(!impl)
+ {
+ return;
+ }
+
+ ser_atom_init(&ser);
+ ser_atom_reset(&ser, &handle->forge);
+
+ LV2_Atom_Forge_Ref ref = 1;
+
+ props_set(&handle->props, &handle->forge, 0, key, &ref);
+
+ const LV2_Atom_Event *ev = (const LV2_Atom_Event *)ser_atom_get(&ser);
+ const LV2_Atom *atom = &ev->body;
+ handle->writer(handle->controller, 0, lv2_atom_total_size(atom),
+ handle->atom_eventTransfer, atom);
+
+ ser_atom_deinit(&ser);
+}
+
+static void
+_message_set_control(plughandle_t *handle, unsigned k)
+{
+ const LV2_URID key = handle->urid_control[k];
+
+ _message_set_key(handle, key);
+}
+
+static void
+_message_get(plughandle_t *handle, LV2_URID key)
+{
+ ser_atom_t ser;
+ props_impl_t *impl = _props_impl_get(&handle->props, key);
+ if(!impl)
+ {
+ return;
+ }
+
+ ser_atom_init(&ser);
+ ser_atom_reset(&ser, &handle->forge);
+
+ LV2_Atom_Forge_Ref ref = 1;
+
+ props_get(&handle->props, &handle->forge, 0, key, &ref);
+
+ const LV2_Atom_Event *ev = (const LV2_Atom_Event *)ser_atom_get(&ser);
+ const LV2_Atom *atom = &ev->body;
+ handle->writer(handle->controller, 0, lv2_atom_total_size(atom),
+ handle->atom_eventTransfer, atom);
+
+ ser_atom_deinit(&ser);
+}
+
+static inline void
+_expose_header(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ const d2tk_coord_t frac [3] = { 1, 1, 1 };
+ D2TK_BASE_LAYOUT(rect, 3, frac, D2TK_FLAG_LAYOUT_X_REL, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ d2tk_base_label(base, -1, "Open•Music•Kontrollers", 0.5f, lrect,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_TOP);
+ } break;
+ case 1:
+ {
+ d2tk_base_label(base, -1, "M•E•P•H•I•S•T•O", 1.f, lrect,
+ D2TK_ALIGN_CENTER | D2TK_ALIGN_TOP);
+ } break;
+ case 2:
+ {
+ d2tk_base_label(base, -1, "Version "MEPHISTO_VERSION, 0.5f, lrect,
+ D2TK_ALIGN_RIGHT | D2TK_ALIGN_TOP);
+ } break;
+ }
+ }
+}
+
+#if 0
+static inline void
+_expose_vkb(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const uint8_t off_black [7] = {
+ 0, 1, 3, 0, 6, 8, 10,
+ };
+ static const uint8_t off_white [7] = {
+ 0, 2, 4, 5, 7, 9, 11
+ };
+
+ D2TK_BASE_TABLE(rect, 48, 2, D2TK_FLAG_TABLE_REL, tab)
+ {
+ const unsigned x = d2tk_table_get_index_x(tab);
+ const unsigned y = d2tk_table_get_index_y(tab);
+ const unsigned k = d2tk_table_get_index(tab);
+ const d2tk_rect_t *trect = d2tk_table_get_rect(tab);
+
+ switch(y)
+ {
+ case 0:
+ {
+ d2tk_rect_t trect2 = *trect;
+ trect2.x -= trect2.w/2;
+
+ switch(x%7)
+ {
+ case 0:
+ {
+ char buf [32];
+ const ssize_t len = snprintf(buf, sizeof(buf), "%+i", x/7);
+
+ d2tk_base_label(base, len, buf, 0.35f, &trect2,
+ D2TK_ALIGN_TOP| D2TK_ALIGN_RIGHT);
+ } break;
+ case 1:
+ // fall-through
+ case 2:
+ // fall-through
+ case 4:
+ // fall-through
+ case 5:
+ // fall-through
+ case 6:
+ // fall-through
+ {
+ const d2tk_state_t state = d2tk_base_button(base, D2TK_ID_IDX(k),
+ &trect2);
+
+ if(d2tk_state_is_down(state))
+ {
+ const uint8_t note = x/7*12 + off_black[x%7];
+
+ _message_midi_note(handle, 0x0, LV2_MIDI_MSG_NOTE_ON, note, 0x7f);
+ }
+ if(d2tk_state_is_up(state))
+ {
+ const uint8_t note = x/7*12 + off_black[x%7];
+
+ _message_midi_note(handle, 0x0, LV2_MIDI_MSG_NOTE_OFF, note, 0x0);
+ }
+ } break;
+ }
+ } break;
+ case 1:
+ {
+ const d2tk_state_t state = d2tk_base_button(base, D2TK_ID_IDX(k), trect);
+
+ if(d2tk_state_is_down(state))
+ {
+ const uint8_t note = x/7*12 + off_white[x%7];
+
+ _message_midi_note(handle, 0x0, LV2_MIDI_MSG_NOTE_ON, note, 0x7f);
+ }
+ if(d2tk_state_is_up(state))
+ {
+ const uint8_t note = x/7*12 + off_white[x%7];
+
+ _message_midi_note(handle, 0x0, LV2_MIDI_MSG_NOTE_OFF, note, 0x0);
+ }
+ } break;
+ }
+ }
+}
+#endif
+
+static inline void
+_expose_xfade(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char lbl [] = "crossfade•ms";
+
+ if(d2tk_base_spinner_int32_is_changed(base, D2TK_ID, rect,
+ sizeof(lbl), lbl, 10, &handle->state.xfade_dur, 1000, D2TK_FLAG_NONE))
+ {
+ _message_set_key(handle, handle->urid_xfadeDuration);
+ }
+}
+
+static inline void
+_expose_font_height(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char lbl [] = "font-height•px";
+
+ if(d2tk_base_spinner_int32_is_changed(base, D2TK_ID, rect,
+ sizeof(lbl), lbl, 10, &handle->state.font_height, 25, D2TK_FLAG_NONE))
+ {
+ _message_set_key(handle, handle->urid_fontHeight);
+ _update_font_height(handle);
+ }
+}
+
+static inline void
+_expose_panic(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char path [] = "alert-triangle.png";
+ static const char tip [] = "panic";
+
+ const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
+ sizeof(path), path, rect);
+
+ if(d2tk_state_is_changed(state))
+ {
+ _message_midi_allnotesoff(handle);
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+}
+
+static const char *
+_text_basename(plughandle_t *handle)
+{
+ return basename(handle->template);
+}
+
+static void
+_expose_text_link(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);
+
+ static const char tip [] = "open externally";
+ char lbl [PATH_MAX];
+ const size_t lbl_len = snprintf(lbl, sizeof(lbl), "%s",
+ _text_basename(handle));
+
+ const d2tk_state_t state = d2tk_base_link(base, D2TK_ID, lbl_len, lbl, .5f,
+ rect, D2TK_ALIGN_MIDDLE | D2TK_ALIGN_LEFT);
+
+ if(d2tk_state_is_changed(state))
+ {
+ char *argv [] = {
+ "xdg-open",
+ handle->template,
+ NULL
+ };
+
+ d2tk_util_kill(&handle->kid);
+ handle->kid = d2tk_util_spawn(argv);
+ if(handle->kid <= 0)
+ {
+ lv2_log_error(&handle->logger, "[%s] failed to spawn: %s '%s'", __func__,
+ argv[0], argv[1]);
+ }
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+
+ d2tk_util_wait(&handle->kid);
+}
+
+static void
+_update_code(plughandle_t *handle, const char *txt, size_t txt_len)
+{
+ ser_atom_t ser;
+ ser_atom_init(&ser);
+ ser_atom_reset(&ser, &handle->forge);
+
+ lv2_atom_forge_string(&handle->forge, txt, txt_len);
+
+ const LV2_Atom *atom = ser_atom_get(&ser);
+
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->urid_code);
+ _props_impl_set(&handle->props, impl, atom->type, atom->size,
+ LV2_ATOM_BODY_CONST(atom));
+
+ ser_atom_deinit(&ser);
+
+ _message_set_key(handle, handle->urid_code);
+}
+
+static void
+_expose_text_clear(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char path [] = "delete.png";
+ static const char none [] = "";
+ static const char tip [] = "clear";
+
+ const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
+ sizeof(path), path, rect);
+
+ if(d2tk_state_is_changed(state))
+ {
+ _update_code(handle, none, sizeof(none) - 1);
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+}
+
+static void
+_expose_text_copy(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char path [] = "copy.png";
+ static const char tip [] = "copy to clipboard";
+
+ const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
+ sizeof(path), path, rect);
+
+ if(d2tk_state_is_changed(state))
+ {
+ d2tk_frontend_set_clipboard(dpugl, "UTF8_STRING",
+ handle->state.code, strlen(handle->state.code) + 1);
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+}
+
+static void
+_expose_text_paste(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char path [] = "clipboard.png";
+ static const char tip [] = "paste from clipboard";
+
+ const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
+ sizeof(path), path, rect);
+
+ if(d2tk_state_is_changed(state))
+ {
+ size_t txt_len = 0;
+ const char *mime = "UTF8_STRING";
+ const char *txt = d2tk_frontend_get_clipboard(dpugl, &mime, &txt_len);
+
+ if(txt && txt_len && mime && !strcmp(mime, "UTF8_STRING"))
+ {
+ _update_code(handle, txt, txt_len);
+ }
+ else
+ {
+ lv2_log_error(&handle->logger, "[%s] failed to paste text: %s", __func__,
+ mime);
+ }
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+}
+
+#ifdef _LV2_HAS_REQUEST_VALUE
+static void
+_expose_text_load(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ static const char path [] = "save.png";
+ static const char tip [] = "load from file";
+
+ if(!handle->request_code)
+ {
+ return;
+ }
+
+ const d2tk_state_t state = d2tk_base_button_image(base, D2TK_ID,
+ sizeof(path), path, rect);
+
+ if(d2tk_state_is_changed(state))
+ {
+ const LV2_URID key = handle->urid_code;
+ const LV2_URID type = handle->forge.String;
+
+ const LV2UI_Request_Value_Status status = handle->request_code->request(
+ handle->request_code->handle, key, type, NULL);
+
+ if( (status != LV2UI_REQUEST_VALUE_SUCCESS)
+ && (status != LV2UI_REQUEST_VALUE_BUSY) )
+ {
+ lv2_log_error(&handle->logger, "[%s] requestValue failed: %i", __func__, status);
+
+ if(status == LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED)
+ {
+ handle->request_code = NULL;
+ }
+ }
+ }
+ if(d2tk_state_is_over(state))
+ {
+ d2tk_base_set_tooltip(base, sizeof(tip), tip, handle->tip_height);
+ }
+}
+#endif
+
+static inline void
+_expose_footer(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ const d2tk_coord_t frac [8] = {
+ rect->h, 0, 0, 0, rect->h, rect->h, rect->h, rect->h
+ };
+ D2TK_BASE_LAYOUT(rect, 8, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ _expose_panic(handle, lrect);
+ } break;
+ case 1:
+ {
+ _expose_text_link(handle, lrect);
+ } break;
+ case 2:
+ {
+ _expose_xfade(handle, lrect);
+ } break;
+ case 3:
+ {
+ _expose_font_height(handle, lrect);
+ } break;
+ case 4:
+ {
+ _expose_text_clear(handle, lrect);
+ } break;
+ case 5:
+ {
+ _expose_text_copy(handle, lrect);
+ } break;
+ case 6:
+ {
+ _expose_text_paste(handle, lrect);
+ } break;
+ case 7:
+ {
+#ifdef _LV2_HAS_REQUEST_VALUE
+ _expose_text_load(handle, lrect);
+#endif
+ } break;
+ }
+ }
+}
+
+static inline void
+_expose_slot(plughandle_t *handle, const d2tk_rect_t *rect, unsigned k)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ cntrl_type_t type = handle->state.control_type[k];
+
+ if( (handle->state.control_min[k] == 0.f)
+ && (handle->state.control_max[k] == 0.f) )
+ {
+ type = CNTRL_NONE;
+ }
+
+ switch(type)
+ {
+ case CNTRL_BUTTON:
+ {
+ bool val = handle->state.control[k] > 0.5;
+
+ const d2tk_state_t state = d2tk_base_spinner_bool(base, D2TK_ID_IDX(k), rect,
+ -1, handle->state.control_label[k], &val, D2TK_FLAG_NONE);
+
+ if(d2tk_state_is_down(state))
+ {
+ handle->state.control[k] = 1;
+
+ _message_set_control(handle, k);
+ }
+ else if(d2tk_state_is_up(state))
+ {
+ handle->state.control[k] = 0;
+
+ _message_set_control(handle, k);
+ }
+ } break;
+ case CNTRL_CHECK_BUTTON:
+ {
+ bool val = handle->state.control[k] > 0.5;
+
+ if(d2tk_base_spinner_bool_is_changed(base, D2TK_ID_IDX(k), rect,
+ -1, handle->state.control_label[k], &val, D2TK_FLAG_NONE))
+ {
+ handle->state.control[k] = val;
+
+ _message_set_control(handle, k);
+ }
+ } break;
+
+ case CNTRL_VERTICAL_SLIDER:
+ // fall-through
+ case CNTRL_HORIZONTAL_SLIDER:
+ // fall-through
+ case CNTRL_NUM_ENTRY:
+ {
+ const float min = handle->state.control_min[k];
+ const float max = handle->state.control_max[k];
+ const float range = max - min; //FIXME cache this somewhere
+ float abs = handle->state.control[k] * range + min;
+
+ if(d2tk_base_spinner_float_is_changed(base, D2TK_ID_IDX(k), rect,
+ -1, handle->state.control_label[k], min, &abs, max, D2TK_FLAG_NONE))
+ {
+ handle->state.control[k] = (abs - min) / range;
+
+ _message_set_control(handle, k);
+ }
+ } break;
+
+ case CNTRL_VERTICAL_BARGRAPH:
+ // fall-through
+ case CNTRL_HORIZONTAL_BARGRAPH:
+ {
+ const float min = 0.f;
+ const float max = 1.f;
+
+ d2tk_base_spinner_wave_float(base, D2TK_ID_IDX(k), rect,
+ -1, handle->state.control_label[k],
+ min, handle->wavs[k].vals, WAV_MAX, max);
+ } break;
+
+ case CNTRL_NONE:
+ // fall-through
+ case CNTRL_SOUND_FILE:
+ {
+ char lbl [32];
+ const size_t lbl_len = snprintf(lbl, sizeof(lbl), "- slot•%02d empty -", k);
+
+ d2tk_base_label(base, lbl_len, lbl, 0.375f, rect, D2TK_ALIGN_CENTERED);
+ } break;
+ }
+}
+
+static inline void
+_expose_sidebar_right(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ const uint32_t numv = rect->h / handle->item_height;
+ const uint32_t max [2] = { 0, NCONTROLS };
+ const uint32_t num [2] = { 0, numv };
+ D2TK_BASE_SCROLLBAR(base, rect, D2TK_ID, D2TK_FLAG_SCROLL_Y, max, num, vscroll)
+ {
+ const unsigned offy = d2tk_scrollbar_get_offset_y(vscroll);
+ const d2tk_rect_t *vrect = d2tk_scrollbar_get_rect(vscroll);
+
+ D2TK_BASE_TABLE(vrect, 1, numv, D2TK_FLAG_TABLE_REL, tab)
+ {
+ const unsigned k = d2tk_table_get_index(tab) + offy;
+ const d2tk_rect_t *trect = d2tk_table_get_rect(tab);
+
+ if(k >= NCONTROLS)
+ {
+ break;
+ }
+
+ _expose_slot(handle, trect, k);
+ }
+ }
+}
+
+/* list of tested console editors:
+ *
+ * e3
+ * joe
+ * nano
+ * vi
+ * vis
+ * vim
+ * neovim
+ * emacs
+ * zile
+ * mg
+ * kakoune
+ */
+
+/* list of tested graphical editors:
+ *
+ * acme
+ * adie
+ * beaver
+ * deepin-editor
+ * gedit (does not work properly)
+ * gobby
+ * howl
+ * jedit (does not work properly)
+ * xed (does not work properly)
+ * leafpad
+ * mousepad
+ * nedit
+ * notepadqq
+ * pluma (does not work properly)
+ * sublime3 (needs to be started with -w)
+ */
+
+static inline void
+_expose_term(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ char **args = handle->wordexp.we_wordv;
+
+ d2tk_flag_t flag = D2TK_FLAG_NONE;
+ if(handle->reinit)
+ {
+ flag = D2TK_FLAG_PTY_REINIT;
+ }
+
+ D2TK_BASE_PTY(base, D2TK_ID, NULL, args, handle->font_height, rect, flag, pty)
+ {
+ const d2tk_state_t state = d2tk_pty_get_state(pty);
+ const uint32_t max_red = d2tk_pty_get_max_red(pty);
+
+ if(max_red != handle->max_red)
+ {
+ handle->max_red = max_red;
+ d2tk_frontend_redisplay(handle->dpugl);
+ }
+
+ if(d2tk_state_is_close(state))
+ {
+ handle->done = 1;
+ }
+ }
+
+ handle->reinit = false;
+}
+
+static inline unsigned
+_num_lines(const char *err)
+{
+ unsigned nlines = 1;
+
+ for(const char *from = err, *to = strchr(from, '\n');
+ to;
+ from = &to[1],
+ to = strchr(from, '\n'))
+ {
+ nlines += 1;
+ }
+
+ return nlines;
+}
+
+static inline void
+_expose_error(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ const char *from = handle->state.error;
+ const unsigned nlines = _num_lines(from);
+
+ //FIXME wrap in scroll widget
+ D2TK_BASE_TABLE(rect, rect->w, handle->font_height, D2TK_FLAG_TABLE_ABS, tab)
+ {
+ const d2tk_rect_t *trect = d2tk_table_get_rect(tab);
+ const unsigned k = d2tk_table_get_index(tab);
+
+ if(k >= nlines)
+ {
+ break;
+ }
+
+ const char *to = strchr(from, '\n');
+ const size_t len = to ? to - from : -1;
+
+ d2tk_base_label(base, len, from, 1.f, trect,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
+
+ from = &to[1];
+ }
+}
+
+static inline void
+_expose_editor(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ const size_t err_len = strlen(handle->state.error);
+ const unsigned n = err_len > 0 ? 2 : 1;
+ const d2tk_coord_t frac [2] = { 2, 1 };
+ D2TK_BASE_LAYOUT(rect, n, frac, D2TK_FLAG_LAYOUT_Y_REL, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ _expose_term(handle, lrect);
+ } break;
+ case 1:
+ {
+ _expose_error(handle, lrect);
+ } break;
+ }
+ }
+}
+
+static inline void
+_expose_upper(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ d2tk_frontend_t *dpugl = handle->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
+
+ const d2tk_coord_t frac [3] = { 0, 5, handle->sidebar_width };
+ D2TK_BASE_LAYOUT(rect, 3, frac, D2TK_FLAG_LAYOUT_X_ABS, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ _expose_editor(handle, lrect);
+ } break;
+ case 1:
+ {
+ d2tk_base_separator(base, lrect, D2TK_FLAG_SEPARATOR_X);
+ } break;
+ case 2:
+ {
+ _expose_sidebar_right(handle, lrect);
+ } break;
+ }
+ }
+}
+
+static inline void
+_expose_body(plughandle_t *handle, const d2tk_rect_t *rect)
+{
+ const d2tk_coord_t frac [2] = { 0, handle->footer_height };
+ D2TK_BASE_LAYOUT(rect, 2, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ _expose_upper(handle, lrect);
+ } break;
+ case 1:
+ {
+ _expose_footer(handle, lrect);
+ } break;
+ }
+ }
+}
+
+static int
+_expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
+{
+ plughandle_t *handle = data;
+ d2tk_base_t *base = d2tk_frontend_get_base(handle->dpugl);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, w, h);
+
+ const d2tk_style_t *old_style = d2tk_base_get_style(base);
+ d2tk_style_t style = *old_style;
+ const uint32_t light = handle->max_red;
+ const uint32_t dark = (light & ~0xff) | 0x7f;
+ style.fill_color[D2TK_TRIPLE_ACTIVE] = dark;
+ style.fill_color[D2TK_TRIPLE_ACTIVE_HOT] = light;
+ style.fill_color[D2TK_TRIPLE_ACTIVE_FOCUS] = dark;
+ style.fill_color[D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = light;
+ style.text_stroke_color[D2TK_TRIPLE_HOT] = light;
+ style.text_stroke_color[D2TK_TRIPLE_HOT_FOCUS] = light;
+
+ d2tk_base_set_style(base, &style);
+
+ const d2tk_coord_t frac [2] = { handle->header_height, 0 };
+ D2TK_BASE_LAYOUT(&rect, 2, frac, D2TK_FLAG_LAYOUT_Y_ABS, lay)
+ {
+ const unsigned k = d2tk_layout_get_index(lay);
+ const d2tk_rect_t *lrect = d2tk_layout_get_rect(lay);
+
+ switch(k)
+ {
+ case 0:
+ {
+ _expose_header(handle, lrect);
+ } break;
+ case 1:
+ {
+ _expose_body(handle, lrect);
+ } break;
+ }
+ }
+
+ d2tk_base_set_style(base, old_style);
+
+ return 0;
+}
+
+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;
+
+ _reset_wavs(handle);
+
+ 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_LOG__log))
+ {
+ handle->log = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_OPTIONS__options))
+ {
+ opts = features[i]->data;
+ }
+#ifdef _LV2_HAS_REQUEST_VALUE
+ else if(!strcmp(features[i]->URI, LV2_UI__requestValue))
+ {
+ handle->request_code = features[i]->data;
+ }
+#endif
+ }
+
+ 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);
+
+ handle->atom_eventTransfer = handle->map->map(handle->map->handle,
+ LV2_ATOM__eventTransfer);
+ handle->midi_MidiEvent = handle->map->map(handle->map->handle,
+ LV2_MIDI__MidiEvent);
+ handle->urid_code = handle->map->map(handle->map->handle,
+ MEPHISTO__code);
+ handle->urid_error = handle->map->map(handle->map->handle,
+ MEPHISTO__error);
+ handle->urid_xfadeDuration = handle->map->map(handle->map->handle,
+ MEPHISTO__xfadeDuration);
+ handle->urid_fontHeight = handle->map->map(handle->map->handle,
+ MEPHISTO__fontHeight);
+ handle->urid_control[0] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_1);
+ handle->urid_control[1] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_2);
+ handle->urid_control[2] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_3);
+ handle->urid_control[3] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_4);
+ handle->urid_control[4] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_5);
+ handle->urid_control[5] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_6);
+ handle->urid_control[6] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_7);
+ handle->urid_control[7] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_8);
+ handle->urid_control[8] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_9);
+ handle->urid_control[9] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_10);
+ handle->urid_control[10] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_11);
+ handle->urid_control[11] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_12);
+ handle->urid_control[12] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_13);
+ handle->urid_control[13] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_14);
+ handle->urid_control[14] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_15);
+ handle->urid_control[15] = handle->map->map(handle->map->handle,
+ MEPHISTO__control_16);
+
+ if(!props_init(&handle->props, plugin_uri,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->controller = controller;
+ handle->writer = write_function;
+
+ const d2tk_coord_t w = 1024;
+ const d2tk_coord_t h = 720;
+
+ d2tk_pugl_config_t *config = &handle->config;
+ config->parent = (uintptr_t)parent;
+ config->bundle_path = bundle_path;
+ config->min_w = w/2;
+ config->min_h = h/2;
+ config->w = w;
+ config->h = h;
+ config->fixed_size = false;
+ config->fixed_aspect = false;
+ config->expose = _expose;
+ config->data = handle;
+
+ handle->dpugl = d2tk_pugl_new(config, (uintptr_t *)widget);
+ if(!handle->dpugl)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ const LV2_URID ui_scaleFactor = handle->map->map(handle->map->handle,
+ LV2_UI__scaleFactor);
+ const LV2_URID params_sample_rate = handle->map->map(handle->map->handle,
+ LV2_PARAMETERS__sampleRate);
+
+ // fall-back
+ handle->sample_rate = 48000.f;
+
+ for(LV2_Options_Option *opt = opts;
+ opt && (opt->key != 0) && (opt->value != NULL);
+ opt++)
+ {
+ if( (opt->key == ui_scaleFactor) && (opt->type == handle->forge.Float) )
+ {
+ handle->scale = *(float*)opt->value;
+ }
+ else if( (opt->key == params_sample_rate) && (opt->type == handle->forge.Float) )
+ {
+ handle->sample_rate = *(float*)opt->value;
+ }
+ }
+
+ if(handle->scale == 0.f)
+ {
+ handle->scale = d2tk_frontend_get_scale(handle->dpugl);
+ }
+
+ handle->header_height = 32 * handle->scale;
+ handle->footer_height = 32 * handle->scale;
+ handle->tip_height = 20 * handle->scale;
+ handle->sidebar_width = 256 * handle->scale;
+ handle->item_height = 40 * handle->scale;
+
+ handle->state.font_height = 16;
+ _update_font_height(handle);
+
+ if(host_resize)
+ {
+ host_resize->ui_resize(host_resize->handle, w, h);
+ }
+
+ strncpy(handle->template, "/tmp/XXXXXX.dsp", sizeof(handle->template));
+ handle->fd = mkstemps(handle->template, 4);
+ if(handle->fd == -1)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ lv2_log_note(&handle->logger, "template: %s\n", handle->template);
+
+ static const char *fallback= "vi";
+ const char *editor = getenv("EDITOR");
+ char cmdline [PATH_MAX];
+ snprintf(cmdline, sizeof(cmdline), "%s %s",
+ editor ? editor : fallback,
+ handle->template);
+ if(wordexp(cmdline, &handle->wordexp, WRDE_NOCMD) != 0)
+ {
+ fprintf(stderr, "failed to parse EDITOR");
+ free(handle);
+ return NULL;
+ }
+
+ for(unsigned i = 0; i < MAX_NPROPS; i++)
+ {
+ const props_def_t *def = &defs[i];
+ const LV2_URID urid = props_map(&handle->props, def->property);
+
+ _message_get(handle, urid);
+ }
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ d2tk_util_kill(&handle->kid);
+ d2tk_frontend_free(handle->dpugl);
+
+ wordfree(&handle->wordexp);
+
+ unlink(handle->template);
+ close(handle->fd);
+ free(handle);
+}
+
+static void
+port_event(LV2UI_Handle instance, uint32_t index __attribute__((unused)),
+ uint32_t size __attribute__((unused)), uint32_t protocol, const void *buf)
+{
+ plughandle_t *handle = instance;
+ const LV2_Atom_Object *obj = buf;
+
+ if(protocol != handle->atom_eventTransfer)
+ {
+ return;
+ }
+
+ ser_atom_t ser;
+ ser_atom_init(&ser);
+ ser_atom_reset(&ser, &handle->forge);
+
+ LV2_Atom_Forge_Ref ref = 0;
+ props_advance(&handle->props, &handle->forge, 0, obj, &ref);
+
+ ser_atom_deinit(&ser);
+
+ d2tk_frontend_redisplay(handle->dpugl);
+}
+
+static void
+_file_read(plughandle_t *handle)
+{
+ lseek(handle->fd, 0, SEEK_SET);
+ const size_t len = lseek(handle->fd, 0, SEEK_END);
+
+ lseek(handle->fd, 0, SEEK_SET);
+
+ read(handle->fd, handle->state.code, len);
+ handle->state.code[len] = '\0';
+
+ handle->hash = d2tk_hash(handle->state.code, len);
+
+ _message_set_code(handle, len + 1);
+}
+
+static int
+_idle(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ struct stat st;
+ if(stat(handle->template, &st) == -1)
+ {
+ lv2_log_error(&handle->logger, "stat: %s\n", strerror(errno));
+ }
+
+ if( (st.st_mtime > handle->modtime) && (handle->modtime > 0) )
+ {
+ _file_read(handle);
+
+ handle->modtime = st.st_mtime;
+ }
+
+ if(d2tk_frontend_step(handle->dpugl))
+ {
+ handle->done = 1;
+ }
+
+ return handle->done;
+}
+
+static const LV2UI_Idle_Interface idle_ext = {
+ .idle = _idle
+};
+
+static int
+_resize(LV2UI_Handle instance, int width, int height)
+{
+ plughandle_t *handle = instance;
+
+ return d2tk_frontend_set_size(handle->dpugl, 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;
+}
+
+static const LV2UI_Descriptor mephisto_ui= {
+ .URI = MEPHISTO__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 &mephisto_ui;
+ default:
+ return NULL;
+ }
+}
diff --git a/mephisto_ui.ttl b/mephisto_ui.ttl
new file mode 100644
index 0000000..4af6baa
--- /dev/null
+++ b/mephisto_ui.ttl
@@ -0,0 +1,65 @@
+# Copyright (c) 2019-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 ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix opts: <http://lv2plug.in/ns/ext/options#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
+
+@prefix mephisto: <http://open-music-kontrollers.ch/lv2/mephisto#> .
+
+mephisto:ui
+ ui:portNotification [
+ ui:plugin mephisto:audio_1x1;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:audio_2x2;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:audio_4x4;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:audio_8x8;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:cv_1x1 ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:cv_2x2 ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:cv_4x4 ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] , [
+ ui:plugin mephisto:cv_8x8 ;
+ lv2:symbol "notify" ;
+ ui:protocol atom:eventTransfer ;
+ ] ;
+ lv2:requiredFeature ui:idleInterface, urid:map, ui:parent ;
+ lv2:optionalFeature ui:requestValue, ui:resize, opts:options, log:log ;
+ opts:supportedOption ui:scaleFactor ;
+ #opts:requiredOption param:sampleRate ;
+ lv2:extensionData ui:idleInterface, ui:resize .
diff --git a/meson.build b/meson.build
index 58a2d69..d70ddc9 100644
--- a/meson.build
+++ b/meson.build
@@ -1,433 +1,260 @@
-project('d2tk', 'c', default_options : [
+project('mephisto.lv2', 'c', default_options : [
'buildtype=release',
'warning_level=3',
'werror=false',
'b_lto=false',
'c_std=gnu11'])
-static_link = false #meson.is_cross_build()
+d2tk = subproject('d2tk')
-build_debug_overlay = get_option('build-debug-overlay')
-build_examples = get_option('build-examples')
+lv2libdir = get_option('lv2libdir')
build_tests = get_option('build-tests')
-build_doc = get_option('build-doc')
+use_fontconfig = get_option('use-fontconfig')
-use_backend_cairo = get_option('use-backend-cairo')
-use_backend_nanovg = get_option('use-backend-nanovg')
-use_frontend_fbdev = get_option('use-frontend-fbdev')
-use_frontend_pugl = get_option('use-frontend-pugl')
-use_frontend_glfw = get_option('use-frontend-glfw')
+inst_dir = join_paths(lv2libdir, meson.project_name())
-use_evdev = get_option('use-evdev')
-use_fontconfig = get_option('use-fontconfig')
+if get_option('use-backend-nanovg').enabled()
+ d2tk_dep = d2tk.get_variable('d2tk_nanovg')
+elif get_option('use-backend-cairo').enabled()
+ d2tk_dep = d2tk.get_variable('d2tk_cairo')
+else
+ error('no valid UI backend given')
+endif
-grep = find_program('grep',
- native : true,
- required : use_fontconfig)
-fc_list = find_program('fc-list',
- native : true,
- required : use_fontconfig)
-check_for_font = find_program('check_for_font',
- native : true,
- required : use_fontconfig)
-robodoc = find_program('robodoc',
- native : true,
- required : build_doc)
+source_root = meson.source_root()
+build_root = meson.build_root()
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
cc = meson.get_compiler('c')
-# mandatory dependencies
-m_dep = cc.find_library('m')
-thread_dep = dependency('threads')
-
-# dependencies for backend_cairo/frontend_pugl
-freetype_dep = dependency('freetype2',
- version : '>=18.0.0',
- static : static_link,
- required : use_backend_cairo)
-pixman_dep = dependency('pixman-1',
- version : '>=0.34.0',
- static : static_link,
- required : use_backend_cairo)
-cairo_dep = dependency('cairo',
- version : '>=1.14.0',
- static : static_link,
- required : use_backend_cairo)
-cairo_deps = [freetype_dep, pixman_dep, cairo_dep]
-if use_frontend_pugl.enabled()
- cairo_xlib_dep = dependency('cairo-xlib',
- version : '>=1.14.0',
- static : static_link,
- required : use_backend_cairo)
-endif
+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)
+faust = find_program('faust', required : false)
-# dependencies for frontend_fbdev
-input_dep = dependency('libinput',
- version : '>=1.6.0',
- static : static_link,
- required : use_frontend_fbdev)
-udev_dep = dependency('libudev',
- static : static_link,
- required : use_frontend_fbdev)
-evdev_dep = dependency('libevdev',
- version : '>=1.5.0',
- static : static_link,
- required : use_frontend_fbdev)
-
-# dependencies for backend_nanovg
-glu_dep = dependency('glu',
- version : '>=9.0.0',
- static : static_link,
- required : use_backend_nanovg)
-glew_dep = dependency('glew',
- version : '>=2.0.0',
- static : static_link,
- required : use_backend_nanovg)
-if use_frontend_glfw.enabled()
- glfw_dep = dependency('glfw3',
- version : '>=3.3.0',
- static : static_link,
- required : use_backend_nanovg)
+if cc.has_member('UIGlue', 'addSoundFile',
+ prefix : '#include <faust/gui/CInterface.h>')
+ add_project_arguments('-DFAUST_ADDSOUNDFILE=addSoundFile', language : 'c')
+else
+ add_project_arguments('-DFAUST_ADDSOUNDFILE=addSoundfile', language : 'c')
endif
-# optional dependencies
-util_dep = cc.find_library('util')
-vterm_dep = dependency('vterm',
- version : '>=0.1',
- static : static_link)
-if not use_frontend_fbdev.enabled()
- evdev_dep = dependency('libevdev',
- version : '>=1.5.0',
- static : static_link,
- required : use_evdev)
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2', version : '>=1.16.0')
+faust_dep = cc.find_library('faust')
+
+if cc.has_member('LV2UI_Request_Value', 'request',
+ prefix : '#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>')
+ add_project_arguments('-D_LV2_HAS_REQUEST_VALUE', language : 'c')
+ message('building with ui:requestValue support')
endif
-fontconfig_dep = dependency('fontconfig',
- version : '>=2.0.0',
- static : static_link,
- required : use_fontconfig)
-deps = [m_dep, thread_dep, evdev_dep, util_dep, vterm_dep, fontconfig_dep]
-links = []
+dsp_deps = [m_dep, lv2_dep, faust_dep]
+ui_deps = [lv2_dep, d2tk_dep]
-pugl_inc = include_directories(join_paths('pugl', 'include'))
-nanovg_inc = include_directories(join_paths('nanovg', 'src'))
-linenoise_inc = include_directories('linenoise')
-inc_dir = [pugl_inc, nanovg_inc, linenoise_inc]
+props_inc = include_directories('props.lv2')
+timely_inc = include_directories('timely.lv2')
+ser_inc = include_directories('ser_atom.lv2')
+varchunk_inc = include_directories('varchunk')
+d2tk_inc = include_directories(join_paths('subprojects', 'd2tk'))
+inc_dir = [props_inc, timely_inc, ser_inc, varchunk_inc, d2tk_inc]
-rawvers = run_command('cat', 'VERSION').stdout().strip()
-version = rawvers.split('.')
+dsp_srcs = ['mephisto.c']
-conf_data = configuration_data()
+ui_srcs = ['mephisto_ui.c']
+
+c_args = ['-fvisibility=hidden']
+
+version = run_command('cat', 'VERSION').stdout().strip().split('.')
+add_project_arguments('-DMEPHISTO_VERSION="'
+ + version[0] + '.' + version[1] + '.' + version[2] + '"', language : 'c')
conf_data.set('MAJOR_VERSION', version[0])
conf_data.set('MINOR_VERSION', version[1])
conf_data.set('MICRO_VERSION', version[2])
-add_project_arguments('-D_GNU_SOURCE', language : 'c')
+mod = shared_module('mephisto', dsp_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : dsp_deps,
+ install : true,
+ install_dir : inst_dir)
-if build_debug_overlay
- conf_data.set('D2TK_DEBUG', 1)
-else
- conf_data.set('D2TK_DEBUG', 0)
-endif
+ui = shared_module('mephisto_ui', ui_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : ui_deps,
+ install : true,
+ install_dir : inst_dir)
-lib_srcs = [
- join_paths('src', 'hash.c'),
- join_paths('src', 'core.c'),
- join_paths('src', 'base.c'),
- join_paths('src', 'base_table.c'),
- join_paths('src', 'base_frame.c'),
- join_paths('src', 'base_layout.c'),
- join_paths('src', 'base_scrollbar.c'),
- join_paths('src', 'base_pane.c'),
- join_paths('src', 'base_cursor.c'),
- join_paths('src', 'base_button.c'),
- join_paths('src', 'base_image.c'),
- join_paths('src', 'base_bitmap.c'),
- join_paths('src', 'base_custom.c'),
- join_paths('src', 'base_meter.c'),
- join_paths('src', 'base_combo.c'),
- join_paths('src', 'base_textfield.c'),
- join_paths('src', 'base_label.c'),
- join_paths('src', 'base_separator.c'),
- join_paths('src', 'base_tooltip.c'),
- join_paths('src', 'base_link.c'),
- join_paths('src', 'base_dial.c'),
- join_paths('src', 'base_spinner.c'),
- join_paths('src', 'base_bar.c'),
- join_paths('src', 'base_wave.c'),
- join_paths('src', 'base_flowmatrix.c'),
- join_paths('src', 'base_pty.c'),
- join_paths('src', 'base_lineedit.c'),
- join_paths('src', 'util_spawn.c'),
- join_paths('linenoise', 'linenoise.c'),
- join_paths('linenoise', 'encodings', 'utf8.c')
-]
-
-if use_evdev.enabled()
- conf_data.set('D2TK_EVDEV', 1)
- lib_srcs += join_paths('src', 'base_vkb.c')
-else
- conf_data.set('D2TK_EVDEV', 0)
-endif
-
-if input_dep.found() and input_dep.version().version_compare('>=1.15.0')
- conf_data.set('D2TK_INPUT_1_15', 1)
-else
- conf_data.set('D2TK_INPUT_1_15', 0)
-endif
+bank_filter_through = run_command('cat', 'bank-filter_through.dsp').stdout()
+conf_data.set('BANK-FILTER_THROUGH', bank_filter_through)
-if use_fontconfig.enabled()
- conf_data.set('D2TK_FONTCONFIG', 1)
-else
- conf_data.set('D2TK_FONTCONFIG', 0)
-endif
+bank_filter_gain = run_command('cat', 'bank-filter_gain.dsp').stdout()
+conf_data.set('BANK-FILTER_GAIN', bank_filter_gain)
-example_srcs = [
- join_paths('example', 'example.c')
-]
-
-example_cairo_srcs = [
- join_paths('example', 'custom_cairo.c')
-]
-
-example_nanovg_srcs = [
- join_paths('example', 'custom_nanovg.c')
-]
-
-example_pugl_srcs = [
- join_paths('example', 'd2tk_pugl.c')
-]
-
-example_fbdev_srcs = [
- join_paths('example', 'd2tk_fbdev.c')
-]
-
-example_glfw_srcs = [
- join_paths('example', 'd2tk_glfw.c')
-]
-
-pugl_srcs = [
- join_paths('src', 'frontend_pugl.c'),
- join_paths('pugl', 'src', 'implementation.c')
-]
-
-pugl_gl_srcs = []
-
-pugl_cairo_srcs = []
-
-nanovg_srcs = [
- join_paths('nanovg', 'src', 'nanovg.c'),
- join_paths('src', 'backend_nanovg.c')
-]
-
-cairo_srcs = [
- join_paths('src', 'backend_cairo.c')
-]
-
-fbdev_srcs = [
- join_paths('src', 'frontend_fbdev.c')
-]
-
-glfw_srcs = [
- join_paths('src', 'frontend_glfw.c')
-]
-
-test_core_srcs = [
- join_paths('test', 'core.c'),
- join_paths('test', 'mock.c')
-]
-
-test_base_srcs = [
- join_paths('test', 'base.c'),
- join_paths('test', 'mock.c')
-]
-
-c_args = ['-fvisibility=hidden',
- '-ffast-math']
-
-if host_machine.system() == 'windows'
- deps += cc.find_library('opengl32', required : use_frontend_pugl)
- deps += cc.find_library('gdi32', required : use_frontend_pugl)
- deps += cc.find_library('ws2_32', required : true)
- pugl_srcs += join_paths('pugl', 'src', 'win.c')
- pugl_gl_srcs += join_paths('pugl', 'src', 'win_gl.c')
- pugl_cairo_srcs += join_paths('pugl', 'src', 'win_cairo.c')
-elif host_machine.system() == 'darwin'
- add_languages('objc')
- links += ['-framework', 'OpenGL']
- links += ['-framework', 'Cocoa']
- pugl_srcs += join_paths('pugl','src', 'mac.m')
- pugl_gl_srcs += join_paths('pugl', 'src', 'mac_gl.m')
- pugl_cairo_srcs += join_paths('pugl', 'src', 'mac_cairo.m')
-else
- deps += dependency('gl', required : use_frontend_pugl)
- deps += dependency('x11', version : '>=1.6.0', required : use_frontend_pugl)
- deps += dependency('xext', version : '>=1.3.0', required : use_frontend_pugl)
- pugl_srcs += join_paths('pugl', 'src', 'x11.c')
- pugl_srcs += join_paths('pugl', 'src', 'x11_stub.c')
- pugl_gl_srcs += join_paths('pugl', 'src', 'x11_gl.c')
- pugl_cairo_srcs += join_paths('pugl', 'src', 'x11_cairo.c')
-endif
+bank_time_lfo = run_command('cat', 'bank-time_lfo.dsp').stdout()
+conf_data.set('BANK-TIME_LFO', bank_time_lfo)
-if use_backend_cairo.enabled()
- if use_frontend_pugl.enabled()
- d2tk_cairo = declare_dependency(
- compile_args : ['-DPUGL_HAVE_CAIRO', '-DPUGL_STATIC'],
- include_directories : inc_dir,
- dependencies : [deps, cairo_deps, cairo_xlib_dep],
- link_args : links,
- sources : [lib_srcs, cairo_srcs, pugl_srcs, pugl_cairo_srcs])
-
- if build_examples
- executable('d2tk.cairo', [example_srcs, example_pugl_srcs, example_cairo_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: d2tk_cairo,
- install : false)
- endif
- endif
+bank_instrument_osc = run_command('cat', 'bank-instrument_osc.dsp').stdout()
+conf_data.set('BANK-INSTRUMENT_OSC', bank_instrument_osc)
- if use_frontend_fbdev.enabled()
- d2tk_fbdev = declare_dependency(
- include_directories : inc_dir,
- dependencies : [deps, cairo_deps, input_dep, udev_dep, evdev_dep],
- link_args : links,
- sources : [lib_srcs, cairo_srcs, fbdev_srcs])
-
- if build_examples
- executable('d2tk.fbdev', [example_srcs, example_fbdev_srcs, example_cairo_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: d2tk_fbdev,
- install : false)
- endif
- endif
-endif
+bank_analyzer_vu_meter = run_command('cat', 'bank-analyzer_vu-meter.dsp').stdout()
+conf_data.set('BANK-ANALYZER_VU-METER', bank_analyzer_vu_meter)
-if use_backend_nanovg.enabled()
- if use_frontend_pugl.enabled()
- d2tk_nanovg = declare_dependency(
- compile_args : ['-DPUGL_STATIC'],
- include_directories : inc_dir,
- dependencies : [deps, glu_dep, glew_dep],
- link_args : links,
- sources : [lib_srcs, nanovg_srcs, pugl_srcs, pugl_gl_srcs])
-
- if build_examples
- executable('d2tk.nanovg', [example_srcs, example_pugl_srcs, example_nanovg_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: d2tk_nanovg,
- install : false)
- endif
- endif
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
- if use_frontend_glfw.enabled()
- d2tk_glfw = declare_dependency(
- include_directories : inc_dir,
- dependencies : [deps, glfw_dep, glew_dep],
- link_args : links,
- sources : [lib_srcs, nanovg_srcs, glfw_srcs])
-
- if build_examples
- executable('d2tk.glfw', [example_srcs, example_glfw_srcs, example_nanovg_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: d2tk_glfw,
- install : false)
- endif
- endif
-endif
+manifest_ttl = configure_file(
+ input : 'manifest.ttl.in',
+ output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
-config_h = configure_file(
- input : join_paths('d2tk', 'config.h.in'),
- output : 'config.h',
+dsp_ttl = configure_file(
+ input : 'mephisto.ttl.in',
+ output : 'mephisto.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+
+ui_ttl = configure_file(
+ input : 'mephisto_ui.ttl',
+ output : 'mephisto_ui.ttl',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+
+pset_ttl = configure_file(
+ input : 'presets.ttl.in',
+ output : 'presets.ttl',
configuration : conf_data,
- install : false)
+ install : true,
+ install_dir : inst_dir)
+
+alert_triangle_png = configure_file(
+ input : join_paths('png', 'alert-triangle.png'),
+ output : 'alert-triangle.png',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+clipboard_png = configure_file(
+ input : join_paths('png', 'clipboard.png'),
+ output : 'clipboard.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+copy_png = configure_file(
+ input : join_paths('png', 'copy.png'),
+ output : 'copy.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+delete_png = configure_file(
+ input : join_paths('png', 'delete.png'),
+ output : 'delete.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+eye_off_png = configure_file(
+ input : join_paths('png', 'eye-off.png'),
+ output : 'eye-off.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+eye_png = configure_file(
+ input : join_paths('png', 'eye.png'),
+ output : 'eye.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
+save_png = configure_file(
+ input : join_paths('png', 'save.png'),
+ output : 'save.png',
+ copy: true,
+ install : true,
+ install_dir : inst_dir)
if not use_fontconfig.enabled()
- fira_sans_bold_ttf = configure_file(
- input : join_paths('ttf', 'FiraSans-Bold.ttf'),
+ fira_sans_bold_ttf = d2tk.get_variable('fira_sans_bold_ttf')
+ fira_code_bold_ttf = d2tk.get_variable('fira_code_bold_ttf')
+ fira_code_light_ttf = d2tk.get_variable('fira_code_light_ttf')
+ fira_code_medium_ttf = d2tk.get_variable('fira_code_medium_ttf')
+ fira_code_regular_ttf = d2tk.get_variable('fira_code_regular_ttf')
+
+ configure_file(
+ input : fira_sans_bold_ttf,
output : 'FiraSans:bold.ttf',
copy : true,
- install : false)
-
- fira_code_bold_ttf = configure_file(
- input : join_paths('ttf', 'FiraCode-Bold.ttf'),
+ install : true,
+ install_dir : inst_dir)
+ configure_file(
+ input : fira_code_bold_ttf,
output : 'FiraCode:bold.ttf',
copy : true,
- install : false)
-
- fira_code_light_ttf = configure_file(
- input : join_paths('ttf', 'FiraCode-Light.ttf'),
- output : 'FiraCode:light.ttf',
- copy : true,
- install : false)
-
- fira_code_medium_ttf = configure_file(
- input : join_paths('ttf', 'FiraCode-Medium.ttf'),
- output : 'FiraCode:medium.ttf',
- copy : true,
- install : false)
-
- fira_code_regular_ttf = configure_file(
- input : join_paths('ttf', 'FiraCode-Regular.ttf'),
- output : 'FiraCode:regular.ttf',
- copy : true,
- install : false)
-endif
-
-if build_examples
+ install : true,
+ install_dir : inst_dir)
configure_file(
- input : join_paths('example', 'libre-arrow-circle-right.png'),
- output : 'libre-arrow-circle-right.png',
+ input : fira_code_light_ttf,
+ output : 'FiraCode:light.ttf',
copy : true,
- install : false)
-
+ install : true,
+ install_dir : inst_dir)
configure_file(
- input : join_paths('example', 'libre-gui-folder.png'),
- output : 'libre-gui-folder.png',
+ input : fira_code_medium_ttf,
+ output : 'FiraCode:medium.ttf',
copy : true,
- install : false)
-
+ install : true,
+ install_dir : inst_dir)
configure_file(
- input : join_paths('example', 'libre-gui-file.png'),
- output : 'libre-gui-file.png',
+ input : fira_code_regular_ttf,
+ output : 'FiraCode:regular.ttf',
copy : true,
- install : false)
+ install : true,
+ install_dir : inst_dir)
endif
if build_tests
- test_core = executable('test.core', [test_core_srcs, lib_srcs],
- c_args : c_args,
- dependencies : deps,
- include_directories : inc_dir,
- install : false)
-
- test_base = executable('test.base', [test_base_srcs, lib_srcs],
- c_args : c_args,
- dependencies : deps,
- include_directories : inc_dir,
- install : false)
-
- test('Test core', test_core)
- test('Test base', test_base)
-
- if fc_list.found() and grep.found() and check_for_font.found()
- test('FiraSans-Bold.ttf', check_for_font, args : ['FiraSans-Bold.ttf'])
- test('FiraCode-Light.ttf', check_for_font, args : ['FiraCode-Light.tt'])
- test('FiraCode-Regular.ttf', check_for_font, args : ['FiraCode-Regular.ttf'])
- test('FiraCode-Medium.ttf', check_for_font, args : ['FiraCode-Medium.ttf'])
- test('FiraCode-Bold.ttf', check_for_font, args : ['FiraCode-Bold.ttf'])
+ if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl, ui_ttl, pset_ttl])
endif
-endif
-if build_doc
- run_command('rm', '-rf', 'doc')
+ if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-M', 'pack',
+ '-E', 'warn',
+ '-I', join_paths(build_root, ''),
+ 'http://open-music-kontrollers.ch/lv2/mephisto#audio_1x1',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#audio_2x2',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#audio_4x4',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#audio_8x8',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#cv_1x1',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#cv_2x2',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#cv_4x4',
+ 'http://open-music-kontrollers.ch/lv2/mephisto#cv_8x8'
+ ])
+ endif
- run_command(robodoc,
- '--src', './d2tk',
- '--doc', 'doc',
- '--multidoc', '--troff', '--nosort', '--nodesc', '--cmode',
- '--compress', 'gzip')
+ if faust.found()
+ test('FAUST bank-filter_through', faust, args : [
+ join_paths(source_root, 'bank-filter_through.dsp')
+ ])
+ test('FAUST bank-filter_gain', faust, args : [
+ join_paths(source_root, 'bank-filter_gain.dsp')
+ ])
+ test('FAUST bank-time_lfo', faust, args : [
+ join_paths(source_root, 'bank-time_lfo.dsp')
+ ])
+ test('FAUST bank-instrument_osc', faust, args : [
+ join_paths(source_root, 'bank-instrument_osc.dsp')
+ ])
+ test('FAUST bank-analyzer_vu-meter', faust, args : [
+ join_paths(source_root, 'bank-analyzer_vu-meter.dsp')
+ ])
+ endif
endif
diff --git a/meson_options.txt b/meson_options.txt
index 242cbe0..016e54b 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,47 +1,25 @@
option('build-debug-overlay',
type : 'boolean',
- value : false,
- yield : true)
-option('build-examples',
- type : 'boolean',
- value : false,
- yield : true)
+ value : false)
option('build-tests',
type : 'boolean',
- value : true,
- yield : true)
-option('build-doc',
- type : 'boolean',
- value : false,
- yield : true)
+ value : true)
option('use-backend-cairo',
type : 'feature',
- value : 'disabled',
- yield : true)
+ value : 'disabled')
option('use-backend-nanovg',
type : 'feature',
- value : 'disabled',
- yield : true)
+ value : 'enabled')
-option('use-frontend-fbdev',
- type : 'feature',
- value : 'disabled',
- yield : true)
option('use-frontend-pugl',
type : 'feature',
- value : 'disabled',
- yield : true)
-option('use-frontend-glfw',
- type : 'feature',
- value : 'disabled',
- yield : true)
+ value : 'enabled')
-option('use-evdev',
- type : 'feature',
- value : 'disabled',
- yield : true)
option('use-fontconfig',
type : 'feature',
- value : 'disabled',
- yield : true)
+ value : 'enabled')
+
+option('lv2libdir',
+ type : 'string',
+ value : 'lib/lv2')
diff --git a/png/alert-triangle.png b/png/alert-triangle.png
new file mode 100644
index 0000000..2630c48
--- /dev/null
+++ b/png/alert-triangle.png
Binary files differ
diff --git a/png/clipboard.png b/png/clipboard.png
new file mode 100644
index 0000000..110e286
--- /dev/null
+++ b/png/clipboard.png
Binary files differ
diff --git a/png/copy.png b/png/copy.png
new file mode 100644
index 0000000..0991cf0
--- /dev/null
+++ b/png/copy.png
Binary files differ
diff --git a/png/delete.png b/png/delete.png
new file mode 100644
index 0000000..91c23af
--- /dev/null
+++ b/png/delete.png
Binary files differ
diff --git a/png/eye-off.png b/png/eye-off.png
new file mode 100644
index 0000000..a87f89d
--- /dev/null
+++ b/png/eye-off.png
Binary files differ
diff --git a/png/eye.png b/png/eye.png
new file mode 100644
index 0000000..cad96e4
--- /dev/null
+++ b/png/eye.png
Binary files differ
diff --git a/png/save.png b/png/save.png
new file mode 100644
index 0000000..2ad2fee
--- /dev/null
+++ b/png/save.png
Binary files differ
diff --git a/presets.ttl.in b/presets.ttl.in
new file mode 100644
index 0000000..5ed7744
--- /dev/null
+++ b/presets.ttl.in
@@ -0,0 +1,141 @@
+# Copyright (c) 2019-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 doap: <http://usefulinc.com/ns/doap#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix pset: <http://lv2plug.in/ns/ext/presets#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@prefix mephisto: <http://open-music-kontrollers.ch/lv2/mephisto#> .
+
+mephisto:bank-filter_through
+ a pset:Preset ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ state:state [
+ mephisto:code """@BANK-FILTER_THROUGH@""" ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:bank-filter_gain
+ a pset:Preset ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ state:state [
+ mephisto:code """@BANK-FILTER_GAIN@""" ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:bank-time_lfo
+ a pset:Preset ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ state:state [
+ mephisto:code """@BANK-TIME_LFO@""" ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:bank-instrument_osc
+ a pset:Preset ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ state:state [
+ mephisto:code """@BANK-INSTRUMENT_OSC@""" ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
+
+mephisto:bank-analyzer_vu-meter
+ a pset:Preset ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ state:state [
+ mephisto:code """@BANK-ANALYZER_VU-METER@""" ;
+ mephisto:control_1 "0.0"^^xsd:float ;
+ mephisto:control_2 "0.0"^^xsd:float ;
+ mephisto:control_3 "0.0"^^xsd:float ;
+ mephisto:control_4 "0.0"^^xsd:float ;
+ mephisto:control_5 "0.0"^^xsd:float ;
+ mephisto:control_6 "0.0"^^xsd:float ;
+ mephisto:control_7 "0.0"^^xsd:float ;
+ mephisto:control_8 "0.0"^^xsd:float ;
+ mephisto:control_9 "0.0"^^xsd:float ;
+ mephisto:control_10 "0.0"^^xsd:float ;
+ mephisto:control_11 "0.0"^^xsd:float ;
+ mephisto:control_12 "0.0"^^xsd:float ;
+ mephisto:control_13 "0.0"^^xsd:float ;
+ mephisto:control_14 "0.0"^^xsd:float ;
+ mephisto:control_15 "0.0"^^xsd:float ;
+ mephisto:control_16 "0.0"^^xsd:float ;
+ ] .
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
index 461d52a..a358fa4 100644
--- a/screenshots/screenshot_1.png
+++ b/screenshots/screenshot_1.png
Binary files differ
diff --git a/ser_atom.lv2/.gitlab-ci.yml b/ser_atom.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..9846434
--- /dev/null
+++ b/ser_atom.lv2/.gitlab-ci.yml
@@ -0,0 +1,60 @@
+stages:
+ - test
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "ser_atom.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: test
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+
+.test_template: &test_definition
+ <<: *common_definition
+ script:
+ - meson --cross-file "${CI_BUILD_NAME}" build
+ - ninja -C build
+ - cd build
+ - meson test --verbose --wrap "${CI_BUILD_NAME}.wrap"
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *test_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *test_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *test_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
diff --git a/ser_atom.lv2/COPYING b/ser_atom.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/ser_atom.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/ser_atom.lv2/README.md b/ser_atom.lv2/README.md
new file mode 100644
index 0000000..00e0e03
--- /dev/null
+++ b/ser_atom.lv2/README.md
@@ -0,0 +1,33 @@
+# ser_atom.lv2
+
+## General purpose LV2 atom forge serializer
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/ser_atom.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/ser_atom.lv2/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lv2/ser_atom.lv2
+ cd ser_atom.lv2
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### License
+
+Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/ser_atom.lv2/VERSION b/ser_atom.lv2/VERSION
new file mode 100644
index 0000000..17e51c3
--- /dev/null
+++ b/ser_atom.lv2/VERSION
@@ -0,0 +1 @@
+0.1.1
diff --git a/ser_atom.lv2/meson.build b/ser_atom.lv2/meson.build
new file mode 100644
index 0000000..16943c1
--- /dev/null
+++ b/ser_atom.lv2/meson.build
@@ -0,0 +1,26 @@
+project('ser_atom.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=true',
+ 'b_lto=true',
+ 'c_std=c11'])
+
+version = run_command('cat', 'VERSION').stdout().strip()
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+lv2_dep = dependency('lv2')
+deps = [lv2_dep]
+
+c_args = []
+
+ser_atom_test = executable('ser_atom_test',
+ join_paths('test', 'ser_atom_test.c'),
+ c_args : c_args,
+ dependencies : deps,
+ install : false)
+
+test('Test', ser_atom_test)
diff --git a/ser_atom.lv2/ser_atom.lv2/ser_atom.h b/ser_atom.lv2/ser_atom.lv2/ser_atom.h
new file mode 100644
index 0000000..7357e65
--- /dev/null
+++ b/ser_atom.lv2/ser_atom.lv2/ser_atom.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _SER_ATOM_H
+#define _SER_ATOM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+
+#ifndef SER_ATOM_API
+# define SER_ATOM_API static
+#endif
+
+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;
+
+SER_ATOM_API int
+ser_atom_init(ser_atom_t *ser);
+
+SER_ATOM_API int
+ser_atom_funcs(ser_atom_t *ser, ser_atom_realloc_t realloc,
+ ser_atom_free_t free, void *data);
+
+SER_ATOM_API int
+ser_atom_reset(ser_atom_t *ser, LV2_Atom_Forge *forge);
+
+SER_ATOM_API LV2_Atom *
+ser_atom_get(ser_atom_t *ser);
+
+SER_ATOM_API int
+ser_atom_deinit(ser_atom_t *ser);
+
+#ifdef SER_ATOM_IMPLEMENTATION
+
+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);
+}
+
+SER_ATOM_API 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;
+}
+
+SER_ATOM_API 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);
+}
+
+SER_ATOM_API 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;
+}
+
+SER_ATOM_API LV2_Atom *
+ser_atom_get(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return NULL;
+ }
+
+ return ser->atom;
+}
+
+SER_ATOM_API 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;
+}
+
+#endif // SER_ATOM_IMPLEMENTATION
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_SER_ATOM_H
diff --git a/ser_atom.lv2/test/ser_atom_test.c b/ser_atom.lv2/test/ser_atom_test.c
new file mode 100644
index 0000000..00898f0
--- /dev/null
+++ b/ser_atom.lv2/test/ser_atom_test.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#define SER_ATOM_IMPLEMENTATION
+#include <ser_atom.lv2/ser_atom.h>
+
+#define MAX_ITEMS 0x100000
+
+static uint32_t
+_map(void *data, const char *uri)
+{
+ (void)uri;
+
+ uint32_t *urid = data;
+
+ return ++(*urid);
+}
+
+static void *
+_realloc(void *data, void *buf, size_t size)
+{
+ (void)data;
+ (void)buf;
+ (void)size;
+
+ return NULL;
+}
+
+static void
+_free(void *data, void *buf)
+{
+ (void)data;
+ (void)buf;
+}
+
+int
+main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ ser_atom_t ser;
+ LV2_Atom_Forge forge;
+
+ uint32_t urid = 0;
+ LV2_URID_Map map = {
+ .handle = &urid,
+ .map = _map
+ };
+
+ lv2_atom_forge_init(&forge, &map);
+
+ assert(ser_atom_init(NULL) != 0);
+ assert(ser_atom_init(&ser) == 0);
+
+ assert(ser.size == 0);
+ assert(ser.offset == 0);
+ assert(ser.buf == NULL);
+
+ assert(ser_atom_funcs(NULL, NULL, NULL, NULL) != 0);
+ assert(ser_atom_funcs(&ser, _realloc, _free, NULL) == 0);
+ assert(ser_atom_reset(&ser, &forge) == 0);
+ assert(lv2_atom_forge_bool(&forge, true) == 0);
+ _free(NULL, NULL);
+
+ assert(ser_atom_init(&ser) == 0);
+
+ assert(ser.realloc == _ser_atom_realloc);
+ assert(ser.free == _ser_atom_free);
+ assert(ser.data == NULL);
+
+ assert(ser.size == 0);
+ assert(ser.offset == 0);
+ assert(ser.buf == NULL);
+
+ assert(ser_atom_reset(NULL, &forge) != 0);
+ assert(ser_atom_reset(&ser, NULL) != 0);
+ assert(ser_atom_reset(NULL, NULL) != 0);
+ assert(ser_atom_reset(&ser, &forge) == 0);
+
+ LV2_Atom_Forge_Frame frame;
+ int64_t i;
+
+ assert(lv2_atom_forge_tuple(&forge, &frame) != 0);
+ for(i = 0; i < MAX_ITEMS; i++)
+ {
+ assert(lv2_atom_forge_long(&forge, i) != 0);
+ }
+ lv2_atom_forge_pop(&forge, &frame);
+
+ i = 0;
+ LV2_ATOM_TUPLE_FOREACH((const LV2_Atom_Tuple *)ser.buf, atom)
+ {
+ assert(atom->size == sizeof(int64_t));
+ assert(atom->type == forge.Long);
+
+ const int64_t *i64 = LV2_ATOM_BODY_CONST(atom);
+ assert(*i64 == i++);
+ }
+
+ const size_t tot_size = MAX_ITEMS*sizeof(LV2_Atom_Long) + sizeof(LV2_Atom_Tuple);
+ assert(ser.offset == tot_size);
+ assert(ser.size >= tot_size);
+
+ assert(lv2_atom_forge_deref(&forge, 0) == NULL);
+
+ assert(ser_atom_get(NULL) == NULL);
+ assert(ser_atom_get(&ser) == ser.atom);
+
+ assert(ser_atom_deinit(NULL) != 0);
+ assert(ser_atom_deinit(&ser) == 0);
+
+ assert(ser.size == 0);
+ assert(ser.offset == 0);
+ assert(ser.buf == NULL);
+
+ return 0;
+}
diff --git a/.gitignore b/subprojects/d2tk/.gitignore
index 651404e..651404e 100644
--- a/.gitignore
+++ b/subprojects/d2tk/.gitignore
diff --git a/subprojects/d2tk/.gitlab-ci.yml b/subprojects/d2tk/.gitlab-ci.yml
new file mode 100644
index 0000000..4847758
--- /dev/null
+++ b/subprojects/d2tk/.gitlab-ci.yml
@@ -0,0 +1,108 @@
+stages:
+ - build
+ - deploy
+
+# templates
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "d2tk"
+ 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
+ stage: build
+ script:
+ - meson --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
+
+.test_template: &test_definition
+ <<: *common_definition
+ stage: build
+ script:
+ - meson --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
+
+ - meson test -C build
+
+.analyze_template: &analyze_definition
+ <<: *common_definition
+ stage: build
+ script:
+ - meson --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
+
+ - meson test -C build
+ - meson test -C build --wrap=valgrind
+
+ - CC=clang CXX=clang++ meson --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
+ - ninja -C clang
+ - ninja -C clang test
+
+ - scan-build --status-bugs meson --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+ - scan-build --status-bugs ninja -C scanbuild test
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu:buster
+ <<: *analyze_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf:buster
+ <<: *test_definition
+
+# targets
+x86_64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev libevdev-dev libvterm-dev
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386 libvterm-dev:i386
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf libvterm-dev:armhf
+ <<: *arm_linux_definition
+
+aarch64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64 libvterm-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)/"
+
+pages:
+ stage: deploy
+ before_script:
+ - apt-get update -y
+ - apt-get install -y doxygen
+ script:
+ - doxygen
+ - cp -r doc/html public
+ artifacts:
+ paths:
+ - public/
diff --git a/subprojects/d2tk/COPYING b/subprojects/d2tk/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/subprojects/d2tk/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/Doxyfile b/subprojects/d2tk/Doxyfile
index 294d19d..294d19d 100644
--- a/Doxyfile
+++ b/subprojects/d2tk/Doxyfile
diff --git a/subprojects/d2tk/README.md b/subprojects/d2tk/README.md
new file mode 100644
index 0000000..51c9529
--- /dev/null
+++ b/subprojects/d2tk/README.md
@@ -0,0 +1,62 @@
+# d2tk
+
+## Data Driven Tool Kit
+
+A performant, dyamic, immediate-mode GUI tool kit in C which partially renders
+on-change only by massively hashing-and-cashing of vector drawing instructions
+and on-demand rendered sprites.
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lad/d2tk
+ cd d2tk
+ meson build
+ cd build
+ ninja -j4
+
+#### Pugl/NanoVG backend
+
+ ./d2tk.nanovg
+
+#### Pugl/Cairo backend
+
+ ./d2tk.cairo
+
+#### FBdev/Cairo backend
+
+ ./d2tk.fbdev
+
+### Screenshots
+
+![Screenshot 1](/screenshots/screenshot_1.png)
+
+![Screenshot 2](/screenshots/screenshot_2.png)
+
+![Screenshot 3](/screenshots/screenshot_3.png)
+
+![Screenshot 4](/screenshots/screenshot_4.png)
+
+![Screenshot 5](/screenshots/screenshot_5.png)
+
+![Screenshot 6](/screenshots/screenshot_6.png)
+
+![Screenshot 7](/screenshots/screenshot_7.png)
+
+![Screenshot 8](/screenshots/screenshot_8.png)
+
+### License
+
+Copyright (c) 2018-2019 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/subprojects/d2tk/VERSION b/subprojects/d2tk/VERSION
new file mode 100644
index 0000000..f25c9b0
--- /dev/null
+++ b/subprojects/d2tk/VERSION
@@ -0,0 +1 @@
+0.1.1253
diff --git a/check_for_font b/subprojects/d2tk/check_for_font
index befcbf4..befcbf4 100755
--- a/check_for_font
+++ b/subprojects/d2tk/check_for_font
diff --git a/d2tk/backend.h b/subprojects/d2tk/d2tk/backend.h
index 862f6d2..862f6d2 100644
--- a/d2tk/backend.h
+++ b/subprojects/d2tk/d2tk/backend.h
diff --git a/d2tk/base.h b/subprojects/d2tk/d2tk/base.h
index c37582c..c37582c 100644
--- a/d2tk/base.h
+++ b/subprojects/d2tk/d2tk/base.h
diff --git a/d2tk/config.h.in b/subprojects/d2tk/d2tk/config.h.in
index e8be923..e8be923 100644
--- a/d2tk/config.h.in
+++ b/subprojects/d2tk/d2tk/config.h.in
diff --git a/d2tk/core.h b/subprojects/d2tk/d2tk/core.h
index 265a06c..265a06c 100644
--- a/d2tk/core.h
+++ b/subprojects/d2tk/d2tk/core.h
diff --git a/d2tk/d2tk.h b/subprojects/d2tk/d2tk/d2tk.h
index 2d12a82..2d12a82 100644
--- a/d2tk/d2tk.h
+++ b/subprojects/d2tk/d2tk/d2tk.h
diff --git a/d2tk/frontend.h b/subprojects/d2tk/d2tk/frontend.h
index 716bad5..716bad5 100644
--- a/d2tk/frontend.h
+++ b/subprojects/d2tk/d2tk/frontend.h
diff --git a/d2tk/frontend_fbdev.h b/subprojects/d2tk/d2tk/frontend_fbdev.h
index 969c644..969c644 100644
--- a/d2tk/frontend_fbdev.h
+++ b/subprojects/d2tk/d2tk/frontend_fbdev.h
diff --git a/d2tk/frontend_glfw.h b/subprojects/d2tk/d2tk/frontend_glfw.h
index b7b934f..b7b934f 100644
--- a/d2tk/frontend_glfw.h
+++ b/subprojects/d2tk/d2tk/frontend_glfw.h
diff --git a/d2tk/frontend_pugl.h b/subprojects/d2tk/d2tk/frontend_pugl.h
index 4be0b86..4be0b86 100644
--- a/d2tk/frontend_pugl.h
+++ b/subprojects/d2tk/d2tk/frontend_pugl.h
diff --git a/d2tk/hash.h b/subprojects/d2tk/d2tk/hash.h
index a6cf614..a6cf614 100644
--- a/d2tk/hash.h
+++ b/subprojects/d2tk/d2tk/hash.h
diff --git a/d2tk/util.h b/subprojects/d2tk/d2tk/util.h
index f8eebf8..f8eebf8 100644
--- a/d2tk/util.h
+++ b/subprojects/d2tk/d2tk/util.h
diff --git a/example/custom_cairo.c b/subprojects/d2tk/example/custom_cairo.c
index 56904ed..56904ed 100644
--- a/example/custom_cairo.c
+++ b/subprojects/d2tk/example/custom_cairo.c
diff --git a/example/custom_nanovg.c b/subprojects/d2tk/example/custom_nanovg.c
index 7bd2db3..7bd2db3 100644
--- a/example/custom_nanovg.c
+++ b/subprojects/d2tk/example/custom_nanovg.c
diff --git a/example/d2tk_fbdev.c b/subprojects/d2tk/example/d2tk_fbdev.c
index 47baed8..47baed8 100644
--- a/example/d2tk_fbdev.c
+++ b/subprojects/d2tk/example/d2tk_fbdev.c
diff --git a/example/d2tk_glfw.c b/subprojects/d2tk/example/d2tk_glfw.c
index 31ce905..31ce905 100644
--- a/example/d2tk_glfw.c
+++ b/subprojects/d2tk/example/d2tk_glfw.c
diff --git a/example/d2tk_pugl.c b/subprojects/d2tk/example/d2tk_pugl.c
index 4132547..4132547 100644
--- a/example/d2tk_pugl.c
+++ b/subprojects/d2tk/example/d2tk_pugl.c
diff --git a/example/example.c b/subprojects/d2tk/example/example.c
index be8ed79..be8ed79 100644
--- a/example/example.c
+++ b/subprojects/d2tk/example/example.c
diff --git a/example/example.h b/subprojects/d2tk/example/example.h
index bbb013b..bbb013b 100644
--- a/example/example.h
+++ b/subprojects/d2tk/example/example.h
diff --git a/example/libre-arrow-circle-right.png b/subprojects/d2tk/example/libre-arrow-circle-right.png
index 21c8013..21c8013 100644
--- a/example/libre-arrow-circle-right.png
+++ b/subprojects/d2tk/example/libre-arrow-circle-right.png
Binary files differ
diff --git a/example/libre-gui-file.png b/subprojects/d2tk/example/libre-gui-file.png
index 1c6d984..1c6d984 100644
--- a/example/libre-gui-file.png
+++ b/subprojects/d2tk/example/libre-gui-file.png
Binary files differ
diff --git a/example/libre-gui-folder.png b/subprojects/d2tk/example/libre-gui-folder.png
index 6960606..6960606 100644
--- a/example/libre-gui-folder.png
+++ b/subprojects/d2tk/example/libre-gui-folder.png
Binary files differ
diff --git a/linenoise/.gitignore b/subprojects/d2tk/linenoise/.gitignore
index 7ab7825..7ab7825 100644
--- a/linenoise/.gitignore
+++ b/subprojects/d2tk/linenoise/.gitignore
diff --git a/linenoise/LICENSE b/subprojects/d2tk/linenoise/LICENSE
index 18e8148..18e8148 100644
--- a/linenoise/LICENSE
+++ b/subprojects/d2tk/linenoise/LICENSE
diff --git a/linenoise/Makefile b/subprojects/d2tk/linenoise/Makefile
index 76bcc4e..76bcc4e 100644
--- a/linenoise/Makefile
+++ b/subprojects/d2tk/linenoise/Makefile
diff --git a/linenoise/README.markdown b/subprojects/d2tk/linenoise/README.markdown
index 1afea2a..1afea2a 100644
--- a/linenoise/README.markdown
+++ b/subprojects/d2tk/linenoise/README.markdown
diff --git a/linenoise/encodings/tools/EastAsianWidth.txt b/subprojects/d2tk/linenoise/encodings/tools/EastAsianWidth.txt
index b43aec9..b43aec9 100644
--- a/linenoise/encodings/tools/EastAsianWidth.txt
+++ b/subprojects/d2tk/linenoise/encodings/tools/EastAsianWidth.txt
diff --git a/linenoise/encodings/tools/UnicodeData.txt b/subprojects/d2tk/linenoise/encodings/tools/UnicodeData.txt
index e22f967..e22f967 100644
--- a/linenoise/encodings/tools/UnicodeData.txt
+++ b/subprojects/d2tk/linenoise/encodings/tools/UnicodeData.txt
diff --git a/linenoise/encodings/tools/generate_combining_char_table.rb b/subprojects/d2tk/linenoise/encodings/tools/generate_combining_char_table.rb
index f454051..f454051 100644
--- a/linenoise/encodings/tools/generate_combining_char_table.rb
+++ b/subprojects/d2tk/linenoise/encodings/tools/generate_combining_char_table.rb
diff --git a/linenoise/encodings/tools/generate_wide_char_table.rb b/subprojects/d2tk/linenoise/encodings/tools/generate_wide_char_table.rb
index 8337ca6..8337ca6 100644
--- a/linenoise/encodings/tools/generate_wide_char_table.rb
+++ b/subprojects/d2tk/linenoise/encodings/tools/generate_wide_char_table.rb
diff --git a/linenoise/encodings/utf8.c b/subprojects/d2tk/linenoise/encodings/utf8.c
index 5cd7767..5cd7767 100755
--- a/linenoise/encodings/utf8.c
+++ b/subprojects/d2tk/linenoise/encodings/utf8.c
diff --git a/linenoise/encodings/utf8.h b/subprojects/d2tk/linenoise/encodings/utf8.h
index d401bc8..d401bc8 100755
--- a/linenoise/encodings/utf8.h
+++ b/subprojects/d2tk/linenoise/encodings/utf8.h
diff --git a/linenoise/example.c b/subprojects/d2tk/linenoise/example.c
index adbf1bd..adbf1bd 100755
--- a/linenoise/example.c
+++ b/subprojects/d2tk/linenoise/example.c
diff --git a/linenoise/linenoise.c b/subprojects/d2tk/linenoise/linenoise.c
index aac26fa..aac26fa 100755
--- a/linenoise/linenoise.c
+++ b/subprojects/d2tk/linenoise/linenoise.c
diff --git a/linenoise/linenoise.h b/subprojects/d2tk/linenoise/linenoise.h
index 643ba10..643ba10 100755
--- a/linenoise/linenoise.h
+++ b/subprojects/d2tk/linenoise/linenoise.h
diff --git a/subprojects/d2tk/meson.build b/subprojects/d2tk/meson.build
new file mode 100644
index 0000000..58a2d69
--- /dev/null
+++ b/subprojects/d2tk/meson.build
@@ -0,0 +1,433 @@
+project('d2tk', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=gnu11'])
+
+static_link = false #meson.is_cross_build()
+
+build_debug_overlay = get_option('build-debug-overlay')
+build_examples = get_option('build-examples')
+build_tests = get_option('build-tests')
+build_doc = get_option('build-doc')
+
+use_backend_cairo = get_option('use-backend-cairo')
+use_backend_nanovg = get_option('use-backend-nanovg')
+use_frontend_fbdev = get_option('use-frontend-fbdev')
+use_frontend_pugl = get_option('use-frontend-pugl')
+use_frontend_glfw = get_option('use-frontend-glfw')
+
+use_evdev = get_option('use-evdev')
+use_fontconfig = get_option('use-fontconfig')
+
+grep = find_program('grep',
+ native : true,
+ required : use_fontconfig)
+fc_list = find_program('fc-list',
+ native : true,
+ required : use_fontconfig)
+check_for_font = find_program('check_for_font',
+ native : true,
+ required : use_fontconfig)
+robodoc = find_program('robodoc',
+ native : true,
+ required : build_doc)
+
+cc = meson.get_compiler('c')
+
+# mandatory dependencies
+m_dep = cc.find_library('m')
+thread_dep = dependency('threads')
+
+# dependencies for backend_cairo/frontend_pugl
+freetype_dep = dependency('freetype2',
+ version : '>=18.0.0',
+ static : static_link,
+ required : use_backend_cairo)
+pixman_dep = dependency('pixman-1',
+ version : '>=0.34.0',
+ static : static_link,
+ required : use_backend_cairo)
+cairo_dep = dependency('cairo',
+ version : '>=1.14.0',
+ static : static_link,
+ required : use_backend_cairo)
+cairo_deps = [freetype_dep, pixman_dep, cairo_dep]
+if use_frontend_pugl.enabled()
+ cairo_xlib_dep = dependency('cairo-xlib',
+ version : '>=1.14.0',
+ static : static_link,
+ required : use_backend_cairo)
+endif
+
+# dependencies for frontend_fbdev
+input_dep = dependency('libinput',
+ version : '>=1.6.0',
+ static : static_link,
+ required : use_frontend_fbdev)
+udev_dep = dependency('libudev',
+ static : static_link,
+ required : use_frontend_fbdev)
+evdev_dep = dependency('libevdev',
+ version : '>=1.5.0',
+ static : static_link,
+ required : use_frontend_fbdev)
+
+# dependencies for backend_nanovg
+glu_dep = dependency('glu',
+ version : '>=9.0.0',
+ static : static_link,
+ required : use_backend_nanovg)
+glew_dep = dependency('glew',
+ version : '>=2.0.0',
+ static : static_link,
+ required : use_backend_nanovg)
+if use_frontend_glfw.enabled()
+ glfw_dep = dependency('glfw3',
+ version : '>=3.3.0',
+ static : static_link,
+ required : use_backend_nanovg)
+endif
+
+# optional dependencies
+util_dep = cc.find_library('util')
+vterm_dep = dependency('vterm',
+ version : '>=0.1',
+ static : static_link)
+if not use_frontend_fbdev.enabled()
+ evdev_dep = dependency('libevdev',
+ version : '>=1.5.0',
+ static : static_link,
+ required : use_evdev)
+endif
+fontconfig_dep = dependency('fontconfig',
+ version : '>=2.0.0',
+ static : static_link,
+ required : use_fontconfig)
+
+deps = [m_dep, thread_dep, evdev_dep, util_dep, vterm_dep, fontconfig_dep]
+links = []
+
+pugl_inc = include_directories(join_paths('pugl', 'include'))
+nanovg_inc = include_directories(join_paths('nanovg', 'src'))
+linenoise_inc = include_directories('linenoise')
+inc_dir = [pugl_inc, nanovg_inc, linenoise_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('-D_GNU_SOURCE', language : 'c')
+
+if build_debug_overlay
+ conf_data.set('D2TK_DEBUG', 1)
+else
+ conf_data.set('D2TK_DEBUG', 0)
+endif
+
+lib_srcs = [
+ join_paths('src', 'hash.c'),
+ join_paths('src', 'core.c'),
+ join_paths('src', 'base.c'),
+ join_paths('src', 'base_table.c'),
+ join_paths('src', 'base_frame.c'),
+ join_paths('src', 'base_layout.c'),
+ join_paths('src', 'base_scrollbar.c'),
+ join_paths('src', 'base_pane.c'),
+ join_paths('src', 'base_cursor.c'),
+ join_paths('src', 'base_button.c'),
+ join_paths('src', 'base_image.c'),
+ join_paths('src', 'base_bitmap.c'),
+ join_paths('src', 'base_custom.c'),
+ join_paths('src', 'base_meter.c'),
+ join_paths('src', 'base_combo.c'),
+ join_paths('src', 'base_textfield.c'),
+ join_paths('src', 'base_label.c'),
+ join_paths('src', 'base_separator.c'),
+ join_paths('src', 'base_tooltip.c'),
+ join_paths('src', 'base_link.c'),
+ join_paths('src', 'base_dial.c'),
+ join_paths('src', 'base_spinner.c'),
+ join_paths('src', 'base_bar.c'),
+ join_paths('src', 'base_wave.c'),
+ join_paths('src', 'base_flowmatrix.c'),
+ join_paths('src', 'base_pty.c'),
+ join_paths('src', 'base_lineedit.c'),
+ join_paths('src', 'util_spawn.c'),
+ join_paths('linenoise', 'linenoise.c'),
+ join_paths('linenoise', 'encodings', 'utf8.c')
+]
+
+if use_evdev.enabled()
+ conf_data.set('D2TK_EVDEV', 1)
+ lib_srcs += join_paths('src', 'base_vkb.c')
+else
+ conf_data.set('D2TK_EVDEV', 0)
+endif
+
+if input_dep.found() and input_dep.version().version_compare('>=1.15.0')
+ conf_data.set('D2TK_INPUT_1_15', 1)
+else
+ conf_data.set('D2TK_INPUT_1_15', 0)
+endif
+
+if use_fontconfig.enabled()
+ conf_data.set('D2TK_FONTCONFIG', 1)
+else
+ conf_data.set('D2TK_FONTCONFIG', 0)
+endif
+
+example_srcs = [
+ join_paths('example', 'example.c')
+]
+
+example_cairo_srcs = [
+ join_paths('example', 'custom_cairo.c')
+]
+
+example_nanovg_srcs = [
+ join_paths('example', 'custom_nanovg.c')
+]
+
+example_pugl_srcs = [
+ join_paths('example', 'd2tk_pugl.c')
+]
+
+example_fbdev_srcs = [
+ join_paths('example', 'd2tk_fbdev.c')
+]
+
+example_glfw_srcs = [
+ join_paths('example', 'd2tk_glfw.c')
+]
+
+pugl_srcs = [
+ join_paths('src', 'frontend_pugl.c'),
+ join_paths('pugl', 'src', 'implementation.c')
+]
+
+pugl_gl_srcs = []
+
+pugl_cairo_srcs = []
+
+nanovg_srcs = [
+ join_paths('nanovg', 'src', 'nanovg.c'),
+ join_paths('src', 'backend_nanovg.c')
+]
+
+cairo_srcs = [
+ join_paths('src', 'backend_cairo.c')
+]
+
+fbdev_srcs = [
+ join_paths('src', 'frontend_fbdev.c')
+]
+
+glfw_srcs = [
+ join_paths('src', 'frontend_glfw.c')
+]
+
+test_core_srcs = [
+ join_paths('test', 'core.c'),
+ join_paths('test', 'mock.c')
+]
+
+test_base_srcs = [
+ join_paths('test', 'base.c'),
+ join_paths('test', 'mock.c')
+]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('opengl32', required : use_frontend_pugl)
+ deps += cc.find_library('gdi32', required : use_frontend_pugl)
+ deps += cc.find_library('ws2_32', required : true)
+ pugl_srcs += join_paths('pugl', 'src', 'win.c')
+ pugl_gl_srcs += join_paths('pugl', 'src', 'win_gl.c')
+ pugl_cairo_srcs += join_paths('pugl', 'src', 'win_cairo.c')
+elif host_machine.system() == 'darwin'
+ add_languages('objc')
+ links += ['-framework', 'OpenGL']
+ links += ['-framework', 'Cocoa']
+ pugl_srcs += join_paths('pugl','src', 'mac.m')
+ pugl_gl_srcs += join_paths('pugl', 'src', 'mac_gl.m')
+ pugl_cairo_srcs += join_paths('pugl', 'src', 'mac_cairo.m')
+else
+ deps += dependency('gl', required : use_frontend_pugl)
+ deps += dependency('x11', version : '>=1.6.0', required : use_frontend_pugl)
+ deps += dependency('xext', version : '>=1.3.0', required : use_frontend_pugl)
+ pugl_srcs += join_paths('pugl', 'src', 'x11.c')
+ pugl_srcs += join_paths('pugl', 'src', 'x11_stub.c')
+ pugl_gl_srcs += join_paths('pugl', 'src', 'x11_gl.c')
+ pugl_cairo_srcs += join_paths('pugl', 'src', 'x11_cairo.c')
+endif
+
+if use_backend_cairo.enabled()
+ if use_frontend_pugl.enabled()
+ d2tk_cairo = declare_dependency(
+ compile_args : ['-DPUGL_HAVE_CAIRO', '-DPUGL_STATIC'],
+ include_directories : inc_dir,
+ dependencies : [deps, cairo_deps, cairo_xlib_dep],
+ link_args : links,
+ sources : [lib_srcs, cairo_srcs, pugl_srcs, pugl_cairo_srcs])
+
+ if build_examples
+ executable('d2tk.cairo', [example_srcs, example_pugl_srcs, example_cairo_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: d2tk_cairo,
+ install : false)
+ endif
+ endif
+
+ if use_frontend_fbdev.enabled()
+ d2tk_fbdev = declare_dependency(
+ include_directories : inc_dir,
+ dependencies : [deps, cairo_deps, input_dep, udev_dep, evdev_dep],
+ link_args : links,
+ sources : [lib_srcs, cairo_srcs, fbdev_srcs])
+
+ if build_examples
+ executable('d2tk.fbdev', [example_srcs, example_fbdev_srcs, example_cairo_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: d2tk_fbdev,
+ install : false)
+ endif
+ endif
+endif
+
+if use_backend_nanovg.enabled()
+ if use_frontend_pugl.enabled()
+ d2tk_nanovg = declare_dependency(
+ compile_args : ['-DPUGL_STATIC'],
+ include_directories : inc_dir,
+ dependencies : [deps, glu_dep, glew_dep],
+ link_args : links,
+ sources : [lib_srcs, nanovg_srcs, pugl_srcs, pugl_gl_srcs])
+
+ if build_examples
+ executable('d2tk.nanovg', [example_srcs, example_pugl_srcs, example_nanovg_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: d2tk_nanovg,
+ install : false)
+ endif
+ endif
+
+ if use_frontend_glfw.enabled()
+ d2tk_glfw = declare_dependency(
+ include_directories : inc_dir,
+ dependencies : [deps, glfw_dep, glew_dep],
+ link_args : links,
+ sources : [lib_srcs, nanovg_srcs, glfw_srcs])
+
+ if build_examples
+ executable('d2tk.glfw', [example_srcs, example_glfw_srcs, example_nanovg_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: d2tk_glfw,
+ install : false)
+ endif
+ endif
+endif
+
+config_h = configure_file(
+ input : join_paths('d2tk', 'config.h.in'),
+ output : 'config.h',
+ configuration : conf_data,
+ install : false)
+
+if not use_fontconfig.enabled()
+ fira_sans_bold_ttf = configure_file(
+ input : join_paths('ttf', 'FiraSans-Bold.ttf'),
+ output : 'FiraSans:bold.ttf',
+ copy : true,
+ install : false)
+
+ fira_code_bold_ttf = configure_file(
+ input : join_paths('ttf', 'FiraCode-Bold.ttf'),
+ output : 'FiraCode:bold.ttf',
+ copy : true,
+ install : false)
+
+ fira_code_light_ttf = configure_file(
+ input : join_paths('ttf', 'FiraCode-Light.ttf'),
+ output : 'FiraCode:light.ttf',
+ copy : true,
+ install : false)
+
+ fira_code_medium_ttf = configure_file(
+ input : join_paths('ttf', 'FiraCode-Medium.ttf'),
+ output : 'FiraCode:medium.ttf',
+ copy : true,
+ install : false)
+
+ fira_code_regular_ttf = configure_file(
+ input : join_paths('ttf', 'FiraCode-Regular.ttf'),
+ output : 'FiraCode:regular.ttf',
+ copy : true,
+ install : false)
+endif
+
+if build_examples
+ configure_file(
+ input : join_paths('example', 'libre-arrow-circle-right.png'),
+ output : 'libre-arrow-circle-right.png',
+ copy : true,
+ install : false)
+
+ configure_file(
+ input : join_paths('example', 'libre-gui-folder.png'),
+ output : 'libre-gui-folder.png',
+ copy : true,
+ install : false)
+
+ configure_file(
+ input : join_paths('example', 'libre-gui-file.png'),
+ output : 'libre-gui-file.png',
+ copy : true,
+ install : false)
+endif
+
+if build_tests
+ test_core = executable('test.core', [test_core_srcs, lib_srcs],
+ c_args : c_args,
+ dependencies : deps,
+ include_directories : inc_dir,
+ install : false)
+
+ test_base = executable('test.base', [test_base_srcs, lib_srcs],
+ c_args : c_args,
+ dependencies : deps,
+ include_directories : inc_dir,
+ install : false)
+
+ test('Test core', test_core)
+ test('Test base', test_base)
+
+ if fc_list.found() and grep.found() and check_for_font.found()
+ test('FiraSans-Bold.ttf', check_for_font, args : ['FiraSans-Bold.ttf'])
+ test('FiraCode-Light.ttf', check_for_font, args : ['FiraCode-Light.tt'])
+ test('FiraCode-Regular.ttf', check_for_font, args : ['FiraCode-Regular.ttf'])
+ test('FiraCode-Medium.ttf', check_for_font, args : ['FiraCode-Medium.ttf'])
+ test('FiraCode-Bold.ttf', check_for_font, args : ['FiraCode-Bold.ttf'])
+ endif
+endif
+
+if build_doc
+ run_command('rm', '-rf', 'doc')
+
+ run_command(robodoc,
+ '--src', './d2tk',
+ '--doc', 'doc',
+ '--multidoc', '--troff', '--nosort', '--nodesc', '--cmode',
+ '--compress', 'gzip')
+endif
diff --git a/subprojects/d2tk/meson_options.txt b/subprojects/d2tk/meson_options.txt
new file mode 100644
index 0000000..242cbe0
--- /dev/null
+++ b/subprojects/d2tk/meson_options.txt
@@ -0,0 +1,47 @@
+option('build-debug-overlay',
+ type : 'boolean',
+ value : false,
+ yield : true)
+option('build-examples',
+ type : 'boolean',
+ value : false,
+ yield : true)
+option('build-tests',
+ type : 'boolean',
+ value : true,
+ yield : true)
+option('build-doc',
+ type : 'boolean',
+ value : false,
+ yield : true)
+
+option('use-backend-cairo',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+option('use-backend-nanovg',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+
+option('use-frontend-fbdev',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+option('use-frontend-pugl',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+option('use-frontend-glfw',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+
+option('use-evdev',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
+option('use-fontconfig',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
diff --git a/nanovg/.gitignore b/subprojects/d2tk/nanovg/.gitignore
index 70f534e..70f534e 100644
--- a/nanovg/.gitignore
+++ b/subprojects/d2tk/nanovg/.gitignore
diff --git a/nanovg/LICENSE.txt b/subprojects/d2tk/nanovg/LICENSE.txt
index 2a03a1a..2a03a1a 100644
--- a/nanovg/LICENSE.txt
+++ b/subprojects/d2tk/nanovg/LICENSE.txt
diff --git a/nanovg/README.md b/subprojects/d2tk/nanovg/README.md
index 4f9709b..4f9709b 100644
--- a/nanovg/README.md
+++ b/subprojects/d2tk/nanovg/README.md
diff --git a/nanovg/example/LICENSE_OFL.txt b/subprojects/d2tk/nanovg/example/LICENSE_OFL.txt
index d952d62..d952d62 100644
--- a/nanovg/example/LICENSE_OFL.txt
+++ b/subprojects/d2tk/nanovg/example/LICENSE_OFL.txt
diff --git a/nanovg/example/NotoEmoji-Regular.ttf b/subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf
index 19b7bad..19b7bad 100644
--- a/nanovg/example/NotoEmoji-Regular.ttf
+++ b/subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf
Binary files differ
diff --git a/nanovg/example/Roboto-Bold.ttf b/subprojects/d2tk/nanovg/example/Roboto-Bold.ttf
index aaf374d..aaf374d 100755
--- a/nanovg/example/Roboto-Bold.ttf
+++ b/subprojects/d2tk/nanovg/example/Roboto-Bold.ttf
Binary files differ
diff --git a/nanovg/example/Roboto-Light.ttf b/subprojects/d2tk/nanovg/example/Roboto-Light.ttf
index 664e1b2..664e1b2 100755
--- a/nanovg/example/Roboto-Light.ttf
+++ b/subprojects/d2tk/nanovg/example/Roboto-Light.ttf
Binary files differ
diff --git a/nanovg/example/Roboto-Regular.ttf b/subprojects/d2tk/nanovg/example/Roboto-Regular.ttf
index 3e6e2e7..3e6e2e7 100755
--- a/nanovg/example/Roboto-Regular.ttf
+++ b/subprojects/d2tk/nanovg/example/Roboto-Regular.ttf
Binary files differ
diff --git a/nanovg/example/demo.c b/subprojects/d2tk/nanovg/example/demo.c
index cfad99e..cfad99e 100644
--- a/nanovg/example/demo.c
+++ b/subprojects/d2tk/nanovg/example/demo.c
diff --git a/nanovg/example/demo.h b/subprojects/d2tk/nanovg/example/demo.h
index aace449..aace449 100644
--- a/nanovg/example/demo.h
+++ b/subprojects/d2tk/nanovg/example/demo.h
diff --git a/nanovg/example/entypo.ttf b/subprojects/d2tk/nanovg/example/entypo.ttf
index fc305d2..fc305d2 100644
--- a/nanovg/example/entypo.ttf
+++ b/subprojects/d2tk/nanovg/example/entypo.ttf
Binary files differ
diff --git a/nanovg/example/example_fbo.c b/subprojects/d2tk/nanovg/example/example_fbo.c
index cff4ed2..cff4ed2 100644
--- a/nanovg/example/example_fbo.c
+++ b/subprojects/d2tk/nanovg/example/example_fbo.c
diff --git a/nanovg/example/example_gl2.c b/subprojects/d2tk/nanovg/example/example_gl2.c
index 7fd5621..7fd5621 100644
--- a/nanovg/example/example_gl2.c
+++ b/subprojects/d2tk/nanovg/example/example_gl2.c
diff --git a/nanovg/example/example_gl3.c b/subprojects/d2tk/nanovg/example/example_gl3.c
index 409a145..409a145 100644
--- a/nanovg/example/example_gl3.c
+++ b/subprojects/d2tk/nanovg/example/example_gl3.c
diff --git a/nanovg/example/example_gles2.c b/subprojects/d2tk/nanovg/example/example_gles2.c
index ed78838..ed78838 100644
--- a/nanovg/example/example_gles2.c
+++ b/subprojects/d2tk/nanovg/example/example_gles2.c
diff --git a/nanovg/example/example_gles3.c b/subprojects/d2tk/nanovg/example/example_gles3.c
index 4a6084c..4a6084c 100644
--- a/nanovg/example/example_gles3.c
+++ b/subprojects/d2tk/nanovg/example/example_gles3.c
diff --git a/nanovg/example/images.txt b/subprojects/d2tk/nanovg/example/images.txt
index 96ad626..96ad626 100644
--- a/nanovg/example/images.txt
+++ b/subprojects/d2tk/nanovg/example/images.txt
diff --git a/nanovg/example/images/image1.jpg b/subprojects/d2tk/nanovg/example/images/image1.jpg
index c2ce39b..c2ce39b 100644
--- a/nanovg/example/images/image1.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image1.jpg
Binary files differ
diff --git a/nanovg/example/images/image10.jpg b/subprojects/d2tk/nanovg/example/images/image10.jpg
index d443fb0..d443fb0 100644
--- a/nanovg/example/images/image10.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image10.jpg
Binary files differ
diff --git a/nanovg/example/images/image11.jpg b/subprojects/d2tk/nanovg/example/images/image11.jpg
index 7429fe5..7429fe5 100644
--- a/nanovg/example/images/image11.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image11.jpg
Binary files differ
diff --git a/nanovg/example/images/image12.jpg b/subprojects/d2tk/nanovg/example/images/image12.jpg
index eb0369d..eb0369d 100644
--- a/nanovg/example/images/image12.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image12.jpg
Binary files differ
diff --git a/nanovg/example/images/image2.jpg b/subprojects/d2tk/nanovg/example/images/image2.jpg
index 1db15ab..1db15ab 100644
--- a/nanovg/example/images/image2.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image2.jpg
Binary files differ
diff --git a/nanovg/example/images/image3.jpg b/subprojects/d2tk/nanovg/example/images/image3.jpg
index 884f9f2..884f9f2 100644
--- a/nanovg/example/images/image3.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image3.jpg
Binary files differ
diff --git a/nanovg/example/images/image4.jpg b/subprojects/d2tk/nanovg/example/images/image4.jpg
index f6e1039..f6e1039 100644
--- a/nanovg/example/images/image4.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image4.jpg
Binary files differ
diff --git a/nanovg/example/images/image5.jpg b/subprojects/d2tk/nanovg/example/images/image5.jpg
index d952d16..d952d16 100644
--- a/nanovg/example/images/image5.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image5.jpg
Binary files differ
diff --git a/nanovg/example/images/image6.jpg b/subprojects/d2tk/nanovg/example/images/image6.jpg
index f098087..f098087 100644
--- a/nanovg/example/images/image6.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image6.jpg
Binary files differ
diff --git a/nanovg/example/images/image7.jpg b/subprojects/d2tk/nanovg/example/images/image7.jpg
index 623b4cb..623b4cb 100644
--- a/nanovg/example/images/image7.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image7.jpg
Binary files differ
diff --git a/nanovg/example/images/image8.jpg b/subprojects/d2tk/nanovg/example/images/image8.jpg
index 123b6da..123b6da 100644
--- a/nanovg/example/images/image8.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image8.jpg
Binary files differ
diff --git a/nanovg/example/images/image9.jpg b/subprojects/d2tk/nanovg/example/images/image9.jpg
index 045fadb..045fadb 100644
--- a/nanovg/example/images/image9.jpg
+++ b/subprojects/d2tk/nanovg/example/images/image9.jpg
Binary files differ
diff --git a/nanovg/example/perf.c b/subprojects/d2tk/nanovg/example/perf.c
index a74dc3c..a74dc3c 100644
--- a/nanovg/example/perf.c
+++ b/subprojects/d2tk/nanovg/example/perf.c
diff --git a/nanovg/example/perf.h b/subprojects/d2tk/nanovg/example/perf.h
index 3ca67b2..3ca67b2 100644
--- a/nanovg/example/perf.h
+++ b/subprojects/d2tk/nanovg/example/perf.h
diff --git a/nanovg/example/screenshot-01.png b/subprojects/d2tk/nanovg/example/screenshot-01.png
index d8febe9..d8febe9 100644
--- a/nanovg/example/screenshot-01.png
+++ b/subprojects/d2tk/nanovg/example/screenshot-01.png
Binary files differ
diff --git a/nanovg/example/screenshot-02.png b/subprojects/d2tk/nanovg/example/screenshot-02.png
index 7cfa4bc..7cfa4bc 100644
--- a/nanovg/example/screenshot-02.png
+++ b/subprojects/d2tk/nanovg/example/screenshot-02.png
Binary files differ
diff --git a/nanovg/example/stb_image_write.h b/subprojects/d2tk/nanovg/example/stb_image_write.h
index 5de3159..5de3159 100644
--- a/nanovg/example/stb_image_write.h
+++ b/subprojects/d2tk/nanovg/example/stb_image_write.h
diff --git a/nanovg/obsolete/nanovg_gl2.h b/subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h
index ad8883a..ad8883a 100644
--- a/nanovg/obsolete/nanovg_gl2.h
+++ b/subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h
diff --git a/nanovg/obsolete/nanovg_gl3.h b/subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h
index 971fb6b..971fb6b 100644
--- a/nanovg/obsolete/nanovg_gl3.h
+++ b/subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h
diff --git a/nanovg/obsolete/obsolete.md b/subprojects/d2tk/nanovg/obsolete/obsolete.md
index 1dee5d1..1dee5d1 100644
--- a/nanovg/obsolete/obsolete.md
+++ b/subprojects/d2tk/nanovg/obsolete/obsolete.md
diff --git a/nanovg/premake4.lua b/subprojects/d2tk/nanovg/premake4.lua
index 0f86168..0f86168 100644
--- a/nanovg/premake4.lua
+++ b/subprojects/d2tk/nanovg/premake4.lua
diff --git a/nanovg/src/fontstash.h b/subprojects/d2tk/nanovg/src/fontstash.h
index 9df68b3..9df68b3 100644
--- a/nanovg/src/fontstash.h
+++ b/subprojects/d2tk/nanovg/src/fontstash.h
diff --git a/nanovg/src/nanovg.c b/subprojects/d2tk/nanovg/src/nanovg.c
index f642788..f642788 100644
--- a/nanovg/src/nanovg.c
+++ b/subprojects/d2tk/nanovg/src/nanovg.c
diff --git a/nanovg/src/nanovg.h b/subprojects/d2tk/nanovg/src/nanovg.h
index 8aaec30..8aaec30 100644
--- a/nanovg/src/nanovg.h
+++ b/subprojects/d2tk/nanovg/src/nanovg.h
diff --git a/nanovg/src/nanovg_gl.h b/subprojects/d2tk/nanovg/src/nanovg_gl.h
index 75d51e9..75d51e9 100644
--- a/nanovg/src/nanovg_gl.h
+++ b/subprojects/d2tk/nanovg/src/nanovg_gl.h
diff --git a/nanovg/src/nanovg_gl_utils.h b/subprojects/d2tk/nanovg/src/nanovg_gl_utils.h
index f7384d8..f7384d8 100644
--- a/nanovg/src/nanovg_gl_utils.h
+++ b/subprojects/d2tk/nanovg/src/nanovg_gl_utils.h
diff --git a/nanovg/src/stb_image.h b/subprojects/d2tk/nanovg/src/stb_image.h
index e06f7a1..e06f7a1 100644
--- a/nanovg/src/stb_image.h
+++ b/subprojects/d2tk/nanovg/src/stb_image.h
diff --git a/nanovg/src/stb_truetype.h b/subprojects/d2tk/nanovg/src/stb_truetype.h
index bfb1841..bfb1841 100644
--- a/nanovg/src/stb_truetype.h
+++ b/subprojects/d2tk/nanovg/src/stb_truetype.h
diff --git a/pugl/.clang-format b/subprojects/d2tk/pugl/.clang-format
index 7b30bd2..7b30bd2 100644
--- a/pugl/.clang-format
+++ b/subprojects/d2tk/pugl/.clang-format
diff --git a/pugl/.clang-tidy b/subprojects/d2tk/pugl/.clang-tidy
index 1e40901..1e40901 100644
--- a/pugl/.clang-tidy
+++ b/subprojects/d2tk/pugl/.clang-tidy
diff --git a/pugl/.clant.json b/subprojects/d2tk/pugl/.clant.json
index 6f48901..6f48901 100644
--- a/pugl/.clant.json
+++ b/subprojects/d2tk/pugl/.clant.json
diff --git a/pugl/.editorconfig b/subprojects/d2tk/pugl/.editorconfig
index c2d35dd..c2d35dd 100644
--- a/pugl/.editorconfig
+++ b/subprojects/d2tk/pugl/.editorconfig
diff --git a/pugl/.gitattributes b/subprojects/d2tk/pugl/.gitattributes
index 32967c1..32967c1 100644
--- a/pugl/.gitattributes
+++ b/subprojects/d2tk/pugl/.gitattributes
diff --git a/pugl/.gitignore b/subprojects/d2tk/pugl/.gitignore
index 41c45d2..41c45d2 100644
--- a/pugl/.gitignore
+++ b/subprojects/d2tk/pugl/.gitignore
diff --git a/pugl/.gitlab-ci.yml b/subprojects/d2tk/pugl/.gitlab-ci.yml
index dc82cf8..dc82cf8 100644
--- a/pugl/.gitlab-ci.yml
+++ b/subprojects/d2tk/pugl/.gitlab-ci.yml
diff --git a/pugl/.gitmodules b/subprojects/d2tk/pugl/.gitmodules
index e69de29..e69de29 100644
--- a/pugl/.gitmodules
+++ b/subprojects/d2tk/pugl/.gitmodules
diff --git a/pugl/.includes.imp b/subprojects/d2tk/pugl/.includes.imp
index 74a3105..74a3105 100644
--- a/pugl/.includes.imp
+++ b/subprojects/d2tk/pugl/.includes.imp
diff --git a/pugl/AUTHORS b/subprojects/d2tk/pugl/AUTHORS
index 99f6dac..99f6dac 100644
--- a/pugl/AUTHORS
+++ b/subprojects/d2tk/pugl/AUTHORS
diff --git a/pugl/COPYING b/subprojects/d2tk/pugl/COPYING
index 63e6829..63e6829 100644
--- a/pugl/COPYING
+++ b/subprojects/d2tk/pugl/COPYING
diff --git a/pugl/README.md b/subprojects/d2tk/pugl/README.md
index 5b4f82f..5b4f82f 100644
--- a/pugl/README.md
+++ b/subprojects/d2tk/pugl/README.md
diff --git a/pugl/bindings/cxx/include/.clang-tidy b/subprojects/d2tk/pugl/bindings/cxx/include/.clang-tidy
index 816223d..816223d 100644
--- a/pugl/bindings/cxx/include/.clang-tidy
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/.clang-tidy
diff --git a/pugl/bindings/cxx/include/pugl/cairo.hpp b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/cairo.hpp
index 15dc5de..15dc5de 100644
--- a/pugl/bindings/cxx/include/pugl/cairo.hpp
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/cairo.hpp
diff --git a/pugl/bindings/cxx/include/pugl/gl.hpp b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/gl.hpp
index 023dd45..023dd45 100644
--- a/pugl/bindings/cxx/include/pugl/gl.hpp
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/gl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/pugl.hpp b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/pugl.hpp
index fc3bb03..fc3bb03 100644
--- a/pugl/bindings/cxx/include/pugl/pugl.hpp
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/pugl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/stub.hpp b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/stub.hpp
index fbafcee..fbafcee 100644
--- a/pugl/bindings/cxx/include/pugl/stub.hpp
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/stub.hpp
diff --git a/pugl/bindings/cxx/include/pugl/vulkan.hpp b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/vulkan.hpp
index f3dbcad..f3dbcad 100644
--- a/pugl/bindings/cxx/include/pugl/vulkan.hpp
+++ b/subprojects/d2tk/pugl/bindings/cxx/include/pugl/vulkan.hpp
diff --git a/pugl/doc/_static/meson.build b/subprojects/d2tk/pugl/doc/_static/meson.build
index fc7792c..fc7792c 100644
--- a/pugl/doc/_static/meson.build
+++ b/subprojects/d2tk/pugl/doc/_static/meson.build
diff --git a/pugl/doc/c/Doxyfile.in b/subprojects/d2tk/pugl/doc/c/Doxyfile.in
index 96bbf63..96bbf63 100644
--- a/pugl/doc/c/Doxyfile.in
+++ b/subprojects/d2tk/pugl/doc/c/Doxyfile.in
diff --git a/pugl/doc/c/api/meson.build b/subprojects/d2tk/pugl/doc/c/api/meson.build
index 5c1e30e..5c1e30e 100644
--- a/pugl/doc/c/api/meson.build
+++ b/subprojects/d2tk/pugl/doc/c/api/meson.build
diff --git a/pugl/doc/c/event-loop.rst b/subprojects/d2tk/pugl/doc/c/event-loop.rst
index 3b9915f..3b9915f 100644
--- a/pugl/doc/c/event-loop.rst
+++ b/subprojects/d2tk/pugl/doc/c/event-loop.rst
diff --git a/pugl/doc/c/events.rst b/subprojects/d2tk/pugl/doc/c/events.rst
index bf964db..bf964db 100644
--- a/pugl/doc/c/events.rst
+++ b/subprojects/d2tk/pugl/doc/c/events.rst
diff --git a/pugl/doc/c/index.rst b/subprojects/d2tk/pugl/doc/c/index.rst
index 020cf32..020cf32 100644
--- a/pugl/doc/c/index.rst
+++ b/subprojects/d2tk/pugl/doc/c/index.rst
diff --git a/pugl/doc/c/meson.build b/subprojects/d2tk/pugl/doc/c/meson.build
index df9363e..df9363e 100644
--- a/pugl/doc/c/meson.build
+++ b/subprojects/d2tk/pugl/doc/c/meson.build
diff --git a/pugl/doc/c/overview.rst b/subprojects/d2tk/pugl/doc/c/overview.rst
index 4bd024d..4bd024d 100644
--- a/pugl/doc/c/overview.rst
+++ b/subprojects/d2tk/pugl/doc/c/overview.rst
diff --git a/pugl/doc/c/shutting-down.rst b/subprojects/d2tk/pugl/doc/c/shutting-down.rst
index dfb56cd..dfb56cd 100644
--- a/pugl/doc/c/shutting-down.rst
+++ b/subprojects/d2tk/pugl/doc/c/shutting-down.rst
diff --git a/pugl/doc/c/view.rst b/subprojects/d2tk/pugl/doc/c/view.rst
index 12f146d..12f146d 100644
--- a/pugl/doc/c/view.rst
+++ b/subprojects/d2tk/pugl/doc/c/view.rst
diff --git a/pugl/doc/c/world.rst b/subprojects/d2tk/pugl/doc/c/world.rst
index 83d9dbd..83d9dbd 100644
--- a/pugl/doc/c/world.rst
+++ b/subprojects/d2tk/pugl/doc/c/world.rst
diff --git a/pugl/doc/c/xml/meson.build b/subprojects/d2tk/pugl/doc/c/xml/meson.build
index d79d59a..d79d59a 100644
--- a/pugl/doc/c/xml/meson.build
+++ b/subprojects/d2tk/pugl/doc/c/xml/meson.build
diff --git a/pugl/doc/conf.py.in b/subprojects/d2tk/pugl/doc/conf.py.in
index 3fa8ea2..3fa8ea2 100644
--- a/pugl/doc/conf.py.in
+++ b/subprojects/d2tk/pugl/doc/conf.py.in
diff --git a/pugl/doc/cpp/Doxyfile.in b/subprojects/d2tk/pugl/doc/cpp/Doxyfile.in
index 889ac0b..889ac0b 100644
--- a/pugl/doc/cpp/Doxyfile.in
+++ b/subprojects/d2tk/pugl/doc/cpp/Doxyfile.in
diff --git a/pugl/doc/cpp/api/meson.build b/subprojects/d2tk/pugl/doc/cpp/api/meson.build
index 4bbbec2..4bbbec2 100644
--- a/pugl/doc/cpp/api/meson.build
+++ b/subprojects/d2tk/pugl/doc/cpp/api/meson.build
diff --git a/pugl/doc/cpp/event-loop.rst b/subprojects/d2tk/pugl/doc/cpp/event-loop.rst
index 1d2ac41..1d2ac41 100644
--- a/pugl/doc/cpp/event-loop.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/event-loop.rst
diff --git a/pugl/doc/cpp/events.rst b/subprojects/d2tk/pugl/doc/cpp/events.rst
index 72c396c..72c396c 100644
--- a/pugl/doc/cpp/events.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/events.rst
diff --git a/pugl/doc/cpp/index.rst b/subprojects/d2tk/pugl/doc/cpp/index.rst
index b11d028..b11d028 100644
--- a/pugl/doc/cpp/index.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/index.rst
diff --git a/pugl/doc/cpp/meson.build b/subprojects/d2tk/pugl/doc/cpp/meson.build
index d8bae11..d8bae11 100644
--- a/pugl/doc/cpp/meson.build
+++ b/subprojects/d2tk/pugl/doc/cpp/meson.build
diff --git a/pugl/doc/cpp/overview.rst b/subprojects/d2tk/pugl/doc/cpp/overview.rst
index 1928fba..1928fba 100644
--- a/pugl/doc/cpp/overview.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/overview.rst
diff --git a/pugl/doc/cpp/view.rst b/subprojects/d2tk/pugl/doc/cpp/view.rst
index 3f5aee8..3f5aee8 100644
--- a/pugl/doc/cpp/view.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/view.rst
diff --git a/pugl/doc/cpp/world.rst b/subprojects/d2tk/pugl/doc/cpp/world.rst
index 1a3b432..1a3b432 100644
--- a/pugl/doc/cpp/world.rst
+++ b/subprojects/d2tk/pugl/doc/cpp/world.rst
diff --git a/pugl/doc/cpp/xml/meson.build b/subprojects/d2tk/pugl/doc/cpp/xml/meson.build
index 3f87f2a..3f87f2a 100644
--- a/pugl/doc/cpp/xml/meson.build
+++ b/subprojects/d2tk/pugl/doc/cpp/xml/meson.build
diff --git a/pugl/doc/deployment.rst b/subprojects/d2tk/pugl/doc/deployment.rst
index 4afc51a..4afc51a 100644
--- a/pugl/doc/deployment.rst
+++ b/subprojects/d2tk/pugl/doc/deployment.rst
diff --git a/pugl/doc/meson.build b/subprojects/d2tk/pugl/doc/meson.build
index 583f09d..583f09d 100644
--- a/pugl/doc/meson.build
+++ b/subprojects/d2tk/pugl/doc/meson.build
diff --git a/pugl/doc/summary.rst b/subprojects/d2tk/pugl/doc/summary.rst
index f05515f..f05515f 100644
--- a/pugl/doc/summary.rst
+++ b/subprojects/d2tk/pugl/doc/summary.rst
diff --git a/pugl/examples/.clang-tidy b/subprojects/d2tk/pugl/examples/.clang-tidy
index fdfa4ea..fdfa4ea 100644
--- a/pugl/examples/.clang-tidy
+++ b/subprojects/d2tk/pugl/examples/.clang-tidy
diff --git a/pugl/examples/cube_view.h b/subprojects/d2tk/pugl/examples/cube_view.h
index 71ae88d..71ae88d 100644
--- a/pugl/examples/cube_view.h
+++ b/subprojects/d2tk/pugl/examples/cube_view.h
diff --git a/pugl/examples/demo_utils.h b/subprojects/d2tk/pugl/examples/demo_utils.h
index 2dda756..2dda756 100644
--- a/pugl/examples/demo_utils.h
+++ b/subprojects/d2tk/pugl/examples/demo_utils.h
diff --git a/pugl/examples/file_utils.c b/subprojects/d2tk/pugl/examples/file_utils.c
index 8ecbca4..8ecbca4 100644
--- a/pugl/examples/file_utils.c
+++ b/subprojects/d2tk/pugl/examples/file_utils.c
diff --git a/pugl/examples/file_utils.h b/subprojects/d2tk/pugl/examples/file_utils.h
index 1530157..1530157 100644
--- a/pugl/examples/file_utils.h
+++ b/subprojects/d2tk/pugl/examples/file_utils.h
diff --git a/pugl/examples/glad/glad.c b/subprojects/d2tk/pugl/examples/glad/glad.c
index 38f442c..38f442c 100644
--- a/pugl/examples/glad/glad.c
+++ b/subprojects/d2tk/pugl/examples/glad/glad.c
diff --git a/pugl/examples/glad/glad.h b/subprojects/d2tk/pugl/examples/glad/glad.h
index 9efb229..9efb229 100644
--- a/pugl/examples/glad/glad.h
+++ b/subprojects/d2tk/pugl/examples/glad/glad.h
diff --git a/pugl/examples/glad/khrplatform.h b/subprojects/d2tk/pugl/examples/glad/khrplatform.h
index 5b55ea2..5b55ea2 100644
--- a/pugl/examples/glad/khrplatform.h
+++ b/subprojects/d2tk/pugl/examples/glad/khrplatform.h
diff --git a/pugl/examples/meson.build b/subprojects/d2tk/pugl/examples/meson.build
index d455faf..d455faf 100644
--- a/pugl/examples/meson.build
+++ b/subprojects/d2tk/pugl/examples/meson.build
diff --git a/pugl/examples/pugl_cairo_demo.c b/subprojects/d2tk/pugl/examples/pugl_cairo_demo.c
index 67bc13c..67bc13c 100644
--- a/pugl/examples/pugl_cairo_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_cairo_demo.c
diff --git a/pugl/examples/pugl_cursor_demo.c b/subprojects/d2tk/pugl/examples/pugl_cursor_demo.c
index 97e3b9f..97e3b9f 100644
--- a/pugl/examples/pugl_cursor_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_cursor_demo.c
diff --git a/pugl/examples/pugl_cxx_demo.cpp b/subprojects/d2tk/pugl/examples/pugl_cxx_demo.cpp
index d663a3f..d663a3f 100644
--- a/pugl/examples/pugl_cxx_demo.cpp
+++ b/subprojects/d2tk/pugl/examples/pugl_cxx_demo.cpp
diff --git a/pugl/examples/pugl_embed_demo.c b/subprojects/d2tk/pugl/examples/pugl_embed_demo.c
index 0e12ddb..0e12ddb 100644
--- a/pugl/examples/pugl_embed_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_embed_demo.c
diff --git a/pugl/examples/pugl_print_events.c b/subprojects/d2tk/pugl/examples/pugl_print_events.c
index dfa217e..dfa217e 100644
--- a/pugl/examples/pugl_print_events.c
+++ b/subprojects/d2tk/pugl/examples/pugl_print_events.c
diff --git a/pugl/examples/pugl_shader_demo.c b/subprojects/d2tk/pugl/examples/pugl_shader_demo.c
index aa5c38e..aa5c38e 100644
--- a/pugl/examples/pugl_shader_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_shader_demo.c
diff --git a/pugl/examples/pugl_vulkan_cxx_demo.cpp b/subprojects/d2tk/pugl/examples/pugl_vulkan_cxx_demo.cpp
index d92e652..d92e652 100644
--- a/pugl/examples/pugl_vulkan_cxx_demo.cpp
+++ b/subprojects/d2tk/pugl/examples/pugl_vulkan_cxx_demo.cpp
diff --git a/pugl/examples/pugl_vulkan_demo.c b/subprojects/d2tk/pugl/examples/pugl_vulkan_demo.c
index 0dfbadd..0dfbadd 100644
--- a/pugl/examples/pugl_vulkan_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_vulkan_demo.c
diff --git a/pugl/examples/pugl_window_demo.c b/subprojects/d2tk/pugl/examples/pugl_window_demo.c
index f7d5b2c..f7d5b2c 100644
--- a/pugl/examples/pugl_window_demo.c
+++ b/subprojects/d2tk/pugl/examples/pugl_window_demo.c
diff --git a/pugl/examples/rects.h b/subprojects/d2tk/pugl/examples/rects.h
index b99d9f0..b99d9f0 100644
--- a/pugl/examples/rects.h
+++ b/subprojects/d2tk/pugl/examples/rects.h
diff --git a/pugl/examples/shader_utils.h b/subprojects/d2tk/pugl/examples/shader_utils.h
index 2575f47..2575f47 100644
--- a/pugl/examples/shader_utils.h
+++ b/subprojects/d2tk/pugl/examples/shader_utils.h
diff --git a/pugl/examples/shaders/header_330.glsl b/subprojects/d2tk/pugl/examples/shaders/header_330.glsl
index 59d5f6f..59d5f6f 100644
--- a/pugl/examples/shaders/header_330.glsl
+++ b/subprojects/d2tk/pugl/examples/shaders/header_330.glsl
diff --git a/pugl/examples/shaders/header_420.glsl b/subprojects/d2tk/pugl/examples/shaders/header_420.glsl
index 2beaad0..2beaad0 100644
--- a/pugl/examples/shaders/header_420.glsl
+++ b/subprojects/d2tk/pugl/examples/shaders/header_420.glsl
diff --git a/pugl/examples/shaders/meson.build b/subprojects/d2tk/pugl/examples/shaders/meson.build
index e47be9d..e47be9d 100644
--- a/pugl/examples/shaders/meson.build
+++ b/subprojects/d2tk/pugl/examples/shaders/meson.build
diff --git a/pugl/examples/shaders/rect.frag b/subprojects/d2tk/pugl/examples/shaders/rect.frag
index 33bfbb2..33bfbb2 100644
--- a/pugl/examples/shaders/rect.frag
+++ b/subprojects/d2tk/pugl/examples/shaders/rect.frag
diff --git a/pugl/examples/shaders/rect.vert b/subprojects/d2tk/pugl/examples/shaders/rect.vert
index 2c7b5f1..2c7b5f1 100644
--- a/pugl/examples/shaders/rect.vert
+++ b/subprojects/d2tk/pugl/examples/shaders/rect.vert
diff --git a/pugl/examples/sybok.hpp b/subprojects/d2tk/pugl/examples/sybok.hpp
index 7740824..7740824 100644
--- a/pugl/examples/sybok.hpp
+++ b/subprojects/d2tk/pugl/examples/sybok.hpp
diff --git a/pugl/include/.clang-tidy b/subprojects/d2tk/pugl/include/.clang-tidy
index dd2fd47..dd2fd47 100644
--- a/pugl/include/.clang-tidy
+++ b/subprojects/d2tk/pugl/include/.clang-tidy
diff --git a/pugl/include/pugl/cairo.h b/subprojects/d2tk/pugl/include/pugl/cairo.h
index 48e868e..48e868e 100644
--- a/pugl/include/pugl/cairo.h
+++ b/subprojects/d2tk/pugl/include/pugl/cairo.h
diff --git a/pugl/include/pugl/gl.h b/subprojects/d2tk/pugl/include/pugl/gl.h
index 51c4a7d..51c4a7d 100644
--- a/pugl/include/pugl/gl.h
+++ b/subprojects/d2tk/pugl/include/pugl/gl.h
diff --git a/pugl/include/pugl/pugl.h b/subprojects/d2tk/pugl/include/pugl/pugl.h
index cd77334..cd77334 100644
--- a/pugl/include/pugl/pugl.h
+++ b/subprojects/d2tk/pugl/include/pugl/pugl.h
diff --git a/pugl/include/pugl/stub.h b/subprojects/d2tk/pugl/include/pugl/stub.h
index d1a699a..d1a699a 100644
--- a/pugl/include/pugl/stub.h
+++ b/subprojects/d2tk/pugl/include/pugl/stub.h
diff --git a/pugl/include/pugl/vulkan.h b/subprojects/d2tk/pugl/include/pugl/vulkan.h
index f12ad97..f12ad97 100644
--- a/pugl/include/pugl/vulkan.h
+++ b/subprojects/d2tk/pugl/include/pugl/vulkan.h
diff --git a/pugl/meson.build b/subprojects/d2tk/pugl/meson.build
index 02fae17..02fae17 100644
--- a/pugl/meson.build
+++ b/subprojects/d2tk/pugl/meson.build
diff --git a/pugl/meson/meson.build b/subprojects/d2tk/pugl/meson/meson.build
index 20e0522..20e0522 100644
--- a/pugl/meson/meson.build
+++ b/subprojects/d2tk/pugl/meson/meson.build
diff --git a/pugl/meson_options.txt b/subprojects/d2tk/pugl/meson_options.txt
index dd6ea8c..dd6ea8c 100644
--- a/pugl/meson_options.txt
+++ b/subprojects/d2tk/pugl/meson_options.txt
diff --git a/pugl/pugl.pc.in b/subprojects/d2tk/pugl/pugl.pc.in
index 2cfa644..2cfa644 100644
--- a/pugl/pugl.pc.in
+++ b/subprojects/d2tk/pugl/pugl.pc.in
diff --git a/pugl/resources/Info.plist.in b/subprojects/d2tk/pugl/resources/Info.plist.in
index a08dbd0..a08dbd0 100644
--- a/pugl/resources/Info.plist.in
+++ b/subprojects/d2tk/pugl/resources/Info.plist.in
diff --git a/pugl/resources/pugl.ipe b/subprojects/d2tk/pugl/resources/pugl.ipe
index 238c09c..238c09c 100644
--- a/pugl/resources/pugl.ipe
+++ b/subprojects/d2tk/pugl/resources/pugl.ipe
diff --git a/pugl/resources/pugl.png b/subprojects/d2tk/pugl/resources/pugl.png
index 4641660..4641660 100644
--- a/pugl/resources/pugl.png
+++ b/subprojects/d2tk/pugl/resources/pugl.png
Binary files differ
diff --git a/pugl/resources/pugl.svg b/subprojects/d2tk/pugl/resources/pugl.svg
index 93189f4..93189f4 100644
--- a/pugl/resources/pugl.svg
+++ b/subprojects/d2tk/pugl/resources/pugl.svg
diff --git a/pugl/scripts/cat.py b/subprojects/d2tk/pugl/scripts/cat.py
index 5f628b6..5f628b6 100755
--- a/pugl/scripts/cat.py
+++ b/subprojects/d2tk/pugl/scripts/cat.py
diff --git a/pugl/scripts/dox_to_sphinx.py b/subprojects/d2tk/pugl/scripts/dox_to_sphinx.py
index c3f7c44..c3f7c44 100755
--- a/pugl/scripts/dox_to_sphinx.py
+++ b/subprojects/d2tk/pugl/scripts/dox_to_sphinx.py
diff --git a/pugl/src/.clang-tidy b/subprojects/d2tk/pugl/src/.clang-tidy
index 11b620e..11b620e 100644
--- a/pugl/src/.clang-tidy
+++ b/subprojects/d2tk/pugl/src/.clang-tidy
diff --git a/pugl/src/implementation.c b/subprojects/d2tk/pugl/src/implementation.c
index 47b52b8..47b52b8 100644
--- a/pugl/src/implementation.c
+++ b/subprojects/d2tk/pugl/src/implementation.c
diff --git a/pugl/src/implementation.h b/subprojects/d2tk/pugl/src/implementation.h
index 8c5398c..8c5398c 100644
--- a/pugl/src/implementation.h
+++ b/subprojects/d2tk/pugl/src/implementation.h
diff --git a/pugl/src/mac.h b/subprojects/d2tk/pugl/src/mac.h
index 35e6e0d..35e6e0d 100644
--- a/pugl/src/mac.h
+++ b/subprojects/d2tk/pugl/src/mac.h
diff --git a/pugl/src/mac.m b/subprojects/d2tk/pugl/src/mac.m
index 58f16c7..58f16c7 100644
--- a/pugl/src/mac.m
+++ b/subprojects/d2tk/pugl/src/mac.m
diff --git a/pugl/src/mac_cairo.m b/subprojects/d2tk/pugl/src/mac_cairo.m
index 1c564a0..1c564a0 100644
--- a/pugl/src/mac_cairo.m
+++ b/subprojects/d2tk/pugl/src/mac_cairo.m
diff --git a/pugl/src/mac_gl.m b/subprojects/d2tk/pugl/src/mac_gl.m
index dd06cc0..dd06cc0 100644
--- a/pugl/src/mac_gl.m
+++ b/subprojects/d2tk/pugl/src/mac_gl.m
diff --git a/pugl/src/mac_stub.m b/subprojects/d2tk/pugl/src/mac_stub.m
index ac7bfcc..ac7bfcc 100644
--- a/pugl/src/mac_stub.m
+++ b/subprojects/d2tk/pugl/src/mac_stub.m
diff --git a/pugl/src/mac_vulkan.m b/subprojects/d2tk/pugl/src/mac_vulkan.m
index 22fff10..22fff10 100644
--- a/pugl/src/mac_vulkan.m
+++ b/subprojects/d2tk/pugl/src/mac_vulkan.m
diff --git a/pugl/src/stub.h b/subprojects/d2tk/pugl/src/stub.h
index c816679..c816679 100644
--- a/pugl/src/stub.h
+++ b/subprojects/d2tk/pugl/src/stub.h
diff --git a/pugl/src/types.h b/subprojects/d2tk/pugl/src/types.h
index 6fa658f..6fa658f 100644
--- a/pugl/src/types.h
+++ b/subprojects/d2tk/pugl/src/types.h
diff --git a/pugl/src/win.c b/subprojects/d2tk/pugl/src/win.c
index 1e11520..1e11520 100644
--- a/pugl/src/win.c
+++ b/subprojects/d2tk/pugl/src/win.c
diff --git a/pugl/src/win.h b/subprojects/d2tk/pugl/src/win.h
index ccab36a..ccab36a 100644
--- a/pugl/src/win.h
+++ b/subprojects/d2tk/pugl/src/win.h
diff --git a/pugl/src/win_cairo.c b/subprojects/d2tk/pugl/src/win_cairo.c
index 9dc5ce0..9dc5ce0 100644
--- a/pugl/src/win_cairo.c
+++ b/subprojects/d2tk/pugl/src/win_cairo.c
diff --git a/pugl/src/win_gl.c b/subprojects/d2tk/pugl/src/win_gl.c
index 4abd5ab..4abd5ab 100644
--- a/pugl/src/win_gl.c
+++ b/subprojects/d2tk/pugl/src/win_gl.c
diff --git a/pugl/src/win_stub.c b/subprojects/d2tk/pugl/src/win_stub.c
index cf86390..cf86390 100644
--- a/pugl/src/win_stub.c
+++ b/subprojects/d2tk/pugl/src/win_stub.c
diff --git a/pugl/src/win_vulkan.c b/subprojects/d2tk/pugl/src/win_vulkan.c
index a892a16..a892a16 100644
--- a/pugl/src/win_vulkan.c
+++ b/subprojects/d2tk/pugl/src/win_vulkan.c
diff --git a/pugl/src/x11.c b/subprojects/d2tk/pugl/src/x11.c
index 2cdb0f3..2cdb0f3 100644
--- a/pugl/src/x11.c
+++ b/subprojects/d2tk/pugl/src/x11.c
diff --git a/pugl/src/x11.h b/subprojects/d2tk/pugl/src/x11.h
index 778e5ec..778e5ec 100644
--- a/pugl/src/x11.h
+++ b/subprojects/d2tk/pugl/src/x11.h
diff --git a/pugl/src/x11_cairo.c b/subprojects/d2tk/pugl/src/x11_cairo.c
index a0e7d08..a0e7d08 100644
--- a/pugl/src/x11_cairo.c
+++ b/subprojects/d2tk/pugl/src/x11_cairo.c
diff --git a/pugl/src/x11_gl.c b/subprojects/d2tk/pugl/src/x11_gl.c
index 34152de..34152de 100644
--- a/pugl/src/x11_gl.c
+++ b/subprojects/d2tk/pugl/src/x11_gl.c
diff --git a/pugl/src/x11_stub.c b/subprojects/d2tk/pugl/src/x11_stub.c
index de89a86..de89a86 100644
--- a/pugl/src/x11_stub.c
+++ b/subprojects/d2tk/pugl/src/x11_stub.c
diff --git a/pugl/src/x11_vulkan.c b/subprojects/d2tk/pugl/src/x11_vulkan.c
index 1ff5759..1ff5759 100644
--- a/pugl/src/x11_vulkan.c
+++ b/subprojects/d2tk/pugl/src/x11_vulkan.c
diff --git a/pugl/test/.clang-tidy b/subprojects/d2tk/pugl/test/.clang-tidy
index 8014e6a..8014e6a 100644
--- a/pugl/test/.clang-tidy
+++ b/subprojects/d2tk/pugl/test/.clang-tidy
diff --git a/pugl/test/meson.build b/subprojects/d2tk/pugl/test/meson.build
index 340a7dd..340a7dd 100644
--- a/pugl/test/meson.build
+++ b/subprojects/d2tk/pugl/test/meson.build
diff --git a/pugl/test/test_build.c b/subprojects/d2tk/pugl/test/test_build.c
index 957e0bd..957e0bd 100644
--- a/pugl/test/test_build.c
+++ b/subprojects/d2tk/pugl/test/test_build.c
diff --git a/pugl/test/test_build.cpp b/subprojects/d2tk/pugl/test/test_build.cpp
index 20235e8..20235e8 100644
--- a/pugl/test/test_build.cpp
+++ b/subprojects/d2tk/pugl/test/test_build.cpp
diff --git a/pugl/test/test_clipboard.c b/subprojects/d2tk/pugl/test/test_clipboard.c
index a458d00..a458d00 100644
--- a/pugl/test/test_clipboard.c
+++ b/subprojects/d2tk/pugl/test/test_clipboard.c
diff --git a/pugl/test/test_gl_hints.c b/subprojects/d2tk/pugl/test/test_gl_hints.c
index 3be3651..3be3651 100644
--- a/pugl/test/test_gl_hints.c
+++ b/subprojects/d2tk/pugl/test/test_gl_hints.c
diff --git a/pugl/test/test_realize.c b/subprojects/d2tk/pugl/test/test_realize.c
index 3c4e09a..3c4e09a 100644
--- a/pugl/test/test_realize.c
+++ b/subprojects/d2tk/pugl/test/test_realize.c
diff --git a/pugl/test/test_redisplay.c b/subprojects/d2tk/pugl/test/test_redisplay.c
index c5b9887..c5b9887 100644
--- a/pugl/test/test_redisplay.c
+++ b/subprojects/d2tk/pugl/test/test_redisplay.c
diff --git a/pugl/test/test_show_hide.c b/subprojects/d2tk/pugl/test/test_show_hide.c
index 584448c..584448c 100644
--- a/pugl/test/test_show_hide.c
+++ b/subprojects/d2tk/pugl/test/test_show_hide.c
diff --git a/pugl/test/test_stub_hints.c b/subprojects/d2tk/pugl/test/test_stub_hints.c
index 1cc1180..1cc1180 100644
--- a/pugl/test/test_stub_hints.c
+++ b/subprojects/d2tk/pugl/test/test_stub_hints.c
diff --git a/pugl/test/test_timer.c b/subprojects/d2tk/pugl/test/test_timer.c
index 200ddc2..200ddc2 100644
--- a/pugl/test/test_timer.c
+++ b/subprojects/d2tk/pugl/test/test_timer.c
diff --git a/pugl/test/test_update.c b/subprojects/d2tk/pugl/test/test_update.c
index 89de0b6..89de0b6 100644
--- a/pugl/test/test_update.c
+++ b/subprojects/d2tk/pugl/test/test_update.c
diff --git a/pugl/test/test_utils.h b/subprojects/d2tk/pugl/test/test_utils.h
index 2464737..2464737 100644
--- a/pugl/test/test_utils.h
+++ b/subprojects/d2tk/pugl/test/test_utils.h
diff --git a/subprojects/d2tk/screenshots/screenshot_1.png b/subprojects/d2tk/screenshots/screenshot_1.png
new file mode 100644
index 0000000..461d52a
--- /dev/null
+++ b/subprojects/d2tk/screenshots/screenshot_1.png
Binary files differ
diff --git a/screenshots/screenshot_2.png b/subprojects/d2tk/screenshots/screenshot_2.png
index 5b40bb8..5b40bb8 100644
--- a/screenshots/screenshot_2.png
+++ b/subprojects/d2tk/screenshots/screenshot_2.png
Binary files differ
diff --git a/screenshots/screenshot_3.png b/subprojects/d2tk/screenshots/screenshot_3.png
index 13b9843..13b9843 100644
--- a/screenshots/screenshot_3.png
+++ b/subprojects/d2tk/screenshots/screenshot_3.png
Binary files differ
diff --git a/screenshots/screenshot_4.png b/subprojects/d2tk/screenshots/screenshot_4.png
index 8cd8329..8cd8329 100644
--- a/screenshots/screenshot_4.png
+++ b/subprojects/d2tk/screenshots/screenshot_4.png
Binary files differ
diff --git a/screenshots/screenshot_5.png b/subprojects/d2tk/screenshots/screenshot_5.png
index 202ab61..202ab61 100644
--- a/screenshots/screenshot_5.png
+++ b/subprojects/d2tk/screenshots/screenshot_5.png
Binary files differ
diff --git a/screenshots/screenshot_6.png b/subprojects/d2tk/screenshots/screenshot_6.png
index 10e5235..10e5235 100644
--- a/screenshots/screenshot_6.png
+++ b/subprojects/d2tk/screenshots/screenshot_6.png
Binary files differ
diff --git a/screenshots/screenshot_7.png b/subprojects/d2tk/screenshots/screenshot_7.png
index 4850e2c..4850e2c 100644
--- a/screenshots/screenshot_7.png
+++ b/subprojects/d2tk/screenshots/screenshot_7.png
Binary files differ
diff --git a/screenshots/screenshot_8.png b/subprojects/d2tk/screenshots/screenshot_8.png
index d474786..d474786 100644
--- a/screenshots/screenshot_8.png
+++ b/subprojects/d2tk/screenshots/screenshot_8.png
Binary files differ
diff --git a/src/backend_cairo.c b/subprojects/d2tk/src/backend_cairo.c
index 4e17769..4e17769 100644
--- a/src/backend_cairo.c
+++ b/subprojects/d2tk/src/backend_cairo.c
diff --git a/src/backend_nanovg.c b/subprojects/d2tk/src/backend_nanovg.c
index 4787164..4787164 100644
--- a/src/backend_nanovg.c
+++ b/subprojects/d2tk/src/backend_nanovg.c
diff --git a/src/base.c b/subprojects/d2tk/src/base.c
index ebd59c6..ebd59c6 100644
--- a/src/base.c
+++ b/subprojects/d2tk/src/base.c
diff --git a/src/base_bar.c b/subprojects/d2tk/src/base_bar.c
index 67cbfb4..67cbfb4 100644
--- a/src/base_bar.c
+++ b/subprojects/d2tk/src/base_bar.c
diff --git a/src/base_bitmap.c b/subprojects/d2tk/src/base_bitmap.c
index a542169..a542169 100644
--- a/src/base_bitmap.c
+++ b/subprojects/d2tk/src/base_bitmap.c
diff --git a/src/base_button.c b/subprojects/d2tk/src/base_button.c
index 244e7d1..244e7d1 100644
--- a/src/base_button.c
+++ b/subprojects/d2tk/src/base_button.c
diff --git a/src/base_combo.c b/subprojects/d2tk/src/base_combo.c
index 3ab073c..3ab073c 100644
--- a/src/base_combo.c
+++ b/subprojects/d2tk/src/base_combo.c
diff --git a/src/base_cursor.c b/subprojects/d2tk/src/base_cursor.c
index e9be4ed..e9be4ed 100644
--- a/src/base_cursor.c
+++ b/subprojects/d2tk/src/base_cursor.c
diff --git a/src/base_custom.c b/subprojects/d2tk/src/base_custom.c
index 2396530..2396530 100644
--- a/src/base_custom.c
+++ b/subprojects/d2tk/src/base_custom.c
diff --git a/src/base_dial.c b/subprojects/d2tk/src/base_dial.c
index ae3c97a..ae3c97a 100644
--- a/src/base_dial.c
+++ b/subprojects/d2tk/src/base_dial.c
diff --git a/src/base_flowmatrix.c b/subprojects/d2tk/src/base_flowmatrix.c
index 0edc1c5..0edc1c5 100644
--- a/src/base_flowmatrix.c
+++ b/subprojects/d2tk/src/base_flowmatrix.c
diff --git a/src/base_frame.c b/subprojects/d2tk/src/base_frame.c
index d78bdf0..d78bdf0 100644
--- a/src/base_frame.c
+++ b/subprojects/d2tk/src/base_frame.c
diff --git a/src/base_image.c b/subprojects/d2tk/src/base_image.c
index cfed2d1..cfed2d1 100644
--- a/src/base_image.c
+++ b/subprojects/d2tk/src/base_image.c
diff --git a/src/base_internal.h b/subprojects/d2tk/src/base_internal.h
index f801418..f801418 100644
--- a/src/base_internal.h
+++ b/subprojects/d2tk/src/base_internal.h
diff --git a/src/base_label.c b/subprojects/d2tk/src/base_label.c
index 64cbd4b..64cbd4b 100644
--- a/src/base_label.c
+++ b/subprojects/d2tk/src/base_label.c
diff --git a/src/base_layout.c b/subprojects/d2tk/src/base_layout.c
index 770e5df..770e5df 100644
--- a/src/base_layout.c
+++ b/subprojects/d2tk/src/base_layout.c
diff --git a/src/base_lineedit.c b/subprojects/d2tk/src/base_lineedit.c
index dc741a4..dc741a4 100644
--- a/src/base_lineedit.c
+++ b/subprojects/d2tk/src/base_lineedit.c
diff --git a/src/base_link.c b/subprojects/d2tk/src/base_link.c
index 355d394..355d394 100644
--- a/src/base_link.c
+++ b/subprojects/d2tk/src/base_link.c
diff --git a/src/base_meter.c b/subprojects/d2tk/src/base_meter.c
index 6d8d080..6d8d080 100644
--- a/src/base_meter.c
+++ b/subprojects/d2tk/src/base_meter.c
diff --git a/src/base_pane.c b/subprojects/d2tk/src/base_pane.c
index 07b6b5d..07b6b5d 100644
--- a/src/base_pane.c
+++ b/subprojects/d2tk/src/base_pane.c
diff --git a/src/base_pty.c b/subprojects/d2tk/src/base_pty.c
index b380a4c..b380a4c 100644
--- a/src/base_pty.c
+++ b/subprojects/d2tk/src/base_pty.c
diff --git a/src/base_scrollbar.c b/subprojects/d2tk/src/base_scrollbar.c
index c98b038..c98b038 100644
--- a/src/base_scrollbar.c
+++ b/subprojects/d2tk/src/base_scrollbar.c
diff --git a/src/base_separator.c b/subprojects/d2tk/src/base_separator.c
index f45afe3..f45afe3 100644
--- a/src/base_separator.c
+++ b/subprojects/d2tk/src/base_separator.c
diff --git a/src/base_spinner.c b/subprojects/d2tk/src/base_spinner.c
index e2c821c..e2c821c 100644
--- a/src/base_spinner.c
+++ b/subprojects/d2tk/src/base_spinner.c
diff --git a/src/base_table.c b/subprojects/d2tk/src/base_table.c
index eefa6e1..eefa6e1 100644
--- a/src/base_table.c
+++ b/subprojects/d2tk/src/base_table.c
diff --git a/src/base_textfield.c b/subprojects/d2tk/src/base_textfield.c
index 9d0fbc1..9d0fbc1 100644
--- a/src/base_textfield.c
+++ b/subprojects/d2tk/src/base_textfield.c
diff --git a/src/base_tooltip.c b/subprojects/d2tk/src/base_tooltip.c
index d105470..d105470 100644
--- a/src/base_tooltip.c
+++ b/subprojects/d2tk/src/base_tooltip.c
diff --git a/src/base_vkb.c b/subprojects/d2tk/src/base_vkb.c
index bbe65c6..bbe65c6 100644
--- a/src/base_vkb.c
+++ b/subprojects/d2tk/src/base_vkb.c
diff --git a/src/base_wave.c b/subprojects/d2tk/src/base_wave.c
index b9dddc6..b9dddc6 100644
--- a/src/base_wave.c
+++ b/subprojects/d2tk/src/base_wave.c
diff --git a/src/core.c b/subprojects/d2tk/src/core.c
index 5383776..5383776 100644
--- a/src/core.c
+++ b/subprojects/d2tk/src/core.c
diff --git a/src/core_internal.h b/subprojects/d2tk/src/core_internal.h
index d07252b..d07252b 100644
--- a/src/core_internal.h
+++ b/subprojects/d2tk/src/core_internal.h
diff --git a/src/frontend_fbdev.c b/subprojects/d2tk/src/frontend_fbdev.c
index 3ae87ed..3ae87ed 100644
--- a/src/frontend_fbdev.c
+++ b/subprojects/d2tk/src/frontend_fbdev.c
diff --git a/src/frontend_glfw.c b/subprojects/d2tk/src/frontend_glfw.c
index 6950c00..6950c00 100644
--- a/src/frontend_glfw.c
+++ b/subprojects/d2tk/src/frontend_glfw.c
diff --git a/src/frontend_pugl.c b/subprojects/d2tk/src/frontend_pugl.c
index 62ade95..62ade95 100644
--- a/src/frontend_pugl.c
+++ b/subprojects/d2tk/src/frontend_pugl.c
diff --git a/src/hash.c b/subprojects/d2tk/src/hash.c
index f02b7f2..f02b7f2 100644
--- a/src/hash.c
+++ b/subprojects/d2tk/src/hash.c
diff --git a/src/mum.h b/subprojects/d2tk/src/mum.h
index e42e2d7..e42e2d7 100644
--- a/src/mum.h
+++ b/subprojects/d2tk/src/mum.h
diff --git a/src/util_spawn.c b/subprojects/d2tk/src/util_spawn.c
index 28aff32..28aff32 100644
--- a/src/util_spawn.c
+++ b/subprojects/d2tk/src/util_spawn.c
diff --git a/test/base.c b/subprojects/d2tk/test/base.c
index cfb31a2..cfb31a2 100644
--- a/test/base.c
+++ b/subprojects/d2tk/test/base.c
diff --git a/test/core.c b/subprojects/d2tk/test/core.c
index 5389ed3..5389ed3 100644
--- a/test/core.c
+++ b/subprojects/d2tk/test/core.c
diff --git a/test/mock.c b/subprojects/d2tk/test/mock.c
index 5986109..5986109 100644
--- a/test/mock.c
+++ b/subprojects/d2tk/test/mock.c
diff --git a/test/mock.h b/subprojects/d2tk/test/mock.h
index 8b18ddb..8b18ddb 100644
--- a/test/mock.h
+++ b/subprojects/d2tk/test/mock.h
diff --git a/ttf/FiraCode-Bold.ttf b/subprojects/d2tk/ttf/FiraCode-Bold.ttf
index 5030383..5030383 100644
--- a/ttf/FiraCode-Bold.ttf
+++ b/subprojects/d2tk/ttf/FiraCode-Bold.ttf
Binary files differ
diff --git a/ttf/FiraCode-Light.ttf b/subprojects/d2tk/ttf/FiraCode-Light.ttf
index 95913af..95913af 100644
--- a/ttf/FiraCode-Light.ttf
+++ b/subprojects/d2tk/ttf/FiraCode-Light.ttf
Binary files differ
diff --git a/ttf/FiraCode-Medium.ttf b/subprojects/d2tk/ttf/FiraCode-Medium.ttf
index eca9e18..eca9e18 100644
--- a/ttf/FiraCode-Medium.ttf
+++ b/subprojects/d2tk/ttf/FiraCode-Medium.ttf
Binary files differ
diff --git a/ttf/FiraCode-Regular.ttf b/subprojects/d2tk/ttf/FiraCode-Regular.ttf
index 97c1159..97c1159 100644
--- a/ttf/FiraCode-Regular.ttf
+++ b/subprojects/d2tk/ttf/FiraCode-Regular.ttf
Binary files differ
diff --git a/ttf/FiraSans-Bold.ttf b/subprojects/d2tk/ttf/FiraSans-Bold.ttf
index f03425a..f03425a 100644
--- a/ttf/FiraSans-Bold.ttf
+++ b/subprojects/d2tk/ttf/FiraSans-Bold.ttf
Binary files differ
diff --git a/ttf/LICENSE b/subprojects/d2tk/ttf/LICENSE
index 805e0b3..805e0b3 100644
--- a/ttf/LICENSE
+++ b/subprojects/d2tk/ttf/LICENSE
diff --git a/utf8.h/.clang-format b/subprojects/d2tk/utf8.h/.clang-format
index be1e9c4..be1e9c4 100644
--- a/utf8.h/.clang-format
+++ b/subprojects/d2tk/utf8.h/.clang-format
diff --git a/utf8.h/.gitignore b/subprojects/d2tk/utf8.h/.gitignore
index 378eac2..378eac2 100644
--- a/utf8.h/.gitignore
+++ b/subprojects/d2tk/utf8.h/.gitignore
diff --git a/utf8.h/.travis.yml b/subprojects/d2tk/utf8.h/.travis.yml
index b6711ea..b6711ea 100644
--- a/utf8.h/.travis.yml
+++ b/subprojects/d2tk/utf8.h/.travis.yml
diff --git a/utf8.h/LICENSE b/subprojects/d2tk/utf8.h/LICENSE
index 68a49da..68a49da 100644
--- a/utf8.h/LICENSE
+++ b/subprojects/d2tk/utf8.h/LICENSE
diff --git a/utf8.h/README.md b/subprojects/d2tk/utf8.h/README.md
index 80e806b..80e806b 100644
--- a/utf8.h/README.md
+++ b/subprojects/d2tk/utf8.h/README.md
diff --git a/utf8.h/appveyor.yml b/subprojects/d2tk/utf8.h/appveyor.yml
index 8458679..8458679 100644
--- a/utf8.h/appveyor.yml
+++ b/subprojects/d2tk/utf8.h/appveyor.yml
diff --git a/utf8.h/test/CMakeLists.txt b/subprojects/d2tk/utf8.h/test/CMakeLists.txt
index b00114b..b00114b 100644
--- a/utf8.h/test/CMakeLists.txt
+++ b/subprojects/d2tk/utf8.h/test/CMakeLists.txt
diff --git a/utf8.h/test/main.c b/subprojects/d2tk/utf8.h/test/main.c
index bc28b3c..bc28b3c 100644
--- a/utf8.h/test/main.c
+++ b/subprojects/d2tk/utf8.h/test/main.c
diff --git a/utf8.h/test/test.c b/subprojects/d2tk/utf8.h/test/test.c
index f8cd1d6..f8cd1d6 100644
--- a/utf8.h/test/test.c
+++ b/subprojects/d2tk/utf8.h/test/test.c
diff --git a/utf8.h/test/test.cpp b/subprojects/d2tk/utf8.h/test/test.cpp
index f8cd1d6..f8cd1d6 100644
--- a/utf8.h/test/test.cpp
+++ b/subprojects/d2tk/utf8.h/test/test.cpp
diff --git a/utf8.h/test/utest.h b/subprojects/d2tk/utf8.h/test/utest.h
index 2a58af6..2a58af6 100644
--- a/utf8.h/test/utest.h
+++ b/subprojects/d2tk/utf8.h/test/utest.h
diff --git a/utf8.h/utf8.h b/subprojects/d2tk/utf8.h/utf8.h
index f4c62a0..f4c62a0 100644
--- a/utf8.h/utf8.h
+++ b/subprojects/d2tk/utf8.h/utf8.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/varchunk/.gitlab-ci.yml b/varchunk/.gitlab-ci.yml
new file mode 100644
index 0000000..979769c
--- /dev/null
+++ b/varchunk/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/varchunk/COPYING b/varchunk/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/varchunk/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/varchunk/README.md b/varchunk/README.md
new file mode 100644
index 0000000..d587a9a
--- /dev/null
+++ b/varchunk/README.md
@@ -0,0 +1,112 @@
+# Varchunk
+
+## Ringbuffer optimized for realtime event handling
+
+### Properties
+
+* Is realtime-safe
+* Is lock-free
+* Supports variably sized chunks
+* Supports contiguous memory chunks
+* Supports zero copy operation
+* Uses a simplistic API
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/varchunk/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/varchunk/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lad/varchunk
+ cd varchunk
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### Usage
+
+ #include <pthread.h>
+ #include <varchunk.h>
+
+ static void *
+ producer_main(void *arg)
+ {
+ varchunk_t *varchunk = arg;
+ void *ptr;
+ const size_t towrite = sizeof(uint32_t);
+ uint32_t counter = 0;
+
+ while(counter <= 1000000)
+ {
+ if( (ptr = varchunk_write_request(varchunk, towrite)) )
+ {
+ // write 'towrite' bytes to 'ptr'
+ *(uint32_t *)ptr = counter++;
+ varchunk_write_advance(varchunk, towrite);
+ }
+ }
+
+ return NULL;
+ }
+
+ static void *
+ consumer_main(void *arg)
+ {
+ varchunk_t *varchunk = arg;
+ const void *ptr;
+ size_t toread;
+
+ while(1)
+ {
+ if( (ptr = varchunk_read_request(varchunk, &toread)) )
+ {
+ // read 'toread' bytes from 'ptr'
+ if(*(uint32_t *)ptr >= 1000000)
+ break;
+ varchunk_read_advance(varchunk);
+ }
+ }
+
+ return NULL;
+ }
+
+ int
+ main(int argc, char **argv)
+ {
+ if(!varchunk_is_lock_free())
+ return -1;
+
+ pthread_t producer;
+ pthread_t consumer;
+ varchunk_t *varchunk = varchunk_new(8192, true);
+ if(!varchunk)
+ return -1;
+
+ pthread_create(&consumer, NULL, consumer_main, varchunk);
+ pthread_create(&producer, NULL, producer_main, varchunk);
+
+ pthread_join(producer, NULL);
+ pthread_join(consumer, NULL);
+
+ varchunk_free(varchunk);
+
+ return 0;
+ }
+
+### License
+
+Copyright (c) 2015-2017 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/varchunk/VERSION b/varchunk/VERSION
new file mode 100644
index 0000000..5096aa3
--- /dev/null
+++ b/varchunk/VERSION
@@ -0,0 +1 @@
+0.1.89
diff --git a/varchunk/gitlab-ci/generic.yml b/varchunk/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/varchunk/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/varchunk/meson.build b/varchunk/meson.build
new file mode 100644
index 0000000..83dd626
--- /dev/null
+++ b/varchunk/meson.build
@@ -0,0 +1,30 @@
+project('varchunk', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=true',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+version = run_command('cat', 'VERSION').stdout().strip()
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+thread_dep = dependency('threads')
+deps = [thread_dep]
+
+if host_machine.system() == 'linux'
+ rt_dep = cc.find_library('rt')
+ deps += rt_dep
+endif
+
+test_varchunk = executable('test_varchunk',
+ 'test_varchunk.c',
+ dependencies : deps,
+ install : false)
+
+test('Test', test_varchunk,
+ args : ['100000'],
+ timeout : 360) # seconds
diff --git a/varchunk/test_varchunk.c b/varchunk/test_varchunk.c
new file mode 100644
index 0000000..1cce435
--- /dev/null
+++ b/varchunk/test_varchunk.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2015-2017 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <varchunk.h>
+
+#if !defined(_WIN32)
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <string.h>
+# define VARCHUNK_USE_SHARED_MEM
+
+static const struct timespec req = {
+ .tv_sec = 0,
+ .tv_nsec = 1
+};
+#endif
+
+static uint64_t iterations = 10000000;
+#define THRESHOLD (RAND_MAX / 256)
+#define PAD(SIZE) ( ( (size_t)(SIZE) + 7U ) & ( ~7U ) )
+
+static void *
+producer_main(void *arg)
+{
+ varchunk_t *varchunk = arg;
+ uint8_t *ptr;
+ const uint8_t *end;
+ size_t written;
+ uint64_t cnt = 0;
+
+ while(cnt < iterations)
+ {
+#if !defined(_WIN32)
+ if(rand() < THRESHOLD)
+ {
+ nanosleep(&req, NULL);
+ }
+#endif
+
+ written = PAD(rand() * 1024.f / RAND_MAX);
+
+ size_t maximum;
+ if( (ptr = varchunk_write_request_max(varchunk, written, &maximum)) )
+ {
+ assert(maximum >= written);
+ end = ptr + written;
+ for(uint8_t *src=ptr; src<end; src+=sizeof(uint64_t))
+ {
+ *(uint64_t *)src = cnt;
+ assert(*(uint64_t *)src == cnt);
+ }
+ varchunk_write_advance(varchunk, written);
+ cnt++;
+ }
+ else
+ {
+ // buffer full
+ }
+ }
+
+ return NULL;
+}
+
+static void *
+consumer_main(void *arg)
+{
+ varchunk_t *varchunk = arg;
+ const uint8_t *ptr;
+ const uint8_t *end;
+ size_t toread;
+ uint64_t cnt = 0;
+
+ while(cnt < iterations)
+ {
+#if !defined(_WIN32)
+ if(rand() < THRESHOLD)
+ {
+ nanosleep(&req, NULL);
+ }
+#endif
+
+ if( (ptr = varchunk_read_request(varchunk, &toread)) )
+ {
+ end = ptr + toread;
+ for(const uint8_t *src=ptr; src<end; src+=sizeof(uint64_t))
+ {
+ assert(*(const uint64_t *)src == cnt);
+ }
+ varchunk_read_advance(varchunk);
+ cnt++;
+ }
+ else
+ {
+ // buffer empty
+ }
+ }
+
+ return NULL;
+}
+
+static void
+test_threaded()
+{
+ pthread_t producer;
+ pthread_t consumer;
+ varchunk_t *varchunk = varchunk_new(8192, true);
+ assert(varchunk);
+
+ pthread_create(&consumer, NULL, consumer_main, varchunk);
+ pthread_create(&producer, NULL, producer_main, varchunk);
+
+ pthread_join(producer, NULL);
+ pthread_join(consumer, NULL);
+
+ varchunk_free(varchunk);
+}
+
+#if defined(VARCHUNK_USE_SHARED_MEM)
+typedef struct _varchunk_shm_t varchunk_shm_t;
+
+struct _varchunk_shm_t {
+ char *name;
+ int fd;
+ varchunk_t *varchunk;
+};
+
+static int
+varchunk_shm_init(varchunk_shm_t *varchunk_shm, const char *name, size_t minimum, bool release_and_acquire)
+{
+ const size_t body_size = varchunk_body_size(minimum);
+ const size_t total_size = sizeof(varchunk_t) + body_size;
+
+ varchunk_shm->name = strdup(name);
+ if(!varchunk_shm->name)
+ {
+ return -1;
+ }
+
+ bool is_first = true;
+ varchunk_shm->fd = shm_open(varchunk_shm->name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if(varchunk_shm->fd == -1)
+ {
+ is_first = false;
+ varchunk_shm->fd = shm_open(varchunk_shm->name, O_RDWR , S_IRUSR | S_IWUSR);
+ }
+ if(varchunk_shm->fd == -1)
+ {
+ free(varchunk_shm->name);
+ return -1;
+ }
+
+ if( (ftruncate(varchunk_shm->fd, total_size) == -1)
+ || ((varchunk_shm->varchunk = mmap(NULL, total_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, varchunk_shm->fd, 0)) == MAP_FAILED) )
+ {
+ shm_unlink(varchunk_shm->name);
+ close(varchunk_shm->fd);
+ free(varchunk_shm->name);
+ return -1;
+ }
+
+ if(is_first)
+ {
+ varchunk_init(varchunk_shm->varchunk, body_size, release_and_acquire);
+ }
+
+ return 0;
+}
+
+static void
+varchunk_shm_deinit(varchunk_shm_t *varchunk_shm)
+{
+ const size_t total_size = sizeof(varchunk_t) + varchunk_shm->varchunk->size;
+
+ munmap(varchunk_shm->varchunk, total_size);
+ shm_unlink(varchunk_shm->name);
+ close(varchunk_shm->fd);
+ free(varchunk_shm->name);
+}
+
+static void
+test_shared()
+{
+ const char *name = "/varchunk_shm_test";
+ pid_t pid = fork();
+
+ assert(pid != -1);
+
+ if(pid == 0) // child
+ {
+ varchunk_shm_t varchunk_shm;
+ assert(varchunk_shm_init(&varchunk_shm, name, 8192, true) == 0);
+
+ consumer_main(varchunk_shm.varchunk);
+
+ varchunk_shm_deinit(&varchunk_shm);
+ }
+ else // parent
+ {
+ varchunk_shm_t varchunk_shm;
+ assert(varchunk_shm_init(&varchunk_shm, name, 8192, true) == 0);
+
+ producer_main(varchunk_shm.varchunk);
+
+ varchunk_shm_deinit(&varchunk_shm);
+ }
+}
+#endif
+
+int
+main(int argc, char **argv)
+{
+#if !defined(_WIN32)
+ const int seed = time(NULL);
+ srand(seed);
+#endif
+
+ if(argc >= 2)
+ {
+ iterations = atoi(argv[1]);
+ }
+
+ assert(varchunk_is_lock_free());
+
+ test_threaded();
+
+#if defined(VARCHUNK_USE_SHARED_MEM)
+ test_shared();
+#endif
+
+ return 0;
+}
diff --git a/varchunk/varchunk.h b/varchunk/varchunk.h
new file mode 100644
index 0000000..6fc1d5b
--- /dev/null
+++ b/varchunk/varchunk.h
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2015-2017 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _VARCHUNK_H
+#define _VARCHUNK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#if !defined(_WIN32)
+# include <sys/mman.h> // mlock
+#endif
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+typedef struct _varchunk_t varchunk_t;
+
+static inline bool
+varchunk_is_lock_free(void);
+
+static inline size_t
+varchunk_body_size(size_t minimum);
+
+static inline varchunk_t *
+varchunk_new(size_t minimum, bool release_and_acquire);
+
+static inline void
+varchunk_free(varchunk_t *varchunk);
+
+static inline void
+varchunk_init(varchunk_t *varchunk, size_t body_size, bool release_and_acquire);
+
+static inline void *
+varchunk_write_request_max(varchunk_t *varchunk, size_t minimum, size_t *maximum);
+
+static inline void *
+varchunk_write_request(varchunk_t *varchunk, size_t minimum);
+
+static inline void
+varchunk_write_advance(varchunk_t *varchunk, size_t written);
+
+static inline const void *
+varchunk_read_request(varchunk_t *varchunk, size_t *toread);
+
+static inline void
+varchunk_read_advance(varchunk_t *varchunk);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+#define VARCHUNK_PAD(SIZE) ( ( (size_t)(SIZE) + 7U ) & ( ~7U ) )
+
+typedef struct _varchunk_elmnt_t varchunk_elmnt_t;
+
+struct _varchunk_elmnt_t {
+ uint32_t size;
+ uint32_t gap;
+};
+
+struct _varchunk_t {
+ size_t size;
+ size_t mask;
+ size_t rsvd;
+ size_t gapd;
+
+ memory_order acquire;
+ memory_order release;
+
+ atomic_size_t head;
+ atomic_size_t tail;
+
+ uint8_t buf [] __attribute__((aligned(sizeof(varchunk_elmnt_t))));
+};
+
+static inline bool
+varchunk_is_lock_free(void)
+{
+ varchunk_t varchunk;
+
+ return atomic_is_lock_free(&varchunk.head)
+ && atomic_is_lock_free(&varchunk.tail);
+}
+
+static inline size_t
+varchunk_body_size(size_t minimum)
+{
+ size_t size = 1;
+ while(size < minimum)
+ size <<= 1; // assure size to be a power of 2
+ return size;
+}
+
+static inline void
+varchunk_init(varchunk_t *varchunk, size_t body_size, bool release_and_acquire)
+{
+ varchunk->acquire = release_and_acquire
+ ? memory_order_acquire
+ : memory_order_relaxed;
+ varchunk->release = release_and_acquire
+ ? memory_order_release
+ : memory_order_relaxed;
+
+ atomic_init(&varchunk->head, 0);
+ atomic_init(&varchunk->tail, 0);
+
+ varchunk->size = body_size;
+ varchunk->mask = varchunk->size - 1;
+}
+
+static inline varchunk_t *
+varchunk_new(size_t minimum, bool release_and_acquire)
+{
+ varchunk_t *varchunk = NULL;
+
+ const size_t body_size = varchunk_body_size(minimum);
+ const size_t total_size = sizeof(varchunk_t) + body_size;
+
+#if defined(_WIN32)
+ varchunk = _aligned_malloc(total_size, sizeof(varchunk_elmnt_t));
+#else
+ posix_memalign((void **)&varchunk, sizeof(varchunk_elmnt_t), total_size);
+ mlock(varchunk, total_size); // prevent memory from being flushed to disk
+#endif
+
+ if(varchunk)
+ varchunk_init(varchunk, body_size, release_and_acquire);
+
+ return varchunk;
+}
+
+static inline void
+varchunk_free(varchunk_t *varchunk)
+{
+ if(varchunk)
+ {
+#if !defined(_WIN32)
+ munlock(varchunk->buf, varchunk->size);
+#endif
+ free(varchunk);
+ }
+}
+
+static inline void
+_varchunk_write_advance_raw(varchunk_t *varchunk, size_t head, size_t written)
+{
+ // only producer is allowed to advance write head
+ const size_t new_head = (head + written) & varchunk->mask;
+ atomic_store_explicit(&varchunk->head, new_head, varchunk->release);
+}
+
+static inline void *
+varchunk_write_request_max(varchunk_t *varchunk, size_t minimum, size_t *maximum)
+{
+ assert(varchunk);
+
+ size_t space; // size of writable buffer
+ size_t end; // virtual end of writable buffer
+ const size_t head = atomic_load_explicit(&varchunk->head, memory_order_relaxed); // read head
+ const size_t tail = atomic_load_explicit(&varchunk->tail, varchunk->acquire); // read tail (consumer modifies it any time)
+ const size_t padded = 2*sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(minimum);
+
+ // calculate writable space
+ if(head > tail)
+ space = ((tail - head + varchunk->size) & varchunk->mask) - 1;
+ else if(head < tail)
+ space = (tail - head) - 1;
+ else // head == tail
+ space = varchunk->size - 1;
+ end = head + space;
+
+ if(end > varchunk->size) // available region wraps over at end of buffer
+ {
+ // get first part of available buffer
+ uint8_t *buf1 = varchunk->buf + head;
+ const size_t len1 = varchunk->size - head;
+
+ if(len1 < padded) // not enough space left on first part of buffer
+ {
+ // get second part of available buffer
+ uint8_t *buf2 = varchunk->buf;
+ const size_t len2 = end & varchunk->mask;
+
+ if(len2 < padded) // not enough space left on second buffer, either
+ {
+ varchunk->rsvd = 0;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return NULL;
+ }
+ else // enough space left on second buffer, use it!
+ {
+ varchunk->rsvd = len2;
+ varchunk->gapd = len1;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf2 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // enough space left on first part of buffer, use it!
+ {
+ varchunk->rsvd = len1;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf1 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // available region is contiguous
+ {
+ uint8_t *buf = varchunk->buf + head;
+
+ if(space < padded) // no space left on contiguous buffer
+ {
+ varchunk->rsvd = 0;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return NULL;
+ }
+ else // enough space left on contiguous buffer, use it!
+ {
+ varchunk->rsvd = space;
+ varchunk->gapd = 0;
+ if(maximum)
+ *maximum = varchunk->rsvd;
+ return buf + sizeof(varchunk_elmnt_t);
+ }
+ }
+}
+
+static inline void *
+varchunk_write_request(varchunk_t *varchunk, size_t minimum)
+{
+ return varchunk_write_request_max(varchunk, minimum, NULL);
+}
+
+static inline void
+varchunk_write_advance(varchunk_t *varchunk, size_t written)
+{
+ assert(varchunk);
+ // fail miserably if stupid programmer tries to write more than rsvd
+ assert(written <= varchunk->rsvd);
+
+ // write elmnt header at head
+ const size_t head = atomic_load_explicit(&varchunk->head, memory_order_relaxed);
+ if(varchunk->gapd > 0)
+ {
+ // fill end of first buffer with gap
+ varchunk_elmnt_t *elmnt = (varchunk_elmnt_t *)(varchunk->buf + head);
+ elmnt->size = varchunk->gapd - sizeof(varchunk_elmnt_t);
+ elmnt->gap = 1;
+
+ // fill written element header
+ elmnt = (void *)varchunk->buf;
+ elmnt->size = written;
+ elmnt->gap = 0;
+ }
+ else // varchunk->gapd == 0
+ {
+ // fill written element header
+ varchunk_elmnt_t *elmnt = (varchunk_elmnt_t *)(varchunk->buf + head);
+ elmnt->size = written;
+ elmnt->gap = 0;
+ }
+
+ // advance write head
+ _varchunk_write_advance_raw(varchunk, head,
+ varchunk->gapd + sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(written));
+}
+
+static inline void
+_varchunk_read_advance_raw(varchunk_t *varchunk, size_t tail, size_t read)
+{
+ // only consumer is allowed to advance read tail
+ const size_t new_tail = (tail + read) & varchunk->mask;
+ atomic_store_explicit(&varchunk->tail, new_tail, varchunk->release);
+}
+
+static inline const void *
+varchunk_read_request(varchunk_t *varchunk, size_t *toread)
+{
+ assert(varchunk);
+ size_t space; // size of available buffer
+ const size_t tail = atomic_load_explicit(&varchunk->tail, memory_order_relaxed); // read tail
+ const size_t head = atomic_load_explicit(&varchunk->head, varchunk->acquire); // read head (producer modifies it any time)
+
+ // calculate readable space
+ if(head > tail)
+ space = head - tail;
+ else
+ space = (head - tail + varchunk->size) & varchunk->mask;
+
+ if(space > 0) // there may be chunks available for reading
+ {
+ const size_t end = tail + space; // virtual end of available buffer
+
+ if(end > varchunk->size) // available buffer wraps around at end
+ {
+ // first part of available buffer
+ const uint8_t *buf1 = varchunk->buf + tail;
+ const size_t len1 = varchunk->size - tail;
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)buf1;
+
+ if(elmnt->gap) // gap elmnt?
+ {
+ // skip gap
+ _varchunk_read_advance_raw(varchunk, tail, len1);
+
+ // second part of available buffer
+ const uint8_t *buf2 = varchunk->buf;
+ // there will always be at least on element after a gap
+ elmnt = (const varchunk_elmnt_t *)buf2;
+
+ *toread = elmnt->size;
+ return buf2 + sizeof(varchunk_elmnt_t);
+ }
+ else // valid chunk, use it!
+ {
+ *toread = elmnt->size;
+ return buf1 + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // available buffer is contiguous
+ {
+ // get buffer
+ const uint8_t *buf = varchunk->buf + tail;
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)buf;
+
+ *toread = elmnt->size;
+ return buf + sizeof(varchunk_elmnt_t);
+ }
+ }
+ else // no chunks available aka empty buffer
+ {
+ *toread = 0;
+ return NULL;
+ }
+}
+
+static inline void
+varchunk_read_advance(varchunk_t *varchunk)
+{
+ assert(varchunk);
+ // get elmnt header from tail (for size)
+ const size_t tail = atomic_load_explicit(&varchunk->tail, memory_order_relaxed);
+ const varchunk_elmnt_t *elmnt = (const varchunk_elmnt_t *)(varchunk->buf + tail);
+
+ // advance read tail
+ _varchunk_read_advance_raw(varchunk, tail,
+ sizeof(varchunk_elmnt_t) + VARCHUNK_PAD(elmnt->size));
+}
+
+#undef VARCHUNK_PAD
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_VARCHUNK_H