aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml79
-rw-r--r--ChangeLog67
-rw-r--r--README.md91
-rw-r--r--VERSION2
-rw-r--r--atom_inspector.c341
-rw-r--r--atom_inspector_nk.c472
-rw-r--r--encoder.h39
-rw-r--r--encoder.l212
-rw-r--r--gitlab-ci/generic.yml106
-rw-r--r--manifest.ttl.in62
-rw-r--r--meson.build160
-rw-r--r--meson_options.txt7
-rw-r--r--midi_inspector.c266
-rw-r--r--midi_inspector_nk.c538
-rw-r--r--osc.lv2/.gitlab-ci.yml2
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md33
-rw-r--r--osc.lv2/VERSION1
-rw-r--r--osc.lv2/gitlab-ci/generic.yml106
-rw-r--r--osc.lv2/lv2-osc.doap.ttl40
-rw-r--r--osc.lv2/manifest.ttl23
-rw-r--r--osc.lv2/meson.build36
-rw-r--r--osc.lv2/osc.lv2/endian.h120
-rw-r--r--osc.lv2/osc.lv2/forge.h474
-rw-r--r--osc.lv2/osc.lv2/osc.h192
-rw-r--r--osc.lv2/osc.lv2/reader.h629
-rw-r--r--osc.lv2/osc.lv2/stream.h1433
-rw-r--r--osc.lv2/osc.lv2/util.h631
-rw-r--r--osc.lv2/osc.lv2/writer.h579
-rw-r--r--osc.lv2/osc.ttl42
-rw-r--r--osc.lv2/test/osc_test.c1373
-rw-r--r--osc_inspector.c270
-rw-r--r--osc_inspector_nk.c446
-rw-r--r--props.lv2/.gitlab-ci.yml2
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/VERSION1
-rw-r--r--props.lv2/gitlab-ci/generic.yml106
-rw-r--r--props.lv2/meson.build82
-rw-r--r--props.lv2/props.h1226
-rw-r--r--props.lv2/test/chunk.binbin0 -> 16 bytes
-rw-r--r--props.lv2/test/manifest.ttl.in28
-rw-r--r--props.lv2/test/props.c323
-rw-r--r--props.lv2/test/props.ttl151
-rw-r--r--props.lv2/test/props_test.c647
-rw-r--r--screenshots/screenshot_1.pngbin0 -> 271523 bytes
-rw-r--r--screenshots/screenshot_2.pngbin0 -> 154677 bytes
-rw-r--r--screenshots/screenshot_3.pngbin0 -> 94754 bytes
-rw-r--r--ser_atom.lv2/COPYING201
-rw-r--r--ser_atom.lv2/README.md24
-rw-r--r--ser_atom.lv2/ser_atom.lv2/ser_atom.h215
-rw-r--r--ser_atom.lv2/test/Makefile13
-rw-r--r--ser_atom.lv2/test/ser_atom_test.c136
-rw-r--r--sherlock.c39
-rw-r--r--sherlock.h134
-rw-r--r--sherlock.ttl280
-rw-r--r--sherlock_nk.c713
-rw-r--r--sherlock_nk.h145
-rw-r--r--sherlock_ui.ttl66
-rw-r--r--subprojects/nk_pugl/.gitlab-ci.yml77
-rw-r--r--subprojects/nk_pugl/COPYING201
-rw-r--r--subprojects/nk_pugl/VERSION1
-rw-r--r--subprojects/nk_pugl/example/example.c (renamed from example/example.c)0
-rw-r--r--subprojects/nk_pugl/meson.build83
-rw-r--r--subprojects/nk_pugl/meson_options.txt4
-rw-r--r--subprojects/nk_pugl/nk_pugl/nk_pugl.h (renamed from nk_pugl/nk_pugl.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitattributes (renamed from nuklear/.gitattributes)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitignore (renamed from nuklear/.gitignore)0
-rw-r--r--subprojects/nk_pugl/nuklear/.gitmodules (renamed from nuklear/.gitmodules)0
-rw-r--r--subprojects/nk_pugl/nuklear/.travis.yml (renamed from nuklear/.travis.yml)0
-rw-r--r--subprojects/nk_pugl/nuklear/Readme.md (renamed from nuklear/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h (renamed from nuklear/demo/allegro5/KeyboardHandleriOS.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m (renamed from nuklear/demo/allegro5/KeyboardHandleriOS.m)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/Makefile (renamed from nuklear/demo/allegro5/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md (renamed from nuklear/demo/allegro5/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/main.c (renamed from nuklear/demo/allegro5/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h (renamed from nuklear/demo/allegro5/nuklear_allegro5.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/calculator.c (renamed from nuklear/demo/calculator.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/build.bat (renamed from nuklear/demo/d3d11/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/main.c (renamed from nuklear/demo/d3d11/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h (renamed from nuklear/demo/d3d11/nuklear_d3d11.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl (renamed from nuklear/demo/d3d11/nuklear_d3d11.hlsl)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h (renamed from nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h (renamed from nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/build.bat (renamed from nuklear/demo/d3d9/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/main.c (renamed from nuklear/demo/d3d9/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h (renamed from nuklear/demo/d3d9/nuklear_d3d9.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/build.bat (renamed from nuklear/demo/gdi/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/main.c (renamed from nuklear/demo/gdi/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h (renamed from nuklear/demo/gdi/nuklear_gdi.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/build.bat (renamed from nuklear/demo/gdip/build.bat)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/main.c (renamed from nuklear/demo/gdip/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h (renamed from nuklear/demo/gdip/nuklear_gdip.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile (renamed from nuklear/demo/glfw_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c (renamed from nuklear/demo/glfw_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h (renamed from nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile (renamed from nuklear/demo/glfw_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c (renamed from nuklear/demo/glfw_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h (renamed from nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/node_editor.c (renamed from nuklear/demo/node_editor.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/overview.c (renamed from nuklear/demo/overview.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile (renamed from nuklear/demo/sdl_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c (renamed from nuklear/demo/sdl_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h (renamed from nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile (renamed from nuklear/demo/sdl_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c (renamed from nuklear/demo/sdl_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h (renamed from nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile (renamed from nuklear/demo/sdl_opengles2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c (renamed from nuklear/demo/sdl_opengles2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h (renamed from nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile (renamed from nuklear/demo/sfml_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md (renamed from nuklear/demo/sfml_opengl2/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp (renamed from nuklear/demo/sfml_opengl2/main.cpp)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h (renamed from nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile (renamed from nuklear/demo/sfml_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md (renamed from nuklear/demo/sfml_opengl3/Readme.md)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp (renamed from nuklear/demo/sfml_opengl3/main.cpp)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h (renamed from nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/style.c (renamed from nuklear/demo/style.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/Makefile (renamed from nuklear/demo/x11/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/main.c (renamed from nuklear/demo/x11/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h (renamed from nuklear/demo/x11/nuklear_xlib.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile (renamed from nuklear/demo/x11_opengl2/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c (renamed from nuklear/demo/x11_opengl2/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h (renamed from nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile (renamed from nuklear/demo/x11_opengl3/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c (renamed from nuklear/demo/x11_opengl3/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h (renamed from nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile (renamed from nuklear/demo/x11_rawfb/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c (renamed from nuklear/demo/x11_rawfb/main.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h (renamed from nuklear/demo/x11_rawfb/nuklear_rawfb.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h (renamed from nuklear/demo/x11_rawfb/nuklear_xlib.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/Makefile (renamed from nuklear/doc/Makefile)0
-rwxr-xr-xsubprojects/nk_pugl/nuklear/doc/build.sh (renamed from nuklear/doc/build.sh)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/nuklear.html (renamed from nuklear/doc/nuklear.html)0
-rw-r--r--subprojects/nk_pugl/nuklear/doc/stddoc.c (renamed from nuklear/doc/stddoc.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/Makefile (renamed from nuklear/example/Makefile)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/canvas.c (renamed from nuklear/example/canvas.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/extended.c (renamed from nuklear/example/extended.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/file_browser.c (renamed from nuklear/example/file_browser.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/checked.png (renamed from nuklear/example/icon/checked.png)bin1813 -> 1813 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/cloud.png (renamed from nuklear/example/icon/cloud.png)bin7509 -> 7509 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/computer.png (renamed from nuklear/example/icon/computer.png)bin620 -> 620 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/copy.png (renamed from nuklear/example/icon/copy.png)bin655 -> 655 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/default.png (renamed from nuklear/example/icon/default.png)bin460 -> 460 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/delete.png (renamed from nuklear/example/icon/delete.png)bin11040 -> 11040 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/desktop.png (renamed from nuklear/example/icon/desktop.png)bin583 -> 583 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/directory.png (renamed from nuklear/example/icon/directory.png)bin533 -> 533 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/edit.png (renamed from nuklear/example/icon/edit.png)bin14998 -> 14998 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/export.png (renamed from nuklear/example/icon/export.png)bin13336 -> 13336 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/font.png (renamed from nuklear/example/icon/font.png)bin561 -> 561 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/home.png (renamed from nuklear/example/icon/home.png)bin819 -> 819 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/img.png (renamed from nuklear/example/icon/img.png)bin648 -> 648 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/movie.png (renamed from nuklear/example/icon/movie.png)bin626 -> 626 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/music.png (renamed from nuklear/example/icon/music.png)bin610 -> 610 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/next.png (renamed from nuklear/example/icon/next.png)bin703 -> 703 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/pause.png (renamed from nuklear/example/icon/pause.png)bin1338 -> 1338 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/pen.png (renamed from nuklear/example/icon/pen.png)bin5949 -> 5949 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/phone.png (renamed from nuklear/example/icon/phone.png)bin15778 -> 15778 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/plane.png (renamed from nuklear/example/icon/plane.png)bin13546 -> 13546 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/play.png (renamed from nuklear/example/icon/play.png)bin566 -> 566 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/prev.png (renamed from nuklear/example/icon/prev.png)bin701 -> 701 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/rocket.png (renamed from nuklear/example/icon/rocket.png)bin1121 -> 1121 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/settings.png (renamed from nuklear/example/icon/settings.png)bin15671 -> 15671 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/stop.png (renamed from nuklear/example/icon/stop.png)bin520 -> 520 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/text.png (renamed from nuklear/example/icon/text.png)bin601 -> 601 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/tools.png (renamed from nuklear/example/icon/tools.png)bin24483 -> 24483 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/unchecked.png (renamed from nuklear/example/icon/unchecked.png)bin1044 -> 1044 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/volume.png (renamed from nuklear/example/icon/volume.png)bin25438 -> 25438 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/icon/wifi.png (renamed from nuklear/example/icon/wifi.png)bin18857 -> 18857 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image1.png (renamed from nuklear/example/images/image1.png)bin42882 -> 42882 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image2.png (renamed from nuklear/example/images/image2.png)bin5671 -> 5671 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image3.png (renamed from nuklear/example/images/image3.png)bin131502 -> 131502 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image4.png (renamed from nuklear/example/images/image4.png)bin185821 -> 185821 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image5.png (renamed from nuklear/example/images/image5.png)bin98475 -> 98475 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image6.png (renamed from nuklear/example/images/image6.png)bin35633 -> 35633 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image7.png (renamed from nuklear/example/images/image7.png)bin13960 -> 13960 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image8.png (renamed from nuklear/example/images/image8.png)bin45987 -> 45987 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/images/image9.png (renamed from nuklear/example/images/image9.png)bin30759 -> 30759 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/skinning.c (renamed from nuklear/example/skinning.c)0
-rw-r--r--subprojects/nk_pugl/nuklear/example/skins/gwen.png (renamed from nuklear/example/skins/gwen.png)bin24565 -> 24565 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/example/stb_image.h (renamed from nuklear/example/stb_image.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf (renamed from nuklear/extra_font/Cousine-Regular.ttf)bin43912 -> 43912 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf (renamed from nuklear/extra_font/DroidSans.ttf)bin190044 -> 190044 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf (renamed from nuklear/extra_font/Karla-Regular.ttf)bin16848 -> 16848 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf (renamed from nuklear/extra_font/ProggyClean.ttf)bin41208 -> 41208 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf (renamed from nuklear/extra_font/ProggyTiny.ttf)bin35656 -> 35656 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf (renamed from nuklear/extra_font/Raleway-Bold.ttf)bin176280 -> 176280 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf (renamed from nuklear/extra_font/Roboto-Bold.ttf)bin135820 -> 135820 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf (renamed from nuklear/extra_font/Roboto-Light.ttf)bin140276 -> 140276 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf (renamed from nuklear/extra_font/Roboto-Regular.ttf)bin145348 -> 145348 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf (renamed from nuklear/extra_font/kenvector_future.ttf)bin34136 -> 34136 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf (renamed from nuklear/extra_font/kenvector_future_thin.ttf)bin34100 -> 34100 bytes
-rw-r--r--subprojects/nk_pugl/nuklear/nuklear.h (renamed from nuklear/nuklear.h)0
-rw-r--r--subprojects/nk_pugl/nuklear/package.json (renamed from nuklear/package.json)0
-rw-r--r--subprojects/nk_pugl/pugl/.clang-format (renamed from pugl/.clang-format)0
-rw-r--r--subprojects/nk_pugl/pugl/.clang-tidy (renamed from pugl/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/.clant.json (renamed from pugl/.clant.json)0
-rw-r--r--subprojects/nk_pugl/pugl/.editorconfig (renamed from pugl/.editorconfig)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitattributes (renamed from pugl/.gitattributes)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitignore (renamed from pugl/.gitignore)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitlab-ci.yml (renamed from pugl/.gitlab-ci.yml)0
-rw-r--r--subprojects/nk_pugl/pugl/.gitmodules (renamed from pugl/.gitmodules)0
-rw-r--r--subprojects/nk_pugl/pugl/.includes.imp (renamed from pugl/.includes.imp)0
-rw-r--r--subprojects/nk_pugl/pugl/AUTHORS (renamed from pugl/AUTHORS)0
-rw-r--r--subprojects/nk_pugl/pugl/COPYING (renamed from pugl/COPYING)0
-rw-r--r--subprojects/nk_pugl/pugl/README.md (renamed from pugl/README.md)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy (renamed from pugl/bindings/cxx/include/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp (renamed from pugl/bindings/cxx/include/pugl/cairo.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp (renamed from pugl/bindings/cxx/include/pugl/gl.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp (renamed from pugl/bindings/cxx/include/pugl/pugl.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp (renamed from pugl/bindings/cxx/include/pugl/stub.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp (renamed from pugl/bindings/cxx/include/pugl/vulkan.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/_static/custom.css (renamed from pugl/doc/_static/custom.css)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/_templates/about.html (renamed from pugl/doc/_templates/about.html)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/Doxyfile (renamed from pugl/doc/c/Doxyfile)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/index.rst (renamed from pugl/doc/c/index.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/overview.rst (renamed from pugl/doc/c/overview.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/reference.rst (renamed from pugl/doc/c/reference.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/c/wscript (renamed from pugl/doc/c/wscript)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/conf.py.in (renamed from pugl/doc/conf.py.in)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/Doxyfile (renamed from pugl/doc/cpp/Doxyfile)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/c-reference.rst (renamed from pugl/doc/cpp/c-reference.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/cpp-reference.rst (renamed from pugl/doc/cpp/cpp-reference.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/index.rst (renamed from pugl/doc/cpp/index.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/overview.rst (renamed from pugl/doc/cpp/overview.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/cpp/wscript (renamed from pugl/doc/cpp/wscript)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/deployment.rst (renamed from pugl/doc/deployment.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/mainpage.md (renamed from pugl/doc/mainpage.md)0
-rw-r--r--subprojects/nk_pugl/pugl/doc/pugl.rst (renamed from pugl/doc/pugl.rst)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/.clang-tidy (renamed from pugl/examples/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/cube_view.h (renamed from pugl/examples/cube_view.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/demo_utils.h (renamed from pugl/examples/demo_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/file_utils.c (renamed from pugl/examples/file_utils.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/file_utils.h (renamed from pugl/examples/file_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/glad.c (renamed from pugl/examples/glad/glad.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/glad.h (renamed from pugl/examples/glad/glad.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/glad/khrplatform.h (renamed from pugl/examples/glad/khrplatform.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c (renamed from pugl/examples/pugl_cairo_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c (renamed from pugl/examples/pugl_cursor_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp (renamed from pugl/examples/pugl_cxx_demo.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c (renamed from pugl/examples/pugl_embed_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_print_events.c (renamed from pugl/examples/pugl_print_events.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c (renamed from pugl/examples/pugl_shader_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp (renamed from pugl/examples/pugl_vulkan_cxx_demo.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c (renamed from pugl/examples/pugl_vulkan_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/pugl_window_demo.c (renamed from pugl/examples/pugl_window_demo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/rects.h (renamed from pugl/examples/rects.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shader_utils.h (renamed from pugl/examples/shader_utils.h)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl (renamed from pugl/examples/shaders/header_330.glsl)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl (renamed from pugl/examples/shaders/header_420.glsl)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/rect.frag (renamed from pugl/examples/shaders/rect.frag)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/shaders/rect.vert (renamed from pugl/examples/shaders/rect.vert)0
-rw-r--r--subprojects/nk_pugl/pugl/examples/sybok.hpp (renamed from pugl/examples/sybok.hpp)0
-rw-r--r--subprojects/nk_pugl/pugl/include/.clang-tidy (renamed from pugl/include/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/cairo.h (renamed from pugl/include/pugl/cairo.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/gl.h (renamed from pugl/include/pugl/gl.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/pugl.h (renamed from pugl/include/pugl/pugl.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/stub.h (renamed from pugl/include/pugl/stub.h)0
-rw-r--r--subprojects/nk_pugl/pugl/include/pugl/vulkan.h (renamed from pugl/include/pugl/vulkan.h)0
-rw-r--r--subprojects/nk_pugl/pugl/pugl.pc.in (renamed from pugl/pugl.pc.in)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/Info.plist.in (renamed from pugl/resources/Info.plist.in)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.ipe (renamed from pugl/resources/pugl.ipe)0
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.png (renamed from pugl/resources/pugl.png)bin2641 -> 2641 bytes
-rw-r--r--subprojects/nk_pugl/pugl/resources/pugl.svg (renamed from pugl/resources/pugl.svg)0
-rwxr-xr-xsubprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py (renamed from pugl/scripts/dox_to_sphinx.py)0
-rw-r--r--subprojects/nk_pugl/pugl/src/.clang-tidy (renamed from pugl/src/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/src/implementation.c (renamed from pugl/src/implementation.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/implementation.h (renamed from pugl/src/implementation.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac.h (renamed from pugl/src/mac.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac.m (renamed from pugl/src/mac.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_cairo.m (renamed from pugl/src/mac_cairo.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_gl.m (renamed from pugl/src/mac_gl.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_stub.m (renamed from pugl/src/mac_stub.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/mac_vulkan.m (renamed from pugl/src/mac_vulkan.m)0
-rw-r--r--subprojects/nk_pugl/pugl/src/stub.h (renamed from pugl/src/stub.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/types.h (renamed from pugl/src/types.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win.c (renamed from pugl/src/win.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win.h (renamed from pugl/src/win.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_cairo.c (renamed from pugl/src/win_cairo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_gl.c (renamed from pugl/src/win_gl.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_stub.c (renamed from pugl/src/win_stub.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/win_vulkan.c (renamed from pugl/src/win_vulkan.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11.c (renamed from pugl/src/x11.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11.h (renamed from pugl/src/x11.h)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_cairo.c (renamed from pugl/src/x11_cairo.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_gl.c (renamed from pugl/src/x11_gl.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_stub.c (renamed from pugl/src/x11_stub.c)0
-rw-r--r--subprojects/nk_pugl/pugl/src/x11_vulkan.c (renamed from pugl/src/x11_vulkan.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/.clang-tidy (renamed from pugl/test/.clang-tidy)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_build.c (renamed from pugl/test/test_build.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_build.cpp (renamed from pugl/test/test_build.cpp)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_clipboard.c (renamed from pugl/test/test_clipboard.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_gl_hints.c (renamed from pugl/test/test_gl_hints.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_realize.c (renamed from pugl/test/test_realize.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_redisplay.c (renamed from pugl/test/test_redisplay.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_show_hide.c (renamed from pugl/test/test_show_hide.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_stub_hints.c (renamed from pugl/test/test_stub_hints.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_timer.c (renamed from pugl/test/test_timer.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_update.c (renamed from pugl/test/test_update.c)0
-rw-r--r--subprojects/nk_pugl/pugl/test/test_utils.h (renamed from pugl/test/test_utils.h)0
-rwxr-xr-xsubprojects/nk_pugl/pugl/waf (renamed from pugl/waf)0
-rw-r--r--subprojects/nk_pugl/pugl/wscript (renamed from pugl/wscript)0
303 files changed, 14058 insertions, 134 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 62aa6f8..979769c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,77 +1,2 @@
-stages:
- - build
- - deploy
-
-.variables_template: &variables_definition
- variables:
- BASE_NAME: "nk_pugl"
- PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
-
-.common_template: &common_definition
- <<: *variables_definition
- stage: build
- artifacts:
- name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
- paths:
- - "${BASE_NAME}-$(cat VERSION)/"
-
-.build_template: &build_definition
- <<: *common_definition
- script:
- - meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
- - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' build/build.ninja
- - ninja -C build
- - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
-
-.analyze_template: &analyze_definition
- <<: *common_definition
- script:
- - meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
- - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' build/build.ninja
- - ninja -C build
- - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
-
- - CC=clang CXX=clang++ meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
- - ninja -C clang
-
- - scan-build --status-bugs meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
- - scan-build --status-bugs ninja -C scanbuild
-
-.universal_linux_template: &universal_linux_definition
- image: ventosus/universal-linux-gnu
- <<: *analyze_definition
-
-.arm_linux_template: &arm_linux_definition
- image: ventosus/arm-linux-gnueabihf
- <<: *build_definition
-
-# building in docker
-x86_64-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev libevdev-dev
- <<: *universal_linux_definition
-
-i686-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386
- <<: *universal_linux_definition
-
-arm-linux-gnueabihf:
- before_script:
- - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf
- <<: *arm_linux_definition
-
-aarch64-linux-gnu:
- before_script:
- - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64
- <<: *arm_linux_definition
-
-pack:
- <<: *variables_definition
- stage: deploy
- script:
- - echo 'packing up...'
- artifacts:
- name: "${BASE_NAME}-$(cat VERSION)"
- paths:
- - "${BASE_NAME}-$(cat VERSION)/"
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..b0bc2b1
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,67 @@
+# Changelog
+
+## [0.26.0] - 15 Jan 2021
+
+### Added
+
+* support for host provided scaling factor
+* support for notifying host about changed state
+
+## [0.24.0] - 15 Jul 2020
+
+### Added
+
+* support for GL double buffering
+
+## [0.22.0] - 13 Apr 2020
+
+### Added
+
+* reporting of decimal note/controller numbers in midi inspector
+
+### Fixed
+
+* scrolling in atom inspector TTL preview
+
+### Changed
+
+* to build with pugl master
+
+## [0.20.0] - 13 Oct 2019
+
+### Added
+
+* sherlock:matchAll as default filter URI in atom inspector
+* filter buttons for sherlock:matchAll and time:Position in atom inspector
+
+### Changed
+
+* position of negate toggle in atom inspector
+
+### Fixed
+
+* filter logic in atom inspector
+
+## [0.18.0] - 15 Apr 2019
+
+### Added
+
+* ui resize extension
+* tracing to host's log for atom_inspector
+* filtering by URID for atom_inspector
+
+### Changed
+
+* build system from CMake to meson
+* ui checkboxes
+* routing only OSC messages/bundles to ui
+* style to always light color row after frame row
+* put OSC arguments on separate rows
+
+### Fixed
+
+* automatic scaling on hiDPI displays
+* end-of-line handling in turtle lexer
+* atom item sizes reporting
+* turtle pretty number configuration switching
+* strobing shadowing when in non-following mode
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..aaa13ad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+## Sherlock
+
+### An investigative LV2 plugin bundle
+
+This plugin bundle contains plugins for visualizing LV2 atom, MIDI and
+OSC events.
+
+Use them for monitoring and debugging of event signal flows inside plugin graphs.
+
+#### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/sherlock.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/sherlock.lv2/commits/master)
+
+### Binaries
+
+For GNU/Linux (64-bit, 32-bit, armv7, arm64).
+
+To install the plugin bundle on your system, simply copy the __sherlock.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
+
+* [sherlock.lv2-0.26.0.zip](https://dl.open-music-kontrollers.ch/sherlock.lv2/stable/sherlock.lv2-0.26.0.zip) ([sig](https://dl.open-music-kontrollers.ch/sherlock.lv2/stable/sherlock.lv2-0.26.0.zip.sig))
+
+#### Unstable (nightly) release
+
+* [sherlock.lv2-latest-unstable.zip](https://dl.open-music-kontrollers.ch/sherlock.lv2/unstable/sherlock.lv2-latest-unstable.zip) ([sig](https://dl.open-music-kontrollers.ch/sherlock.lv2/unstable/sherlock.lv2-latest-unstable.zip.sig))
+
+### Sources
+
+#### Stable release
+
+* [sherlock.lv2-0.26.0.tar.xz](https://git.open-music-kontrollers.ch/lv2/sherlock.lv2/snapshot/sherlock.lv2-0.26.0.tar.xz)
+([sig](https://git.open-music-kontrollers.ch/lv2/sherlock.lv2/snapshot/sherlock.lv2-0.26.0.tar.xz.asc))
+
+#### Git repository
+
+* <https://git.open-music-kontrollers.ch/lv2/sherlock.lv2>
+
+### Packages
+
+* [ArchLinux](https://www.archlinux.org/packages/community/x86_64/sherlock.lv2/)
+
+### Bugs and feature requests
+
+* [Gitlab](https://gitlab.com/OpenMusicKontrollers/sherlock.lv2)
+* [Github](https://github.com/OpenMusicKontrollers/sherlock.lv2)
+
+### Plugins
+
+#### Atom Inspector
+
+![Sherlock Atom Inspector](/screenshots/screenshot_1.png)
+
+The _Atom Inspector_ is meant as a monitor/debug tool for LV2 plugin
+and host authors. It captures all Atom events sent to its event input port
+and presents them on its user interface for convenient nested browsing.
+
+#### MIDI Inspector
+
+![Sherlock MIDI Inspector](/screenshots/screenshot_2.png)
+
+The _MIDI Inspector_ is meant as a monitor/debug tool for LV2 plugin
+and host authors. It captures all MIDI events sent to its event input port
+and presents them on its user interface for convenient nested browsing.
+
+#### OSC Inspector
+
+![Sherlock OSC Inspector](/screenshots/screenshot_3.png)
+
+The _OSC Inspector_ is meant as a monitor/debug tool for LV2 plugin
+and host authors. It captures all OSC events sent to its event input port
+and presents them on its user interface for convenient nested browsing.
+
+#### License
+
+Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/VERSION b/VERSION
index d3bfa59..b38e1e7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.173
+0.27.3
diff --git a/atom_inspector.c b/atom_inspector.c
new file mode 100644
index 0000000..817c31d
--- /dev/null
+++ b/atom_inspector.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2015-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 <sherlock.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ craft_t through;
+ craft_t notify;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+ LV2_URID sherlock_matchAll;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate __attribute__((unused)),
+ const char *bundle_path __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(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_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "%s: Host does not support urid:(un)map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+ handle->sherlock_matchAll = handle->map->map(handle->map->handle, SHERLOCK_URI"#matchAll");
+
+ lv2_atom_forge_init(&handle->through.forge, handle->map);
+ lv2_atom_forge_init(&handle->notify.forge, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->through.seq = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify.seq = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ craft_t *through = &handle->through;
+ craft_t *notify = &handle->notify;
+
+ uint32_t capacity = through->seq->atom.size;
+ lv2_atom_forge_set_buffer(&through->forge, through->buf, capacity);
+ through->ref = lv2_atom_forge_sequence_head(&through->forge, &through->frame[0], 0);
+
+ capacity = notify->seq->atom.size;
+ lv2_atom_forge_set_buffer(&notify->forge, notify->buf, capacity);
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[0], 0);
+
+ props_idle(&handle->props, &notify->forge, 0, &notify->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ if(handle->state.trace && handle->log)
+ {
+ if(lv2_atom_forge_is_object_type(&through->forge, atom->type))
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", object, %s\n", ev->time.frames,
+ handle->unmap->unmap(handle->unmap->handle, obj->body.otype));
+ //FIXME introspect object?
+ }
+ else if(atom->type == through->forge.Bool)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", bool , %s\n", ev->time.frames,
+ ((const LV2_Atom_Bool *)atom)->body ? "true" : "false");
+ }
+ else if(atom->type == through->forge.Int)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", int32 , %"PRIi32"\n", ev->time.frames,
+ ((const LV2_Atom_Int *)atom)->body);
+ }
+ else if(atom->type == through->forge.Long)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", int64 , %"PRIi64"\n", ev->time.frames,
+ ((const LV2_Atom_Long *)atom)->body);
+ }
+ else if(atom->type == through->forge.Float)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", flt32 , %f\n", ev->time.frames,
+ ((const LV2_Atom_Float *)atom)->body);
+ }
+ else if(atom->type == through->forge.Double)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", flt64 , %lf\n", ev->time.frames,
+ ((const LV2_Atom_Double *)atom)->body);
+ }
+ else if(atom->type == through->forge.String)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", urid , %s\n", ev->time.frames,
+ handle->unmap->unmap(handle->unmap->handle, ((const LV2_Atom_URID *)atom)->body));
+ }
+ else if(atom->type == through->forge.String)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", string, %s\n", ev->time.frames,
+ (const char *)LV2_ATOM_BODY_CONST(atom));
+ }
+ else if(atom->type == through->forge.URI)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", uri , %s\n", ev->time.frames,
+ (const char *)LV2_ATOM_BODY_CONST(atom));
+ }
+ else if(atom->type == through->forge.Path)
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", path , %s\n", ev->time.frames,
+ (const char *)LV2_ATOM_BODY_CONST(atom));
+ }
+ //FIXME more types
+ else
+ {
+ lv2_log_trace(&handle->logger, "%4"PRIi64", %s\n", ev->time.frames,
+ handle->unmap->unmap(handle->unmap->handle, atom->type));
+ }
+ }
+
+ // copy all events to through port
+ if(through->ref)
+ through->ref = lv2_atom_forge_frame_time(&through->forge, frames);
+ if(through->ref)
+ through->ref = lv2_atom_forge_raw(&through->forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(through->ref)
+ lv2_atom_forge_pad(&through->forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, &notify->forge, frames, obj, &notify->ref)
+ && lv2_atom_forge_is_object_type(&notify->forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(through->ref)
+ lv2_atom_forge_pop(&through->forge, &through->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(through->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "through buffer overflow\n");
+ }
+
+ bool has_event = notify->seq->atom.size > sizeof(LV2_Atom_Sequence_Body);
+
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, nsamples-1);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_tuple(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_long(&notify->forge, handle->frame);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_int(&notify->forge, nsamples);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[2], 0);
+
+ // only serialize filtered events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ bool type_matches;
+
+ if(handle->state.filter == handle->sherlock_matchAll)
+ {
+ type_matches = true;
+ }
+ else
+ {
+ type_matches = lv2_atom_forge_is_object_type(&notify->forge, obj->atom.type)
+ ? (obj->body.otype == handle->state.filter)
+ : (obj->atom.type == handle->state.filter);
+ }
+
+ if( (!handle->state.negate && type_matches)
+ || (handle->state.negate && !type_matches) )
+ {
+ has_event = true;
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, ev->time.frames);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_write(&notify->forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[2]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(notify->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "notify buffer overflow\n");
+ }
+
+ if(!has_event) // don't send anything
+ lv2_atom_sequence_clear(notify->seq);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)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)
+{
+ handle_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)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor atom_inspector = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/atom_inspector_nk.c b/atom_inspector_nk.c
new file mode 100644
index 0000000..4e17380
--- /dev/null
+++ b/atom_inspector_nk.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2015-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 <ctype.h>
+#include <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+#include <encoder.h>
+
+#ifdef Bool // hack for xlib
+# undef Bool
+#endif
+
+#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RDFS (const uint8_t*)"http://www.w3.org/2000/01/rdf-schema#"
+#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#"
+#define NS_OSC (const uint8_t*)"http://open-music-kontrollers.ch/lv2/osc#"
+#define NS_XPRESS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/xpress#"
+#define NS_SPOD (const uint8_t*)"http://open-music-kontrollers.ch/lv2/synthpod#"
+#define NS_CANVAS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/canvas#"
+
+// copyied and adapted from libsratom
+static inline char *
+_sratom_to_turtle(Sratom* sratom,
+ LV2_URID_Unmap* unmap,
+ const char* base_uri,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ uint32_t type,
+ uint32_t size,
+ const void* body)
+{
+ SerdURI buri = SERD_URI_NULL;
+ SerdNode base = serd_node_new_uri_from_string((uint8_t *)(base_uri), NULL, &buri);
+ SerdEnv* env = serd_env_new(&base);
+ SerdChunk str = { NULL, 0 };
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdf", NS_RDF);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdfs", NS_RDFS);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xsd", NS_XSD);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"lv2", (const uint8_t *)LV2_CORE_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"midi", (const uint8_t *)LV2_MIDI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"atom", (const uint8_t *)LV2_ATOM_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"units", (const uint8_t *)LV2_UNITS_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"ui", (const uint8_t *)LV2_UI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"time", (const uint8_t *)LV2_TIME_URI"#");
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"patch", (const uint8_t *)LV2_PATCH_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"state", (const uint8_t *)LV2_STATE_PREFIX);
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"osc", NS_OSC);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xpress", NS_XPRESS);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"spod", NS_SPOD);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"canvas", NS_CANVAS);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_ABBREVIATED |
+ SERD_STYLE_RESOLVED |
+ SERD_STYLE_CURIED |
+ SERD_STYLE_ASCII),
+ env, &buri, serd_chunk_sink, &str);
+
+ // Write @prefix directives
+ serd_env_foreach(env,
+ (SerdPrefixSink)serd_writer_set_prefix,
+ writer);
+
+ sratom_set_sink(sratom, base_uri,
+ (SerdStatementSink)serd_writer_write_statement,
+ (SerdEndSink)serd_writer_end_anon,
+ writer);
+ sratom_write(sratom, unmap, SERD_EMPTY_S,
+ subject, predicate, type, size, body);
+ serd_writer_finish(writer);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+ serd_node_free(&base);
+ return (char*)serd_chunk_sink_finish(&str);
+}
+
+static void
+_set_string(struct nk_str *str, uint32_t size, const char *body)
+{
+ nk_str_clear(str);
+
+ // replace tab with 2 spaces
+ const char *end = body + size - 1;
+ const char *from = body;
+ for(const char *to = strchr(from, '\t');
+ to && (to < end);
+ from = to + 1, to = strchr(from, '\t'))
+ {
+ nk_str_append_text_utf8(str, from, to-from);
+ nk_str_append_text_utf8(str, " ", 2);
+ }
+ nk_str_append_text_utf8(str, from, end-from);
+}
+
+static inline void
+_shadow(struct nk_context *ctx, bool *shadow)
+{
+ if(*shadow)
+ {
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 5*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+
+ *shadow = !*shadow;
+}
+
+void
+_atom_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ handle->dy = 20.f * _get_scale(handle);
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ style->selectable.normal.data.color.a = 0x0;
+ style->selectable.hover.data.color.a = 0x0;
+
+ const char *window_name = "Sherlock";
+ if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ struct nk_panel *panel= nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 2*window_padding.y;
+ nk_layout_row_dynamic(ctx, body_h, 2);
+ if(nk_group_begin(ctx, "Left", NK_WINDOW_NO_SCROLLBAR))
+ {
+ {
+ // has filter URID been updated meanwhile ?
+ if(handle->filter != handle->state.filter)
+ {
+ const char *uri = handle->unmap->unmap(handle->unmap->handle, handle->state.filter);
+
+ if(uri)
+ {
+ strncpy(handle->filter_uri, uri, sizeof(handle->filter_uri) - 1);
+ }
+ else
+ {
+ handle->filter_uri[0] = '\0';
+ }
+
+ handle->filter = handle->state.filter;
+ }
+
+ bool dirty = false;
+
+ const float n = 4;
+ const float r0 = 1.f / n;
+ const float r1 = 0.1f / 3;
+ const float r2 = r0 - r1;
+ const float footer [5] = {r1+r2, r1+r2, r1+r2, r1, r2};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 5, footer);
+ {
+ nk_label(ctx, "match:", NK_TEXT_LEFT);
+
+ if(nk_button_label(ctx, "time"))
+ {
+ strncpy(handle->filter_uri, LV2_TIME__Position, sizeof(handle->filter_uri) - 1);
+ dirty = true;
+ }
+
+ if(nk_button_label(ctx, "all"))
+ {
+ strncpy(handle->filter_uri, SHERLOCK_URI"#matchAll", sizeof(handle->filter_uri) - 1);
+ dirty = true;
+ }
+
+ const int32_t state_negate = _check(ctx, handle->state.negate);
+ if(state_negate != handle->state.negate)
+ {
+ handle->state.negate = state_negate;
+ _set_bool(handle, handle->urid.negate, handle->state.negate);
+ }
+ nk_label(ctx, "negate", NK_TEXT_LEFT);
+ }
+
+ nk_layout_row_dynamic(ctx, widget_h, 1);
+ {
+ const nk_flags flags = NK_EDIT_FIELD
+ | NK_EDIT_AUTO_SELECT
+ | NK_EDIT_SIG_ENTER;
+ //nk_edit_focus(ctx, flags);
+ nk_flags mode = nk_edit_string_zero_terminated(ctx, flags, handle->filter_uri, sizeof(handle->filter_uri) - 1, nk_filter_ascii);
+ if(mode & NK_EDIT_COMMITED)
+ {
+ if(strlen(handle->filter_uri) == 0)
+ {
+ strncpy(handle->filter_uri, SHERLOCK_URI"#matchAll", sizeof(handle->filter_uri) - 1);
+ }
+ dirty = true;
+ }
+ }
+
+ if(dirty)
+ {
+ handle->state.filter = handle->map->map(handle->map->handle, handle->filter_uri);
+ _set_urid(handle, handle->urid.filter, handle->state.filter);
+ }
+ }
+
+ const float content_h = nk_window_get_height(ctx) - 3*window_padding.y - 7*group_padding.y - 4*widget_h;
+ nk_layout_row_dynamic(ctx, content_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ {
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ }
+ else
+ {
+ handle->shadow = false;
+ }
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, never reached
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+
+ handle->shadow = false;
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom *body = &ev->body;
+ const int64_t frames = ev->time.frames;
+ const char *uri = NULL;
+ if(lv2_atom_forge_is_object_type(&handle->forge, body->type))
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)body;
+
+ if(obj->body.otype)
+ uri = handle->unmap->unmap(handle->unmap->handle, obj->body.otype);
+ else if(obj->body.id)
+ uri = handle->unmap->unmap(handle->unmap->handle, obj->body.id);
+ else
+ uri = "Unknown";
+ }
+ else // not an object
+ {
+ uri = handle->unmap->unmap(handle->unmap->handle, body->type);
+ }
+
+ const float entry [4] = {0.1, 0.65, 0.15, 0.1};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 4, entry);
+ {
+ _shadow(ctx, &handle->shadow);
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+
+ if(nk_select_label(ctx, uri, NK_TEXT_LEFT, handle->selected == body))
+ {
+ handle->ttl_dirty = handle->ttl_dirty
+ || (handle->selected != body); // has selection actually changed?
+ handle->selected = body;
+ }
+
+ if(body->type == handle->forge.Bool)
+ {
+ const LV2_Atom_Bool *ref = (const LV2_Atom_Bool *)body;
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%s", ref->body ? "true" : "false");
+ }
+ else if(body->type == handle->forge.Int)
+ {
+ const LV2_Atom_Int *ref = (const LV2_Atom_Int *)body;
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%"PRIi32, ref->body);
+ }
+ else if(body->type == handle->forge.Long)
+ {
+ const LV2_Atom_Long *ref = (const LV2_Atom_Long *)body;
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%"PRIi64, ref->body);
+ }
+ else if(body->type == handle->forge.Float)
+ {
+ const LV2_Atom_Float *ref = (const LV2_Atom_Float *)body;
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%f", ref->body);
+ }
+ else if(body->type == handle->forge.Double)
+ {
+ const LV2_Atom_Double *ref = (const LV2_Atom_Double *)body;
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%lf", ref->body);
+ }
+ /*
+ else if(body->type == handle->forge.String)
+ {
+ nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, red);
+ }
+ else if(body->type == handle->forge.URI)
+ {
+ nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, yellow);
+ }
+ else if(body->type == handle->forge.URID)
+ {
+ const LV2_Atom_URID *urid = (const LV2_Atom_URID *)body;
+ const char *_uri = handle->unmap->unmap(handle->unmap->handle, urid->body);
+ nk_label_colored(ctx, _uri, NK_TEXT_RIGHT, yellow);
+ }
+ else if(body->type == handle->forge.Path)
+ {
+ nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, red);
+ }
+ else if(body->type == handle->forge.Literal)
+ {
+ nk_label_colored(ctx, LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, body), NK_TEXT_RIGHT, red);
+ }
+ */
+ else
+ {
+ nk_spacing(ctx, 1);
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
+ }
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ const float n = 4;
+ const float r0 = 1.f / n;
+ const float r1 = 0.1f / 3;
+ const float r2 = r0 - r1;
+ const float footer [8] = {r1, r2, r1, r2, r1, r2, r1, r2};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 8, footer);
+ {
+ const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
+ if(state_overwrite != handle->state.overwrite)
+ {
+ handle->state.overwrite = state_overwrite;
+ _set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
+ }
+ nk_label(ctx, "overwrite", NK_TEXT_LEFT);
+
+ const int32_t state_block = _check(ctx, handle->state.block);
+ if(state_block != handle->state.block)
+ {
+ handle->state.block = state_block;
+ _set_bool(handle, handle->urid.block, handle->state.block);
+ }
+ nk_label(ctx, "block", NK_TEXT_LEFT);
+
+ const int32_t state_follow = _check(ctx, handle->state.follow);
+ if(state_follow != handle->state.follow)
+ {
+ handle->state.follow = state_follow;
+ _set_bool(handle, handle->urid.follow, handle->state.follow);
+ }
+ nk_label(ctx, "follow", NK_TEXT_LEFT);
+
+ const int32_t state_pretty = _check(ctx, handle->state.pretty);
+ if(state_pretty != handle->state.pretty)
+ {
+ handle->state.pretty = state_pretty;
+ _set_bool(handle, handle->urid.pretty, handle->state.pretty);
+
+ handle->ttl_dirty = true;
+ }
+ nk_label(ctx, "pretty", NK_TEXT_LEFT);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+
+ nk_group_end(ctx);
+ }
+
+ if(nk_group_begin(ctx, "Right", NK_WINDOW_NO_SCROLLBAR))
+ {
+ const LV2_Atom *atom = handle->selected;
+ if(handle->ttl_dirty && atom)
+ {
+ sratom_set_pretty_numbers(handle->sratom, handle->state.pretty);
+
+ char *ttl = _sratom_to_turtle(handle->sratom, handle->unmap,
+ handle->base_uri, NULL, NULL,
+ atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+ if(ttl)
+ {
+ struct nk_str *str = &handle->editor.string;
+ const size_t len = strlen(ttl);
+
+ _set_string(str, len, ttl);
+
+ handle->editor.lexer.needs_refresh = 1;
+
+ free(ttl);
+ }
+
+ handle->ttl_dirty = false;
+ }
+
+ const nk_flags flags = NK_EDIT_BOX;
+ int len = nk_str_len(&handle->editor.string);
+
+ if(len > 0) //FIXME
+ {
+ const float content_h = nk_window_get_height(ctx) - 2*window_padding.y - 2*group_padding.y;
+ nk_layout_row_dynamic(ctx, content_h, 1);
+ const nk_flags mode = nk_edit_buffer(ctx, flags, &handle->editor, nk_filter_default);
+ (void)mode;
+ }
+
+ nk_group_end(ctx);
+ }
+ }
+ nk_end(ctx);
+}
diff --git a/encoder.h b/encoder.h
new file mode 100644
index 0000000..baf3da4
--- /dev/null
+++ b/encoder.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015-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 ENCODER_H
+#define ENCODER_H
+
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+
+#define NK_PUGL_API
+#include "nk_pugl/nk_pugl.h"
+
+extern const struct nk_color cwhite;
+extern const struct nk_color gray;
+extern const struct nk_color yellow;
+extern const struct nk_color magenta;
+extern const struct nk_color green;
+extern const struct nk_color blue;
+extern const struct nk_color orange;
+extern const struct nk_color violet;
+extern const struct nk_color red;
+
+struct nk_token *
+ttl_lex(void *data, const char *utf8, int len);
+
+#endif
diff --git a/encoder.l b/encoder.l
new file mode 100644
index 0000000..89f3b12
--- /dev/null
+++ b/encoder.l
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2015-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 <string.h>
+
+#include <encoder.h>
+
+#define ful 0xff
+#define one 0xbb
+#define two 0x66
+#define non 0x0
+const struct nk_color cwhite = {.r = one, .g = one, .b = one, .a = ful};
+const struct nk_color gray = {.r = two, .g = two, .b = two, .a = ful};
+const struct nk_color yellow = {.r = one, .g = one, .b = non, .a = ful};
+const struct nk_color magenta = {.r = one, .g = two, .b = one, .a = ful};
+const struct nk_color green = {.r = non, .g = one, .b = non, .a = ful};
+const struct nk_color blue = {.r = non, .g = one, .b = one, .a = ful};
+const struct nk_color orange = {.r = one, .g = two, .b = non, .a = ful};
+const struct nk_color violet = {.r = two, .g = two, .b = one, .a = ful};
+const struct nk_color red = {.r = one, .g = non, .b = non, .a = ful};
+
+enum {
+ TK_NONE,
+ TK_PREFIX,
+ TK_SUBJECT,
+ TK_PREDICATE,
+ TK_BOOL,
+ TK_NUMBER,
+ TK_URI_IN,
+ TK_URI_OUT,
+ TK_URI_ERR,
+ TK_STRING_IN,
+ TK_STRING_OUT,
+ TK_STRING_ERR,
+ TK_LONG_STRING_IN,
+ TK_LONG_STRING_OUT,
+ TK_WHITESPACE,
+ TK_RAW,
+ TK_EOL
+};
+
+%}
+
+%option reentrant noyywrap
+
+w [ \v\a\t]+
+name [_a-zA-Z@][_a-zA-Z0-9\.]*
+n [0-9]+
+exp [Ee][+-]?{n}
+number ({n}|{n}[.]{n}){exp}?
+eol [\n\r]
+
+%x XSTRING
+%x XLONG_STRING
+%x XURI
+
+%%
+
+{w} return TK_WHITESPACE;
+"<" BEGIN(XURI); return TK_URI_IN;
+\"\"\" BEGIN(XLONG_STRING); return TK_LONG_STRING_IN;
+\" BEGIN(XSTRING); return TK_STRING_IN;
+{name}: return TK_SUBJECT;
+"@prefix" return TK_PREFIX;
+"a" return TK_PREFIX;
+true return TK_BOOL;
+false return TK_BOOL;
+{name} return TK_PREDICATE;
+{number} return TK_NUMBER;
+{eol} return TK_RAW;
+. return TK_RAW;
+
+<XURI>
+{
+ ">" BEGIN(0); return TK_URI_OUT;
+ {eol} BEGIN(0); return TK_URI_ERR;
+ . return TK_RAW;
+}
+
+<XLONG_STRING>
+{
+ \"\"\" BEGIN(0); return TK_LONG_STRING_OUT;
+ {eol} return TK_EOL;
+ . return TK_RAW;
+}
+
+<XSTRING>
+{
+ \\\" return TK_RAW;
+ \" BEGIN(0); return TK_STRING_OUT;
+ {eol} BEGIN(0); return TK_STRING_ERR;
+ . return TK_RAW;
+}
+
+%%
+
+struct nk_token *
+ttl_lex(void *data __attribute__((unused)), const char *utf8, int len)
+{
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+
+ enclex_init(&scanner);
+ if(utf8)
+ {
+ buf = enc_scan_bytes(utf8, len, scanner);
+ }
+ else
+ {
+ enclex_destroy(scanner);
+ return NULL;
+ }
+
+ struct nk_token *tokens = NULL;
+ int n_tokens = 0;
+
+ const char *base = encget_text(scanner);
+ int offset0 = 0;
+ struct nk_color col0 = {0xff, 0xff, 0xff, 0xff};
+
+ for(int tok=enclex(scanner); tok; tok=enclex(scanner))
+ {
+ const char *txt = encget_text(scanner);
+ const int offset1 = txt - base;
+ struct nk_color col1 = col0;
+
+ switch(tok)
+ {
+ case TK_PREFIX:
+ col1 = blue;
+ break;
+ case TK_SUBJECT:
+ col1 = magenta;
+ break;
+ case TK_PREDICATE:
+ col1 = orange;
+ break;
+ case TK_BOOL:
+ col1 = violet;
+ break;
+ case TK_NUMBER:
+ col1 = green;
+ break;
+ case TK_URI_IN:
+ case TK_URI_OUT:
+ case TK_URI_ERR:
+ col1 = yellow;
+ break;
+
+ case TK_STRING_IN:
+ case TK_STRING_OUT:
+ case TK_STRING_ERR:
+ case TK_LONG_STRING_IN:
+ case TK_LONG_STRING_OUT:
+ col1 = red;
+ break;
+
+ case TK_NONE:
+ case TK_WHITESPACE:
+ col1 = cwhite;
+ break;
+
+ case TK_EOL:
+ continue;
+
+ case TK_RAW:
+ default:
+ // skip over
+ break;
+ }
+
+ if(offset1)
+ {
+ tokens = realloc(tokens, (n_tokens + 1) * sizeof(struct nk_token));
+ tokens[n_tokens].offset = offset1;
+ tokens[n_tokens++].color = col0;
+ }
+
+ offset0 = offset1;
+ col0 = col1;
+ }
+
+ tokens = realloc(tokens, (n_tokens + 1) * sizeof(struct nk_token));
+ tokens[n_tokens].offset = len;
+ tokens[n_tokens++].color = (struct nk_color){0xff, 0xff, 0xff, 0xff};
+
+ enc_delete_buffer(buf, scanner);
+ enclex_destroy(scanner);
+
+ // to please compiler
+ (void)offset0;
+ (void)input;
+ (void)yyunput;
+
+ return tokens;
+}
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..5061e99
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,62 @@
+# Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <sherlock@MODULE_SUFFIX@> ;
+ ui:ui sherlock:atom_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:atom_inspector_4_nk
+ a ui:@UI_TYPE@ ;
+ ui:binary <sherlock_nk@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <sherlock@MODULE_SUFFIX@> ;
+ ui:ui sherlock:midi_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:midi_inspector_4_nk
+ a ui:@UI_TYPE@ ;
+ ui:binary <sherlock_nk@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <sherlock@MODULE_SUFFIX@> ;
+ ui:ui sherlock:osc_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:osc_inspector_4_nk
+ a ui:@UI_TYPE@ ;
+ ui:binary <sherlock_nk@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
diff --git a/meson.build b/meson.build
index 19b855f..2c5f7df 100644
--- a/meson.build
+++ b/meson.build
@@ -1,83 +1,139 @@
-project('nk_pugl', 'c', default_options : [
+project('sherlock.lv2', 'c', default_options : [
'buildtype=release',
'warning_level=3',
'werror=false',
'b_lto=false',
'c_std=gnu11'])
-build_examples = get_option('build-examples')
+nk_pugl = subproject('nk_pugl')
-static_link = false #meson.is_cross_build()
+lv2libdir = get_option('lv2libdir')
+
+inst_dir = join_paths(lv2libdir, meson.project_name())
+
+nk_pugl_dep = nk_pugl.get_variable('nk_pugl_gl')
+cousine_regular_ttf = nk_pugl.get_variable('cousine_regular_ttf')
+
+source_root = meson.source_root()
+build_root = meson.build_root()
+
+static_link = meson.is_cross_build()
cc = meson.get_compiler('c')
m_dep = cc.find_library('m')
-lv2_dep = dependency('lv2',
- version : '>=1.14.0')
-glew_dep = dependency('glew',
- version : '>=2.0.0',
- static : static_link)
-glu_dep = dependency('glu',
- version : '>=9.0.0',
- static : static_link)
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+sratom_dep = dependency('sratom-0', version : '>=0.6.0', static : static_link)
+
+dsp_deps = [m_dep, lv2_dep]
+ui_deps = [m_dep, lv2_dep, sratom_dep, nk_pugl_dep]
-deps = [m_dep, lv2_dep, glu_dep, glew_dep]
-links = []
+props_inc = include_directories('props.lv2')
+osc_inc = include_directories('osc.lv2')
+ser_inc = include_directories('ser_atom.lv2')
+nk_pugl_inc = include_directories(join_paths('subprojects', 'nk_pugl'))
+inc_dir = [props_inc, osc_inc, ser_inc, nk_pugl_inc]
-pugl_inc = include_directories(join_paths('pugl', 'include'))
-inc_dir = [pugl_inc]
+rawvers = run_command('cat', 'VERSION').stdout().strip()
+version = rawvers.split('.')
+conf_data = configuration_data()
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+add_project_arguments('-DSHERLOCK_VERSION="'+rawvers+'"', language : 'c')
add_project_arguments('-D_GNU_SOURCE', language : 'c')
-bin_srcs = [
- join_paths('example', 'example.c')
-]
+flex = find_program('flex')
+lgen = generator(flex,
+ output : '@PLAINNAME@.c',
+ arguments : ['--prefix=enc', '-o', '@OUTPUT@', '@INPUT@'])
+
+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)
+
+lfiles = lgen.process('encoder.l')
+
+dsp_srcs = ['sherlock.c',
+ 'atom_inspector.c',
+ 'midi_inspector.c',
+ 'osc_inspector.c']
-lib_srcs = [
- join_paths('pugl', 'src', 'implementation.c')
-]
+ui_srcs = ['sherlock_nk.c',
+ 'atom_inspector_nk.c',
+ 'midi_inspector_nk.c',
+ 'osc_inspector_nk.c',
+ lfiles]
-c_args = ['-fvisibility=hidden',
- '-ffast-math']
+c_args = [
+ '-fvisibility=hidden']
if host_machine.system() == 'windows'
- deps += cc.find_library('opengl32')
- deps += cc.find_library('gdi32')
- deps += cc.find_library('ws2_32')
- lib_srcs += join_paths('pugl', 'src', 'win.c')
- lib_srcs += join_paths('pugl', 'src', 'win_gl.c')
+ conf_data.set('UI_TYPE', 'WindowsUI')
elif host_machine.system() == 'darwin'
- add_languages('objc')
- links += ['-framework', 'OpenGL']
- links += ['-framework', 'Cocoa']
- lib_srcs += join_paths('pugl', 'src', 'mac.m')
- lib_srcs += join_paths('pugl', 'src', 'mac_gl.m')
+ conf_data.set('UI_TYPE', 'CocoaUI')
else
- deps += dependency('gl')
- deps += dependency('x11', version : '>=1.6.0')
- deps += dependency('xext', version : '>=1.3.0')
- lib_srcs += join_paths('pugl', 'src', 'x11.c')
- lib_srcs += join_paths('pugl', 'src', 'x11_gl.c')
+ conf_data.set('UI_TYPE', 'X11UI')
endif
-nk_pugl_gl = declare_dependency(
- compile_args : ['-DPUGL_STATIC'],
+mod = shared_module('sherlock', dsp_srcs,
+ c_args : c_args,
include_directories : inc_dir,
- dependencies : deps,
- link_args : links,
- sources : lib_srcs)
+ name_prefix : '',
+ dependencies : dsp_deps,
+ install : true,
+ install_dir : inst_dir)
-cousine_regular_ttf = configure_file(
- input : join_paths('nuklear', 'extra_font', 'Cousine-Regular.ttf'),
+ui = shared_module('sherlock_nk', ui_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : ui_deps,
+ install : true,
+ install_dir : inst_dir)
+
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+manifest_ttl = configure_file(
+ input : 'manifest.ttl.in',
+ output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+
+dsp_ttl = configure_file(
+ input : 'sherlock.ttl',
+ output : 'sherlock.ttl',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+
+ui_ttl = configure_file(
+ input : 'sherlock_ui.ttl',
+ output : 'sherlock_ui.ttl',
+ copy : true,
+ install : true,
+ install_dir : inst_dir)
+
+configure_file(
+ input : cousine_regular_ttf,
output : 'Cousine-Regular.ttf',
copy : true,
- install : false)
+ install : true,
+ install_dir : inst_dir)
-if build_examples
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl, ui_ttl])
+endif
- executable('nk_pugl.gl', [bin_srcs],
- c_args : c_args,
- include_directories : inc_dir,
- dependencies: nk_pugl_gl,
- install : false)
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-Ewarn', '-I', join_paths(build_root, ''),
+ 'http://open-music-kontrollers.ch/lv2/sherlock#atom_inspector',
+ 'http://open-music-kontrollers.ch/lv2/sherlock#midi_inspector',
+ 'http://open-music-kontrollers.ch/lv2/sherlock#osc_inspector'])
endif
diff --git a/meson_options.txt b/meson_options.txt
index 0a4a2a9..8a007bd 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,4 +1,3 @@
-option('build-examples',
- type : 'boolean',
- value : false,
- yield : true)
+option('lv2libdir',
+ type : 'string',
+ value : 'lib/lv2')
diff --git a/midi_inspector.c b/midi_inspector.c
new file mode 100644
index 0000000..2247931
--- /dev/null
+++ b/midi_inspector.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2015-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 <sherlock.h>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ craft_t through;
+ craft_t notify;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+ LV2_URID midi_event;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate __attribute__((unused)),
+ const char *bundle_path __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(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_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "%s: Host does not support urid:(un)map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+ handle->midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ lv2_atom_forge_init(&handle->through.forge, handle->map);
+ lv2_atom_forge_init(&handle->notify.forge, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->through.seq = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify.seq = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ craft_t *through = &handle->through;
+ craft_t *notify = &handle->notify;
+
+ uint32_t capacity = through->seq->atom.size;
+ lv2_atom_forge_set_buffer(&through->forge, through->buf, capacity);
+ through->ref = lv2_atom_forge_sequence_head(&through->forge, &through->frame[0], 0);
+
+ capacity = notify->seq->atom.size;
+ lv2_atom_forge_set_buffer(&notify->forge, notify->buf, capacity);
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[0], 0);
+
+ props_idle(&handle->props, &notify->forge, 0, &notify->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ // copy all events to through port
+ if(through->ref)
+ through->ref = lv2_atom_forge_frame_time(&through->forge, frames);
+ if(through->ref)
+ through->ref = lv2_atom_forge_raw(&through->forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(through->ref)
+ lv2_atom_forge_pad(&through->forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, &notify->forge, frames, obj, &notify->ref)
+ && lv2_atom_forge_is_object_type(&notify->forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(through->ref)
+ lv2_atom_forge_pop(&through->forge, &through->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(through->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "through buffer overflow\n");
+ }
+
+ bool has_event = notify->seq->atom.size > sizeof(LV2_Atom_Sequence_Body);
+
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, nsamples-1);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_tuple(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_long(&notify->forge, handle->frame);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_int(&notify->forge, nsamples);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[2], 0);
+
+ // only serialize MIDI events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ if(ev->body.type == handle->midi_event)
+ {
+ has_event = true;
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, ev->time.frames);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_write(&notify->forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[2]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(notify->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "notify buffer overflow\n");
+ }
+
+ if(!has_event) // don't send anything
+ lv2_atom_sequence_clear(notify->seq);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)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)
+{
+ handle_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)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor midi_inspector = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/midi_inspector_nk.c b/midi_inspector_nk.c
new file mode 100644
index 0000000..5c985c4
--- /dev/null
+++ b/midi_inspector_nk.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (c) 2015-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 <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+#include <encoder.h>
+
+typedef struct _midi_msg_t midi_msg_t;
+
+struct _midi_msg_t {
+ uint8_t type;
+ const char *key;
+};
+
+#define COMMANDS_NUM 18
+static const midi_msg_t commands [COMMANDS_NUM] = {
+ { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" },
+ { LV2_MIDI_MSG_NOTE_ON , "NoteOn" },
+ { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" },
+ { LV2_MIDI_MSG_CONTROLLER , "Controller" },
+ { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" },
+ { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" },
+ { LV2_MIDI_MSG_BENDER , "Bender" },
+ { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" },
+ { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" },
+ { LV2_MIDI_MSG_SONG_POS , "SongPosition" },
+ { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" },
+ { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" },
+ { LV2_MIDI_MSG_CLOCK , "Clock" },
+ { LV2_MIDI_MSG_START , "Start" },
+ { LV2_MIDI_MSG_CONTINUE , "Continue" },
+ { LV2_MIDI_MSG_STOP , "Stop" },
+ { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" },
+ { LV2_MIDI_MSG_RESET , "Reset" },
+};
+
+#define CONTROLLERS_NUM 72
+static const midi_msg_t controllers [CONTROLLERS_NUM] = {
+ { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" },
+ { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" },
+ { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" },
+ { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" },
+ { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" },
+ { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" },
+ { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" },
+ { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" },
+ { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" },
+ { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" },
+
+ { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" },
+ { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" },
+ { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" },
+ { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" },
+ { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" },
+ { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" },
+ { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" },
+ { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" },
+ { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" },
+ { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" },
+
+ { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" },
+ { LV2_MIDI_CTL_PORTAMENTO , "Portamento" },
+ { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" },
+ { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" },
+ { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" },
+ { LV2_MIDI_CTL_HOLD2 , "Hold2" },
+
+ { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" },
+ { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" },
+ { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" },
+ { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" },
+ { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" },
+ { LV2_MIDI_CTL_SC6 , "SC6" },
+ { LV2_MIDI_CTL_SC7 , "SC7" },
+ { LV2_MIDI_CTL_SC8 , "SC8" },
+ { LV2_MIDI_CTL_SC9 , "SC9" },
+ { LV2_MIDI_CTL_SC10 , "SC10" },
+
+ { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" },
+ { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" },
+
+ { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" },
+ { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" },
+ { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" },
+ { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" },
+ { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" },
+
+ { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" },
+ { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" },
+
+ { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" },
+ { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" },
+
+ { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" },
+ { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" },
+
+ { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" },
+ { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" },
+ { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" },
+ { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" },
+ { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" },
+ { LV2_MIDI_CTL_OMNI_ON , "OmniOn" },
+ { LV2_MIDI_CTL_MONO1 , "Mono1" },
+ { LV2_MIDI_CTL_MONO2 , "Mono2" },
+};
+
+#define TIMECODES_NUM 8
+static const midi_msg_t timecodes [TIMECODES_NUM] = {
+ { 0 , "FrameNumber_LSB" },
+ { 1 , "FrameNumber_MSB" },
+ { 2 , "Second_LSB" },
+ { 3 , "Second_MSB" },
+ { 4 , "Minute_LSB" },
+ { 5 , "Minute_MSB" },
+ { 6 , "Hour_LSB" },
+ { 7 , "RateAndHour_MSB" },
+};
+
+static int
+_cmp_search(const void *itm1, const void *itm2)
+{
+ const midi_msg_t *msg1 = itm1;
+ const midi_msg_t *msg2 = itm2;
+
+ if(msg1->type < msg2->type)
+ return -1;
+ else if(msg1->type > msg2->type)
+ return 1;
+
+ return 0;
+}
+
+static inline const midi_msg_t *
+_search_command(uint8_t type)
+{
+ return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_controller(uint8_t type)
+{
+ return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_timecode(uint8_t type)
+{
+ return bsearch(&type, timecodes, TIMECODES_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static const char *keys [12] = {
+ "C", "C#",
+ "D", "D#",
+ "E",
+ "F", "F#",
+ "G", "G#",
+ "A", "A#",
+ "B"
+};
+
+static inline const char *
+_note(uint8_t val, int8_t *octave)
+{
+ *octave = val / 12 - 1;
+
+ return keys[val % 12];
+}
+
+static inline void
+_shadow(struct nk_context *ctx, bool *shadow)
+{
+ if(*shadow)
+ {
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 5*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+
+ *shadow = !*shadow;
+}
+
+void
+_midi_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ handle->dy = 20.f * _get_scale(handle);
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ const char *window_name = "Sherlock";
+ if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ struct nk_panel *panel = nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
+ nk_layout_row_dynamic(ctx, body_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ {
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ }
+ else
+ {
+ handle->shadow = false;
+ }
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, was sysex payload
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+
+ handle->shadow = false;
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom *body = &ev->body;
+ const int64_t frames = ev->time.frames;
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(body);
+ const uint8_t cmd = (msg[0] & 0xf0) == 0xf0
+ ? msg[0]
+ : msg[0] & 0xf0;
+
+ const midi_msg_t *command_msg = _search_command(cmd);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ char tmp [16];
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _shadow(ctx, &handle->shadow);
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+
+ nk_layout_row_push(ctx, 0.2);
+ const unsigned rem = body->size;
+ const unsigned to = rem >= 4 ? 4 : rem % 4;
+ for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
+ sprintf(&tmp[ptr], "%02"PRIX8" ", msg[i]);
+ tmp[to*3 - 1] = '\0';
+ nk_label_colored(ctx, tmp, NK_TEXT_LEFT, cwhite);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_label_colored(ctx, command_str, NK_TEXT_LEFT, magenta);
+
+ switch(cmd)
+ {
+ case LV2_MIDI_MSG_NOTE_OFF:
+ // fall-through
+ case LV2_MIDI_MSG_NOTE_ON:
+ // fall-through
+ case LV2_MIDI_MSG_NOTE_PRESSURE:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ int8_t octave;
+ const char *key = _note(msg[1], &octave);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%s%+"PRIi8"=%3"PRIu8,
+ key, octave, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[2]);
+ } break;
+ case LV2_MIDI_MSG_CONTROLLER:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ const midi_msg_t *controller_msg = _search_controller(msg[1]);
+ const char *controller_str = controller_msg
+ ? controller_msg->key
+ : "Unknown";
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%s=%3"PRIu8"",
+ controller_str, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[2]);
+ } break;
+ case LV2_MIDI_MSG_PGM_CHANGE:
+ // fall-through
+ case LV2_MIDI_MSG_CHANNEL_PRESSURE:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_BENDER:
+ {
+ const int16_t bender = (((int16_t)msg[2] << 7) | msg[1]) - 0x2000;
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIi16, bender);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_MTC_QUARTER:
+ {
+ const uint8_t msg_type = msg[1] >> 4;
+ const uint8_t msg_val = msg[1] & 0xf;
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ const midi_msg_t *timecode_msg = _search_timecode(msg_type);
+ const char *timecode_str = timecode_msg
+ ? timecode_msg->key
+ : "Unknown";
+ nk_layout_row_push(ctx, 0.2);
+ nk_label_colored(ctx, timecode_str, NK_TEXT_RIGHT, cwhite);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg_val);
+ } break;
+ case LV2_MIDI_MSG_SONG_POS:
+ {
+ const int16_t song_pos= (((int16_t)msg[2] << 7) | msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu16, song_pos);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_SONG_SELECT:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE:
+ // fall-throuh
+ case LV2_MIDI_MSG_TUNE_REQUEST:
+ // fall-throuh
+ case LV2_MIDI_MSG_CLOCK:
+ // fall-throuh
+ case LV2_MIDI_MSG_START:
+ // fall-throuh
+ case LV2_MIDI_MSG_CONTINUE:
+ // fall-throuh
+ case LV2_MIDI_MSG_STOP:
+ // fall-throuh
+ case LV2_MIDI_MSG_ACTIVE_SENSE:
+ // fall-throuh
+ case LV2_MIDI_MSG_RESET:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ }
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
+ }
+ nk_layout_row_end(ctx);
+
+ for(unsigned j=4; j<body->size; j+=4)
+ {
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _shadow(ctx, &handle->shadow);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ const unsigned rem = body->size - j;
+ const unsigned to = rem >= 4 ? 4 : rem % 4;
+ for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
+ sprintf(&tmp[ptr], "%02"PRIX8" ", msg[j+i]);
+ tmp[to*3 - 1] = '\0';
+ nk_label_colored(ctx, tmp, NK_TEXT_LEFT, cwhite);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ }
+ }
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ const float n = 3;
+ const float r0 = 1.f / n;
+ const float r1 = 0.1f / 3;
+ const float r2 = r0 - r1;
+ const float footer [6] = {r1, r2, r1, r2, r1, r2};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 6, footer);
+ {
+ const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
+ if(state_overwrite != handle->state.overwrite)
+ {
+ handle->state.overwrite = state_overwrite;
+ _set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
+ }
+ nk_label(ctx, "overwrite", NK_TEXT_LEFT);
+
+ const int32_t state_block = _check(ctx, handle->state.block);
+ if(state_block != handle->state.block)
+ {
+ handle->state.block = state_block;
+ _set_bool(handle, handle->urid.block, handle->state.block);
+ }
+ nk_label(ctx, "block", NK_TEXT_LEFT);
+
+ const int32_t state_follow = _check(ctx, handle->state.follow);
+ if(state_follow != handle->state.follow)
+ {
+ handle->state.follow = state_follow;
+ _set_bool(handle, handle->urid.follow, handle->state.follow);
+ }
+ nk_label(ctx, "follow", NK_TEXT_LEFT);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+ }
+ nk_end(ctx);
+}
diff --git a/osc.lv2/.gitlab-ci.yml b/osc.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..979769c
--- /dev/null
+++ b/osc.lv2/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/osc.lv2/COPYING b/osc.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/osc.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/osc.lv2/README.md b/osc.lv2/README.md
new file mode 100644
index 0000000..1a30571
--- /dev/null
+++ b/osc.lv2/README.md
@@ -0,0 +1,33 @@
+# osc.lv2
+
+## Open Sound Control Extension for the LV2 Plugin Specification
+
+### Build Status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/osc.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/osc.lv2/commits/master)
+
+### Build / test
+
+ git clone https://git.open-music-kontrollers.ch/lv2/osc.lv2
+ cd osc.lv2
+ meson build
+ cd build
+ ninja -j4
+ ninja test
+
+### License
+
+Copyright (c) 2017 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/osc.lv2/VERSION b/osc.lv2/VERSION
new file mode 100644
index 0000000..a63a9fe
--- /dev/null
+++ b/osc.lv2/VERSION
@@ -0,0 +1 @@
+0.1.159
diff --git a/osc.lv2/gitlab-ci/generic.yml b/osc.lv2/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/osc.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/osc.lv2/lv2-osc.doap.ttl b/osc.lv2/lv2-osc.doap.ttl
new file mode 100644
index 0000000..ef74f92
--- /dev/null
+++ b/osc.lv2/lv2-osc.doap.ttl
@@ -0,0 +1,40 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix dcs: <http://ontologi.es/doap-changeset#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a doap:Project ;
+ doap:license lic:Artistic-2.0 ;
+ doap:name "LV2 OSC" ;
+ doap:shortdesc "A definition of atomified OSC." ;
+ doap:maintainer omk:me ;
+ doap:created "2015-06-19" ;
+ doap:developer omk:me ;
+ doap:release [
+ doap:revision "1.0" ;
+ doap:created "2015-06-19" ;
+ dcs:blame omk:me ;
+ dcs:changeset [
+ dcs:item [
+ rdfs:label "Initial release."
+ ]
+ ]
+ ] .
diff --git a/osc.lv2/manifest.ttl b/osc.lv2/manifest.ttl
new file mode 100644
index 0000000..a2bbaf8
--- /dev/null
+++ b/osc.lv2/manifest.ttl
@@ -0,0 +1,23 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a lv2:Specification ;
+ lv2:minorVersion 1 ;
+ lv2:microVersion 0 ;
+ rdfs:seeAlso <osc.ttl> .
diff --git a/osc.lv2/meson.build b/osc.lv2/meson.build
new file mode 100644
index 0000000..750f0dc
--- /dev/null
+++ b/osc.lv2/meson.build
@@ -0,0 +1,36 @@
+project('osc.lv2', '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')
+
+lv2_dep = dependency('lv2')
+thread_dep = dependency('threads')
+deps = [lv2_dep, thread_dep]
+
+c_args = []
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('ws2_32')
+ c_args += '-Wno-error=format'
+ c_args += '-Wno-error=format-extra-args'
+endif
+
+osc_test = executable('osc_test',
+ join_paths('test', 'osc_test.c'),
+ c_args : c_args,
+ dependencies : deps,
+ install : false)
+
+# FIXME start virautl serial pair before test
+# socat -d -d pty,raw,echo=0 pty,raw,echo=0
+test('Test', osc_test,
+ timeout : 240)
diff --git a/osc.lv2/osc.lv2/endian.h b/osc.lv2/osc.lv2/endian.h
new file mode 100644
index 0000000..f310c51
--- /dev/null
+++ b/osc.lv2/osc.lv2/endian.h
@@ -0,0 +1,120 @@
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef PORTABLE_ENDIAN_H__
+#define PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+# define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+# include <endian.h>
+
+#elif defined(__APPLE__)
+
+# include <libkern/OSByteOrder.h>
+
+# define htobe16(x) OSSwapHostToBigInt16(x)
+# define htole16(x) OSSwapHostToLittleInt16(x)
+# define be16toh(x) OSSwapBigToHostInt16(x)
+# define le16toh(x) OSSwapLittleToHostInt16(x)
+
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define htole32(x) OSSwapHostToLittleInt32(x)
+# define be32toh(x) OSSwapBigToHostInt32(x)
+# define le32toh(x) OSSwapLittleToHostInt32(x)
+
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define htole64(x) OSSwapHostToLittleInt64(x)
+# define be64toh(x) OSSwapBigToHostInt64(x)
+# define le64toh(x) OSSwapLittleToHostInt64(x)
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#elif defined(__WINDOWS__)
+
+# include <winsock2.h>
+# include <sys/param.h>
+
+# if BYTE_ORDER == LITTLE_ENDIAN
+
+# define htobe16(x) htons(x)
+# define htole16(x) (x)
+# define be16toh(x) ntohs(x)
+# define le16toh(x) (x)
+
+# define htobe32(x) htonl(x)
+# define htole32(x) (x)
+# define be32toh(x) ntohl(x)
+# define le32toh(x) (x)
+
+# ifndef htonll
+static inline uint64_t htonll(uint64_t n)
+{
+ return (((uint64_t)htonl(n)) << 32) + htonl(n >> 32);
+}
+# endif
+
+# ifndef ntohll
+# define ntohll htonll
+# endif
+
+# define htobe64(x) htonll(x)
+# define htole64(x) (x)
+# define be64toh(x) ntohll(x)
+# define le64toh(x) (x)
+
+# elif BYTE_ORDER == BIG_ENDIAN
+
+ /* that would be xbox 360 */
+# define htobe16(x) (x)
+# define htole16(x) __builtin_bswap16(x)
+# define be16toh(x) (x)
+# define le16toh(x) __builtin_bswap16(x)
+
+# define htobe32(x) (x)
+# define htole32(x) __builtin_bswap32(x)
+# define be32toh(x) (x)
+# define le32toh(x) __builtin_bswap32(x)
+
+# define htobe64(x) (x)
+# define htole64(x) __builtin_bswap64(x)
+# define be64toh(x) (x)
+# define le64toh(x) __builtin_bswap64(x)
+
+# else
+
+# error byte order not supported
+
+# endif
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#else
+
+# error platform not supported
+
+#endif
+
+#endif
diff --git a/osc.lv2/osc.lv2/forge.h b/osc.lv2/osc.lv2/forge.h
new file mode 100644
index 0000000..6dc5fe7
--- /dev/null
+++ b/osc.lv2/osc.lv2/forge.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_FORGE_H
+#define LV2_OSC_FORGE_H
+
+#include <inttypes.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/reader.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define lv2_osc_forge_int(forge, osc_urid, val) \
+ lv2_atom_forge_int((forge), (val))
+
+#define lv2_osc_forge_float(forge, osc_urid, val) \
+ lv2_atom_forge_float((forge), (val))
+
+#define lv2_osc_forge_string(forge, osc_urid, val, len) \
+ lv2_atom_forge_string((forge), (val), (len))
+
+#define lv2_osc_forge_long(forge, osc_urid, val) \
+ lv2_atom_forge_long((forge), (val))
+
+#define lv2_osc_forge_double(forge, osc_urid, val) \
+ lv2_atom_forge_double((forge), (val))
+
+#define lv2_osc_forge_true(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 1)
+
+#define lv2_osc_forge_false(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 0)
+
+#define lv2_osc_forge_nil(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Nil, 0)
+
+#define lv2_osc_forge_impulse(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Impulse, 0)
+
+#define lv2_osc_forge_symbol(forge, osc_urid, val) \
+ lv2_atom_forge_urid((forge), (val))
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_chunk(LV2_Atom_Forge *forge, LV2_URID type,
+ const uint8_t *buf, uint32_t size)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_atom(forge, size, type))
+ && (ref = lv2_atom_forge_raw(forge, buf, size)) )
+ {
+ lv2_atom_forge_pad(forge, size);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_midi(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ assert(size <= 3);
+ return lv2_osc_forge_chunk(forge, osc_urid->MIDI_MidiEvent, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_blob(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ return lv2_osc_forge_chunk(forge, osc_urid->ATOM_Chunk, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_char(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ char val)
+{
+ return lv2_atom_forge_literal(forge, &val, 1, osc_urid->OSC_Char, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_rgba(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ char val [9];
+ sprintf(val, "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, r, g, b, a);
+ return lv2_atom_forge_literal(forge, val, 8, osc_urid->OSC_RGBA, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_timetag(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame, 0, osc_urid->OSC_Timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagIntegral))
+ && (ref = lv2_atom_forge_long(forge, timetag->integral))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagFraction))
+ && (ref = lv2_atom_forge_long(forge, timetag->fraction)) )
+ {
+ lv2_atom_forge_pop(forge, &frame);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_bundle_head(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Bundle))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleTimetag))
+ && (ref = lv2_osc_forge_timetag(forge, osc_urid, timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleItems))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_head(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const char *path)
+{
+ assert(path);
+
+ LV2_Atom_Forge_Ref ref;
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Message))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messagePath))
+ && (ref = lv2_atom_forge_string(forge, path, strlen(path)))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messageArguments))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline void
+lv2_osc_forge_pop(LV2_Atom_Forge *forge, LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(forge, &frame[1]); // a LV2_Atom_Tuple
+ lv2_atom_forge_pop(forge, &frame[0]); // a LV2_Atom_Object
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_varlist(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, va_list args)
+{
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ if(!lv2_osc_check_path(path) || !lv2_osc_check_fmt(fmt, 0))
+ return 0;
+ if(!(ref = lv2_osc_forge_message_head(forge, osc_urid, frame, path)))
+ return 0;
+
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, va_arg(args, int32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, (float)va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ const char *s = va_arg(args, const char *);
+ if(!s || !(ref = lv2_osc_forge_string(forge, osc_urid, s, strlen(s))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *b = va_arg(args, const uint8_t *);
+ if(!b || !(ref = lv2_osc_forge_blob(forge, osc_urid, b, size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, va_arg(args, int64_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ const LV2_OSC_Timetag timetag = {
+ .integral = va_arg(args, uint32_t),
+ .fraction = va_arg(args, uint32_t)
+ };
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, &timetag)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid, va_arg(args, uint32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *m = va_arg(args, const uint8_t *);
+ if(!m || !(ref = lv2_osc_forge_midi(forge, osc_urid, m, size)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, (char)va_arg(args, int))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid,
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned))))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_vararg(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, ...)
+{
+ LV2_Atom_Forge_Ref ref;
+ va_list args;
+
+ va_start(args, fmt);
+
+ ref = lv2_osc_forge_message_varlist(forge, osc_urid, path, fmt, args);
+
+ va_end(args);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_packet(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_URID_Map *map, const uint8_t *buf, size_t size)
+{
+ LV2_OSC_Reader reader;
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ lv2_osc_reader_initialize(&reader, buf, size);
+
+ if(lv2_osc_reader_is_bundle(&reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(&reader, size);
+
+ if(itm && (ref = lv2_osc_forge_bundle_head(forge, osc_urid, frame,
+ LV2_OSC_TIMETAG_CREATE(itm->timetag))))
+ {
+ OSC_READER_BUNDLE_ITERATE(&reader, itm)
+ {
+ if(!(ref = lv2_osc_forge_packet(forge, osc_urid, map, itm->body, itm->size)))
+ return 0;
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+ else if(lv2_osc_reader_is_message(&reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(&reader, size);
+
+ if(arg && (ref = lv2_osc_forge_message_head(forge, osc_urid, frame, arg->path)))
+ {
+ OSC_READER_MESSAGE_ITERATE(&reader, arg)
+ {
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, arg->i)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, arg->f)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!(ref = lv2_osc_forge_string(forge, osc_urid, arg->s, arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!(ref = lv2_osc_forge_blob(forge, osc_urid, arg->b, arg->size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, arg->h)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, arg->d)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, LV2_OSC_TIMETAG_CREATE(arg->t))))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid,
+ map->map(map->handle, arg->S))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ if(!(ref = lv2_osc_forge_midi(forge, osc_urid, &arg->b[1], arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, arg->c)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid, arg->R, arg->G, arg->B, arg->A)))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_FORGE_H
diff --git a/osc.lv2/osc.lv2/osc.h b/osc.lv2/osc.lv2/osc.h
new file mode 100644
index 0000000..1ada68c
--- /dev/null
+++ b/osc.lv2/osc.lv2/osc.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_H
+#define LV2_OSC_H
+
+#include <stdint.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/midi/midi.h>
+
+#define LV2_OSC_URI "http://open-music-kontrollers.ch/lv2/osc"
+#define LV2_OSC_PREFIX LV2_OSC_URI "#"
+
+#define LV2_OSC__Event LV2_OSC_PREFIX "Event" // atom message type
+#define LV2_OSC__schedule LV2_OSC_PREFIX "schedule" // feature
+
+#define LV2_OSC__Packet LV2_OSC_PREFIX "Packet" // atom object type
+
+#define LV2_OSC__Bundle LV2_OSC_PREFIX "Bundle" // atom object type
+#define LV2_OSC__bundleTimetag LV2_OSC_PREFIX "bundleTimetag" // atom object property
+#define LV2_OSC__bundleItems LV2_OSC_PREFIX "bundleItems"
+
+#define LV2_OSC__Message LV2_OSC_PREFIX "Message" // atom object type
+#define LV2_OSC__messagePath LV2_OSC_PREFIX "messagePath" // atom object property
+#define LV2_OSC__messageArguments LV2_OSC_PREFIX "messageArguments" // atom object property
+
+#define LV2_OSC__Timetag LV2_OSC_PREFIX "Timetag" // atom object type
+#define LV2_OSC__timetagIntegral LV2_OSC_PREFIX "timetagIntegral" // atom object property
+#define LV2_OSC__timetagFraction LV2_OSC_PREFIX "timetagFraction" // atom object property
+
+#define LV2_OSC__Nil LV2_OSC_PREFIX "Nil" // atom literal type
+#define LV2_OSC__Impulse LV2_OSC_PREFIX "Impulse" // atom literal type
+#define LV2_OSC__Char LV2_OSC_PREFIX "Char" // atom literal type
+#define LV2_OSC__RGBA LV2_OSC_PREFIX "RGBA" // atom literal type
+
+#define LV2_OSC_PADDED_SIZE(size) ( ( (size_t)(size) + 3 ) & ( ~3 ) )
+#define LV2_OSC_IMMEDIATE 1ULL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *LV2_OSC_Schedule_Handle;
+
+typedef double (*LV2_OSC_Schedule_OSC2Frames)(
+ LV2_OSC_Schedule_Handle handle,
+ uint64_t timetag);
+
+typedef uint64_t (*LV2_OSC_Schedule_Frames2OSC)(
+ LV2_OSC_Schedule_Handle handle,
+ double frames);
+
+typedef struct _LV2_OSC_Schedule {
+ LV2_OSC_Schedule_Handle handle;
+ LV2_OSC_Schedule_OSC2Frames osc2frames;
+ LV2_OSC_Schedule_Frames2OSC frames2osc;
+} LV2_OSC_Schedule;
+
+typedef enum LV2_OSC_Type {
+ LV2_OSC_INT32 = 'i',
+ LV2_OSC_FLOAT = 'f',
+ LV2_OSC_STRING = 's',
+ LV2_OSC_BLOB = 'b',
+
+ LV2_OSC_TRUE = 'T',
+ LV2_OSC_FALSE = 'F',
+ LV2_OSC_NIL = 'N',
+ LV2_OSC_IMPULSE = 'I',
+
+ LV2_OSC_INT64 = 'h',
+ LV2_OSC_DOUBLE = 'd',
+ LV2_OSC_TIMETAG = 't',
+
+ LV2_OSC_SYMBOL = 'S',
+ LV2_OSC_CHAR = 'c',
+ LV2_OSC_MIDI = 'm',
+ LV2_OSC_RGBA = 'r'
+} LV2_OSC_Type;
+
+union swap32_t {
+ uint32_t u;
+
+ int32_t i;
+ float f;
+};
+
+union swap64_t {
+ uint64_t u;
+
+ int64_t h;
+ uint64_t t;
+ double d;
+};
+
+typedef struct _LV2_OSC_Timetag {
+ uint32_t integral;
+ uint32_t fraction;
+} LV2_OSC_Timetag;
+
+typedef struct _LV2_OSC_URID {
+ LV2_URID OSC_Packet;
+
+ LV2_URID OSC_Bundle;
+ LV2_URID OSC_bundleTimetag;
+ LV2_URID OSC_bundleItems;
+
+ LV2_URID OSC_Message;
+ LV2_URID OSC_messagePath;
+ LV2_URID OSC_messageArguments;
+
+ LV2_URID OSC_Timetag;
+ LV2_URID OSC_timetagIntegral;
+ LV2_URID OSC_timetagFraction;
+
+ LV2_URID OSC_Nil;
+ LV2_URID OSC_Impulse;
+ LV2_URID OSC_Char;
+ LV2_URID OSC_RGBA;
+
+ LV2_URID MIDI_MidiEvent;
+
+ LV2_URID ATOM_Int;
+ LV2_URID ATOM_Long;
+ LV2_URID ATOM_String;
+ LV2_URID ATOM_Literal;
+ LV2_URID ATOM_Float;
+ LV2_URID ATOM_Double;
+ LV2_URID ATOM_URID;
+ LV2_URID ATOM_Bool;
+ LV2_URID ATOM_Tuple;
+ LV2_URID ATOM_Object;
+ LV2_URID ATOM_Chunk;
+} LV2_OSC_URID;
+
+static inline void
+lv2_osc_urid_init(LV2_OSC_URID *osc_urid, LV2_URID_Map *map)
+{
+ osc_urid->OSC_Packet = map->map(map->handle, LV2_OSC__Packet);
+
+ osc_urid->OSC_Bundle = map->map(map->handle, LV2_OSC__Bundle);
+ osc_urid->OSC_bundleTimetag = map->map(map->handle, LV2_OSC__bundleTimetag);
+ osc_urid->OSC_bundleItems = map->map(map->handle, LV2_OSC__bundleItems);
+
+ osc_urid->OSC_Message = map->map(map->handle, LV2_OSC__Message);
+ osc_urid->OSC_messagePath = map->map(map->handle, LV2_OSC__messagePath);
+ osc_urid->OSC_messageArguments = map->map(map->handle, LV2_OSC__messageArguments);
+
+ osc_urid->OSC_Timetag = map->map(map->handle, LV2_OSC__Timetag);
+ osc_urid->OSC_timetagIntegral = map->map(map->handle, LV2_OSC__timetagIntegral);
+ osc_urid->OSC_timetagFraction = map->map(map->handle, LV2_OSC__timetagFraction);
+
+ osc_urid->OSC_Nil = map->map(map->handle, LV2_OSC__Nil);
+ osc_urid->OSC_Impulse = map->map(map->handle, LV2_OSC__Impulse);
+ osc_urid->OSC_Char = map->map(map->handle, LV2_OSC__Char);
+ osc_urid->OSC_RGBA = map->map(map->handle, LV2_OSC__RGBA);
+
+ osc_urid->MIDI_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ osc_urid->ATOM_Int = map->map(map->handle, LV2_ATOM__Int);
+ osc_urid->ATOM_Long = map->map(map->handle, LV2_ATOM__Long);
+ osc_urid->ATOM_String = map->map(map->handle, LV2_ATOM__String);
+ osc_urid->ATOM_Literal = map->map(map->handle, LV2_ATOM__Literal);
+ osc_urid->ATOM_Float = map->map(map->handle, LV2_ATOM__Float);
+ osc_urid->ATOM_Double = map->map(map->handle, LV2_ATOM__Double);
+ osc_urid->ATOM_URID = map->map(map->handle, LV2_ATOM__URID);
+ osc_urid->ATOM_Bool = map->map(map->handle, LV2_ATOM__Bool);
+ osc_urid->ATOM_Tuple = map->map(map->handle, LV2_ATOM__Tuple);
+ osc_urid->ATOM_Object = map->map(map->handle, LV2_ATOM__Object);
+ osc_urid->ATOM_Chunk = map->map(map->handle, LV2_ATOM__Chunk);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_H
diff --git a/osc.lv2/osc.lv2/reader.h b/osc.lv2/osc.lv2/reader.h
new file mode 100644
index 0000000..ae46dfa
--- /dev/null
+++ b/osc.lv2/osc.lv2/reader.h
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_READER_H
+#define LV2_OSC_READER_H
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/endian.h>
+#include <osc.lv2/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct _LV2_OSC_Tree LV2_OSC_Tree;
+typedef struct _LV2_OSC_Reader LV2_OSC_Reader;
+typedef struct _LV2_OSC_Item LV2_OSC_Item;
+typedef struct _LV2_OSC_Arg LV2_OSC_Arg;
+typedef void (*LV2_OSC_Branch)(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg,
+ const LV2_OSC_Tree *tree, void *data);
+
+struct _LV2_OSC_Tree {
+ const char *name;
+ const LV2_OSC_Tree *trees;
+ LV2_OSC_Branch branch;
+};
+
+struct _LV2_OSC_Reader {
+ const uint8_t *buf;
+ const uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Item {
+ int32_t size;
+ const uint8_t *body;
+
+ uint64_t timetag;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Arg {
+ const char *type;
+ int32_t size;
+ union {
+ int32_t i;
+ float f;
+ const char *s;
+ const uint8_t *b;
+
+ int64_t h;
+ double d;
+ uint64_t t;
+
+ const uint8_t *m;
+ const char *S;
+ char c;
+ struct {
+ uint8_t R;
+ uint8_t G;
+ uint8_t B;
+ uint8_t A;
+ }; // anonymous RGBA struct
+ };
+
+ const char *path;
+ const uint8_t *end;
+};
+
+static inline void
+lv2_osc_reader_initialize(LV2_OSC_Reader *reader, const uint8_t *buf, size_t size)
+{
+ reader->buf = buf;
+ reader->ptr = buf;
+ reader->end = buf + size;
+}
+
+static inline bool
+lv2_osc_reader_overflow(LV2_OSC_Reader *reader, size_t size)
+{
+ return reader->ptr + size > reader->end;
+}
+
+static inline bool
+lv2_osc_reader_be32toh(LV2_OSC_Reader *reader, union swap32_t *s32)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ s32->u = *(const uint32_t *)reader->ptr;
+ s32->u = be32toh(s32->u);
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_be64toh(LV2_OSC_Reader *reader, union swap64_t *s64)
+{
+ if(lv2_osc_reader_overflow(reader, 8))
+ return false;
+
+ s64->u = *(const uint64_t *)reader->ptr;
+ s64->u = be64toh(s64->u);
+ reader->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int32(LV2_OSC_Reader *reader, int32_t *i)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *i = s32.i;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_float(LV2_OSC_Reader *reader, float *f)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *f = s32.f;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int64(LV2_OSC_Reader *reader, int64_t *h)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *h = s64.h;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_timetag(LV2_OSC_Reader *reader, uint64_t *t)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *t = s64.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_double(LV2_OSC_Reader *reader, double *d)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *d = s64.d;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_string(LV2_OSC_Reader *reader, const char **s)
+{
+ const char *str = (const char *)reader->ptr;
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(str) + 1);
+ if(lv2_osc_reader_overflow(reader, padded ))
+ return false;
+
+ *s = str;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_symbol(LV2_OSC_Reader *reader, const char **S)
+{
+ return lv2_osc_reader_get_string(reader, S);
+}
+
+static inline bool
+lv2_osc_reader_get_midi(LV2_OSC_Reader *reader, const uint8_t **m)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *m = reader->ptr;
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_blob(LV2_OSC_Reader *reader, int32_t *len, const uint8_t **body)
+{
+ if(!lv2_osc_reader_get_int32(reader, len))
+ return false;
+
+ const size_t padded = LV2_OSC_PADDED_SIZE(*len);
+ if(lv2_osc_reader_overflow(reader, padded))
+ return false;
+
+ *body = reader->ptr;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_rgba(LV2_OSC_Reader *reader, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *r = reader->ptr[0];
+ *g = reader->ptr[1];
+ *b = reader->ptr[2];
+ *a = reader->ptr[3];
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_char(LV2_OSC_Reader *reader, char *c)
+{
+ int32_t i;
+ if(!lv2_osc_reader_get_int32(reader, &i))
+ return false;
+
+ *c = i;
+
+ return true;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_raw(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ if(!lv2_osc_reader_get_int32(reader, &itm->size))
+ return NULL;
+
+ if(lv2_osc_reader_overflow(reader, itm->size))
+ return NULL;
+
+ itm->body = reader->ptr;
+
+ return itm;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_begin(LV2_OSC_Reader *reader, LV2_OSC_Item *itm, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ itm->end = reader->ptr + len;
+
+ if(lv2_osc_reader_overflow(reader, 16))
+ return NULL;
+
+ if(strncmp((const char *)reader->ptr, "#bundle", 8))
+ return NULL;
+ reader->ptr += 8;
+
+ if(!lv2_osc_reader_get_timetag(reader, &itm->timetag))
+ return NULL;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+static inline bool
+lv2_osc_reader_item_is_end(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ return reader->ptr > itm->end;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_next(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ reader->ptr += itm->size;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+#define OSC_READER_BUNDLE_BEGIN(reader, len) \
+ lv2_osc_reader_item_begin( \
+ (reader), \
+ &(LV2_OSC_Item){ .size = 0, .body = NULL, .timetag = 1ULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_BUNDLE_ITERATE(reader, itm) \
+ for(itm = itm; \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+#define OSC_READER_BUNDLE_FOREACH(reader, itm, len) \
+ for(LV2_OSC_Item *(itm) = OSC_READER_BUNDLE_BEGIN((reader), (len)); \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_raw(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!lv2_osc_reader_get_int32(reader, &arg->i))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!lv2_osc_reader_get_float(reader, &arg->f))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!lv2_osc_reader_get_string(reader, &arg->s))
+ return NULL;
+ arg->size = strlen(arg->s) + 1;
+
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!lv2_osc_reader_get_blob(reader, &arg->size, &arg->b))
+ return NULL;
+ //arg->size = arg->size;
+
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ {
+ if(!lv2_osc_reader_get_int64(reader, &arg->h))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!lv2_osc_reader_get_double(reader, &arg->d))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!lv2_osc_reader_get_timetag(reader, &arg->t))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+
+ case LV2_OSC_MIDI:
+ {
+ if(!lv2_osc_reader_get_midi(reader, &arg->m))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_SYMBOL:
+ {
+ if(!lv2_osc_reader_get_symbol(reader, &arg->S))
+ return NULL;
+ arg->size = strlen(arg->S) + 1;
+
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!lv2_osc_reader_get_char(reader, &arg->c))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!lv2_osc_reader_get_rgba(reader, &arg->R, &arg->G, &arg->B, &arg->A))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ }
+
+ return arg;
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_begin(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ arg->end = reader->ptr + len;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->path)) //TODO check for validity
+ return NULL;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->type)) //TODO check for validity
+ return NULL;
+
+ if(*arg->type != ',')
+ return NULL;
+
+ arg->type++; // skip ','
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+static inline bool
+lv2_osc_reader_arg_is_end(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ return (*arg->type == '\0') || (reader->ptr > arg->end);
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_next(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ arg->type++;
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+#define OSC_READER_MESSAGE_BEGIN(reader, len) \
+ lv2_osc_reader_arg_begin( \
+ (reader), \
+ &(LV2_OSC_Arg){ .type = NULL, .size = 0, .path = NULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_MESSAGE_ITERATE(reader, arg) \
+ for(arg = arg; \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+#define OSC_READER_MESSAGE_FOREACH(reader, arg, len) \
+ for(LV2_OSC_Arg *(arg) = OSC_READER_MESSAGE_BEGIN((reader), (len)); \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+static inline bool
+lv2_osc_reader_arg_varlist(LV2_OSC_Reader *reader, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_reader_get_int32(reader, va_arg(args, int32_t *)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_reader_get_float(reader, va_arg(args, float *)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_reader_get_string(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!lv2_osc_reader_get_blob(reader, va_arg(args, int32_t *), va_arg(args, const uint8_t **)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_reader_get_int64(reader, va_arg(args, int64_t *)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_reader_get_double(reader, va_arg(args, double *)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_reader_get_timetag(reader, va_arg(args, uint64_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!lv2_osc_reader_get_midi(reader, va_arg(args, const uint8_t **)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_reader_get_symbol(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_reader_get_char(reader, va_arg(args, char *)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!lv2_osc_reader_get_rgba(reader, va_arg(args, uint8_t *), va_arg(args, uint8_t *),
+ va_arg(args, uint8_t *), va_arg(args, uint8_t *)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_arg_vararg(LV2_OSC_Reader *reader, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_reader_arg_varlist(reader, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_reader_is_bundle(LV2_OSC_Reader *reader)
+{
+ return strncmp((const char *)reader->ptr, "#bundle", 8) == 0;
+}
+
+static inline bool
+lv2_osc_reader_is_message(LV2_OSC_Reader *reader)
+{
+ return reader->ptr[0] == '/'; //FIXME check path
+}
+
+static inline void
+_lv2_osc_trees_internal(LV2_OSC_Reader *reader, const char *path, const char *from,
+ LV2_OSC_Arg *arg, const LV2_OSC_Tree *trees, void *data)
+{
+ const char *ptr = strchr(from, '/');
+ const char *pattern = strpbrk(from, "*?[]{}/");
+ const bool has_pattern = pattern && (pattern[0] != '/');
+ (void)has_pattern; //FIXME
+
+ const size_t len = ptr
+ ? (size_t)(ptr - from)
+ : strlen(from);
+
+ for(const LV2_OSC_Tree *tree = trees; tree && tree->name; tree++)
+ {
+ if(lv2_osc_pattern_match(from, tree->name, len))
+ {
+ if(tree->trees && ptr)
+ {
+ if(tree->branch)
+ {
+ LV2_OSC_Reader reader_clone = *reader;
+ tree->branch(&reader_clone, arg, tree, data);
+ }
+
+ _lv2_osc_trees_internal(reader, path, &ptr[1], arg, tree->trees, data);
+ }
+ else if(tree->branch && !ptr)
+ {
+ LV2_OSC_Reader reader_clone = *reader;
+ tree->branch(&reader_clone, arg, tree, data);
+ }
+ }
+ }
+}
+
+static inline void
+lv2_osc_reader_match(LV2_OSC_Reader *reader, size_t len,
+ const LV2_OSC_Tree *trees, void *data)
+{
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, len);
+ const char *path = arg->path;
+ const char *from = &path[1];
+
+ _lv2_osc_trees_internal(reader, path, from, arg, trees, data);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_READER_H
diff --git a/osc.lv2/osc.lv2/stream.h b/osc.lv2/osc.lv2/stream.h
new file mode 100644
index 0000000..55c94d2
--- /dev/null
+++ b/osc.lv2/osc.lv2/stream.h
@@ -0,0 +1,1433 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_STREAM_H
+#define LV2_OSC_STREAM_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#if !defined(_WIN32)
+# include <arpa/inet.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <netinet/tcp.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# include <termios.h>
+# include <limits.h>
+#endif
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <osc.lv2/osc.h>
+
+#if !defined(LV2_OSC_STREAM_SNDBUF)
+# define LV2_OSC_STREAM_SNDBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_RCVBUF)
+# define LV2_OSC_STREAM_RCVBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_REQBUF)
+# define LV2_OSC_STREAM_REQBUF 1024
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *
+(*LV2_OSC_Stream_Write_Request)(void *data, size_t minimum, size_t *maximum);
+
+typedef void
+(*LV2_OSC_Stream_Write_Advance)(void *data, size_t written);
+
+typedef const void *
+(*LV2_OSC_Stream_Read_Request)(void *data, size_t *toread);
+
+typedef void
+(*LV2_OSC_Stream_Read_Advance)(void *data);
+
+typedef struct _LV2_OSC_Address LV2_OSC_Address;
+typedef struct _LV2_OSC_Driver LV2_OSC_Driver;
+typedef struct _LV2_OSC_Stream LV2_OSC_Stream;
+
+struct _LV2_OSC_Address {
+ socklen_t len;
+ union {
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ };
+};
+
+struct _LV2_OSC_Driver {
+ LV2_OSC_Stream_Write_Request write_req;
+ LV2_OSC_Stream_Write_Advance write_adv;
+ LV2_OSC_Stream_Read_Request read_req;
+ LV2_OSC_Stream_Read_Advance read_adv;
+};
+
+struct _LV2_OSC_Stream {
+ int socket_family;
+ int socket_type;
+ int protocol;
+ bool server;
+ bool slip;
+ bool serial;
+ bool connected;
+ int sock;
+ int fd;
+ LV2_OSC_Address self;
+ LV2_OSC_Address peer;
+ const LV2_OSC_Driver *driv;
+ void *data;
+ uint8_t tx_buf [0x4000];
+ uint8_t rx_buf [0x4000];
+ size_t rx_off;
+ char url [PATH_MAX];
+};
+
+typedef enum _LV2_OSC_Enum {
+ LV2_OSC_NONE = 0x000000,
+
+ LV2_OSC_SEND = 0x800000,
+ LV2_OSC_RECV = 0x400000,
+ LV2_OSC_CONN = 0x200000,
+
+ LV2_OSC_ERR = 0x00ffff
+} LV2_OSC_Enum;
+
+static const char *udp_prefix = "osc.udp://";
+static const char *tcp_prefix = "osc.tcp://";
+static const char *tcp_slip_prefix = "osc.slip.tcp://";
+static const char *tcp_prefix_prefix = "osc.prefix.tcp://";
+static const char *ser_prefix = "osc.serial://";
+//FIXME serial
+
+
+static inline int
+_lv2_osc_stream_interface_attribs(int fd, int speed)
+{
+ struct termios tty;
+
+ if(tcgetattr(fd, &tty) < 0)
+ {
+ return -1;
+ }
+
+ cfsetospeed(&tty, (speed_t)speed);
+ cfsetispeed(&tty, (speed_t)speed);
+
+ tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
+ tty.c_cflag &= ~CSIZE;
+ tty.c_cflag |= CS8; /* 8-bit characters */
+ tty.c_cflag &= ~PARENB; /* no parity bit */
+ tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
+ tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
+
+ /* setup for non-canonical mode */
+ tty.c_iflag &= ~(IGNCR | ONLCR | IXON);
+ tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ tty.c_oflag &= ~OPOST;
+
+ /* fetch bytes as they become available */
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 0;
+
+ if(tcsetattr(fd, TCSANOW, &tty) != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+#define LV2_OSC_STREAM_ERRNO(EV, ERRNO) ( (EV & (~LV2_OSC_ERR)) | (ERRNO) )
+
+static inline void
+_close_socket(int *fd)
+{
+ if(fd)
+ {
+ if(*fd >= 0)
+ {
+ close(*fd);
+ }
+
+ *fd = -1;
+ }
+}
+
+static inline int
+lv2_osc_stream_deinit(LV2_OSC_Stream *stream)
+{
+ _close_socket(&stream->fd);
+ _close_socket(&stream->sock);
+
+ return 0;
+}
+
+static inline int
+_lv2_osc_stream_reinit(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+ lv2_osc_stream_deinit(stream);
+
+ char *dup = strdup(stream->url);
+ if(!dup)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ goto fail;
+ }
+
+ char *ptr = dup;
+ char *tmp;
+
+ if(strncmp(ptr, udp_prefix, strlen(udp_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_DGRAM;
+ stream->protocol = IPPROTO_UDP;
+ ptr += strlen(udp_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix, strlen(tcp_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix);
+ }
+ else if(strncmp(ptr, tcp_slip_prefix, strlen(tcp_slip_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_slip_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix_prefix, strlen(tcp_prefix_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix_prefix);
+ }
+ else if(strncmp(ptr, ser_prefix, strlen(ser_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->serial = true;
+ ptr += strlen(ser_prefix);
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOPROTOOPT);
+ goto fail;
+ }
+
+ if(ptr[0] == '\0')
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ if(stream->serial)
+ {
+ stream->sock = open(ptr, O_RDWR | O_NOCTTY | O_NDELAY);
+ if(stream->sock < 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(fcntl(stream->sock, F_SETFL, FNDELAY) == -1) //FIXME
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(_lv2_osc_stream_interface_attribs(stream->sock, B115200) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ stream->connected = true;
+ }
+ else // !stream->serial
+ {
+ const char *node = NULL;
+ const char *iface = NULL;
+ const char *service = NULL;
+
+ // optional IPv6
+ if(ptr[0] == '[')
+ {
+ stream->socket_family = AF_INET6;
+ ++ptr;
+ }
+
+ node = ptr;
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, '%')) )
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ iface = ++ptr;
+ }
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, ']')) )
+ if(ptr)
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ ++ptr;
+ }
+
+ // mandatory IPv4/6
+ ptr = strchr(ptr, ':');
+ if(!ptr)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EDESTADDRREQ);
+ goto fail;
+ }
+
+ ptr[0] = '\0';
+
+ service = ++ptr;
+
+ if(strlen(node) == 0)
+ {
+ node = NULL;
+ stream->server = true;
+ }
+
+ stream->sock = socket(stream->socket_family, stream->socket_type,
+ stream->protocol);
+
+ if(stream->sock < 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(fcntl(stream->sock, F_SETFL, O_NONBLOCK) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+ const int reuseaddr = 1;
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(sendbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(recvbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->socket_family == AF_INET) // IPv4
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ memcpy(&stream->self.in4, res->ai_addr, res->ai_addrlen);
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in4,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in4);
+ stream->self.in4.sin_family = stream->socket_family;
+ stream->self.in4.sin_port = htons(0);
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in4,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->peer.len = res->ai_addrlen;
+ memcpy(&stream->peer.in4, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ const int broadcast = 1;
+
+ if(setsockopt(stream->sock, SOL_SOCKET, SO_BROADCAST,
+ &broadcast, sizeof(broadcast)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ //FIXME handle multicast
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in4,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true;
+ }
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+ else if(stream->socket_family == AF_INET6) // IPv6
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ memcpy(&stream->self.in6, res->ai_addr, res->ai_addrlen);
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in6,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in6);
+ stream->self.in6.sin6_family = stream->socket_family;
+ stream->self.in6.sin6_port = htons(0);
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ if(bind(stream->sock, (struct sockaddr *)&stream->self.in6,
+ stream->self.len) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+
+ stream->peer.len = res->ai_addrlen;
+ memcpy(&stream->peer.in6, res->ai_addr, res->ai_addrlen);
+
+ if(iface)
+ {
+ stream->peer.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ // nothing to do
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true;
+ }
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+ else
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EPROTOTYPE);
+ goto fail;
+ }
+ }
+
+ free(dup);
+
+ return ev;
+
+fail:
+ if(dup)
+ {
+ free(dup);
+ }
+
+ _close_socket(&stream->sock);
+
+ return ev;
+}
+
+static inline int
+lv2_osc_stream_init(LV2_OSC_Stream *stream, const char *url,
+ const LV2_OSC_Driver *driv, void *data)
+{
+ memset(stream, 0x0, sizeof(LV2_OSC_Stream));
+
+ strncpy(stream->url, url, sizeof(stream->url) - 1);
+ stream->driv = driv;
+ stream->data = data;
+ stream->sock = -1;
+ stream->fd = -1;
+
+ return _lv2_osc_stream_reinit(stream);
+}
+
+#define SLIP_END 0300 // 0xC0, 192, indicates end of packet
+#define SLIP_ESC 0333 // 0xDB, 219, indicates byte stuffing
+#define SLIP_END_REPLACE 0334 // 0xDC, 220, ESC ESC_END means END data byte
+#define SLIP_ESC_REPLACE 0335 // 0xDD, 221, ESC ESC_ESC means ESC data byte
+
+// SLIP encoding
+static inline size_t
+lv2_osc_slip_encode_inline(uint8_t *dst, size_t len)
+{
+ if(len == 0)
+ return 0;
+
+ const uint8_t *end = dst + len;
+
+ // estimate new size
+ size_t size = 2; // double ended SLIP
+ for(const uint8_t *from=dst; from<end; from++, size++)
+ {
+ if( (*from == SLIP_END) || (*from == SLIP_ESC))
+ size ++;
+ }
+
+ // fast track if no escaping needed
+ if(size == len + 2)
+ {
+ memmove(dst+1, dst, len);
+ dst[0] = SLIP_END;
+ dst[size-1] = SLIP_END;
+
+ return size;
+ }
+
+ // slow track if some escaping needed
+ uint8_t *to = dst + size - 1;
+ *to-- = SLIP_END;
+ for(const uint8_t *from=end-1; from>=dst; from--)
+ {
+ if(*from == SLIP_END)
+ {
+ *to-- = SLIP_END_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else if(*from == SLIP_ESC)
+ {
+ *to-- = SLIP_ESC_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else
+ *to-- = *from;
+ }
+ *to-- = SLIP_END;
+
+ return size;
+}
+
+// SLIP decoding
+static inline size_t
+lv2_osc_slip_decode_inline(uint8_t *dst, size_t len, size_t *size)
+{
+ const uint8_t *src = dst;
+ const uint8_t *end = dst + len;
+ uint8_t *ptr = dst;
+
+ bool whole = false;
+
+ if( (src < end) && (*src == SLIP_END) )
+ {
+ whole = true;
+ src++;
+ }
+
+ while(src < end)
+ {
+ if(*src == SLIP_ESC)
+ {
+ if(src == end-1)
+ break;
+
+ src++;
+ if(*src == SLIP_END_REPLACE)
+ *ptr++ = SLIP_END;
+ else if(*src == SLIP_ESC_REPLACE)
+ *ptr++ = SLIP_ESC;
+ src++;
+ }
+ else if(*src == SLIP_END)
+ {
+ src++;
+
+ *size = whole ? ptr - dst : 0;
+ return src - dst;
+ }
+ else
+ {
+ *ptr++ = *src++;
+ }
+ }
+
+ *size = 0;
+ return 0;
+}
+
+static inline LV2_OSC_Enum
+_lv2_osc_stream_run_udp(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // send everything
+ if(stream->peer.len) // has a peer
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ const ssize_t sent = sendto(stream->sock, buf, tosend, 0,
+ (struct sockaddr *)&stream->peer.in6, stream->peer.len);
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // full queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+
+ // recv everything
+ {
+ uint8_t *buf;
+ size_t max_len;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, &max_len)) )
+ {
+ struct sockaddr_in6 in;
+ socklen_t in_len = sizeof(in);
+
+ memset(&in, 0, in_len);
+ const ssize_t recvd = recvfrom(stream->sock, buf, max_len, 0,
+ (struct sockaddr *)&in, &in_len);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // peer has shut down
+ break;
+ }
+
+ stream->peer.len = in_len;
+ memcpy(&stream->peer.in6, &in, in_len);
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+
+ return ev;
+}
+
+static inline LV2_OSC_Enum
+_lv2_osc_stream_run_tcp(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // handle connections
+ if(!stream->connected) // no peer
+ {
+ if(stream->server)
+ {
+ stream->peer.len = sizeof(stream->peer.in6);
+ stream->fd = accept(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ &stream->peer.len);
+
+ if(stream->fd >= 0)
+ {
+ const int flag = 1;
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+
+ if(fcntl(stream->fd, F_SETFL, O_NONBLOCK) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(flag)) != 0)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(sendbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(recvbuff)) == -1)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+
+ stream->connected = true; // orderly accept
+ }
+ else
+ {
+ //ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+ }
+ else
+ {
+ if(stream->sock < 0)
+ {
+ ev = _lv2_osc_stream_reinit(stream);
+ }
+
+ if(connect(stream->sock, (struct sockaddr *)&stream->peer.in6,
+ stream->peer.len) == 0)
+ {
+ stream->connected = true; // orderly (re)connect
+ }
+ else
+ {
+ //if(errno == EISCONN)
+ //{
+ // _close_socket(&stream->sock);
+ //}
+
+ //ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ }
+ }
+ }
+
+ // send everything
+ if(stream->connected)
+ {
+ int *fd = stream->server
+ ? &stream->fd
+ : &stream->sock;
+
+ if(*fd >= 0)
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ if(stream->slip) // SLIP framed
+ {
+ if(tosend <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ memcpy(stream->tx_buf, buf, tosend);
+ tosend = lv2_osc_slip_encode_inline(stream->tx_buf, tosend);
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ const size_t nsize = tosend + sizeof(uint32_t);
+
+ if(nsize <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ const uint32_t prefix = htonl(tosend);
+
+ memcpy(stream->tx_buf, &prefix, sizeof(uint32_t));
+ memcpy(stream->tx_buf + sizeof(uint32_t), buf, tosend);
+ tosend = nsize;
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+
+ const ssize_t sent = tosend
+ ? send(*fd, stream->tx_buf, tosend, 0)
+ : 0;
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+
+ // recv everything
+ if(stream->connected)
+ {
+ int *fd = stream->server
+ ? &stream->fd
+ : &stream->sock;
+
+ if(*fd >= 0)
+ {
+ if(stream->slip) // SLIP framed
+ {
+ while(true)
+ {
+ ssize_t recvd = recv(*fd, stream->rx_buf + stream->rx_off,
+ sizeof(stream->rx_buf) - stream->rx_off, 0);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ _close_socket(fd);
+ stream->connected = false; // orderly shutdown
+ break;
+ }
+
+ uint8_t *ptr = stream->rx_buf;
+ recvd += stream->rx_off;
+
+ while(recvd > 0)
+ {
+ size_t size;
+ size_t parsed = lv2_osc_slip_decode_inline(ptr, recvd, &size);
+
+ if(size) // dispatch
+ {
+ uint8_t *buf;
+
+ if( (buf = stream->driv->write_req(stream->data, size, NULL)) )
+ {
+ memcpy(buf, ptr, size);
+
+ stream->driv->write_adv(stream->data, size);
+ ev |= LV2_OSC_RECV;
+ }
+ else
+ {
+ parsed = 0;
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ }
+ }
+
+ if(parsed)
+ {
+ ptr += parsed;
+ recvd -= parsed;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(recvd > 0) // is there remaining chunk for next call?
+ {
+ memmove(stream->rx_buf, ptr, recvd);
+ stream->rx_off = recvd;
+ }
+ else
+ {
+ stream->rx_off = 0;
+ }
+
+ break;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ uint8_t *buf;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, NULL)) )
+ {
+ uint32_t prefix;
+
+ ssize_t recvd = recv(*fd, &prefix, sizeof(uint32_t), 0);
+ if(recvd == sizeof(uint32_t))
+ {
+ prefix = ntohl(prefix); //FIXME check prefix <= max_len
+ recvd = recv(*fd, buf, prefix, 0);
+ }
+ else if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ _close_socket(fd);
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ _close_socket(fd);
+ stream->connected = false; // orderly shutdown
+ break;
+ }
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ }
+ }
+
+ if(stream->connected)
+ {
+ ev |= LV2_OSC_CONN;
+ }
+
+ return ev;
+}
+
+static inline LV2_OSC_Enum
+_lv2_osc_stream_run_ser(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // send everything
+ {
+ const int fd = stream->sock;
+
+ if(fd >= 0)
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ if(stream->slip) // SLIP framed
+ {
+ if(tosend <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ memcpy(stream->tx_buf, buf, tosend);
+ tosend = lv2_osc_slip_encode_inline(stream->tx_buf, tosend);
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ const size_t nsize = tosend + sizeof(uint32_t);
+
+ if(nsize <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ const uint32_t prefix = htonl(tosend);
+
+ memcpy(stream->tx_buf, &prefix, sizeof(uint32_t));
+ memcpy(stream->tx_buf + sizeof(uint32_t), buf, tosend);
+ tosend = nsize;
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+
+ const ssize_t sent = tosend
+ ? write(fd, stream->tx_buf, tosend)
+ : 0;
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ ev = LV2_OSC_STREAM_ERRNO(ev, EIO);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+
+ // recv everything
+ {
+ const int fd = stream->sock;
+
+ if(fd >= 0)
+ {
+ if(stream->slip) // SLIP framed
+ {
+ while(true)
+ {
+ ssize_t recvd = read(fd, stream->rx_buf + stream->rx_off,
+ sizeof(stream->rx_buf) - stream->rx_off);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // orderly shutdown
+ break;
+ }
+
+ uint8_t *ptr = stream->rx_buf;
+ recvd += stream->rx_off;
+
+ while(recvd > 0)
+ {
+ size_t size;
+ size_t parsed = lv2_osc_slip_decode_inline(ptr, recvd, &size);
+
+ if(size) // dispatch
+ {
+ uint8_t *buf;
+
+ if( (buf = stream->driv->write_req(stream->data, size, NULL)) )
+ {
+ memcpy(buf, ptr, size);
+
+ stream->driv->write_adv(stream->data, size);
+ ev |= LV2_OSC_RECV;
+ }
+ else
+ {
+ parsed = 0;
+ ev = LV2_OSC_STREAM_ERRNO(ev, ENOMEM);
+ }
+ }
+
+ if(parsed)
+ {
+ ptr += parsed;
+ recvd -= parsed;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(recvd > 0) // is there remaining chunk for next call?
+ {
+ memmove(stream->rx_buf, ptr, recvd);
+ stream->rx_off = recvd;
+ }
+ else
+ {
+ stream->rx_off = 0;
+ }
+
+ break;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ uint8_t *buf;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, NULL)) )
+ {
+ uint32_t prefix;
+
+ ssize_t recvd = read(fd, &prefix, sizeof(uint32_t));
+ if(recvd == sizeof(uint32_t))
+ {
+ prefix = ntohl(prefix); //FIXME check prefix <= max_len
+ recvd = read(fd, buf, prefix);
+ }
+ else if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ stream->connected = false;
+ ev = LV2_OSC_STREAM_ERRNO(ev, errno);
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // orderly shutdown
+ break;
+ }
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ }
+ }
+
+ if(stream->connected)
+ {
+ ev |= LV2_OSC_CONN;
+ }
+
+ return ev;
+}
+
+static inline LV2_OSC_Enum
+lv2_osc_stream_run(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ switch(stream->socket_type)
+ {
+ case SOCK_DGRAM:
+ {
+ ev |= _lv2_osc_stream_run_udp(stream);
+ } break;
+ case SOCK_STREAM:
+ {
+ ev |= _lv2_osc_stream_run_tcp(stream);
+ } break;
+ default:
+ {
+ ev |= _lv2_osc_stream_run_ser(stream);
+ } break;
+ }
+
+ return ev;
+}
+
+static inline int
+lv2_osc_stream_get_file_descriptors(LV2_OSC_Stream *stream, int fds [2])
+{
+ if(!fds)
+ {
+ return 1;
+ }
+
+ fds[0] = stream->sock;
+ fds[1] = stream->fd;
+
+ return 0;
+}
+
+static inline LV2_OSC_Enum
+lv2_osc_stream_pollin(LV2_OSC_Stream *stream, int timeout_ms)
+{
+ int fd [2];
+
+ if(lv2_osc_stream_get_file_descriptors(stream, fd) != 0)
+ {
+ return LV2_OSC_STREAM_ERRNO(LV2_OSC_NONE, EBADF);
+ }
+
+ struct pollfd fds [2] = {
+ [0] = {
+ .fd = fd[0],
+ .events = POLLIN,
+ .revents = 0
+ },
+ [1] = {
+ .fd = fd[1],
+ .events = POLLIN,
+ .revents = 0
+ }
+ };
+
+ const int res = poll(fds, 2, timeout_ms);
+ if(res < 0)
+ {
+ return LV2_OSC_STREAM_ERRNO(LV2_OSC_NONE, errno);
+ }
+
+#if 0
+ fprintf(stderr, "++ %i: %i %i %i %i\n", res,
+ fds[0].fd, (int)fds[0].revents,
+ fds[1].fd, (int)fds[1].revents);
+#endif
+
+ return lv2_osc_stream_run(stream);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_STREAM_H
diff --git a/osc.lv2/osc.lv2/util.h b/osc.lv2/osc.lv2/util.h
new file mode 100644
index 0000000..fdf2da3
--- /dev/null
+++ b/osc.lv2/osc.lv2/util.h
@@ -0,0 +1,631 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_UTIL_H
+#define LV2_OSC_UTIL_H
+
+#include <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if !defined(_WIN32)
+# include <fnmatch.h>
+#endif
+
+#include <osc.lv2/osc.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __unused
+# define __unused __attribute__((unused))
+#endif
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef void (*LV2_OSC_Method)(const char *path,
+ const LV2_Atom_Tuple *arguments, void *data);
+
+typedef struct _LV2_OSC_Hook LV2_OSC_Hook;
+
+struct _LV2_OSC_Hook {
+ const char *name;
+ const LV2_OSC_Hook *hooks;
+ LV2_OSC_Method method;
+ void *data;
+};
+
+// characters not allowed in OSC path string
+static const char invalid_path_chars [] = {
+ ' ', '#',
+ '\0'
+};
+
+// allowed characters in OSC format string
+static const char valid_format_chars [] = {
+ LV2_OSC_INT32, LV2_OSC_FLOAT, LV2_OSC_STRING, LV2_OSC_BLOB,
+ LV2_OSC_TRUE, LV2_OSC_FALSE, LV2_OSC_NIL, LV2_OSC_IMPULSE,
+ LV2_OSC_INT64, LV2_OSC_DOUBLE, LV2_OSC_TIMETAG,
+ LV2_OSC_SYMBOL, LV2_OSC_MIDI,
+ '\0'
+};
+
+static inline bool
+lv2_osc_pattern_match(const char *from, const char *name, size_t len)
+{
+#if !defined(_WIN32)
+ size_t nbrace = 0;
+
+# if defined(FNM_EXTMATCH)
+ // count opening curly braces
+ for(size_t i = 0; i < len; i++)
+ {
+ if(from[i] == '{')
+ {
+ nbrace++;
+ }
+ }
+# endif
+
+ // allocate temporary pattern buffer
+ char *pattern = alloca(len + nbrace + 1);
+
+ if(!pattern)
+ {
+ return false;
+ }
+
+# if defined(FNM_EXTMATCH)
+ // convert {x,y} to @(x|y) for extended fnmatch
+ if(nbrace)
+ {
+ char *ptr = pattern;
+
+ for(size_t i = 0; i < len; i++)
+ {
+ switch(from[i])
+ {
+ case '{':
+ {
+ *ptr++ = '@';
+ *ptr++ = '(';
+ } break;
+ case ',':
+ {
+ *ptr++ = '|';
+ } break;
+ case '}':
+ {
+ *ptr++ = ')';
+ } break;
+ default:
+ {
+ *ptr++ = from[i];
+ } break;
+ }
+ }
+ }
+ else
+# endif
+ {
+ memcpy(pattern, from, len);
+ }
+
+ // terminate pattern string with null terminator
+ pattern[len + nbrace] = '\0';
+
+# if defined(FNM_EXTMATCH)
+ return fnmatch(pattern, name, FNM_NOESCAPE | FNM_EXTMATCH) == 0 ? true : false;
+# else
+ return fnmatch(pattern, name, FNM_NOESCAPE) == 0 ? true : false;
+# endif
+#else
+ return strncmp(from, name, len) == 0 ? true : false;
+#endif
+}
+
+static inline void
+_lv2_osc_hooks_internal(const char *path, const char *from,
+ const LV2_Atom_Tuple *arguments, const LV2_OSC_Hook *hooks)
+{
+ const char *ptr = strchr(from, '/');
+
+ const size_t len = ptr
+ ? (size_t)(ptr - from)
+ : strlen(from);
+
+ for(const LV2_OSC_Hook *hook = hooks; hook && hook->name; hook++)
+ {
+ if(lv2_osc_pattern_match(from, hook->name, len))
+ {
+ if(hook->hooks && ptr)
+ {
+ from = &ptr[1];
+
+ _lv2_osc_hooks_internal(path, from, arguments, hook->hooks);
+ }
+ else if(hook->method && !ptr)
+ {
+ hook->method(path, arguments, hook->data);
+ }
+ }
+ }
+}
+
+/**
+ TODO
+*/
+static inline void
+lv2_osc_hooks(const char *path, const LV2_Atom_Tuple *arguments, void *data)
+{
+ const LV2_OSC_Hook *hooks = data;
+ const char *from = &path[1];
+
+ _lv2_osc_hooks_internal(path, from, arguments, hooks);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_path(const char *path)
+{
+ assert(path);
+
+ if(path[0] != '/')
+ return false;
+
+ for(const char *ptr=path+1; *ptr!='\0'; ptr++)
+ if( (isprint(*ptr) == 0) || (strchr(invalid_path_chars, *ptr) != NULL) )
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_fmt(const char *format, int offset)
+{
+ assert(format);
+
+ if(offset && (format[0] != ',') )
+ return false;
+
+ for(const char *ptr=format+offset; *ptr!='\0'; ptr++)
+ if(strchr(valid_format_chars, *ptr) == NULL)
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline uint64_t
+lv2_osc_timetag_parse(const LV2_OSC_Timetag *timetag)
+{
+ return ((uint64_t)timetag->integral << 32) | timetag->fraction;
+}
+
+/**
+ TODO
+*/
+static inline LV2_OSC_Timetag *
+lv2_osc_timetag_create(LV2_OSC_Timetag *timetag, uint64_t tt)
+{
+ timetag->integral = tt >> 32;
+ timetag->fraction = tt & 0xffffffff;
+
+ return timetag;
+}
+
+#define LV2_OSC_TIMETAG_CREATE(tt) \
+ lv2_osc_timetag_create(&(LV2_OSC_Timetag){.integral = 0, .fraction = 0}, (tt))
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_packet_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Packet;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Bundle;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Message;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_or_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return lv2_osc_is_message_type(osc_urid, type)
+ || lv2_osc_is_bundle_type(osc_urid, type);
+}
+
+static inline LV2_OSC_Type
+lv2_osc_argument_type(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ return LV2_OSC_INT32;
+ else if(atom->type == osc_urid->ATOM_Float)
+ return LV2_OSC_FLOAT;
+ else if(atom->type == osc_urid->ATOM_String)
+ return LV2_OSC_STRING;
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ return LV2_OSC_BLOB;
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ return LV2_OSC_INT64;
+ else if(atom->type == osc_urid->ATOM_Double)
+ return LV2_OSC_DOUBLE;
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ return LV2_OSC_TIMETAG;
+
+ else if(atom->type == osc_urid->ATOM_Bool)
+ {
+ if(((const LV2_Atom_Bool *)atom)->body)
+ return LV2_OSC_TRUE;
+ else
+ return LV2_OSC_FALSE;
+ }
+ else if(atom->type == osc_urid->ATOM_Literal)
+ {
+ const LV2_Atom_Literal *lit = (const LV2_Atom_Literal *)atom;
+ if(lit->body.datatype == osc_urid->OSC_Nil)
+ return LV2_OSC_NIL;
+ else if(lit->body.datatype == osc_urid->OSC_Impulse)
+ return LV2_OSC_IMPULSE;
+ else if(lit->body.datatype == osc_urid->OSC_Char)
+ return LV2_OSC_CHAR;
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ return LV2_OSC_RGBA;
+ }
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ return LV2_OSC_SYMBOL;
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ return LV2_OSC_MIDI;
+
+ return '\0';
+}
+
+static inline const LV2_Atom *
+lv2_osc_int32_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ int32_t *i)
+{
+ assert(i);
+ *i = ((const LV2_Atom_Int *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_float_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ float *f)
+{
+ assert(f);
+ *f = ((const LV2_Atom_Float *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_string_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ const char **s)
+{
+ assert(s);
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_blob_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ uint32_t *size, const uint8_t **b)
+{
+ assert(size && b);
+ *size = atom->size;
+ *b = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_int64_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ int64_t *h)
+{
+ assert(h);
+ *h = ((const LV2_Atom_Long *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_double_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ double *d)
+{
+ assert(d);
+ *d = ((const LV2_Atom_Double *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_timetag_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
+ LV2_OSC_Timetag *timetag)
+{
+ assert(timetag);
+
+ const LV2_Atom_Long *integral = NULL;
+ const LV2_Atom_Long *fraction = NULL;
+
+ lv2_atom_object_get((const LV2_Atom_Object *)atom,
+ osc_urid->OSC_timetagIntegral, &integral,
+ osc_urid->OSC_timetagFraction, &fraction,
+ 0);
+
+ if( integral && (integral->atom.type == osc_urid->ATOM_Long)
+ && fraction && (fraction->atom.type == osc_urid->ATOM_Long) )
+ {
+ timetag->integral = integral->body;
+ timetag->fraction = fraction->body;
+ }
+ else
+ {
+ // set to immediate
+ timetag->integral = 0;
+ timetag->fraction = 1;
+ }
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_true_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_false_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_nil_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_impulse_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_symbol_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ LV2_URID *S)
+{
+ assert(S);
+ *S = ((const LV2_Atom_URID *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_midi_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ uint32_t *size, const uint8_t **m)
+{
+ assert(size && m);
+ *size = atom->size;
+ *m = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_char_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom, char *c)
+{
+ assert(c);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ *c = str[0];
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_rgba_get(LV2_OSC_URID *osc_urid __unused, const LV2_Atom *atom,
+ uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ assert(r && g && b && a);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+
+ uint8_t *key [4] = {
+ r, g, b, a
+ };
+
+ const char *pos = str;
+ char *endptr;
+
+ for(unsigned count = 0; count < 4; count++, pos += 2)
+ {
+ char buf [5] = {'0', 'x', pos[0], pos[1], '\0'};
+
+ *key[count] = strtol(buf, &endptr, 16);
+ }
+
+ return lv2_atom_tuple_next(atom);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ assert(timetag && items);
+
+ *timetag = NULL;
+ *items = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_bundleTimetag, timetag,
+ osc_urid->OSC_bundleItems, items,
+ 0);
+
+ if(!*timetag || ((*timetag)->atom.type != osc_urid->ATOM_Object) || ((*timetag)->body.otype != osc_urid->OSC_Timetag))
+ return false;
+ if(!*items || ((*items)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ return lv2_osc_bundle_body_get(osc_urid, obj->atom.size, &obj->body,
+ timetag, items);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ assert(path && arguments);
+
+ *path = NULL;
+ *arguments = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_messagePath, path,
+ osc_urid->OSC_messageArguments, arguments,
+ 0);
+
+ if(!*path || ((*path)->atom.type != osc_urid->ATOM_String))
+ return false;
+ // message without arguments is valid
+ if( *arguments && ((*arguments)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ return lv2_osc_message_body_get(osc_urid, obj->atom.size, &obj->body,
+ path, arguments);
+}
+
+static inline bool
+lv2_osc_body_unroll(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ LV2_OSC_Method method, void *data)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(!lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data))
+ return false;
+ }
+
+ return true;
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(!lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ return false;
+
+ if(method)
+ method(LV2_ATOM_BODY_CONST(path), arguments, data);
+
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool
+lv2_osc_unroll(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ LV2_OSC_Method method, void *data)
+{
+ return lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_UTIL_H
diff --git a/osc.lv2/osc.lv2/writer.h b/osc.lv2/osc.lv2/writer.h
new file mode 100644
index 0000000..037d44c
--- /dev/null
+++ b/osc.lv2/osc.lv2/writer.h
@@ -0,0 +1,579 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_WRITER_H
+#define LV2_OSC_WRITER_H
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/endian.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef struct _LV2_OSC_Writer LV2_OSC_Writer;
+typedef struct _LV2_OSC_Writer_Frame LV2_OSC_Writer_Frame;
+
+struct _LV2_OSC_Writer {
+ uint8_t *buf;
+ uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Writer_Frame {
+ uint8_t *ref;
+};
+
+static inline void
+lv2_osc_writer_initialize(LV2_OSC_Writer *writer, uint8_t *buf, size_t size)
+{
+ writer->buf = buf;
+ writer->ptr = buf;
+ writer->end = buf + size;
+}
+
+static inline size_t
+lv2_osc_writer_get_size(LV2_OSC_Writer *writer)
+{
+ if(writer->ptr > writer->buf)
+ return writer->ptr - writer->buf;
+
+ return 0;
+}
+
+static inline uint8_t *
+lv2_osc_writer_finalize(LV2_OSC_Writer *writer, size_t *size)
+{
+ *size = lv2_osc_writer_get_size(writer);
+
+ if(*size)
+ return writer->buf;
+
+ return NULL;
+}
+
+static inline bool
+lv2_osc_writer_overflow(LV2_OSC_Writer *writer, size_t size)
+{
+ return writer->ptr + size >= writer->end;
+}
+
+static inline bool
+lv2_osc_writer_htobe32(LV2_OSC_Writer *writer, union swap32_t *s32)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ s32->u = htobe32(s32->u);
+ *(uint32_t *)writer->ptr = s32->u;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_htobe64(LV2_OSC_Writer *writer, union swap64_t *s64)
+{
+ if(lv2_osc_writer_overflow(writer, 8))
+ return false;
+
+ s64->u = htobe64(s64->u);
+ *(uint64_t *)writer->ptr = s64->u;
+ writer->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_int32(LV2_OSC_Writer *writer, int32_t i)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .i = i });
+}
+
+static inline bool
+lv2_osc_writer_add_float(LV2_OSC_Writer *writer, float f)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .f = f });
+}
+
+static inline bool
+lv2_osc_writer_add_string(LV2_OSC_Writer *writer, const char *s)
+{
+ const size_t rawlen = strlen(s) + 1;
+ const size_t padded = LV2_OSC_PADDED_SIZE(rawlen);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ const uint32_t blank = 0;
+ memcpy(writer->ptr + padded - sizeof(uint32_t), &blank, sizeof(uint32_t));
+ memcpy(writer->ptr, s, rawlen);
+ writer->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_symbol(LV2_OSC_Writer *writer, const char *S)
+{
+ return lv2_osc_writer_add_string(writer, S);
+}
+
+static inline bool
+lv2_osc_writer_add_int64(LV2_OSC_Writer *writer, int64_t h)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .h = h });
+}
+
+static inline bool
+lv2_osc_writer_add_double(LV2_OSC_Writer *writer, double d)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .d = d });
+}
+
+static inline bool
+lv2_osc_writer_add_timetag(LV2_OSC_Writer *writer, uint64_t u)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .u = u });
+}
+
+static inline bool
+lv2_osc_writer_add_blob_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **body)
+{
+ const size_t len_padded = LV2_OSC_PADDED_SIZE(len);
+ const size_t size = 4 + len_padded;
+ if(lv2_osc_writer_overflow(writer, size))
+ return false;
+
+ if(!lv2_osc_writer_add_int32(writer, len))
+ return false;
+
+ *body = writer->ptr;
+ //memset(&writer->ptr[len], 0x0, len_padded - len);
+ writer->ptr += len_padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_blob(LV2_OSC_Writer *writer, int32_t len, const uint8_t *body)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_blob_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, body, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **m)
+{
+ if( (len > 4) || lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ *m = writer->ptr;
+ //memset(&writer->ptr[len], 0x0, 4 - len);
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi(LV2_OSC_Writer *writer, int32_t len, const uint8_t *m)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_midi_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, m, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_rgba(LV2_OSC_Writer *writer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ writer->ptr[0] = r;
+ writer->ptr[1] = g;
+ writer->ptr[2] = b;
+ writer->ptr[3] = a;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_char(LV2_OSC_Writer *writer, char c)
+{
+ return lv2_osc_writer_add_int32(writer, (int32_t)c);
+}
+
+static inline bool
+lv2_osc_writer_push_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame, uint64_t t)
+{
+ if(lv2_osc_writer_overflow(writer, 16))
+ return false;
+
+ frame->ref = writer->ptr;
+
+ strncpy((char *)writer->ptr, "#bundle", 8);
+ writer->ptr += 8;
+
+ return lv2_osc_writer_add_timetag(writer, t);
+}
+
+static inline bool
+lv2_osc_writer_pop_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 16};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_push_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ frame->ref = writer->ptr;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_pop_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 4};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ s32.u = htobe32(s32.u);
+ *(uint32_t *)frame->ref = s32.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_path(LV2_OSC_Writer *writer, const char *path)
+{
+ return lv2_osc_writer_add_string(writer, path);
+}
+
+static inline bool
+lv2_osc_writer_add_format(LV2_OSC_Writer *writer, const char *fmt)
+{
+ const size_t rawlen = strlen(fmt) + 1;
+ const size_t padded = LV2_OSC_PADDED_SIZE(rawlen + 1);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ const uint32_t blank = 0;
+ memcpy(writer->ptr + padded - sizeof(uint32_t), &blank, sizeof(uint32_t));
+ *writer->ptr++ = ',';
+ memcpy(writer->ptr, fmt, rawlen);
+ writer->ptr += padded - 1;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_writer_add_int32(writer, va_arg(args, int32_t)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_writer_add_float(writer, (float)va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_writer_add_string(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_blob(writer, len, va_arg(args, const uint8_t *)))
+ return false;
+ } break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_writer_add_int64(writer, va_arg(args, int64_t)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_writer_add_double(writer, va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_writer_add_timetag(writer, va_arg(args, uint64_t)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_midi(writer, len, va_arg(args, const uint8_t *)))
+ return false;
+ } break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_writer_add_symbol(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_writer_add_char(writer, va_arg(args, int)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ {
+ const uint8_t r = va_arg(args, unsigned);
+ const uint8_t g = va_arg(args, unsigned);
+ const uint8_t b = va_arg(args, unsigned);
+ const uint8_t a = va_arg(args, unsigned);
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
+ return false;
+ } break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_vararg(LV2_OSC_Writer *writer, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_arg_varlist(writer, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_message_varlist(LV2_OSC_Writer *writer, const char *path, const char *fmt, va_list args)
+{
+ if(!lv2_osc_writer_add_path(writer, path))
+ return false;
+
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ return lv2_osc_writer_arg_varlist(writer, fmt, args);
+}
+
+static inline bool
+lv2_osc_writer_message_vararg(LV2_OSC_Writer *writer, const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_message_varlist(writer, path, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_packet(LV2_OSC_Writer *writer, LV2_OSC_URID *osc_urid,
+ LV2_URID_Unmap *unmap, uint32_t size, const LV2_Atom_Object_Body *body)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ LV2_OSC_Writer_Frame bndl = { .ref = 0 };
+
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+ if(!lv2_osc_writer_push_bundle(writer, &bndl, lv2_osc_timetag_parse(&tt)))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+ LV2_OSC_Writer_Frame itm = { .ref = 0 };
+
+ if( !lv2_osc_writer_push_item(writer, &itm)
+ || !lv2_osc_writer_packet(writer, osc_urid, unmap, obj->atom.size, &obj->body)
+ || !lv2_osc_writer_pop_item(writer, &itm) )
+ {
+ return false;
+ }
+ }
+
+ return lv2_osc_writer_pop_bundle(writer, &bndl);
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ {
+ if(!lv2_osc_writer_add_path(writer, LV2_ATOM_BODY_CONST(path)))
+ return false;
+
+ char fmt [128]; //TODO how big?
+ char *ptr = fmt;
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ *ptr++ = lv2_osc_argument_type(osc_urid, atom);
+ }
+ *ptr = '\0';
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ {
+ if(!lv2_osc_writer_add_int32(writer, ((const LV2_Atom_Int *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Float)
+ {
+ if(!lv2_osc_writer_add_float(writer, ((const LV2_Atom_Float *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_String)
+ {
+ if(!lv2_osc_writer_add_string(writer, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ {
+ if(!lv2_osc_writer_add_blob(writer, atom->size, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ {
+ if(!lv2_osc_writer_add_int64(writer, ((const LV2_Atom_Long *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Double)
+ {
+ if(!lv2_osc_writer_add_double(writer, ((const LV2_Atom_Double *)atom)->body))
+ return false;
+ }
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &obj->atom, &tt);
+ if(!lv2_osc_writer_add_timetag(writer, lv2_osc_timetag_parse(&tt)))
+ return false;
+ }
+
+ // there is nothing to do for: true, false, nil, impulse
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ {
+ const char *symbol = unmap->unmap(unmap->handle, ((const LV2_Atom_URID *)atom)->body);
+ if(!symbol || !lv2_osc_writer_add_symbol(writer, symbol))
+ return false;
+ }
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ {
+ uint8_t *m = NULL;
+ if(!lv2_osc_writer_add_midi_inline(writer, atom->size + 1, &m))
+ return false;
+ m[0] = 0x0; // port
+ memcpy(&m[1], LV2_ATOM_BODY_CONST(atom), atom->size);
+ }
+ else if(atom->type == osc_urid->ATOM_Literal)
+ {
+ const LV2_Atom_Literal *lit = (LV2_Atom_Literal *)atom;
+
+ if(lit->body.datatype == osc_urid->OSC_Char)
+ {
+ const char c = *(const char *)LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, lit);
+ if(!lv2_osc_writer_add_char(writer, c))
+ return false;
+ }
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ {
+ const char *rgba = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ uint8_t r, g, b, a;
+ if(sscanf(rgba, "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, &r, &g, &b, &a) != 4)
+ return false;
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_WRITER_H
diff --git a/osc.lv2/osc.ttl b/osc.lv2/osc.ttl
new file mode 100644
index 0000000..db4a048
--- /dev/null
+++ b/osc.lv2/osc.ttl
@@ -0,0 +1,42 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a owl:Ontology ;
+ rdfs:seeAlso <lv2_osc.h> ,
+ <lv2-osc.doap.ttl> ;
+ lv2:documentation """
+ <p>This specification defines event data types for OSC bundles and message.
+ To signal support for OSC events on an atom:AtomPort with an atom:bufferType
+ of atom:Sequence, plugin authors should add atom:supports osc:Event to
+ the plugin specification.</p>
+ """ .
+
+osc:schedule
+ a lv2:Feature .
+
+osc:Event
+ a rdfs:Class ,
+ rdfs:Datatype ;
+ rdfs:subClassOf atom:Atom ;
+ owl:onDatatype xsd:hexBinary ;
+ rdfs:label "OSC Event (Bundle or Message)" .
diff --git a/osc.lv2/test/osc_test.c b/osc.lv2/test/osc_test.c
new file mode 100644
index 0000000..2c6a710
--- /dev/null
+++ b/osc.lv2/test/osc_test.c
@@ -0,0 +1,1373 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <time.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/reader.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+#if !defined(_WIN32)
+# include <osc.lv2/stream.h>
+#endif
+
+#define BUF_SIZE 0x100000
+#define MAX_URIDS 512
+
+typedef void (*test_t)(LV2_OSC_Writer *writer);
+typedef struct _urid_t urid_t;
+typedef struct _app_t app_t;
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+struct _app_t {
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+static app_t __app;
+static uint8_t buf0 [BUF_SIZE];
+static uint8_t buf1 [BUF_SIZE];
+static uint8_t buf2 [BUF_SIZE];
+static const LV2_Atom_Object *obj2= (const LV2_Atom_Object *)buf2;
+
+const uint8_t raw_0 [] = {
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_1 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'i', 'f', 's',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x59, 0x99, 0x9a,
+ 'w', 'o', 'r', 'l',
+ 'd', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_2 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'h', 'd', 'S',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x0b, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33,
+ 'h', 't', 't', 'p',
+ ':', '/', '/', 'e',
+ 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c',
+ 'o', 'm', 0x0, 0x0
+};
+
+const uint8_t raw_3 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'T', 'F', 'N',
+ 'I', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_4 [] = {
+ '/', 'm', 'i', 'd',
+ 'i', 0x0, 0x0, 0x0,
+ ',', 'm', 0x0, 0x0,
+ 0x0, 0x90, 24, 0x7f
+};
+
+const uint8_t raw_5 [] = {
+ '/', 'b', 'l', 'o',
+ 'b', 0x0, 0x0, 0x0,
+ ',', 'b', 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x6,
+ 0x1, 0x2, 0x3, 0x4,
+ 0x5, 0x6, 0x0, 0x0
+};
+
+const uint8_t raw_6 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_7 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x1c,
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_8 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 't', 'c', 'r',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+ 0x0, 0x0, 0x0, 'o',
+ 0x1, 0x2, 0x3, 0x4
+};
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ app_t *app = instance;
+
+ urid_t *itm;
+ for(itm=app->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(app->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++app->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const char *
+_unmap(LV2_URID_Unmap_Handle instance, LV2_URID urid)
+{
+ app_t *app = instance;
+
+ urid_t *itm;
+ for(itm=app->urids; itm->urid; itm++)
+ {
+ if(itm->urid == urid)
+ return itm->uri;
+ }
+
+ // not found
+ return NULL;
+}
+
+static LV2_URID_Map map = {
+ .handle = &__app,
+ .map = _map
+};
+
+static LV2_URID_Unmap unmap = {
+ .handle = &__app,
+ .unmap = _unmap
+};
+
+//#define DUMP
+#if defined(DUMP)
+static void
+_dump(const uint8_t *src, const uint8_t *dst, size_t size)
+{
+ for(size_t i = 0; i < size; i++)
+ printf("%zu %02x %02x\n", i, src[i], dst[i]);
+ printf("\n");
+}
+#endif
+
+static void
+_clone(LV2_OSC_Reader *reader, LV2_OSC_Writer *writer, size_t size)
+{
+ if(lv2_osc_reader_is_bundle(reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(reader, size);
+ assert(itm);
+
+ LV2_OSC_Writer_Frame frame_bndl = { .ref = 0 };
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, itm->timetag));
+
+ OSC_READER_BUNDLE_ITERATE(reader, itm)
+ {
+ LV2_OSC_Reader reader2;
+ lv2_osc_reader_initialize(&reader2, itm->body, itm->size);
+
+ LV2_OSC_Writer_Frame frame_itm = { .ref = 0 };
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ _clone(&reader2, writer, itm->size);
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+ }
+ else if(lv2_osc_reader_is_message(reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, size);
+ assert(arg);
+
+ assert(lv2_osc_writer_add_path(writer, arg->path));
+ assert(lv2_osc_writer_add_format(writer, arg->type));
+
+ OSC_READER_MESSAGE_ITERATE(reader, arg)
+ {
+ switch((LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ assert(lv2_osc_writer_add_int32(writer, arg->i));
+ break;
+ case LV2_OSC_FLOAT:
+ assert(lv2_osc_writer_add_float(writer, arg->f));
+ break;
+ case LV2_OSC_STRING:
+ assert(lv2_osc_writer_add_string(writer, arg->s));
+ break;
+ case LV2_OSC_BLOB:
+ assert(lv2_osc_writer_add_blob(writer, arg->size, arg->b));
+ break;
+
+ case LV2_OSC_INT64:
+ assert(lv2_osc_writer_add_int64(writer, arg->h));
+ break;
+ case LV2_OSC_DOUBLE:
+ assert(lv2_osc_writer_add_double(writer, arg->d));
+ break;
+ case LV2_OSC_TIMETAG:
+ assert(lv2_osc_writer_add_timetag(writer, arg->t));
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_MIDI:
+ assert(lv2_osc_writer_add_midi(writer, arg->size, arg->m));
+ break;
+ case LV2_OSC_SYMBOL:
+ assert(lv2_osc_writer_add_symbol(writer, arg->S));
+ break;
+ case LV2_OSC_CHAR:
+ assert(lv2_osc_writer_add_char(writer, arg->c));
+ break;
+ case LV2_OSC_RGBA:
+ assert(lv2_osc_writer_add_rgba(writer, arg->R, arg->G, arg->B, arg->A));
+ break;
+ }
+ }
+ }
+}
+
+static void
+_test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
+{
+ LV2_OSC_URID osc_urid;
+ lv2_osc_urid_init(&osc_urid, &map);
+
+ // check writer against raw bytes
+ size_t len;
+ assert(lv2_osc_writer_finalize(writer, &len) == buf0);
+ assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf0, size) != 0)
+ _dump(raw, buf0, size);
+#endif
+ assert(memcmp(raw, buf0, size) == 0);
+
+ // check reader & writer
+ LV2_OSC_Reader reader;
+ lv2_osc_reader_initialize(&reader, buf0, size);
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ _clone(&reader, writer, size);
+
+ // check cloned against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
+ assert(memcmp(raw, buf1, size) == 0);
+
+ // check forge
+ LV2_Atom_Forge forge;
+ lv2_atom_forge_init(&forge, &map);
+ lv2_atom_forge_set_buffer(&forge, buf2, BUF_SIZE);
+ assert(lv2_osc_forge_packet(&forge, &osc_urid, &map, buf0, size));
+
+ // check deforge
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ assert(lv2_osc_writer_packet(writer, &osc_urid, &unmap, obj2->atom.size, &obj2->body));
+
+ // check deforged against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
+ assert(memcmp(raw, buf1, size) == 0);
+}
+
+static void
+test_0_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ _test_a(writer, raw_0, sizeof(raw_0));
+}
+
+static void
+test_1_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "ifs",
+ 12, 3.4f, "world"));
+ _test_a(writer, raw_1, sizeof(raw_1));
+}
+
+static void
+test_2_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "hdS",
+ (int64_t)12, (double)3.4, "http://example.com"));
+ _test_a(writer, raw_2, sizeof(raw_2));
+}
+
+static void
+test_3_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "TFNI"));
+ _test_a(writer, raw_3, sizeof(raw_3));
+}
+
+static void
+test_4_a(LV2_OSC_Writer *writer)
+{
+ const uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ const int32_t len = sizeof(m);
+ assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", len, m));
+ _test_a(writer, raw_4, sizeof(raw_4));
+}
+
+static void
+test_5_a(LV2_OSC_Writer *writer)
+{
+ const uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ const int32_t len = sizeof(b);
+ assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", len, b));
+ _test_a(writer, raw_5, sizeof(raw_5));
+}
+
+static void
+test_6_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl = { .ref = 0 };
+ LV2_OSC_Writer_Frame frame_itm = { .ref = 0 };
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+
+ _test_a(writer, raw_6, sizeof(raw_6));
+}
+
+static void
+test_7_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl[2] = { { .ref = 0 }, { .ref = 0 } };
+ LV2_OSC_Writer_Frame frame_itm[2] = { { .ref = 0 }, { .ref = 0 } };;
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[0], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[1], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[1]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[1]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[1]));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[0]));
+
+ _test_a(writer, raw_7, sizeof(raw_7));
+}
+
+static void
+test_8_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "tcr",
+ 1ULL,
+ 'o',
+ 0x1, 0x2, 0x3, 0x4));
+ _test_a(writer, raw_8, sizeof(raw_8));
+}
+
+static test_t tests [] = {
+ test_0_a,
+ test_1_a,
+ test_2_a,
+ test_3_a,
+ test_4_a,
+ test_5_a,
+ test_6_a,
+ test_7_a,
+ test_8_a,
+
+ NULL
+}
+;
+static int
+_run_tests()
+{
+ LV2_OSC_Writer writer;
+
+ for(test_t *test=tests; *test; test++)
+ {
+ test_t cb = *test;
+
+ memset(buf0, 0x0, BUF_SIZE);
+ memset(buf1, 0x0, BUF_SIZE);
+
+ lv2_osc_writer_initialize(&writer, buf0, BUF_SIZE);
+
+ cb(&writer);
+ }
+
+ assert(unmap.unmap(unmap.handle, 0)== NULL);
+
+ return 0;
+}
+
+#if !defined(_WIN32)
+typedef struct _item_t item_t;
+typedef struct _stash_t stash_t;
+
+struct _item_t {
+ size_t size;
+ uint8_t buf [];
+};
+
+struct _stash_t {
+ size_t size;
+ item_t **items;
+ item_t *rsvd;
+};
+
+static uint8_t *
+_stash_write_req(stash_t *stash, size_t minimum, size_t *maximum)
+{
+ if(!stash->rsvd || (stash->rsvd->size < minimum))
+ {
+ const size_t sz = sizeof(item_t) + minimum;
+ stash->rsvd = realloc(stash->rsvd, sz);
+ assert(stash->rsvd);
+ stash->rsvd->size = minimum;
+ }
+
+ if(maximum)
+ {
+ *maximum = stash->rsvd->size;
+ }
+
+ return stash->rsvd->buf;
+}
+
+static void
+_stash_write_adv(stash_t *stash, size_t written)
+{
+ assert(stash->rsvd);
+ assert(stash->rsvd->size >= written);
+ stash->rsvd->size = written;
+ stash->size += 1;
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+ stash->items[stash->size - 1] = stash->rsvd;
+ stash->rsvd = NULL;
+}
+
+static const uint8_t *
+_stash_read_req(stash_t *stash, size_t *size)
+{
+ if(stash->size == 0)
+ {
+ if(size)
+ {
+ *size = 0;
+ }
+
+ return NULL;
+ }
+
+ item_t *item = stash->items[0];
+
+ if(size)
+ {
+ *size = item->size;
+ }
+
+ return item->buf;
+}
+
+static void
+_stash_read_adv(stash_t *stash)
+{
+ assert(stash->size);
+
+ free(stash->items[0]);
+ stash->size -= 1;
+
+ for(unsigned i = 0; i < stash->size; i++)
+ {
+ stash->items[i] = stash->items[i+1];
+ }
+
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+}
+
+static void *
+_write_req(void *data, size_t minimum, size_t *maximum)
+{
+ stash_t *stash = data;
+
+ return _stash_write_req(&stash[0], minimum, maximum);
+}
+
+static void
+_write_adv(void *data, size_t written)
+{
+ stash_t *stash = data;
+
+ _stash_write_adv(&stash[0], written);
+}
+
+static const void *
+_read_req(void *data, size_t *toread)
+{
+ stash_t *stash = data;
+
+ return _stash_read_req(&stash[1], toread);
+}
+
+static void
+_read_adv(void *data)
+{
+ stash_t *stash = data;
+
+ _stash_read_adv(&stash[1]);
+}
+
+static const LV2_OSC_Driver driv = {
+ .write_req = _write_req,
+ .write_adv = _write_adv,
+ .read_req = _read_req,
+ .read_adv = _read_adv
+};
+
+#define COUNT 128
+
+typedef struct _pair_t pair_t;
+
+struct _pair_t {
+ const char *server;
+ const char *client;
+ bool lossy;
+};
+
+static void *
+_thread_1(void *data)
+{
+ const pair_t *pair = data;
+ const char *uri = pair->server;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ time_t t0 = time(NULL);
+ unsigned count = 0;
+ while(true)
+ {
+ const time_t t1 = time(NULL);
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 0);
+ check[arg->i] = 1;
+ }
+
+ count++;
+
+ while(true)
+ {
+ // send back
+ uint8_t *buf_tx;
+ if( (buf_tx = _stash_write_req(&stash[1], reat, NULL)) )
+ {
+ memcpy(buf_tx, buf_rx, reat);
+
+ _stash_write_adv(&stash[1], reat);
+ break;
+ }
+ }
+
+ _stash_read_adv(&stash[0]);
+ }
+
+ t0 = t1;
+ }
+
+ if(count >= COUNT)
+ {
+ break;
+ }
+ else if(pair->lossy && (difftime(t1, t0) >= 1.0) )
+ {
+ fprintf(stderr, "%s: timeout: %i\n", __func__, count);
+ break;
+ }
+ }
+
+ LV2_OSC_Enum ev;
+ do
+ {
+ ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+ } while( (ev & LV2_OSC_SEND) || (ev & LV2_OSC_CONN) );
+
+ assert(pair->lossy || (count == COUNT) );
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ free(stash[0].rsvd);
+ while(stash[0].size)
+ {
+ _stash_read_adv(&stash[0]);
+ }
+ free(stash[0].items);
+
+ free(stash[1].rsvd);
+ while(stash[1].size)
+ {
+ _stash_read_adv(&stash[1]);
+ }
+ free(stash[1].items);
+
+ return NULL;
+}
+
+static void *
+_thread_2(void *data)
+{
+ const pair_t *pair = data;
+ const char *uri = pair->client;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ unsigned count = 0;
+ for(int32_t i = 0; i < COUNT; i++)
+ {
+ LV2_OSC_Writer writer;
+
+ while(true)
+ {
+ uint8_t *buf_tx;
+ size_t max;
+ if( (buf_tx = _stash_write_req(&stash[1], 1024, &max)) )
+ {
+ size_t writ;
+ lv2_osc_writer_initialize(&writer, buf_tx, max);
+ assert(lv2_osc_writer_message_vararg(&writer, "/trip", "i", i));
+ assert(lv2_osc_writer_finalize(&writer, &writ) == buf_tx);
+ assert(writ == 16);
+ assert(check[i] == 0);
+ check[i] = 1;
+
+ _stash_write_adv(&stash[1], writ);
+ break;
+ }
+ }
+
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+ }
+ }
+
+ time_t t0 = time(NULL);
+ while(true)
+ {
+ const time_t t1 = time(NULL);
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_ERR)
+ {
+ fprintf(stderr, "%s: %s\n", __func__, strerror(ev & LV2_OSC_ERR));
+ }
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+
+ t0 = t1;
+ }
+
+ if(count >= COUNT)
+ {
+ break;
+ }
+ else if(pair->lossy && (difftime(t1, t0) >= 1.0) )
+ {
+ fprintf(stderr, "%s: timeout: %i\n", __func__, count);
+ break;
+ }
+ }
+
+ assert(pair->lossy || (count == COUNT) );
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ free(stash[0].rsvd);
+ while(stash[0].size)
+ {
+ _stash_read_adv(&stash[0]);
+ }
+ free(stash[0].items);
+
+ free(stash[1].rsvd);
+ while(stash[1].size)
+ {
+ _stash_read_adv(&stash[1]);
+ }
+ free(stash[1].items);
+
+ return NULL;
+}
+
+static const pair_t pairs [] = {
+ {
+ .server = "osc.udp://:2222",
+ .client = "osc.udp://localhost:2222",
+ .lossy = true
+ },
+ {
+ .server = "osc.udp://[]:3333",
+ .client = "osc.udp://[::1]:3333",
+ .lossy = true
+ },
+
+ {
+ .server = "osc.udp://:3344",
+ .client = "osc.udp://255.255.255.255:3344",
+ .lossy = true
+ },
+
+ {
+ .server = "osc.tcp://:4444",
+ .client = "osc.tcp://localhost:4444",
+ .lossy = false
+ },
+ {
+ .server = "osc.tcp://[]:5555",
+ .client = "osc.tcp://[::1]:5555",
+ .lossy = false
+ },
+
+ {
+ .server = "osc.slip.tcp://:6666",
+ .client = "osc.slip.tcp://localhost:6666",
+ .lossy = false
+ },
+ {
+ .server = "osc.slip.tcp://[]:7777",
+ .client = "osc.slip.tcp://[::1]:7777",
+ .lossy = false
+ },
+
+ {
+ .server = "osc.prefix.tcp://:8888",
+ .client = "osc.prefix.tcp://localhost:8888",
+ .lossy = false
+ },
+ {
+ .server = "osc.prefix.tcp://[%lo]:9999",
+ .client = "osc.prefix.tcp://[::1%lo]:9999",
+ .lossy = false
+ },
+
+#if 0
+ {
+ .server = "osc.serial:///dev/pts/4", //FIXME baudrate
+ .client = "osc.serial:///dev/pts/5",
+ .lossy = false
+ },
+#endif
+
+ {
+ .server = NULL,
+ .client = NULL,
+ .lossy = false
+ }
+};
+#endif
+
+#if !defined(_WIN32)
+static unsigned foo_sub_one = 0;
+static unsigned foo_sub_two [2] = { 0, 0 };
+static unsigned foo = 0;
+static unsigned bar = 0;
+
+static void
+_one(const char *path, unsigned *flag)
+{
+ *flag += 1;
+
+ if(!path)
+ {
+ return;
+ }
+
+ assert(!strcmp(path, "/sub/one")
+ || !strcmp(path, "/*/one")
+ || !strcmp(path, "/s*/one")
+ || !strcmp(path, "/su*/one")
+ || !strcmp(path, "/sub*/one")
+ || !strcmp(path, "/sub/*")
+ || !strcmp(path, "/*sub/one")
+ || !strcmp(path, "/*s*u*b*/one")
+ || !strcmp(path, "/su[ab]/one")
+ || !strcmp(path, "/su[a-b]/[!a-np-z]ne")
+ || !strcmp(path, "/su[a-b]/one")
+ || !strcmp(path, "/s?b/?ne")
+ || !strcmp(path, "/s?*/?ne")
+ || !strcmp(path, "/s?*/*?e")
+ || !strcmp(path, "/sub/{one,two}"));
+}
+
+static void
+_two(const char *path, unsigned *flag)
+{
+ *flag += 1;
+
+ if(!path)
+ {
+ return;
+ }
+
+ assert(!strcmp(path, "/sub/two")
+ || !strcmp(path, "/sub/*")
+ || !strcmp(path, "/sub/{one,two}"));
+}
+
+static void
+_foo(const char *path, unsigned *flag)
+{
+ *flag += 1;
+
+ if(!path)
+ {
+ return;
+ }
+
+ assert(!strcmp(path, "/foo")
+ || !strcmp(path, "/{foo,bar}"));
+}
+
+static void
+_bar(const char *path, unsigned *flag)
+{
+ *flag += 1;
+
+ if(!path)
+ {
+ return;
+ }
+
+ assert(!strcmp(path, "/bar")
+ || !strcmp(path, "/{foo,bar}"));
+}
+
+static void
+_hook_one(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)),
+ void *data)
+{
+ _one(path, data);
+}
+
+static void
+_hook_two(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)),
+ void *data)
+{
+ _two(path, data);
+}
+
+static void
+_hook_foo(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)),
+ void *data)
+{
+ _foo(path, data);
+}
+
+static void
+_hook_bar(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)),
+ void *data)
+{
+ _bar(path, data);
+}
+
+static LV2_OSC_Hook hook_sub [] = {
+ { .name = "one", .method = _hook_one, .data = &foo_sub_one },
+ { .name = "two", .method = _hook_two, .data = &foo_sub_two[0] },
+ { .name = "two", .method = _hook_two, .data = &foo_sub_two[1] },
+ { .name = NULL }
+};
+
+static LV2_OSC_Hook hook_root [] = {
+ { .name = "foo", .method = _hook_foo, .data = &foo },
+ { .name = "bar", .method = _hook_bar, .data = &bar },
+ { .name = "sub", .hooks = hook_sub },
+ { .name = NULL }
+};
+
+static LV2_OSC_Tree tree_sub [4];
+
+static void
+_branch_one(LV2_OSC_Reader *reader __attribute__((unused)),
+ LV2_OSC_Arg *arg __attribute__((unused)),
+ const LV2_OSC_Tree *tree __attribute__((unused)),
+ void *data __attribute__((unused)))
+{
+ _one(NULL, &foo_sub_one);
+}
+
+static void
+_branch_two(LV2_OSC_Reader *reader __attribute__((unused)),
+ LV2_OSC_Arg *arg __attribute__((unused)),
+ const LV2_OSC_Tree *tree __attribute__((unused)),
+ void *data __attribute__((unused)))
+{
+ const size_t idx = tree - &tree_sub[1];
+
+ _two(NULL, &foo_sub_two[idx]);
+}
+
+static void
+_branch_foo(LV2_OSC_Reader *reader __attribute__((unused)),
+ LV2_OSC_Arg *arg __attribute__((unused)),
+ const LV2_OSC_Tree *tree __attribute__((unused)),
+ void *data __attribute__((unused)))
+{
+ _foo(NULL, &foo);
+}
+
+static void
+_branch_bar(LV2_OSC_Reader *reader __attribute__((unused)),
+ LV2_OSC_Arg *arg __attribute__((unused)),
+ const LV2_OSC_Tree *tree __attribute__((unused)),
+ void *data __attribute__((unused)))
+{
+ _bar(NULL, &bar);
+}
+
+static LV2_OSC_Tree tree_sub [] = {
+ { .name = "one", .branch = _branch_one },
+ { .name = "two", .branch = _branch_two },
+ { .name = "two", .branch = _branch_two },
+ { .name = NULL }
+};
+
+static LV2_OSC_Tree tree_root [] = {
+ { .name = "foo", .branch = _branch_foo },
+ { .name = "bar", .branch = _branch_bar },
+ { .name = "sub", .trees = tree_sub },
+ { .name = NULL }
+};
+
+static bool
+_run_test_hooks_internal(const char *path)
+{
+ foo_sub_one = foo_sub_two[0] = foo_sub_two[1] = foo = bar = false;
+
+ {
+ LV2_OSC_URID osc_urid;
+ LV2_Atom_Forge forge;
+
+ lv2_osc_urid_init(&osc_urid, &map);
+ lv2_atom_forge_init(&forge, &map);
+
+ lv2_atom_forge_set_buffer(&forge, buf0, BUF_SIZE);
+ assert(lv2_osc_forge_message_vararg(&forge, &osc_urid, path, ""));
+
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)buf0;;
+ assert(lv2_osc_unroll(&osc_urid, obj, lv2_osc_hooks, hook_root) == true);
+ }
+
+ {
+ LV2_OSC_Writer writer;
+ LV2_OSC_Reader reader;
+
+ lv2_osc_writer_initialize(&writer, buf1, BUF_SIZE);
+ assert(lv2_osc_writer_message_vararg(&writer, path, "") == true);
+
+ size_t len;
+ const uint8_t *buf = lv2_osc_writer_finalize(&writer, &len);
+ assert(buf);
+ assert(len);
+
+ lv2_osc_reader_initialize(&reader, buf, len);
+ lv2_osc_reader_match(&reader, len, tree_root, NULL);
+ }
+
+ return true;
+}
+
+static int
+_run_test_hooks()
+{
+ {
+ assert(_run_test_hooks_internal("/nil") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/foo") == true);
+ assert(foo == 2);
+ assert(bar == 0);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/bar") == true);
+ assert(foo == 0);
+ assert(bar == 2);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub/nil") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub/two") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 2);
+ assert(foo_sub_two[1] == 2);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub/*") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 2);
+ assert(foo_sub_two[1] == 2);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/*/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/s*/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/su*/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub*/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/*sub/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/*s*u*b*/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/su[ab]/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/su[a-b]/[!a-np-z]ne") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/su[!a-b]/one") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/s?b/?ne") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/s?*/*?e") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/{foo,bar}") == true);
+ assert(foo == 2);
+ assert(bar == 2);
+ assert(foo_sub_one == 0);
+ assert(foo_sub_two[0] == 0);
+ assert(foo_sub_two[1] == 0);
+ }
+
+ {
+ assert(_run_test_hooks_internal("/sub/{one,two}") == true);
+ assert(foo == 0);
+ assert(bar == 0);
+ assert(foo_sub_one == 2);
+ assert(foo_sub_two[0] == 2);
+ assert(foo_sub_two[1] == 2);
+ }
+
+ return 0;
+}
+#endif
+
+int
+main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
+{
+#if !defined(_WIN32)
+ (void)lv2_osc_stream_pollin; //FIXME
+#endif
+
+ fprintf(stdout, "running main tests:\n");
+ assert(_run_tests() == 0);
+
+#if !defined(_WIN32)
+ fprintf(stdout, "running hook tests:\n");
+ assert(_run_test_hooks() == 0);
+#else
+ (void)lv2_osc_hooks; //FIXME
+#endif
+
+#if !defined(_WIN32)
+ for(const pair_t *pair = pairs; pair->server; pair++)
+ {
+ pthread_t thread_1;
+ pthread_t thread_2;
+
+ fprintf(stdout, "running stream test: <%s> <%s> %i\n",
+ pair->server, pair->client, pair->lossy);
+
+ assert(pthread_create(&thread_1, NULL, _thread_1, (void *)pair) == 0);
+ assert(pthread_create(&thread_2, NULL, _thread_2, (void *)pair) == 0);
+
+ assert(pthread_join(thread_1, NULL) == 0);
+ assert(pthread_join(thread_2, NULL) == 0);
+ }
+#endif
+
+ for(unsigned i=0; i<__app.urid; i++)
+ {
+ urid_t *itm = &__app.urids[i];
+
+ free(itm->uri);
+ }
+
+ return 0;
+}
diff --git a/osc_inspector.c b/osc_inspector.c
new file mode 100644
index 0000000..517abe1
--- /dev/null
+++ b/osc_inspector.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2015-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 <sherlock.h>
+
+#include <osc.lv2/util.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ craft_t through;
+ craft_t notify;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+
+ LV2_OSC_URID osc_urid;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate __attribute__((unused)),
+ const char *bundle_path __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(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_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "%s: Host does not support urid:(un)map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ lv2_atom_forge_init(&handle->through.forge, handle->map);
+ lv2_atom_forge_init(&handle->notify.forge, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->through.seq = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify.seq = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ craft_t *through = &handle->through;
+ craft_t *notify = &handle->notify;
+
+ uint32_t capacity = through->seq->atom.size;
+ lv2_atom_forge_set_buffer(&through->forge, through->buf, capacity);
+ through->ref = lv2_atom_forge_sequence_head(&through->forge, &through->frame[0], 0);
+
+ capacity = notify->seq->atom.size;
+ lv2_atom_forge_set_buffer(&notify->forge, notify->buf, capacity);
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[0], 0);
+
+ props_idle(&handle->props, &notify->forge, 0, &notify->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ // copy all events to through port
+ if(through->ref)
+ through->ref = lv2_atom_forge_frame_time(&through->forge, frames);
+ if(through->ref)
+ through->ref = lv2_atom_forge_raw(&through->forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(through->ref)
+ lv2_atom_forge_pad(&through->forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, &notify->forge, frames, obj, &notify->ref)
+ && lv2_atom_forge_is_object_type(&notify->forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(through->ref)
+ lv2_atom_forge_pop(&through->forge, &through->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(through->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "through buffer overflow\n");
+ }
+
+ bool has_event = notify->seq->atom.size > sizeof(LV2_Atom_Sequence_Body);
+
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, nsamples-1);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_tuple(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_long(&notify->forge, handle->frame);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_int(&notify->forge, nsamples);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_sequence_head(&notify->forge, &notify->frame[2], 0);
+
+ // only serialize OSC events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if( lv2_atom_forge_is_object_type(&notify->forge, obj->atom.type)
+ && lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype) )
+ {
+ has_event = true;
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_frame_time(&notify->forge, ev->time.frames);
+ if(notify->ref)
+ notify->ref = lv2_atom_forge_write(&notify->forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[2]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[1]);
+ if(notify->ref)
+ lv2_atom_forge_pop(&notify->forge, &notify->frame[0]);
+ else
+ {
+ lv2_atom_sequence_clear(notify->seq);
+
+ if(handle->log)
+ lv2_log_trace(&handle->logger, "notify buffer overflow\n");
+ }
+
+ if(!has_event) // don't send anything
+ lv2_atom_sequence_clear(notify->seq);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)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)
+{
+ handle_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)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor osc_inspector = {
+ .URI = SHERLOCK_OSC_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/osc_inspector_nk.c b/osc_inspector_nk.c
new file mode 100644
index 0000000..421d59d
--- /dev/null
+++ b/osc_inspector_nk.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2015-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 <time.h>
+#include <math.h>
+#include <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+#include <encoder.h>
+
+#include <osc.lv2/util.h>
+
+typedef struct _mem_t mem_t;
+
+struct _mem_t {
+ size_t size;
+ char *buf;
+};
+
+static inline void
+_shadow(struct nk_context *ctx, bool *shadow)
+{
+ if(*shadow)
+ {
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 5*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+
+ *shadow = !*shadow;
+}
+
+static inline void
+_mem_printf(mem_t *mem, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ char *src;
+ if(vasprintf(&src, fmt, args) != -1)
+ {
+ const size_t sz = strlen(src);
+
+ mem->buf = realloc(mem->buf, mem->size + sz);
+ if(mem->buf)
+ {
+ char *dst = mem->buf + mem->size - 1;
+
+ strncpy(dst, src, sz + 1);
+ mem->size += sz;
+ }
+ else
+ mem->size = 0;
+
+ free(src);
+ }
+
+ va_end(args);
+}
+
+static void
+_osc_timetag(mem_t *mem, LV2_OSC_Timetag *tt)
+{
+ if(tt->integral <= 1UL)
+ {
+ _mem_printf(mem, "t : %s", "immediate");
+ }
+ else
+ {
+ const uint32_t us = floor(tt->fraction * 0x1p-32 * 1e6);
+ const time_t ttime = tt->integral - 0x83aa7e80;
+ const struct tm *ltime = localtime(&ttime);
+
+ char tmp [32];
+ if(strftime(tmp, 32, "%d-%b-%Y %T", ltime))
+ _mem_printf(mem, "t : %s.%06"PRIu32, tmp, us);
+ }
+}
+
+static void
+_osc_argument(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom *arg,
+ const char *path, bool first)
+{
+ const LV2_OSC_Type type = lv2_osc_argument_type(&handle->osc_urid, arg);
+
+ mem_t mem = {
+ .size = 1,
+ .buf = calloc(1, sizeof(char))
+ };
+
+ switch(type)
+ {
+ case LV2_OSC_INT32:
+ {
+ int32_t i;
+ lv2_osc_int32_get(&handle->osc_urid, arg, &i);
+ _mem_printf(&mem, "i : %"PRIi32, i);
+ } break;
+ case LV2_OSC_FLOAT:
+ {
+ float f;
+ lv2_osc_float_get(&handle->osc_urid, arg, &f);
+ _mem_printf(&mem, "f : %f", f);
+ } break;
+ case LV2_OSC_STRING:
+ {
+ const char *s;
+ lv2_osc_string_get(&handle->osc_urid, arg, &s);
+ _mem_printf(&mem, "s : %s", s);
+ } break;
+ case LV2_OSC_BLOB:
+ {
+ uint32_t sz;
+ const uint8_t *b;
+ lv2_osc_blob_get(&handle->osc_urid, arg, &sz, &b);
+ _mem_printf(&mem, "b : (%"PRIu32") ", sz);
+ for(unsigned i=0; i<sz; i++)
+ _mem_printf(&mem, "%02"PRIx8, b[i]);
+ } break;
+
+ case LV2_OSC_TRUE:
+ {
+ _mem_printf(&mem, "T : true");
+ } break;
+ case LV2_OSC_FALSE:
+ {
+ _mem_printf(&mem, "F : false");
+ } break;
+ case LV2_OSC_NIL:
+ {
+ _mem_printf(&mem, "N : nil");
+ } break;
+ case LV2_OSC_IMPULSE:
+ {
+ _mem_printf(&mem, "I : impulse");
+ } break;
+
+ case LV2_OSC_INT64:
+ {
+ int64_t h;
+ lv2_osc_int64_get(&handle->osc_urid, arg, &h);
+ _mem_printf(&mem, "h : %"PRIi64, h);
+ } break;
+ case LV2_OSC_DOUBLE:
+ {
+ double d;
+ lv2_osc_double_get(&handle->osc_urid, arg, &d);
+ _mem_printf(&mem, "d : %lf", d);
+ } break;
+ case LV2_OSC_TIMETAG:
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&handle->osc_urid, arg, &tt);
+ _osc_timetag(&mem, &tt);
+ } break;
+
+ case LV2_OSC_SYMBOL:
+ {
+ LV2_URID S;
+ lv2_osc_symbol_get(&handle->osc_urid, arg, &S);
+ _mem_printf(&mem, "S : %s", handle->unmap->unmap(handle->unmap->handle, S));
+ } break;
+ case LV2_OSC_CHAR:
+ {
+ char c;
+ lv2_osc_char_get(&handle->osc_urid, arg, &c);
+ _mem_printf(&mem, "c : %c", c);
+ } break;
+ case LV2_OSC_RGBA:
+ {
+ uint8_t r [4];
+ lv2_osc_rgba_get(&handle->osc_urid, arg, r+0, r+1, r+2, r+3);
+ _mem_printf(&mem, "r : ");
+ for(unsigned i=0; i<4; i++)
+ _mem_printf(&mem, "%02"PRIx8, r[i]);
+ } break;
+ case LV2_OSC_MIDI:
+ {
+ uint32_t sz;
+ const uint8_t *m;
+ lv2_osc_midi_get(&handle->osc_urid, arg, &sz, &m);
+ _mem_printf(&mem, "m : (%"PRIu32") ", sz);
+ for(unsigned i=0; i<sz; i++)
+ _mem_printf(&mem, "%02"PRIx8, m[i]);
+ } break;
+ }
+
+ if(!first)
+ {
+ _shadow(ctx, &handle->shadow);
+ _empty(ctx);
+ }
+
+ if(first)
+ {
+ nk_label_colored(ctx, path, NK_TEXT_LEFT, magenta);
+ }
+ else
+ {
+ _empty(ctx);
+ }
+
+ if(mem.buf)
+ {
+ nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, cwhite);
+ free(mem.buf);
+ }
+ else
+ {
+ _empty(ctx);
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, arg->size);
+}
+
+static void
+_osc_message(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj)
+{
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *args = NULL;
+ lv2_osc_message_get(&handle->osc_urid, obj, &path, &args);
+
+ bool first = true;
+ LV2_ATOM_TUPLE_FOREACH(args, arg)
+ {
+ _osc_argument(handle, ctx, arg, LV2_ATOM_BODY_CONST(path), first);
+
+ if(first)
+ first = false;
+ }
+}
+
+static void
+_osc_packet(plughandle_t *handle, struct nk_context *ctx, int64_t frames,
+ const LV2_Atom_Object *obj, float offset);
+
+static void
+_osc_bundle(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset)
+{
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+ lv2_osc_bundle_get(&handle->osc_urid, obj, &timetag, &items);
+
+ // format bundle timestamp
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&handle->osc_urid, &timetag->atom, &tt);
+
+ mem_t mem = {
+ .size = 1,
+ .buf = calloc(1, sizeof(char))
+ };
+
+ nk_label_colored(ctx, "#bundle", NK_TEXT_LEFT, red);
+
+ _osc_timetag(&mem, &tt);
+ if(mem.buf)
+ {
+ nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, cwhite);
+ free(mem.buf);
+ }
+ else
+ {
+ _empty(ctx);
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, obj->atom.size);
+
+ LV2_ATOM_TUPLE_FOREACH(items, item)
+ {
+ _osc_packet(handle, ctx, -1, (const LV2_Atom_Object *)item, offset + 0.025);
+ }
+}
+
+static void
+_osc_packet(plughandle_t *handle, struct nk_context *ctx, int64_t frames,
+ const LV2_Atom_Object *obj, float offset)
+{
+ const float widget_h = handle->dy;
+
+ const float ratios [4] = {offset, 0.3f - offset, 0.6f, 0.1f};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 4, ratios);
+
+ _shadow(ctx, &handle->shadow);
+ if(frames >= 0)
+ {
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+ }
+ else
+ {
+ _empty(ctx);
+ }
+
+ if(lv2_osc_is_message_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_message(handle, ctx, obj);
+ }
+ else if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_bundle(handle, ctx, obj, offset);
+ }
+}
+
+void
+_osc_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ handle->dy = 20.f * _get_scale(handle);
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ const char *window_name = "Sherlock";
+ if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ struct nk_panel *panel = nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
+ nk_layout_row_dynamic(ctx, body_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ {
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ }
+ else
+ {
+ handle->shadow = false;
+ }
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, was bundle payload
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+
+ handle->shadow = false;
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+ const float offset = 0.1;
+
+ _osc_packet(handle, ctx, frames, obj, offset);
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ const float n = 3;
+ const float r0 = 1.f / n;
+ const float r1 = 0.1f / 3;
+ const float r2 = r0 - r1;
+ const float footer [6] = {r1, r2, r1, r2, r1, r2};
+ nk_layout_row(ctx, NK_DYNAMIC, widget_h, 6, footer);
+ {
+ const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
+ if(state_overwrite != handle->state.overwrite)
+ {
+ handle->state.overwrite = state_overwrite;
+ _set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
+ }
+ nk_label(ctx, "overwrite", NK_TEXT_LEFT);
+
+ const int32_t state_block = _check(ctx, handle->state.block);
+ if(state_block != handle->state.block)
+ {
+ handle->state.block = state_block;
+ _set_bool(handle, handle->urid.block, handle->state.block);
+ }
+ nk_label(ctx, "block", NK_TEXT_LEFT);
+
+ const int32_t state_follow = _check(ctx, handle->state.follow);
+ if(state_follow != handle->state.follow)
+ {
+ handle->state.follow = state_follow;
+ _set_bool(handle, handle->urid.follow, handle->state.follow);
+ }
+ nk_label(ctx, "follow", NK_TEXT_LEFT);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+ }
+ nk_end(ctx);
+}
diff --git a/props.lv2/.gitlab-ci.yml b/props.lv2/.gitlab-ci.yml
new file mode 100644
index 0000000..979769c
--- /dev/null
+++ b/props.lv2/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: 'gitlab-ci/generic.yml'
diff --git a/props.lv2/COPYING b/props.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/props.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/props.lv2/README.md b/props.lv2/README.md
new file mode 100644
index 0000000..08466d2
--- /dev/null
+++ b/props.lv2/README.md
@@ -0,0 +1,20 @@
+# Props.lv2
+
+## Utility header for property based LV2 plugins
+
+### License
+
+Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/props.lv2/VERSION b/props.lv2/VERSION
new file mode 100644
index 0000000..5130959
--- /dev/null
+++ b/props.lv2/VERSION
@@ -0,0 +1 @@
+0.1.165
diff --git a/props.lv2/gitlab-ci/generic.yml b/props.lv2/gitlab-ci/generic.yml
new file mode 100644
index 0000000..5cd2abc
--- /dev/null
+++ b/props.lv2/gitlab-ci/generic.yml
@@ -0,0 +1,106 @@
+stages:
+ - build
+ - deploy
+
+variables:
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+ BUILD_OPTS : ""
+
+.native_template: &native_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+
+ - scan-build --status-bugs meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+ - scan-build --status-bugs ninja -C scanbuild test
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+.cross_template: &cross_definition
+ stage: build
+ script:
+ - meson --prefix="${CI_PROJECT_DIR}/${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" -Dlv2libdir="" --cross-file "${CI_BUILD_NAME}" ${BUILD_OPTS} build
+ - ninja -C build
+ - ninja -C build test
+ - ninja -C build install
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/${CI_BUILD_NAME}/"
+
+# build
+.universal_linux_template_stretch: &universal_linux_definition_stretch
+ image: ventosus/universal-linux-gnu:stretch
+ <<: *cross_definition
+
+.universal_linux_template_buster: &universal_linux_definition_buster
+ image: ventosus/universal-linux-gnu:buster
+ <<: *native_definition
+
+.universal_linux_template_bullseye: &universal_linux_definition_bullseye
+ image: ventosus/universal-linux-gnu:bullseye
+ <<: *native_definition
+
+.arm_linux_template_stretch: &arm_linux_definition_stretch
+ image: ventosus/arm-linux-gnueabihf:stretch
+ <<: *cross_definition
+
+.arm_linux_template_buster: &arm_linux_definition_buster
+ image: ventosus/arm-linux-gnueabihf:buster
+ <<: *cross_definition
+
+.arm_linux_template_bullseye: &arm_linux_definition_bullseye
+ image: ventosus/arm-linux-gnueabihf:bullseye
+ <<: *cross_definition
+
+# build
+x86_64-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+x86_64-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+x86_64-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+i686-linux-gnu-stretch:
+ <<: *universal_linux_definition_stretch
+
+i686-linux-gnu-buster:
+ <<: *universal_linux_definition_buster
+
+i686-linux-gnu-bullseye:
+ <<: *universal_linux_definition_bullseye
+
+arm-linux-gnueabihf-stretch:
+ <<: *arm_linux_definition_stretch
+
+arm-linux-gnueabihf-buster:
+ <<: *arm_linux_definition_buster
+
+arm-linux-gnueabihf-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+aarch64-linux-gnu-stretch:
+ <<: *arm_linux_definition_stretch
+
+aarch64-linux-gnu-buster:
+ <<: *arm_linux_definition_buster
+
+aarch64-linux-gnu-bullseye:
+ <<: *arm_linux_definition_bullseye
+
+pack:
+ stage: deploy
+ script:
+ - echo 'packing up'
+ artifacts:
+ name: "${CI_PROJECT_NAME}-$(cat VERSION)"
+ paths:
+ - "${CI_PROJECT_NAME}-$(cat VERSION)/"
diff --git a/props.lv2/meson.build b/props.lv2/meson.build
new file mode 100644
index 0000000..d354d89
--- /dev/null
+++ b/props.lv2/meson.build
@@ -0,0 +1,82 @@
+project('props.lv2', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=c11'])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+conf_data = configuration_data()
+cc = meson.get_compiler('c')
+
+cp = find_program('cp')
+lv2_validate = find_program('lv2_validate', native : true, required : false)
+sord_validate = find_program('sord_validate', native : true, required : false)
+lv2lint = find_program('lv2lint', required : false)
+clone = [cp, '@INPUT@', '@OUTPUT@']
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2', version : '>=1.14.0')
+
+inc_dir = []
+
+inst_dir = join_paths(get_option('libdir'), 'lv2', meson.project_name())
+
+dsp_srcs = [join_paths('test', 'props.c')]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+mod = shared_module('props', dsp_srcs,
+ c_args : c_args,
+ include_directories : inc_dir,
+ name_prefix : '',
+ dependencies : [m_dep, lv2_dep],
+ install : true,
+ install_dir : inst_dir)
+
+version = run_command('cat', 'VERSION').stdout().strip().split('.')
+conf_data.set('MAJOR_VERSION', version[0])
+conf_data.set('MINOR_VERSION', version[1])
+conf_data.set('MICRO_VERSION', version[2])
+
+suffix = mod.full_path().strip().split('.')[-1]
+conf_data.set('MODULE_SUFFIX', '.' + suffix)
+
+manifest_ttl = configure_file(
+ input : join_paths('test', 'manifest.ttl.in'), output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : inst_dir)
+dsp_ttl = custom_target('props_ttl',
+ input : join_paths('test', 'props.ttl'),
+ output : 'props.ttl',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+custom_target('chunk_bin',
+ input : join_paths('test', 'chunk.bin'),
+ output : 'chunk.bin',
+ command : clone,
+ install : true,
+ install_dir : inst_dir)
+
+props_test = executable('props_test',
+ join_paths('test', 'props_test.c'),
+ c_args : c_args,
+ install : false)
+
+test('Test', props_test,
+ timeout : 240)
+
+if lv2_validate.found() and sord_validate.found()
+ test('LV2 validate', lv2_validate,
+ args : [manifest_ttl, dsp_ttl])
+endif
+
+if lv2lint.found()
+ test('LV2 lint', lv2lint,
+ args : ['-Ewarn',
+ 'http://open-music-kontrollers.ch/lv2/props#test'])
+endif
diff --git a/props.lv2/props.h b/props.lv2/props.h
new file mode 100644
index 0000000..b3905ab
--- /dev/null
+++ b/props.lv2/props.h
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _LV2_PROPS_H_
+#define _LV2_PROPS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdatomic.h>
+#include <stdio.h>
+
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+// structures
+typedef struct _props_def_t props_def_t;
+typedef struct _props_impl_t props_impl_t;
+typedef struct _props_dyn_t props_dyn_t;
+typedef struct _props_t props_t;
+
+typedef enum _props_dyn_ev_t {
+ PROPS_DYN_EV_ADD,
+ PROPS_DYN_EV_REM,
+ PROPS_DYN_EV_SET
+} props_dyn_ev_t;
+
+// function callbacks
+typedef void (*props_event_cb_t)(
+ void *data,
+ int64_t frames,
+ props_impl_t *impl);
+
+typedef void (*props_dyn_prop_cb_t)(
+ void *data,
+ props_dyn_ev_t ev,
+ LV2_URID subj,
+ LV2_URID prop,
+ const LV2_Atom *body);
+
+struct _props_def_t {
+ const char *property;
+ const char *type;
+ const char *access;
+ size_t offset;
+ bool hidden;
+
+ uint32_t max_size;
+ props_event_cb_t event_cb;
+};
+
+struct _props_impl_t {
+ LV2_URID property;
+ LV2_URID type;
+ LV2_URID access;
+
+ struct {
+ uint32_t size;
+ void *body;
+ } value;
+ struct {
+ uint32_t size;
+ void *body;
+ } stash;
+
+ const props_def_t *def;
+
+ atomic_int state;
+ bool stashing;
+};
+
+struct _props_dyn_t {
+ props_dyn_prop_cb_t prop;
+};
+
+struct _props_t {
+ struct {
+ LV2_URID subject;
+
+ LV2_URID patch_get;
+ LV2_URID patch_set;
+ LV2_URID patch_put;
+ LV2_URID patch_patch;
+ LV2_URID patch_wildcard;
+ LV2_URID patch_add;
+ LV2_URID patch_remove;
+ LV2_URID patch_subject;
+ LV2_URID patch_body;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_writable;
+ LV2_URID patch_readable;
+ LV2_URID patch_sequence;
+ LV2_URID patch_error;
+ LV2_URID patch_ack;
+
+ LV2_URID atom_int;
+ LV2_URID atom_long;
+ LV2_URID atom_float;
+ LV2_URID atom_double;
+ LV2_URID atom_bool;
+ LV2_URID atom_urid;
+ LV2_URID atom_path;
+ LV2_URID atom_literal;
+ LV2_URID atom_vector;
+ LV2_URID atom_object;
+ LV2_URID atom_sequence;
+
+ LV2_URID state_StateChanged;
+ } urid;
+
+ void *data;
+
+ bool stashing;
+ atomic_bool restoring;
+
+ uint32_t max_size;
+
+ const props_dyn_t *dyn;
+
+ unsigned nimpls;
+ props_impl_t impls [1];
+};
+
+#define PROPS_T(PROPS, MAX_NIMPLS) \
+ props_t (PROPS); \
+ props_impl_t _impls [MAX_NIMPLS]
+
+// rt-safe
+static inline int
+props_init(props_t *props, const char *subject,
+ const props_def_t *defs, int nimpls,
+ void *value_base, void *stash_base,
+ LV2_URID_Map *map, void *data);
+
+// rt-safe
+static inline void
+props_dyn(props_t *props, const props_dyn_t *dyn);
+
+// rt-safe
+static inline void
+props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_URID property, LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_URID property, LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_stash(props_t *props, LV2_URID property);
+
+// rt-safe
+static inline LV2_URID
+props_map(props_t *props, const char *property);
+
+// rt-safe
+static inline const char *
+props_unmap(props_t *props, LV2_URID property);
+
+// non-rt
+static inline LV2_State_Status
+props_save(props_t *props, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+// non-rt
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+// enumerations
+typedef enum _props_state_t {
+ PROP_STATE_NONE = 0,
+ PROP_STATE_LOCK = 1,
+ PROP_STATE_RESTORE = 2
+} props_state_t;
+
+static inline void
+_props_impl_spin_lock(props_impl_t *impl, int from, int to)
+{
+ int expected = from;
+ const int desired = to;
+
+ while(!atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired,
+ memory_order_acquire, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_props_impl_try_lock(props_impl_t *impl, int from, int to)
+{
+ int expected = from;
+ const int desired = to;
+
+ return atomic_compare_exchange_strong_explicit(&impl->state, &expected, desired,
+ memory_order_acquire, memory_order_acquire);
+}
+
+static inline void
+_props_impl_unlock(props_impl_t *impl, int to)
+{
+ atomic_store_explicit(&impl->state, to, memory_order_release);
+}
+
+static inline bool
+_props_restoring_get(props_t *props)
+{
+ return atomic_exchange_explicit(&props->restoring, false, memory_order_acquire);
+}
+
+static inline void
+_props_restoring_set(props_t *props)
+{
+ atomic_store_explicit(&props->restoring, true, memory_order_release);
+}
+
+static inline void
+_props_qsort(props_impl_t *A, int n)
+{
+ if(n < 2)
+ return;
+
+ const props_impl_t *p = A;
+
+ int i = -1;
+ int j = n;
+
+ while(true)
+ {
+ do {
+ i += 1;
+ } while(A[i].property < p->property);
+
+ do {
+ j -= 1;
+ } while(A[j].property > p->property);
+
+ if(i >= j)
+ break;
+
+ const props_impl_t tmp = A[i];
+ A[i] = A[j];
+ A[j] = tmp;
+ }
+
+ _props_qsort(A, j + 1);
+ _props_qsort(A + j + 1, n - j - 1);
+}
+
+static inline props_impl_t *
+_props_impl_get(props_t *props, LV2_URID property)
+{
+ props_impl_t *base = props->impls;
+
+ for(int N = props->nimpls, half; N > 1; N -= half)
+ {
+ half = N/2;
+ props_impl_t *dst = &base[half];
+ base = (dst->property > property) ? base : dst;
+ }
+
+ return (base->property == property) ? base : NULL;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_set);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(sequence_num) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+
+ if(ref)
+ lv2_atom_forge_key(forge, props->urid.patch_value);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, impl->value.size, impl->type);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, impl->value.body, impl->value.size);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.state_StateChanged);
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_get);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(sequence_num) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_error(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_error);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_patch_ack(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_ack);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline void
+_props_impl_stash(props_t *props, props_impl_t *impl)
+{
+ if(_props_impl_try_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK))
+ {
+ impl->stashing = false;
+ impl->stash.size = impl->value.size;
+ memcpy(impl->stash.body, impl->value.body, impl->value.size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+ }
+ else
+ {
+ impl->stashing = true; // try again later
+ props->stashing = true;
+ }
+}
+
+static inline void
+_props_impl_restore(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, LV2_Atom_Forge_Ref *ref)
+{
+ if(_props_impl_try_lock(impl, PROP_STATE_RESTORE, PROP_STATE_LOCK))
+ {
+ impl->stashing = false; // makes no sense to stash a recently restored value
+ impl->value.size = impl->stash.size;
+ memcpy(impl->value.body, impl->stash.body, impl->stash.size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, 0);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb)
+ def->event_cb(props->data, 0, impl);
+ }
+}
+
+static inline void
+_props_impl_set(props_t *props, props_impl_t *impl, LV2_URID type,
+ uint32_t size, const void *body)
+{
+ if( (impl->type == type)
+ && ( (impl->def->max_size == 0) || (size <= impl->def->max_size)) )
+ {
+ impl->value.size = size;
+ memcpy(impl->value.body, body, size);
+
+ _props_impl_stash(props, impl);
+ }
+}
+
+static inline int
+_props_impl_init(props_t *props, props_impl_t *impl, const props_def_t *def,
+ void *value_base, void *stash_base, LV2_URID_Map *map)
+{
+ if(!def->property || !def->type)
+ return 0;
+
+ const LV2_URID type = map->map(map->handle, def->type);
+ const LV2_URID property = map->map(map->handle, def->property);
+ const LV2_URID access = def->access
+ ? map->map(map->handle, def->access)
+ : map->map(map->handle, LV2_PATCH__writable);
+
+ if(!type || !property || !access)
+ return 0;
+
+ impl->property = property;
+ impl->access = access;
+ impl->def = def;
+ impl->value.body = (uint8_t *)value_base + def->offset;
+ impl->stash.body = (uint8_t *)stash_base + def->offset;
+
+ uint32_t size;
+ if( (type == props->urid.atom_int)
+ || (type == props->urid.atom_float)
+ || (type == props->urid.atom_bool)
+ || (type == props->urid.atom_urid) )
+ {
+ size = 4;
+ }
+ else if((type == props->urid.atom_long)
+ || (type == props->urid.atom_double) )
+ {
+ size = 8;
+ }
+ else if(type == props->urid.atom_literal)
+ {
+ size = sizeof(LV2_Atom_Literal_Body);
+ }
+ else if(type == props->urid.atom_vector)
+ {
+ size = sizeof(LV2_Atom_Vector_Body);
+ }
+ else if(type == props->urid.atom_object)
+ {
+ size = sizeof(LV2_Atom_Object_Body);
+ }
+ else if(type == props->urid.atom_sequence)
+ {
+ size = sizeof(LV2_Atom_Sequence_Body);
+ }
+ else
+ {
+ size = 0; // assume everything else as having size 0
+ }
+
+ impl->type = type;
+ impl->value.size = size;
+ impl->stash.size = size;
+
+ atomic_init(&impl->state, PROP_STATE_NONE);
+
+ // update maximal value size
+ const uint32_t max_size = def->max_size
+ ? def->max_size
+ : size;
+
+ if(max_size > props->max_size)
+ {
+ props->max_size = max_size;
+ }
+
+ return 1;
+}
+
+static inline int
+props_init(props_t *props, const char *subject,
+ const props_def_t *defs, int nimpls,
+ void *value_base, void *stash_base,
+ LV2_URID_Map *map, void *data)
+{
+ if(!props || !defs || !value_base || !stash_base || !map)
+ return 0;
+
+ props->nimpls = nimpls;
+ props->data = data;
+
+ props->urid.subject = subject ? map->map(map->handle, subject) : 0;
+
+ props->urid.patch_get = map->map(map->handle, LV2_PATCH__Get);
+ props->urid.patch_set = map->map(map->handle, LV2_PATCH__Set);
+ props->urid.patch_put = map->map(map->handle, LV2_PATCH__Put);
+ props->urid.patch_patch = map->map(map->handle, LV2_PATCH__Patch);
+ props->urid.patch_wildcard = map->map(map->handle, LV2_PATCH__wildcard);
+ props->urid.patch_add = map->map(map->handle, LV2_PATCH__add);
+ props->urid.patch_remove = map->map(map->handle, LV2_PATCH__remove);
+ props->urid.patch_subject = map->map(map->handle, LV2_PATCH__subject);
+ props->urid.patch_body = map->map(map->handle, LV2_PATCH__body);
+ props->urid.patch_property = map->map(map->handle, LV2_PATCH__property);
+ props->urid.patch_value = map->map(map->handle, LV2_PATCH__value);
+ props->urid.patch_writable = map->map(map->handle, LV2_PATCH__writable);
+ props->urid.patch_readable = map->map(map->handle, LV2_PATCH__readable);
+ props->urid.patch_sequence = map->map(map->handle, LV2_PATCH__sequenceNumber);
+ props->urid.patch_ack = map->map(map->handle, LV2_PATCH__Ack);
+ props->urid.patch_error = map->map(map->handle, LV2_PATCH__Error);
+
+ props->urid.atom_int = map->map(map->handle, LV2_ATOM__Int);
+ props->urid.atom_long = map->map(map->handle, LV2_ATOM__Long);
+ props->urid.atom_float = map->map(map->handle, LV2_ATOM__Float);
+ props->urid.atom_double = map->map(map->handle, LV2_ATOM__Double);
+ props->urid.atom_bool = map->map(map->handle, LV2_ATOM__Bool);
+ props->urid.atom_urid = map->map(map->handle, LV2_ATOM__URID);
+ props->urid.atom_path = map->map(map->handle, LV2_ATOM__Path);
+ props->urid.atom_literal = map->map(map->handle, LV2_ATOM__Literal);
+ props->urid.atom_vector = map->map(map->handle, LV2_ATOM__Vector);
+ props->urid.atom_object = map->map(map->handle, LV2_ATOM__Object);
+ props->urid.atom_sequence = map->map(map->handle, LV2_ATOM__Sequence);
+
+ props->urid.state_StateChanged = map->map(map->handle, LV2_STATE__StateChanged);
+
+ atomic_init(&props->restoring, false);
+
+ int status = 1;
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ status = status
+ && _props_impl_init(props, impl, &defs[i], value_base, stash_base, map);
+ }
+
+ _props_qsort(props->impls, props->nimpls);
+
+ return status;
+}
+
+static inline void
+props_dyn(props_t *props, const props_dyn_t *dyn)
+{
+ props->dyn = dyn;
+}
+
+static inline void
+props_idle(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_Atom_Forge_Ref *ref)
+{
+ if(_props_restoring_get(props))
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ _props_impl_restore(props, forge, frames, impl, ref);
+ }
+ }
+
+ if(props->stashing)
+ {
+ props->stashing = false;
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->stashing)
+ _props_impl_stash(props, impl);
+ }
+ }
+}
+
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref)
+{
+ if(!lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ {
+ return 0;
+ }
+
+ if(obj->body.otype == props->urid.patch_get)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+
+ lv2_atom_object_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_property, &property,
+ props->urid.patch_sequence, &sequence,
+ 0);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!property)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(property->atom.type == props->urid.atom_urid)
+ {
+ props_impl_t *impl = _props_impl_get(props, property->body);
+
+ if(impl)
+ {
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ return 1;
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_set)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom *value = NULL;
+
+ lv2_atom_object_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_property, &property,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_value, &value,
+ 0);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!property || (property->atom.type != props->urid.atom_urid) || !value)
+ {
+ if(sequence_num)
+ {
+ if(ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+
+ return 0;
+ }
+
+ props_impl_t *impl = _props_impl_get(props, property->body);
+ if(impl)
+ {
+ _props_impl_set(props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+
+ // send on (e.g. to UI)
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb)
+ def->event_cb(props->data, frames, impl);
+
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(props->dyn && props->dyn->prop)
+ {
+ const LV2_URID subj = subject ? subject->body : 0;
+
+ props->dyn->prop(props->data, PROPS_DYN_EV_SET, subj, property->body, value);
+
+ //TODO send ack
+ }
+ else if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_put)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom_Object *body = NULL;
+
+ lv2_atom_object_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_body, &body,
+ 0);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!body || !lv2_atom_forge_is_object_type(forge, body->atom.type))
+ {
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_error(props, forge, frames, sequence_num);
+ }
+
+ return 0;
+ }
+
+ LV2_ATOM_OBJECT_FOREACH(body, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ props_impl_t *impl = _props_impl_get(props, property);
+ if(impl)
+ {
+ _props_impl_set(props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+
+ // send on (e.g. to UI)
+ if(*ref && !impl->def->hidden)
+ *ref = _props_patch_set(props, forge, frames, impl, sequence_num);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb)
+ def->event_cb(props->data, frames, impl);
+ }
+ else if(props->dyn && props->dyn->prop)
+ {
+ const LV2_URID subj = subject ? subject->body : 0;
+
+ props->dyn->prop(props->data, PROPS_DYN_EV_SET, subj, property, value);
+
+ //TODO send ack
+ }
+ }
+
+ if(sequence_num)
+ {
+ if(*ref)
+ *ref = _props_patch_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(obj->body.otype == props->urid.patch_patch)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom_Object *add = NULL;
+ const LV2_Atom_Object *rem = NULL;
+
+ lv2_atom_object_get(obj,
+ props->urid.patch_subject, &subject,
+ props->urid.patch_sequence, &sequence,
+ props->urid.patch_add, &add,
+ props->urid.patch_remove, &rem,
+ 0);
+
+ LV2_URID subj = 0;
+ if(subject && (subject->atom.type == props->urid.atom_urid))
+ {
+ subj = subject->body;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(rem && lv2_atom_forge_is_object_type(forge, rem->atom.type))
+ {
+ LV2_ATOM_OBJECT_FOREACH(rem, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ if(props->dyn && props->dyn->prop)
+ {
+ props->dyn->prop(props->data, PROPS_DYN_EV_REM, subj, property, value);
+ }
+ }
+ }
+
+ if(add && lv2_atom_forge_is_object_type(forge, add->atom.type))
+ {
+ LV2_ATOM_OBJECT_FOREACH(add, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ if(props->dyn && props->dyn->prop)
+ {
+ props->dyn->prop(props->data, PROPS_DYN_EV_ADD, subj, property, value);
+ }
+ }
+ }
+
+ if(sequence_num && *ref)
+ {
+ *ref = _props_patch_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+
+ return 0; // did not handle a patch event
+}
+
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_URID property, LV2_Atom_Forge_Ref *ref)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ {
+ _props_impl_stash(props, impl);
+
+ if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber
+ *ref = _props_patch_set(props, forge, frames, impl, 0);
+ }
+}
+
+static inline void
+props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ LV2_URID property, LV2_Atom_Forge_Ref *ref)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ {
+ if(*ref && !impl->def->hidden) //TODO use patch:sequenceNumber
+ *ref = _props_patch_get(props, forge, frames, impl, 0);
+ }
+}
+
+static inline void
+props_stash(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ _props_impl_stash(props, impl);
+}
+
+static inline LV2_URID
+props_map(props_t *props, const char *uri)
+{
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(!strcmp(impl->def->property, uri))
+ return impl->property;
+ }
+
+ return 0;
+}
+
+static inline const char *
+props_unmap(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_get(props, property);
+
+ if(impl)
+ return impl->def->property;
+
+ return NULL;
+}
+
+static inline int
+_copy_file(const char *to, const char *from)
+{
+ FILE *dst = NULL;
+ FILE *src = NULL;
+ int ch;
+
+ dst = fopen(to, "wb");
+ if(!dst)
+ {
+ return 1;
+ }
+
+ src = fopen(from, "rb");
+ if(!src)
+ {
+ fclose(dst);
+
+ return 1;
+ }
+
+ while( (ch = fgetc(src)) != EOF)
+ {
+ fputc(ch, dst);
+ }
+
+ fclose(dst);
+ fclose(src);
+
+ return 0;
+}
+
+static inline void
+_free_path(const LV2_State_Free_Path *free_path, char *path)
+{
+ if(free_path && free_path->free_path)
+ {
+ free_path->free_path(free_path->handle, path);
+ }
+ else
+ {
+ free(path);
+ }
+}
+
+static inline LV2_State_Status
+props_save(props_t *props, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+ const LV2_State_Make_Path *make_path = NULL;
+ const LV2_State_Free_Path *free_path = NULL;
+
+ // set POD flag if not already set by host
+ flags |= LV2_STATE_IS_POD;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ {
+ map_path = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_STATE__makePath))
+ {
+ make_path = features[i]->data;
+ }
+ else if(!strcmp(features[i]->URI, LV2_STATE__freePath))
+ {
+ free_path = features[i]->data;
+ }
+ }
+
+ void *body = malloc(props->max_size); // create memory to store widest value
+ if(body)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ // always clear memory
+ memset(body, 0x0, props->max_size);
+
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ // create temporary copy of value, store() may well be blocking
+ const uint32_t size = impl->stash.size;
+ memcpy(body, impl->stash.body, size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+
+ if( map_path && map_path->abstract_path
+ && (impl->type == props->urid.atom_path) )
+ {
+ const char *path = strstr(body, "file://") == body
+ ? (char *)body + 7 // skip "file://"
+ : (char *)body;
+
+ char *abstract = NULL;
+
+ if( make_path && make_path->path
+ && (strstr(path, "/tmp") == path) )
+ {
+ char *absolute = make_path->path(make_path->handle, basename(path));
+
+ if(absolute)
+ {
+ if(_copy_file(absolute, path) == 0)
+ {
+ abstract = map_path->abstract_path(map_path->handle, absolute);
+ }
+
+ _free_path(free_path, absolute);
+ }
+ }
+ else
+ {
+ abstract = map_path->abstract_path(map_path->handle, path);
+ }
+
+ if(abstract)
+ {
+ const uint32_t sz = strlen(abstract) + 1;
+ store(state, impl->property, abstract, sz, impl->type, flags);
+
+ _free_path(free_path, abstract);
+ }
+ }
+ else // !Path
+ {
+ store(state, impl->property, body, size, impl->type, flags);
+ }
+ }
+
+ free(body);
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+ const LV2_State_Free_Path *free_path = NULL;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ {
+ map_path = features[i]->data;
+ }
+ if(!strcmp(features[i]->URI, LV2_STATE__freePath))
+ {
+ free_path = features[i]->data;
+ }
+ }
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ size_t size;
+ uint32_t type;
+ uint32_t _flags;
+ const void *body = retrieve(state, impl->property, &size, &type, &_flags);
+
+ if( body
+ && (type == impl->type)
+ && ( (impl->def->max_size == 0) || (size <= impl->def->max_size) ) )
+ {
+ if( map_path && map_path->absolute_path
+ && (type == props->urid.atom_path) )
+ {
+ char *absolute = map_path->absolute_path(map_path->handle, body);
+ if(absolute)
+ {
+ const uint32_t sz = strlen(absolute) + 1;
+
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ impl->stash.size = sz;
+ memcpy(impl->stash.body, absolute, sz);
+
+ _props_impl_unlock(impl, PROP_STATE_RESTORE);
+
+ _free_path(free_path, absolute);
+ }
+ }
+ else // !Path
+ {
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ impl->stash.size = size;
+ memcpy(impl->stash.body, body, size);
+
+ _props_impl_unlock(impl, PROP_STATE_RESTORE);
+ }
+ }
+ }
+
+ _props_restoring_set(props);
+
+ return LV2_STATE_SUCCESS;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LV2_PROPS_H_
diff --git a/props.lv2/test/chunk.bin b/props.lv2/test/chunk.bin
new file mode 100644
index 0000000..b66efb8
--- /dev/null
+++ b/props.lv2/test/chunk.bin
Binary files differ
diff --git a/props.lv2/test/manifest.ttl.in b/props.lv2/test/manifest.ttl.in
new file mode 100644
index 0000000..0ecc313
--- /dev/null
+++ b/props.lv2/test/manifest.ttl.in
@@ -0,0 +1,28 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Orbit Looper
+props:test
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <props@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <props.ttl> .
diff --git a/props.lv2/test/props.c b/props.lv2/test/props.c
new file mode 100644
index 0000000..590c519
--- /dev/null
+++ b/props.lv2/test/props.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+
+#include <props.h>
+
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+#include <lv2/lv2plug.in/ns/ext/log/logger.h>
+
+#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#"
+#define PROPS_TEST_URI PROPS_PREFIX"test"
+
+#define MAX_NPROPS 7
+#define MAX_STRLEN 256
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ int32_t val1;
+ int64_t val2;
+ float val3;
+ double val4;
+ char val5 [MAX_STRLEN];
+ char val6 [MAX_STRLEN];
+ uint8_t val7 [MAX_STRLEN];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ struct {
+ LV2_URID val2;
+ LV2_URID val4;
+ } urid;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+};
+
+static void
+_intercept(void *data, int64_t frames __attribute__((unused)), props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ lv2_log_trace(&handle->logger, "SET : %s\n", impl->def->property);
+}
+
+static void
+_intercept_stat1(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ handle->state.val2 = handle->state.val1 * 2;
+
+ props_set(&handle->props, &handle->forge, frames, handle->urid.val2, &handle->ref);
+}
+
+static void
+_intercept_stat3(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ handle->state.val4 = handle->state.val3 * 2;
+
+ props_set(&handle->props, &handle->forge, frames, handle->urid.val4, &handle->ref);
+}
+
+static void
+_intercept_stat6(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, frames, impl);
+
+ const char *path = strstr(handle->state.val6, "file://")
+ ? handle->state.val6 + 7 // skip "file://"
+ : handle->state.val6;
+ FILE *f = fopen(path, "wb"); // create empty file
+ if(f)
+ fclose(f);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = PROPS_PREFIX"statInt",
+ .offset = offsetof(plugstate_t, val1),
+ .type = LV2_ATOM__Int,
+ .event_cb = _intercept_stat1,
+ },
+ {
+ .property = PROPS_PREFIX"statLong",
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, val2),
+ .type = LV2_ATOM__Long,
+ .event_cb = _intercept,
+ },
+ {
+ .property = PROPS_PREFIX"statFloat",
+ .offset = offsetof(plugstate_t, val3),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_stat3,
+ },
+ {
+ .property = PROPS_PREFIX"statDouble",
+ .access = LV2_PATCH__readable,
+ .offset = offsetof(plugstate_t, val4),
+ .type = LV2_ATOM__Double,
+ .event_cb = _intercept,
+ },
+ {
+ .property = PROPS_PREFIX"statString",
+ .offset = offsetof(plugstate_t, val5),
+ .type = LV2_ATOM__String,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+ },
+ {
+ .property = PROPS_PREFIX"statPath",
+ .offset = offsetof(plugstate_t, val6),
+ .type = LV2_ATOM__Path,
+ .event_cb = _intercept_stat6,
+ .max_size = MAX_STRLEN // strlen
+ },
+ {
+ .property = PROPS_PREFIX"statChunk",
+ .offset = offsetof(plugstate_t, val7),
+ .type = LV2_ATOM__Chunk,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate __attribute__((unused)),
+ const char *bundle_path __attribute__((unused)),
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->log)
+ {
+ fprintf(stderr,
+ "%s: Host does not support log:log\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ lv2_log_error(&handle->logger, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->urid.val2 = props_map(&handle->props, PROPS_PREFIX"statLong");
+ handle->urid.val4 = props_map(&handle->props, PROPS_PREFIX"statDouble");
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples __attribute__((unused)))
+{
+ plughandle_t *handle = instance;
+
+ uint32_t capacity = handle->event_out->atom.size;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ props_idle(&handle->props, &handle->forge, 0, &handle->ref);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(handle->ref)
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref);
+ }
+
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor props_test = {
+ .URI = PROPS_TEST_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &props_test;
+ default:
+ return NULL;
+ }
+}
diff --git a/props.lv2/test/props.ttl b/props.lv2/test/props.ttl
new file mode 100644
index 0000000..f2ce779
--- /dev/null
+++ b/props.lv2/test/props.ttl
@@ -0,0 +1,151 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:props
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Props Bundle" .
+
+props:statInt
+ a lv2:Parameter ;
+ rdfs:range atom:Int ;
+ rdfs:label "statInt" ;
+ rdfs:comment "This is a 32-bit integer" ;
+ units:unit units:hz ;
+ lv2:minimum 0 ;
+ lv2:maximum 10 .
+
+props:statLong
+ a lv2:Parameter ;
+ rdfs:range atom:Long ;
+ rdfs:label "statLong" ;
+ rdfs:comment "This is a 64-bit integer" ;
+ units:unit units:khz ;
+ lv2:minimum 0 ;
+ lv2:maximum 20 .
+
+props:statFloat
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "statFloat" ;
+ rdfs:comment "This is a 32-bit float" ;
+ units:unit units:mhz ;
+ lv2:minimum -0.5 ;
+ lv2:maximum 0.5 .
+
+props:statDouble
+ a lv2:Parameter ;
+ rdfs:range atom:Double ;
+ rdfs:label "statDouble" ;
+ rdfs:comment "This is a 64-bit double" ;
+ units:unit units:db ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 .
+
+props:statString
+ a lv2:Parameter ;
+ rdfs:range atom:String ;
+ rdfs:label "statString" ;
+ rdfs:comment "This is a string" .
+
+props:statPath
+ a lv2:Parameter ;
+ rdfs:range atom:Path ;
+ rdfs:label "statPath" ;
+ rdfs:comment "This is a path" .
+
+props:statChunk
+ a lv2:Parameter ;
+ rdfs:range atom:Chunk;
+ rdfs:label "statChunk" ;
+ rdfs:comment "This is a chunk" .
+
+# Looper Test
+props:test
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Props Test" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:props ;
+ lv2:requiredFeature urid:map, log:log, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ props:statInt ,
+ props:statFloat ,
+ props:statString ,
+ props:statPath ,
+ props:statChunk ;
+
+ patch:readable
+ props:statLong ,
+ props:statDouble ;
+
+ state:state [
+ props:statInt 4 ;
+ props:statFloat "0.4"^^xsd:float ;
+ props:statString "Hello world" ;
+ props:statPath <> ;
+ props:statChunk "AQIDBAUGBw=="^^xsd:base64Binary ;
+ ] .
diff --git a/props.lv2/test/props_test.c b/props.lv2/test/props_test.c
new file mode 100644
index 0000000..69f3b3f
--- /dev/null
+++ b/props.lv2/test/props_test.c
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <assert.h>
+
+#include <props.h>
+
+#define MAX_URIDS 512
+#define STR_SIZE 32
+#define CHUNK_SIZE 16
+#define VEC_SIZE 13
+
+#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#"
+#define PROPS_TEST_URI PROPS_PREFIX"test"
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _urid_t urid_t;
+typedef struct _handle_t handle_t;
+typedef void (*test_t)(handle_t *handle);
+typedef void *(*ser_atom_realloc_t)(void *data, void *buf, size_t size);
+typedef void (*ser_atom_free_t)(void *data, void *buf);
+
+typedef struct _ser_atom_t ser_atom_t;
+
+struct _plugstate_t {
+ int32_t b32;
+ int32_t i32;
+ int64_t i64;
+ float f32;
+ double f64;
+ uint32_t urid;
+ char str [STR_SIZE];
+ char uri [STR_SIZE];
+ char path [STR_SIZE];
+ uint8_t chunk [CHUNK_SIZE];
+ LV2_Atom_Literal_Body lit;
+ char lit_body [STR_SIZE];
+ LV2_Atom_Vector_Body vec;
+ int32_t vec_body [VEC_SIZE];
+ LV2_Atom_Object_Body obj; //FIXME
+ LV2_Atom_Sequence_Body seq; //FIXME
+};
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+enum {
+ PROP_b32 = 0,
+ PROP_i32,
+ PROP_i64,
+ PROP_f32,
+ PROP_f64,
+ PROP_urid,
+ PROP_str,
+ PROP_uri,
+ PROP_path,
+ PROP_chunk,
+ PROP_lit,
+ PROP_vec,
+ PROP_obj,
+ PROP_seq,
+
+ MAX_NPROPS
+};
+
+struct _handle_t {
+ PROPS_T(props, MAX_NPROPS);
+ plugstate_t state;
+ plugstate_t stash;
+
+ LV2_URID_Map map;
+
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+struct _ser_atom_t {
+ ser_atom_realloc_t realloc;
+ ser_atom_free_t free;
+ void *data;
+
+ size_t size;
+ size_t offset;
+ union {
+ uint8_t *buf;
+ LV2_Atom *atom;
+ };
+};
+
+static LV2_Atom_Forge_Ref
+_ser_atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf,
+ uint32_t size)
+{
+ ser_atom_t *ser = handle;
+ const size_t needed = ser->offset + size;
+
+ while(needed > ser->size)
+ {
+ const size_t augmented = ser->size
+ ? ser->size << 1
+ : 1024;
+ uint8_t *grown = ser->realloc(ser->data, ser->buf, augmented);
+ if(!grown) // out-of-memory
+ {
+ return 0;
+ }
+
+ ser->buf = grown;
+ ser->size = augmented;
+ }
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+ memcpy(&ser->buf[ser->offset], buf, size);
+ ser->offset += size;
+
+ return ref;
+}
+
+static LV2_Atom *
+_ser_atom_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ ser_atom_t *ser = handle;
+
+ if(!ref) // invalid reference
+ {
+ return NULL;
+ }
+
+ const size_t offset = ref - 1;
+ return (LV2_Atom *)&ser->buf[offset];
+}
+
+static void *
+_ser_atom_realloc(void *data, void *buf, size_t size)
+{
+ (void)data;
+
+ return realloc(buf, size);
+}
+
+static void
+_ser_atom_free(void *data, void *buf)
+{
+ (void)data;
+
+ free(buf);
+}
+
+static int
+ser_atom_deinit(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return -1;
+ }
+
+ if(ser->buf)
+ {
+ ser->free(ser->data, ser->buf);
+ }
+
+ ser->size = 0;
+ ser->offset = 0;
+ ser->buf = NULL;
+
+ return 0;
+}
+
+static int
+ser_atom_funcs(ser_atom_t *ser, ser_atom_realloc_t realloc,
+ ser_atom_free_t free, void *data)
+{
+ if(!ser || !realloc || !free || ser_atom_deinit(ser))
+ {
+ return -1;
+ }
+
+ ser->realloc = realloc;
+ ser->free = free;
+ ser->data = data;
+
+ return 0;
+}
+
+static int
+ser_atom_init(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return -1;
+ }
+
+ ser->size = 0;
+ ser->offset = 0;
+ ser->buf = NULL;
+
+ return ser_atom_funcs(ser, _ser_atom_realloc, _ser_atom_free, NULL);
+}
+
+#if 0
+static int
+ser_atom_reset(ser_atom_t *ser, LV2_Atom_Forge *forge)
+{
+ if(!ser || !forge)
+ {
+ return -1;
+ }
+
+ lv2_atom_forge_set_sink(forge, _ser_atom_sink, _ser_atom_deref, ser);
+
+ ser->offset = 0;
+
+ return 0;
+}
+#endif
+
+static LV2_Atom *
+ser_atom_get(ser_atom_t *ser)
+{
+ if(!ser)
+ {
+ return NULL;
+ }
+
+ return ser->atom;
+}
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(handle->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++handle->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ [PROP_b32] = {
+ .property = PROPS_PREFIX"b32",
+ .offset = offsetof(plugstate_t, b32),
+ .type = LV2_ATOM__Bool
+ },
+ [PROP_i32] = {
+ .property = PROPS_PREFIX"i32",
+ .offset = offsetof(plugstate_t, i32),
+ .type = LV2_ATOM__Int
+ },
+ [PROP_i64] = {
+ .property = PROPS_PREFIX"i64",
+ .offset = offsetof(plugstate_t, i64),
+ .type = LV2_ATOM__Long
+ },
+ [PROP_f32] = {
+ .property = PROPS_PREFIX"f32",
+ .offset = offsetof(plugstate_t, f32),
+ .type = LV2_ATOM__Float
+ },
+ [PROP_f64] = {
+ .property = PROPS_PREFIX"f64",
+ .offset = offsetof(plugstate_t, f64),
+ .type = LV2_ATOM__Double
+ },
+ [PROP_urid] = {
+ .property = PROPS_PREFIX"urid",
+ .offset = offsetof(plugstate_t, urid),
+ .type = LV2_ATOM__URID
+ },
+ [PROP_str] = {
+ .property = PROPS_PREFIX"str",
+ .offset = offsetof(plugstate_t, str),
+ .type = LV2_ATOM__String,
+ .max_size = STR_SIZE
+ },
+ [PROP_uri] = {
+ .property = PROPS_PREFIX"uri",
+ .offset = offsetof(plugstate_t, uri),
+ .type = LV2_ATOM__URI,
+ .max_size = STR_SIZE
+ },
+ [PROP_path] = {
+ .property = PROPS_PREFIX"path",
+ .offset = offsetof(plugstate_t, path),
+ .type = LV2_ATOM__Path,
+ .max_size = STR_SIZE
+ },
+ [PROP_chunk] = {
+ .property = PROPS_PREFIX"chunk",
+ .offset = offsetof(plugstate_t, chunk),
+ .type = LV2_ATOM__Chunk,
+ .max_size = CHUNK_SIZE
+ },
+ [PROP_lit] = {
+ .property = PROPS_PREFIX"lit",
+ .offset = offsetof(plugstate_t, lit),
+ .type = LV2_ATOM__Literal,
+ .max_size = sizeof(LV2_Atom_Literal_Body) + STR_SIZE
+ },
+ [PROP_vec] = {
+ .property = PROPS_PREFIX"vec",
+ .offset = offsetof(plugstate_t, vec),
+ .type = LV2_ATOM__Literal,
+ .max_size = sizeof(LV2_Atom_Vector_Body) + VEC_SIZE*sizeof(int32_t)
+ },
+ [PROP_obj] = {
+ .property = PROPS_PREFIX"obj",
+ .offset = offsetof(plugstate_t, obj),
+ .type = LV2_ATOM__Object,
+ .max_size = sizeof(LV2_Atom_Object_Body) + 0 //FIXME
+ },
+ [PROP_seq] = {
+ .property = PROPS_PREFIX"seq",
+ .offset = offsetof(plugstate_t, seq),
+ .type = LV2_ATOM__Sequence,
+ .max_size = sizeof(LV2_Atom_Sequence_Body) + 0 //FIXME
+ }
+};
+
+static void
+_test_1(handle_t *handle)
+{
+ assert(handle);
+
+ props_t *props = &handle->props;
+ plugstate_t *state = &handle->state;
+ plugstate_t *stash = &handle->stash;
+ LV2_URID_Map *map = &handle->map;
+
+ for(unsigned i = 0; i < MAX_NPROPS; i++)
+ {
+ const props_def_t *def = &defs[i];
+
+ const LV2_URID property = props_map(props, def->property);
+ assert(property != 0);
+ assert(property == map->map(map->handle, def->property));
+
+ assert(strcmp(props_unmap(props, property), def->property) == 0);
+
+ props_impl_t *impl = _props_impl_get(props, property);
+ assert(impl);
+
+ const LV2_URID type = map->map(map->handle, def->type);
+ const LV2_URID access = map->map(map->handle, def->access
+ ? def->access : LV2_PATCH__writable);
+
+ assert(impl->property == property);
+ assert(impl->type == type);
+ assert(impl->access == access);
+
+ assert(impl->def == def);
+
+ assert(atomic_load(&impl->state) == PROP_STATE_NONE);
+ assert(impl->stashing == false);
+
+ switch(i)
+ {
+ case PROP_b32:
+ {
+ assert(impl->value.size == sizeof(state->b32));
+ assert(impl->value.body == &state->b32);
+
+ assert(impl->stash.size == sizeof(stash->b32));
+ assert(impl->stash.body == &stash->b32);
+ } break;
+ case PROP_i32:
+ {
+ assert(impl->value.size == sizeof(state->i32));
+ assert(impl->value.body == &state->i32);
+
+ assert(impl->stash.size == sizeof(stash->i32));
+ assert(impl->stash.body == &stash->i32);
+ } break;
+ case PROP_i64:
+ {
+ assert(impl->value.size == sizeof(state->i64));
+ assert(impl->value.body == &state->i64);
+
+ assert(impl->stash.size == sizeof(stash->i64));
+ assert(impl->stash.body == &stash->i64);
+ } break;
+ case PROP_f32:
+ {
+ assert(impl->value.size == sizeof(state->f32));
+ assert(impl->value.body == &state->f32);
+
+ assert(impl->stash.size == sizeof(stash->f32));
+ assert(impl->stash.body == &stash->f32);
+ } break;
+ case PROP_f64:
+ {
+ assert(impl->value.size == sizeof(state->f64));
+ assert(impl->value.body == &state->f64);
+
+ assert(impl->stash.size == sizeof(stash->f64));
+ assert(impl->stash.body == &stash->f64);
+ } break;
+ case PROP_urid:
+ {
+ assert(impl->value.size == sizeof(state->urid));
+ assert(impl->value.body == &state->urid);
+
+ assert(impl->stash.size == sizeof(stash->urid));
+ assert(impl->stash.body == &stash->urid);
+ } break;
+ case PROP_str:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->str);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->str);
+ } break;
+ case PROP_uri:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->uri);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->uri);
+ } break;
+ case PROP_path:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->path);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->path);
+ } break;
+ case PROP_chunk:
+ {
+ assert(impl->value.size == 0);
+ assert(impl->value.body == &state->chunk);
+
+ assert(impl->stash.size == 0);
+ assert(impl->stash.body == &stash->chunk);
+ } break;
+ case PROP_lit:
+ {
+ assert(impl->value.size == sizeof(state->lit));
+ assert(impl->value.body == &state->lit);
+
+ assert(impl->stash.size == sizeof(stash->lit));
+ assert(impl->stash.body == &stash->lit);
+ } break;
+ case PROP_vec:
+ {
+ assert(impl->value.size == sizeof(state->vec));
+ assert(impl->value.body == &state->vec);
+
+ assert(impl->stash.size == sizeof(stash->vec));
+ assert(impl->stash.body == &stash->vec);
+ } break;
+ case PROP_obj:
+ {
+ assert(impl->value.size == sizeof(state->obj));
+ assert(impl->value.body == &state->obj);
+
+ assert(impl->stash.size == sizeof(stash->obj));
+ assert(impl->stash.body == &stash->obj);
+ } break;
+ case PROP_seq:
+ {
+ assert(impl->value.size == sizeof(state->seq));
+ assert(impl->value.body == &state->seq);
+
+ assert(impl->stash.size == sizeof(stash->seq));
+ assert(impl->stash.body == &stash->seq);
+ } break;
+ default:
+ {
+ assert(false);
+ } break;
+ }
+ }
+}
+
+static void
+_test_2(handle_t *handle)
+{
+ assert(handle);
+
+ props_t *props = &handle->props;
+ plugstate_t *state = &handle->state;
+ plugstate_t *stash = &handle->stash;
+ LV2_URID_Map *map = &handle->map;
+
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+ ser_atom_t ser;
+
+ lv2_atom_forge_init(&forge, map);
+ assert(ser_atom_init(&ser) == 0);
+
+ lv2_atom_forge_set_sink(&forge, _ser_atom_sink, _ser_atom_deref, &ser);
+
+ ref = lv2_atom_forge_sequence_head(&forge, &frame, 0);
+ assert(ref);
+
+ props_idle(props, &forge, 0, &ref);
+ assert(ref);
+
+ const LV2_URID property = props_map(props, defs[0].property);
+ assert(property);
+
+ state->b32 = true;
+
+ props_set(props, &forge, 1, property, &ref);
+ assert(ref);
+
+ state->b32 = false;
+
+ lv2_atom_forge_pop(&forge, &frame);
+
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)ser_atom_get(&ser);
+ assert(seq);
+
+ unsigned nevs = 0;
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ assert(ev->time.frames == 1);
+ assert(atom->type == forge.Object);
+
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+ assert(obj->body.id == 0);
+
+ if(obj->body.otype == props->urid.state_StateChanged)
+ {
+ continue;
+ }
+
+ assert(obj->body.otype == props->urid.patch_set);
+
+ unsigned nprops = 0;
+ LV2_ATOM_OBJECT_FOREACH(obj, prop)
+ {
+ assert(prop->context == 0);
+
+ if(prop->key == props->urid.patch_subject)
+ {
+ const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value;
+
+ assert(val->atom.type == forge.URID);
+ assert(val->atom.size == sizeof(uint32_t));
+ assert(val->body == props->urid.subject);
+
+ nprops |= 0x1;
+ }
+ else if(prop->key == props->urid.patch_property)
+ {
+ const LV2_Atom_URID *val = (const LV2_Atom_URID *)&prop->value;
+
+ assert(val->atom.type == forge.URID);
+ assert(val->atom.size == sizeof(uint32_t));
+ assert(val->body == property);
+
+ nprops |= 0x2;
+ }
+ else if(prop->key == props->urid.patch_value)
+ {
+ const LV2_Atom_Bool *val = (const LV2_Atom_Bool *)&prop->value;
+
+ assert(val->atom.type == forge.Bool);
+ assert(val->atom.size == sizeof(int32_t));
+ assert(val->body == true);
+
+ nprops |= 0x4;
+ }
+ else
+ {
+ assert(false);
+ }
+ }
+ assert(nprops == 0x7);
+
+ assert(props_advance(props, &forge, ev->time.frames, obj, &ref) == 1);
+
+ assert(state->b32 == true);
+ assert(stash->b32 == true);
+
+ nevs |= 0x1;
+ }
+ assert(nevs == 0x1);
+
+ assert(ser_atom_deinit(&ser) == 0);
+}
+
+static const test_t tests [] = {
+ _test_1,
+ _test_2,
+ NULL
+};
+
+int
+main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
+{
+ static handle_t handle;
+
+ for(const test_t *test = tests; *test; test++)
+ {
+ memset(&handle, 0, sizeof(handle));
+
+ handle.map.handle = &handle;
+ handle.map.map = _map;
+
+ assert(props_init(&handle.props, PROPS_PREFIX"subj", defs, MAX_NPROPS,
+ &handle.state, &handle.stash, &handle.map, NULL) == 1);
+
+ (*test)(&handle);
+ }
+
+ for(urid_t *itm=handle.urids; itm->urid; itm++)
+ {
+ free(itm->uri);
+ }
+
+ return 0;
+}
diff --git a/screenshots/screenshot_1.png b/screenshots/screenshot_1.png
new file mode 100644
index 0000000..4a3883d
--- /dev/null
+++ b/screenshots/screenshot_1.png
Binary files differ
diff --git a/screenshots/screenshot_2.png b/screenshots/screenshot_2.png
new file mode 100644
index 0000000..2c07aab
--- /dev/null
+++ b/screenshots/screenshot_2.png
Binary files differ
diff --git a/screenshots/screenshot_3.png b/screenshots/screenshot_3.png
new file mode 100644
index 0000000..357b7a1
--- /dev/null
+++ b/screenshots/screenshot_3.png
Binary files differ
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..21efd1b
--- /dev/null
+++ b/ser_atom.lv2/README.md
@@ -0,0 +1,24 @@
+# ser_atom.lv2
+
+## General purpose LV2 atom forge serializer
+
+### Reference
+
+* <http://lv2plug.in/ns/ext/urid>
+
+### 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/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/Makefile b/ser_atom.lv2/test/Makefile
new file mode 100644
index 0000000..95a84de
--- /dev/null
+++ b/ser_atom.lv2/test/Makefile
@@ -0,0 +1,13 @@
+CC ?= gcc
+C_FLAGS ?= -I../ -Wall -Wextra -Wpedantic $(shell pkg-config --cflags lv2) \
+ -fprofile-arcs -ftest-coverage
+
+all: ser_atom_test
+
+ser_atom_test: ser_atom_test.c ../ser_atom.lv2/ser_atom.h
+ $(CC) -std=c11 -g -o $@ $< $(C_FLAGS)
+ valgrind ./$@
+ gcov $<
+
+clean:
+ rm -f ser_atom_test *.gcov *.gc*
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/sherlock.c b/sherlock.c
new file mode 100644
index 0000000..2957a5e
--- /dev/null
+++ b/sherlock.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015-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 <sherlock.h>
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &atom_inspector;
+ case 1:
+ return &midi_inspector;
+ case 2:
+ return &osc_inspector;
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock.h b/sherlock.h
new file mode 100644
index 0000000..2cf5c81
--- /dev/null
+++ b/sherlock.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2015-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 _SHERLOCK_LV2_H
+#define _SHERLOCK_LV2_H
+
+#include <stdint.h>
+#include <inttypes.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/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.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/patch/patch.h"
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/log/logger.h"
+#include "lv2/lv2plug.in/ns/ext/options/options.h"
+#include "lv2/lv2plug.in/ns/extensions/units/units.h"
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#include <stdio.h>
+#include <props.h>
+
+#define SHERLOCK_URI "http://open-music-kontrollers.ch/lv2/sherlock"
+
+#define SHERLOCK_ATOM_INSPECTOR_URI SHERLOCK_URI"#atom_inspector"
+#define SHERLOCK_ATOM_INSPECTOR_NK_URI SHERLOCK_URI"#atom_inspector_4_nk"
+
+#define SHERLOCK_MIDI_INSPECTOR_URI SHERLOCK_URI"#midi_inspector"
+#define SHERLOCK_MIDI_INSPECTOR_NK_URI SHERLOCK_URI"#midi_inspector_4_nk"
+
+#define SHERLOCK_OSC_INSPECTOR_URI SHERLOCK_URI"#osc_inspector"
+#define SHERLOCK_OSC_INSPECTOR_NK_URI SHERLOCK_URI"#osc_inspector_4_nk"
+
+extern const LV2_Descriptor atom_inspector;
+extern const LV2_Descriptor midi_inspector;
+extern const LV2_Descriptor osc_inspector;
+
+typedef struct _position_t position_t;
+typedef struct _state_t state_t;
+typedef struct _craft_t craft_t;
+
+struct _position_t {
+ uint64_t offset;
+ uint32_t nsamples;
+};
+
+struct _state_t {
+ int32_t overwrite;
+ int32_t block;
+ int32_t follow;
+ int32_t pretty;
+ int32_t trace;
+ uint32_t filter;
+ int32_t negate;
+};
+
+struct _craft_t {
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref;
+ union {
+ LV2_Atom_Sequence *seq;
+ uint8_t *buf;
+ };
+};
+
+#define MAX_NPROPS 7
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = SHERLOCK_URI"#overwrite",
+ .offset = offsetof(state_t, overwrite),
+ .type = LV2_ATOM__Bool,
+ },
+ {
+ .property = SHERLOCK_URI"#block",
+ .offset = offsetof(state_t, block),
+ .type = LV2_ATOM__Bool,
+ },
+ {
+ .property = SHERLOCK_URI"#follow",
+ .offset = offsetof(state_t, follow),
+ .type = LV2_ATOM__Bool,
+ },
+ {
+ .property = SHERLOCK_URI"#pretty",
+ .offset = offsetof(state_t, pretty),
+ .type = LV2_ATOM__Bool,
+ },
+ {
+ .property = SHERLOCK_URI"#trace",
+ .offset = offsetof(state_t, trace),
+ .type = LV2_ATOM__Bool,
+ },
+ {
+ .property = SHERLOCK_URI"#filter",
+ .offset = offsetof(state_t, filter),
+ .type = LV2_ATOM__URID,
+ },
+ {
+ .property = SHERLOCK_URI"#negate",
+ .offset = offsetof(state_t, negate),
+ .type = LV2_ATOM__Bool,
+ }
+};
+
+// there is a bug in LV2 <= 0.10
+#if defined(LV2_ATOM_TUPLE_FOREACH)
+# undef LV2_ATOM_TUPLE_FOREACH
+# define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+#endif
+
+#endif // _SHERLOCK_LV2_H
diff --git a/sherlock.ttl b/sherlock.ttl
new file mode 100644
index 0000000..ebd31d3
--- /dev/null
+++ b/sherlock.ttl
@@ -0,0 +1,280 @@
+# Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+
+@prefix xpress: <http://open-music-kontrollers.ch/lv2/xpress#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+xpress:Message
+ a rdfs:Class ,
+ rdfs:Datatype ;
+ rdfs:subClassOf atom:Atom .
+
+# 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:sherlock
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Sherlock Bundle" .
+
+sherlock:overwrite
+ a lv2:Parameter ;
+ rdfs:label "Overwrite" ;
+ rdfs:comment "Overwrite buffer when maximum number of events reached" ;
+ rdfs:range atom:Bool .
+
+sherlock:block
+ a lv2:Parameter ;
+ rdfs:label "Block" ;
+ rdfs:comment "Block addition of newly received events" ;
+ rdfs:range atom:Bool .
+
+sherlock:follow
+ a lv2:Parameter ;
+ rdfs:label "Follow" ;
+ rdfs:comment "Automatically scroll to and show last added event" ;
+ rdfs:range atom:Bool .
+
+sherlock:pretty
+ a lv2:Parameter ;
+ rdfs:label "Pretty" ;
+ rdfs:comment "Toggle whether to pretty print or not" ;
+ rdfs:range atom:Bool .
+
+sherlock:trace
+ a lv2:Parameter ;
+ rdfs:label "Trace" ;
+ rdfs:comment "Toggle whether to show trace output on console" ;
+ rdfs:range atom:Bool .
+
+sherlock:filter
+ a lv2:Parameter ;
+ rdfs:label "Match" ;
+ rdfs:comment "Match events according to type or object type" ;
+ rdfs:range atom:URI .
+
+sherlock:negate
+ a lv2:Parameter ;
+ rdfs:label "Negate" ;
+ rdfs:comment "Toggle negation of filter" ;
+ rdfs:range atom:Bool .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock Atom Inspector" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:requiredFeature urid:map, urid:unmap, state:loadDefaultState ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 1 ;
+ lv2:symbol "through" ;
+ lv2:name "Through" ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ,
+ patch:Message ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite ,
+ sherlock:block ,
+ sherlock:follow ,
+ sherlock:pretty ,
+ sherlock:trace ,
+ sherlock:filter ,
+ sherlock:negate ;
+
+ state:state [
+ sherlock:overwrite true ;
+ sherlock:block false ;
+ sherlock:follow true ;
+ sherlock:pretty true ;
+ sherlock:trace false ;
+ sherlock:filter sherlock:matchAll ;
+ sherlock:negate false ;
+ ] .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock MIDI Inspector" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:requiredFeature urid:map, urid:unmap, state:loadDefaultState ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 1 ;
+ lv2:symbol "through" ;
+ lv2:name "Through" ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ,
+ patch:Message ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite ,
+ sherlock:block ,
+ sherlock:follow ;
+
+ state:state [
+ sherlock:overwrite true ;
+ sherlock:block false ;
+ sherlock:follow true ;
+ ] .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock OSC Inspector" ;
+ doap:license <https://spdx.org/licenses/Artistic-2.0> ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, state:threadSafeRestore, log:log ;
+ lv2:requiredFeature urid:map, urid:unmap, state:loadDefaultState ;
+ lv2:extensionData state:interface ;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ time:Position ,
+ patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control" ;
+ lv2:name "Control" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ;
+ lv2:index 1 ;
+ lv2:symbol "through" ;
+ lv2:name "Through" ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ,
+ patch:Message ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite ,
+ sherlock:block ,
+ sherlock:follow ;
+
+ state:state [
+ sherlock:overwrite true ;
+ sherlock:block false ;
+ sherlock:follow true ;
+ ] .
diff --git a/sherlock_nk.c b/sherlock_nk.c
new file mode 100644
index 0000000..8e0dcf4
--- /dev/null
+++ b/sherlock_nk.c
@@ -0,0 +1,713 @@
+/*
+ * Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sherlock.h>
+#include <osc.lv2/util.h>
+
+#define SER_ATOM_IMPLEMENTATION
+#include <ser_atom.lv2/ser_atom.h>
+
+#include <encoder.h>
+
+#define NK_PUGL_IMPLEMENTATION
+#include <sherlock_nk.h>
+
+static inline void
+_discover(plughandle_t *handle)
+{
+ ser_atom_t ser;
+
+ if(ser_atom_init(&ser) == 0)
+ {
+ LV2_Atom_Forge_Frame frame;
+
+ ser_atom_reset(&ser, &handle->forge);
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_get);
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->write_function(handle->controller, 0, lv2_atom_total_size(ser_atom_get(&ser)),
+ handle->event_transfer, ser_atom_get(&ser));
+
+ ser_atom_deinit(&ser);
+ }
+}
+
+void
+_set_bool(plughandle_t *handle, LV2_URID property, int32_t val)
+{
+ ser_atom_t ser;
+
+ if(ser_atom_init(&ser) == 0)
+ {
+ LV2_Atom_Forge_Frame frame;
+
+ ser_atom_reset(&ser, &handle->forge);
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_set);
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_property);
+ lv2_atom_forge_urid(&handle->forge, property);
+
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_value);
+ lv2_atom_forge_bool(&handle->forge, val);
+
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->write_function(handle->controller, 0, lv2_atom_total_size(ser_atom_get(&ser)),
+ handle->event_transfer, ser_atom_get(&ser));
+
+ ser_atom_deinit(&ser);
+ }
+}
+
+void
+_set_urid(plughandle_t *handle, LV2_URID property, uint32_t val)
+{
+ ser_atom_t ser;
+
+ if(ser_atom_init(&ser) == 0)
+ {
+ LV2_Atom_Forge_Frame frame;
+
+ ser_atom_reset(&ser, &handle->forge);
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_set);
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_property);
+ lv2_atom_forge_urid(&handle->forge, property);
+
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_value);
+ lv2_atom_forge_urid(&handle->forge, val);
+
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->write_function(handle->controller, 0, lv2_atom_total_size(ser_atom_get(&ser)),
+ handle->event_transfer, ser_atom_get(&ser));
+
+ ser_atom_deinit(&ser);
+ }
+}
+
+void
+_ruler(struct nk_context *ctx, float line_thickness, struct nk_color color)
+{
+ struct nk_rect bounds = nk_layout_space_bounds(ctx);
+ const enum nk_widget_layout_states states = nk_widget(&bounds, ctx);
+
+ if(states != NK_WIDGET_INVALID)
+ {
+ struct nk_command_buffer *canv= nk_window_get_canvas(ctx);
+ const float x0 = bounds.x;
+ const float y0 = bounds.y + bounds.h/2;
+ const float x1 = bounds.x + bounds.w;
+ const float y1 = y0;
+
+ nk_stroke_line(canv, x0, y0, x1, y1, line_thickness, color);
+ }
+}
+
+void
+_empty(struct nk_context *ctx)
+{
+ nk_spacing(ctx, 1);
+}
+
+static void
+_clear_items(plughandle_t *handle)
+{
+ if(handle->items)
+ {
+ for(int i = 0; i < handle->n_item; i++)
+ {
+ item_t *itm = handle->items[i];
+
+ if(itm)
+ free(itm);
+ }
+
+ free(handle->items);
+ handle->items = NULL;
+ }
+
+ handle->n_item = 0;
+}
+
+static item_t *
+_append_item(plughandle_t *handle, item_type_t type, size_t sz)
+{
+ handle->items = realloc(handle->items, (handle->n_item + 1)*sizeof(item_t *));
+ handle->items[handle->n_item] = malloc(sizeof(item_t) + sz);
+
+ item_t *itm = handle->items[handle->n_item];
+ itm->type = type;
+
+ handle->n_item += 1;
+
+ return itm;
+}
+
+void
+_clear(plughandle_t *handle)
+{
+ _clear_items(handle);
+ struct nk_str *str = &handle->editor.string;
+ nk_str_clear(str);
+ handle->selected = NULL;
+ handle->counter = 1;
+}
+
+void
+_post_redisplay(plughandle_t *handle)
+{
+ nk_pugl_post_redisplay(&handle->win);
+}
+
+float
+_get_scale (plughandle_t *handle)
+{
+ return handle->scale;
+}
+
+static int
+_dial_bool(struct nk_context *ctx, int32_t *val, struct nk_color color, bool editable)
+{
+ const int32_t tmp = *val;
+ struct nk_rect bounds;
+ const bool left_mouse_click_in_cursor = nk_widget_is_mouse_clicked(ctx, NK_BUTTON_LEFT);
+ const enum nk_widget_layout_states layout_states = nk_widget(&bounds, ctx);
+
+ if(layout_states != NK_WIDGET_INVALID)
+ {
+ enum nk_widget_states states = NK_WIDGET_STATE_INACTIVE;
+ struct nk_input *in = (ctx->current->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+
+ if(in && editable)
+ {
+ bool mouse_has_scrolled = false;
+
+ if(left_mouse_click_in_cursor)
+ {
+ states = NK_WIDGET_STATE_ACTIVED;
+ }
+ else if(nk_input_is_mouse_hovering_rect(in, bounds))
+ {
+ if(in->mouse.scroll_delta.y != 0.f) // has scrolling
+ {
+ mouse_has_scrolled = true;
+ in->mouse.scroll_delta.y = 0.f;
+ }
+
+ states = NK_WIDGET_STATE_HOVER;
+ }
+
+ if(left_mouse_click_in_cursor || mouse_has_scrolled)
+ {
+ *val = !*val;
+ }
+ }
+
+ const struct nk_style_item *fg = NULL;
+
+ switch(states)
+ {
+ case NK_WIDGET_STATE_HOVER:
+ {
+ fg = &ctx->style.progress.cursor_hover;
+ } break;
+ case NK_WIDGET_STATE_ACTIVED:
+ {
+ fg = &ctx->style.progress.cursor_active;
+ } break;
+ default:
+ {
+ fg = &ctx->style.progress.cursor_normal;
+ } break;
+ }
+
+ struct nk_color fg_color = fg->data.color;
+
+ fg_color.r = (int)fg_color.r * color.r / 0xff;
+ fg_color.g = (int)fg_color.g * color.g / 0xff;
+ fg_color.b = (int)fg_color.b * color.b / 0xff;
+ fg_color.a = (int)fg_color.a * color.a / 0xff;
+
+ struct nk_command_buffer *canv= nk_window_get_canvas(ctx);
+ const float w2 = bounds.w/2;
+ const float h2 = bounds.h/2;
+ const float r1 = NK_MIN(w2, h2);
+ const float r2 = r1 / 2;
+ const float cx = bounds.x + w2;
+ const float cy = bounds.y + h2;
+
+ nk_stroke_arc(canv, cx, cy, r2 - 0, 0.f, 2*M_PI, 2.f, fg_color);
+ if(*val)
+ nk_fill_arc(canv, cx, cy, r2 - 2, 0.f, 2*M_PI, fg_color);
+ }
+
+ return tmp != *val;
+}
+
+int32_t
+_check(struct nk_context *ctx, int32_t state)
+{
+ _dial_bool(ctx, &state, nk_rgb(0xff, 0xff, 0xff), true);
+
+ return state;
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor __attribute__((unused)),
+ const char *plugin_uri, const char *bundle_path __attribute__((unused)),
+ LV2UI_Write_Function write_function, LV2UI_Controller controller,
+ LV2UI_Widget *widget, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ void *parent = NULL;
+ LV2UI_Resize *host_resize = NULL;
+ LV2_Options_Option *opts = NULL;
+ for(int 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_URID__unmap))
+ {
+ handle->unmap = features[i]->data;
+ }
+ else 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_OPTIONS__options))
+ {
+ opts = features[i]->data;
+ }
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "LV2 URID extension not supported\n");
+ free(handle);
+ return NULL;
+ }
+ if(!parent)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->event_transfer = handle->map->map(handle->map->handle, LV2_ATOM__eventTransfer);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ handle->write_function = write_function;
+ handle->controller = controller;
+
+ if(!props_init(&handle->props, plugin_uri,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->urid.overwrite = props_map(&handle->props, SHERLOCK_URI"#overwrite");
+ handle->urid.block = props_map(&handle->props, SHERLOCK_URI"#block");
+ handle->urid.follow = props_map(&handle->props, SHERLOCK_URI"#follow");
+ handle->urid.pretty = props_map(&handle->props, SHERLOCK_URI"#pretty");
+ handle->urid.trace = props_map(&handle->props, SHERLOCK_URI"#trace");
+ handle->urid.filter = props_map(&handle->props, SHERLOCK_URI"#filter");
+ handle->urid.negate = props_map(&handle->props, SHERLOCK_URI"#negate");
+
+ nk_pugl_config_t *cfg = &handle->win.cfg;
+ cfg->height = 700;
+ cfg->resizable = true;
+ cfg->ignore = false;
+ cfg->class = "sherlock_inspector";
+ cfg->title = "Sherlock Inspector";
+ cfg->parent = (intptr_t)parent;
+ cfg->host_resize = host_resize;
+ cfg->data = handle;
+ if(!strcmp(plugin_uri, SHERLOCK_MIDI_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_MIDI_INSPECTOR,
+ cfg->width = 600;
+ cfg->expose = _midi_inspector_expose;
+ }
+ else if(!strcmp(plugin_uri, SHERLOCK_ATOM_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_ATOM_INSPECTOR,
+ cfg->width = 1200;
+ cfg->expose = _atom_inspector_expose;
+ }
+ else if(!strcmp(plugin_uri, SHERLOCK_OSC_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_OSC_INSPECTOR,
+ cfg->width = 600;
+ cfg->expose = _osc_inspector_expose;
+ }
+
+ if(asprintf(&cfg->font.face, "%sCousine-Regular.ttf", bundle_path) == -1)
+ cfg->font.face= NULL;
+ cfg->font.size = 13;
+
+ *(intptr_t *)widget = nk_pugl_init(&handle->win);
+
+ const LV2_URID ui_scaleFactor = handle->map->map(handle->map->handle,
+ LV2_UI__scaleFactor);
+
+ 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;
+ }
+ }
+
+ if(handle->scale == 0.f)
+ {
+ handle->scale = nk_pugl_get_scale(&handle->win);
+ }
+
+ nk_pugl_show(&handle->win);
+
+ _clear(handle);
+ _discover(handle);
+
+ nk_textedit_init_default(&handle->editor);
+ handle->editor.lexer.lex = ttl_lex;
+ handle->editor.lexer.data = NULL;
+
+ handle->sratom = sratom_new(handle->map);
+ sratom_set_pretty_numbers(handle->sratom, handle->state.pretty);
+ handle->base_uri = "file:///tmp/base";
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ sratom_free(handle->sratom);
+
+ _clear_items(handle);
+ nk_textedit_free(&handle->editor);
+
+ if(handle->editor.lexer.tokens)
+ free(handle->editor.lexer.tokens);
+
+ if(handle->win.cfg.font.face)
+ free(handle->win.cfg.font.face);
+ nk_pugl_hide(&handle->win);
+ nk_pugl_shutdown(&handle->win);
+
+ free(handle);
+}
+
+static void
+_osc_bundle(plughandle_t *handle, const LV2_Atom_Object *obj);
+
+static void
+_osc_message(plughandle_t *handle, const LV2_Atom_Object *obj)
+{
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *args = NULL;
+ lv2_osc_message_get(&handle->osc_urid, obj, &path, &args);
+
+ bool first = true;
+
+ LV2_ATOM_TUPLE_FOREACH(args, arg)
+ {
+ if(first)
+ {
+ first = false;
+ }
+ else
+ {
+ _append_item(handle, ITEM_TYPE_NONE, 0);
+ }
+ }
+}
+
+static void
+_osc_packet(plughandle_t *handle, const LV2_Atom_Object *obj)
+{
+ if(lv2_osc_is_message_type(&handle->osc_urid, obj->body.otype))
+ {
+ _append_item(handle, ITEM_TYPE_NONE, 0);
+ _osc_message(handle, obj);
+ }
+ else if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _append_item(handle, ITEM_TYPE_NONE, 0);
+ _osc_bundle(handle, obj);
+ }
+}
+
+static void
+_osc_bundle(plughandle_t *handle, const LV2_Atom_Object *obj)
+{
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+ lv2_osc_bundle_get(&handle->osc_urid, obj, &timetag, &items);
+
+ LV2_ATOM_TUPLE_FOREACH(items, item)
+ {
+ _osc_packet(handle, (const LV2_Atom_Object *)item);
+ }
+}
+
+static void
+port_event(LV2UI_Handle instance, uint32_t i, uint32_t size __attribute__((unused)),
+ uint32_t urid, const void *buf)
+{
+ plughandle_t *handle = instance;
+
+ if(urid != handle->event_transfer)
+ return;
+
+ switch(i)
+ {
+ case 0:
+ case 2:
+ {
+ const LV2_Atom_Tuple *tup = buf;
+
+ if(tup->atom.type != handle->forge.Tuple)
+ {
+ ser_atom_t ser;
+
+ if(ser_atom_init(&ser) == 0)
+ {
+ LV2_Atom_Forge_Frame frame;
+
+ ser_atom_reset(&ser, &handle->forge);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ if(props_advance(&handle->props, &handle->forge, 0, buf, &ref))
+ {
+ nk_pugl_post_redisplay(&handle->win);
+ }
+
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ ser_atom_deinit(&ser);
+ }
+
+ break;
+ }
+
+ const LV2_Atom_Long *offset = NULL;
+ const LV2_Atom_Int *nsamples = NULL;
+ const LV2_Atom_Sequence *seq = NULL;
+
+ unsigned k = 0;
+ LV2_ATOM_TUPLE_FOREACH(tup, item)
+ {
+ switch(k)
+ {
+ case 0:
+ {
+ if(item->type == handle->forge.Long)
+ offset = (const LV2_Atom_Long *)item;
+ } break;
+ case 1:
+ {
+ if(item->type == handle->forge.Int)
+ nsamples = (const LV2_Atom_Int *)item;
+ } break;
+ case 2:
+ {
+ if(item->type == handle->forge.Sequence)
+ seq = (const LV2_Atom_Sequence *)item;
+ } break;
+ }
+
+ k++;
+ }
+
+ const bool overflow = handle->n_item > MAX_LINES;
+
+ if(!offset || !nsamples || !seq || (seq->atom.size <= sizeof(LV2_Atom_Sequence_Body)) )
+ {
+ break;
+ }
+
+ if(overflow && handle->state.overwrite)
+ {
+ _clear(handle);
+ }
+
+ if(overflow || handle->state.block)
+ {
+ break;
+ }
+
+ // append frame
+ {
+ item_t *itm = _append_item(handle, ITEM_TYPE_FRAME, 0);
+ itm->frame.offset = offset->body;
+ itm->frame.counter = handle->counter++;
+ itm->frame.nsamples = nsamples->body;
+ }
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const size_t ev_sz = sizeof(LV2_Atom_Event) + ev->body.size;
+ item_t *itm = _append_item(handle, ITEM_TYPE_EVENT, ev_sz);
+ memcpy(&itm->event.ev, ev, ev_sz);
+
+ switch(handle->type)
+ {
+ case SHERLOCK_ATOM_INSPECTOR:
+ {
+ if(handle->state.follow)
+ {
+ handle->selected = &itm->event.ev.body;
+ handle->ttl_dirty = true;
+ }
+ } break;
+ case SHERLOCK_OSC_INSPECTOR:
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ // bundles may span over multiple lines
+ if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_bundle(handle, obj);
+ }
+ else
+ {
+ _osc_message(handle, obj);
+ }
+ } break;
+ case SHERLOCK_MIDI_INSPECTOR:
+ {
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(&ev->body);
+
+ // sysex messages may span over multiple lines
+ if( (msg[0] == LV2_MIDI_MSG_SYSTEM_EXCLUSIVE) && (ev->body.size > 4) )
+ {
+ for(uint32_t j = 4; j < ev->body.size; j += 4)
+ _append_item(handle, ITEM_TYPE_NONE, 0); // place holder
+ }
+ } break;
+ }
+ }
+
+ nk_pugl_post_redisplay(&handle->win);
+
+ break;
+ }
+ }
+}
+
+static int
+_idle(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ return nk_pugl_process_events(&handle->win);
+}
+
+static const LV2UI_Idle_Interface idle_ext = {
+ .idle = _idle
+};
+
+static int
+_resize(LV2UI_Handle instance, int width, int height)
+{
+ plughandle_t *handle = instance;
+
+ return nk_pugl_resize(&handle->win, width, height);
+}
+
+static const LV2UI_Resize resize_ext = {
+ .ui_resize = _resize
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ return &idle_ext;
+ else if(!strcmp(uri, LV2_UI__resize))
+ return &resize_ext;
+
+ return NULL;
+}
+
+const LV2UI_Descriptor midi_inspector_nk = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+const LV2UI_Descriptor atom_inspector_nk = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+const LV2UI_Descriptor osc_inspector_nk = {
+ .URI = SHERLOCK_OSC_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &midi_inspector_nk;
+ case 1:
+ return &atom_inspector_nk;
+ case 2:
+ return &osc_inspector_nk;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock_nk.h b/sherlock_nk.h
new file mode 100644
index 0000000..6856859
--- /dev/null
+++ b/sherlock_nk.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015-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 _SHERLOCK_NK_H
+#define _SHERLOCK_NK_H
+
+#define NK_PUGL_API
+#include "nk_pugl/nk_pugl.h"
+
+#include <osc.lv2/osc.h>
+#include <sratom/sratom.h>
+
+#define MAX_LINES 2048
+
+typedef struct _item_t item_t;
+typedef struct _plughandle_t plughandle_t;
+
+typedef enum _item_type_t {
+ ITEM_TYPE_NONE,
+ ITEM_TYPE_FRAME,
+ ITEM_TYPE_EVENT
+} item_type_t;
+
+struct _item_t {
+ item_type_t type;
+
+ union {
+ struct {
+ int64_t offset;
+ uint32_t counter;
+ int32_t nsamples;
+ } frame;
+
+ struct {
+ LV2_Atom_Event ev;
+ uint8_t body [0];
+ } event;
+ };
+};
+
+typedef enum _plugin_type_t {
+ SHERLOCK_ATOM_INSPECTOR,
+ SHERLOCK_MIDI_INSPECTOR,
+ SHERLOCK_OSC_INSPECTOR
+} plugin_type_t;
+
+struct _plughandle_t {
+ LV2UI_Write_Function write_function;
+ LV2UI_Controller controller;
+
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_URID event_transfer;
+ LV2_OSC_URID osc_urid;
+
+ float scale;
+
+ PROPS_T(props, MAX_NPROPS);
+ struct {
+ LV2_URID overwrite;
+ LV2_URID block;
+ LV2_URID follow;
+ LV2_URID pretty;
+ LV2_URID trace;
+ LV2_URID filter;
+ LV2_URID negate;
+ } urid;
+ state_t state;
+ state_t stash;
+
+ nk_pugl_window_t win;
+
+ bool ttl_dirty;
+ const LV2_Atom *selected;
+ struct nk_text_edit editor;
+
+ Sratom *sratom;
+ const char *base_uri;
+
+ float dy;
+
+ uint32_t counter;
+ int n_item;
+ item_t **items;
+
+ bool shadow;
+ plugin_type_t type;
+
+ char filter_uri [1024];
+ LV2_URID filter;
+};
+
+extern const char *max_items [5];
+extern const int32_t max_values [5];
+
+void
+_midi_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data);
+
+void
+_atom_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data);
+
+void
+_osc_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data);
+
+void
+_empty(struct nk_context *ctx);
+
+void
+_set_bool(plughandle_t *handle, LV2_URID property, int32_t val);
+
+void
+_set_urid(plughandle_t *handle, LV2_URID property, uint32_t val);
+
+void
+_clear(plughandle_t *handle);
+
+void
+_ruler(struct nk_context *ctx, float line_thickness, struct nk_color color);
+
+void
+_post_redisplay(plughandle_t *handle);
+
+float
+_get_scale(plughandle_t *handle);
+
+int32_t
+_check(struct nk_context *ctx, int32_t state);
+
+#endif // _SHERLOCK_NK_H
diff --git a/sherlock_ui.ttl b/sherlock_ui.ttl
new file mode 100644
index 0000000..bee47b0
--- /dev/null
+++ b/sherlock_ui.ttl
@@ -0,0 +1,66 @@
+# Copyright (c) 2015-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 patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix opts: <http://lv2plug.in/ns/ext/options#> .
+
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+# Atom Inspector UI
+sherlock:atom_inspector_4_nk
+ ui:portNotification [
+ ui:plugin sherlock:atom_inspector ;
+ lv2:symbol "notify" ;
+ ui:notifyType patch:Message ,
+ atom:Tuple ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ lv2:extensionData ui:idleInterface, ui:resize ;
+ lv2:optionalFeature ui:resize, opts:options ;
+ opts:supportedOption ui:scaleFactor ;
+ lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap, ui:parent .
+
+# MIDI Inspector UI
+sherlock:midi_inspector_4_nk
+ ui:portNotification [
+ ui:plugin sherlock:midi_inspector ;
+ lv2:symbol "notify" ;
+ ui:notifyType patch:Message ,
+ atom:Tuple ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ lv2:extensionData ui:idleInterface, ui:resize ;
+ lv2:optionalFeature ui:resize, opts:options ;
+ opts:supportedOption ui:scaleFactor ;
+ lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap, ui:parent .
+
+# OSC Inspector UI
+sherlock:osc_inspector_4_nk
+ ui:portNotification [
+ ui:plugin sherlock:osc_inspector ;
+ lv2:symbol "notify" ;
+ ui:notifyType patch:Message ,
+ atom:Tuple ;
+ ui:protocol atom:eventTransfer
+ ] ;
+ lv2:extensionData ui:idleInterface, ui:resize ;
+ lv2:optionalFeature ui:resize, opts:options ;
+ opts:supportedOption ui:scaleFactor ;
+ lv2:requiredFeature ui:idleInterface, urid:map, urid:unmap, ui:parent .
diff --git a/subprojects/nk_pugl/.gitlab-ci.yml b/subprojects/nk_pugl/.gitlab-ci.yml
new file mode 100644
index 0000000..62aa6f8
--- /dev/null
+++ b/subprojects/nk_pugl/.gitlab-ci.yml
@@ -0,0 +1,77 @@
+stages:
+ - build
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "nk_pugl"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
+ - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' build/build.ninja
+ - ninja -C build
+ - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
+
+.analyze_template: &analyze_definition
+ <<: *common_definition
+ script:
+ - meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" build
+ - sed -i -e '/framework/s/-Wl,-O1//g' -e '/framework/s/-Wl,--start-group//g' -e '/framework/s/-Wl,--end-group//g' build/build.ninja
+ - ninja -C build
+ - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
+
+ - CC=clang CXX=clang++ meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" clang
+ - ninja -C clang
+
+ - scan-build --status-bugs meson -Dbuild-examples=true --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" scanbuild
+ - scan-build --status-bugs ninja -C scanbuild
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *analyze_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev libevdev-dev
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf
+ <<: *arm_linux_definition
+
+aarch64-linux-gnu:
+ before_script:
+ - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64
+ <<: *arm_linux_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/subprojects/nk_pugl/COPYING b/subprojects/nk_pugl/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/subprojects/nk_pugl/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/subprojects/nk_pugl/VERSION b/subprojects/nk_pugl/VERSION
new file mode 100644
index 0000000..d3bfa59
--- /dev/null
+++ b/subprojects/nk_pugl/VERSION
@@ -0,0 +1 @@
+0.1.173
diff --git a/example/example.c b/subprojects/nk_pugl/example/example.c
index c55a2ca..c55a2ca 100644
--- a/example/example.c
+++ b/subprojects/nk_pugl/example/example.c
diff --git a/subprojects/nk_pugl/meson.build b/subprojects/nk_pugl/meson.build
new file mode 100644
index 0000000..19b855f
--- /dev/null
+++ b/subprojects/nk_pugl/meson.build
@@ -0,0 +1,83 @@
+project('nk_pugl', 'c', default_options : [
+ 'buildtype=release',
+ 'warning_level=3',
+ 'werror=false',
+ 'b_lto=false',
+ 'c_std=gnu11'])
+
+build_examples = get_option('build-examples')
+
+static_link = false #meson.is_cross_build()
+
+cc = meson.get_compiler('c')
+
+m_dep = cc.find_library('m')
+lv2_dep = dependency('lv2',
+ version : '>=1.14.0')
+glew_dep = dependency('glew',
+ version : '>=2.0.0',
+ static : static_link)
+glu_dep = dependency('glu',
+ version : '>=9.0.0',
+ static : static_link)
+
+deps = [m_dep, lv2_dep, glu_dep, glew_dep]
+links = []
+
+pugl_inc = include_directories(join_paths('pugl', 'include'))
+inc_dir = [pugl_inc]
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+
+bin_srcs = [
+ join_paths('example', 'example.c')
+]
+
+lib_srcs = [
+ join_paths('pugl', 'src', 'implementation.c')
+]
+
+c_args = ['-fvisibility=hidden',
+ '-ffast-math']
+
+if host_machine.system() == 'windows'
+ deps += cc.find_library('opengl32')
+ deps += cc.find_library('gdi32')
+ deps += cc.find_library('ws2_32')
+ lib_srcs += join_paths('pugl', 'src', 'win.c')
+ lib_srcs += join_paths('pugl', 'src', 'win_gl.c')
+elif host_machine.system() == 'darwin'
+ add_languages('objc')
+ links += ['-framework', 'OpenGL']
+ links += ['-framework', 'Cocoa']
+ lib_srcs += join_paths('pugl', 'src', 'mac.m')
+ lib_srcs += join_paths('pugl', 'src', 'mac_gl.m')
+else
+ deps += dependency('gl')
+ deps += dependency('x11', version : '>=1.6.0')
+ deps += dependency('xext', version : '>=1.3.0')
+ lib_srcs += join_paths('pugl', 'src', 'x11.c')
+ lib_srcs += join_paths('pugl', 'src', 'x11_gl.c')
+endif
+
+nk_pugl_gl = declare_dependency(
+ compile_args : ['-DPUGL_STATIC'],
+ include_directories : inc_dir,
+ dependencies : deps,
+ link_args : links,
+ sources : lib_srcs)
+
+cousine_regular_ttf = configure_file(
+ input : join_paths('nuklear', 'extra_font', 'Cousine-Regular.ttf'),
+ output : 'Cousine-Regular.ttf',
+ copy : true,
+ install : false)
+
+if build_examples
+
+ executable('nk_pugl.gl', [bin_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: nk_pugl_gl,
+ install : false)
+endif
diff --git a/subprojects/nk_pugl/meson_options.txt b/subprojects/nk_pugl/meson_options.txt
new file mode 100644
index 0000000..0a4a2a9
--- /dev/null
+++ b/subprojects/nk_pugl/meson_options.txt
@@ -0,0 +1,4 @@
+option('build-examples',
+ type : 'boolean',
+ value : false,
+ yield : true)
diff --git a/nk_pugl/nk_pugl.h b/subprojects/nk_pugl/nk_pugl/nk_pugl.h
index 58fce93..58fce93 100644
--- a/nk_pugl/nk_pugl.h
+++ b/subprojects/nk_pugl/nk_pugl/nk_pugl.h
diff --git a/nuklear/.gitattributes b/subprojects/nk_pugl/nuklear/.gitattributes
index 5a5328c..5a5328c 100644
--- a/nuklear/.gitattributes
+++ b/subprojects/nk_pugl/nuklear/.gitattributes
diff --git a/nuklear/.gitignore b/subprojects/nk_pugl/nuklear/.gitignore
index 6b06b55..6b06b55 100644
--- a/nuklear/.gitignore
+++ b/subprojects/nk_pugl/nuklear/.gitignore
diff --git a/nuklear/.gitmodules b/subprojects/nk_pugl/nuklear/.gitmodules
index e69de29..e69de29 100644
--- a/nuklear/.gitmodules
+++ b/subprojects/nk_pugl/nuklear/.gitmodules
diff --git a/nuklear/.travis.yml b/subprojects/nk_pugl/nuklear/.travis.yml
index 80a5ad5..80a5ad5 100644
--- a/nuklear/.travis.yml
+++ b/subprojects/nk_pugl/nuklear/.travis.yml
diff --git a/nuklear/Readme.md b/subprojects/nk_pugl/nuklear/Readme.md
index d097e35..d097e35 100644
--- a/nuklear/Readme.md
+++ b/subprojects/nk_pugl/nuklear/Readme.md
diff --git a/nuklear/demo/allegro5/KeyboardHandleriOS.h b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h
index 2664d2e..2664d2e 100644
--- a/nuklear/demo/allegro5/KeyboardHandleriOS.h
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.h
diff --git a/nuklear/demo/allegro5/KeyboardHandleriOS.m b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m
index 206f9e4..206f9e4 100644
--- a/nuklear/demo/allegro5/KeyboardHandleriOS.m
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/KeyboardHandleriOS.m
diff --git a/nuklear/demo/allegro5/Makefile b/subprojects/nk_pugl/nuklear/demo/allegro5/Makefile
index 090a126..090a126 100644
--- a/nuklear/demo/allegro5/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/Makefile
diff --git a/nuklear/demo/allegro5/Readme.md b/subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md
index 71a4840..71a4840 100644
--- a/nuklear/demo/allegro5/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/Readme.md
diff --git a/nuklear/demo/allegro5/main.c b/subprojects/nk_pugl/nuklear/demo/allegro5/main.c
index 624e7c9..624e7c9 100644
--- a/nuklear/demo/allegro5/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/main.c
diff --git a/nuklear/demo/allegro5/nuklear_allegro5.h b/subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h
index f396600..f396600 100644
--- a/nuklear/demo/allegro5/nuklear_allegro5.h
+++ b/subprojects/nk_pugl/nuklear/demo/allegro5/nuklear_allegro5.h
diff --git a/nuklear/demo/calculator.c b/subprojects/nk_pugl/nuklear/demo/calculator.c
index b871301..b871301 100644
--- a/nuklear/demo/calculator.c
+++ b/subprojects/nk_pugl/nuklear/demo/calculator.c
diff --git a/nuklear/demo/d3d11/build.bat b/subprojects/nk_pugl/nuklear/demo/d3d11/build.bat
index 31bd0e0..31bd0e0 100644
--- a/nuklear/demo/d3d11/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/build.bat
diff --git a/nuklear/demo/d3d11/main.c b/subprojects/nk_pugl/nuklear/demo/d3d11/main.c
index a7abf1d..a7abf1d 100644
--- a/nuklear/demo/d3d11/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/main.c
diff --git a/nuklear/demo/d3d11/nuklear_d3d11.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h
index 7097d98..7097d98 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.h
diff --git a/nuklear/demo/d3d11/nuklear_d3d11.hlsl b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl
index a932dca..a932dca 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11.hlsl
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11.hlsl
diff --git a/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
index 1447559..1447559 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
diff --git a/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
index 770d2dd..770d2dd 100644
--- a/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
diff --git a/nuklear/demo/d3d9/build.bat b/subprojects/nk_pugl/nuklear/demo/d3d9/build.bat
index 726b6f6..726b6f6 100644
--- a/nuklear/demo/d3d9/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/build.bat
diff --git a/nuklear/demo/d3d9/main.c b/subprojects/nk_pugl/nuklear/demo/d3d9/main.c
index b329e2b..b329e2b 100644
--- a/nuklear/demo/d3d9/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/main.c
diff --git a/nuklear/demo/d3d9/nuklear_d3d9.h b/subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h
index fd8d176..fd8d176 100644
--- a/nuklear/demo/d3d9/nuklear_d3d9.h
+++ b/subprojects/nk_pugl/nuklear/demo/d3d9/nuklear_d3d9.h
diff --git a/nuklear/demo/gdi/build.bat b/subprojects/nk_pugl/nuklear/demo/gdi/build.bat
index 3884317..3884317 100644
--- a/nuklear/demo/gdi/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/build.bat
diff --git a/nuklear/demo/gdi/main.c b/subprojects/nk_pugl/nuklear/demo/gdi/main.c
index 1755b9c..1755b9c 100644
--- a/nuklear/demo/gdi/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/main.c
diff --git a/nuklear/demo/gdi/nuklear_gdi.h b/subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h
index c875de1..c875de1 100644
--- a/nuklear/demo/gdi/nuklear_gdi.h
+++ b/subprojects/nk_pugl/nuklear/demo/gdi/nuklear_gdi.h
diff --git a/nuklear/demo/gdip/build.bat b/subprojects/nk_pugl/nuklear/demo/gdip/build.bat
index 0f24655..0f24655 100644
--- a/nuklear/demo/gdip/build.bat
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/build.bat
diff --git a/nuklear/demo/gdip/main.c b/subprojects/nk_pugl/nuklear/demo/gdip/main.c
index a90a0cf..a90a0cf 100644
--- a/nuklear/demo/gdip/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/main.c
diff --git a/nuklear/demo/gdip/nuklear_gdip.h b/subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h
index 8a6c8d4..8a6c8d4 100644
--- a/nuklear/demo/gdip/nuklear_gdip.h
+++ b/subprojects/nk_pugl/nuklear/demo/gdip/nuklear_gdip.h
diff --git a/nuklear/demo/glfw_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile
index c08d65f..c08d65f 100644
--- a/nuklear/demo/glfw_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/Makefile
diff --git a/nuklear/demo/glfw_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c
index 5d68bb9..5d68bb9 100644
--- a/nuklear/demo/glfw_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/main.c
diff --git a/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
index 7724b25..7724b25 100644
--- a/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
diff --git a/nuklear/demo/glfw_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile
index da95261..da95261 100644
--- a/nuklear/demo/glfw_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/Makefile
diff --git a/nuklear/demo/glfw_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c
index 529e88c..529e88c 100644
--- a/nuklear/demo/glfw_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/main.c
diff --git a/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
index 8fe98fc..8fe98fc 100644
--- a/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
diff --git a/nuklear/demo/node_editor.c b/subprojects/nk_pugl/nuklear/demo/node_editor.c
index d4f34c3..d4f34c3 100644
--- a/nuklear/demo/node_editor.c
+++ b/subprojects/nk_pugl/nuklear/demo/node_editor.c
diff --git a/nuklear/demo/overview.c b/subprojects/nk_pugl/nuklear/demo/overview.c
index 3c3f381..3c3f381 100644
--- a/nuklear/demo/overview.c
+++ b/subprojects/nk_pugl/nuklear/demo/overview.c
diff --git a/nuklear/demo/sdl_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile
index b73174a..b73174a 100644
--- a/nuklear/demo/sdl_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/Makefile
diff --git a/nuklear/demo/sdl_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c
index 0fbf3ff..0fbf3ff 100644
--- a/nuklear/demo/sdl_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/main.c
diff --git a/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
index 62dc64e..62dc64e 100644
--- a/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
diff --git a/nuklear/demo/sdl_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile
index c6fcb45..c6fcb45 100644
--- a/nuklear/demo/sdl_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/Makefile
diff --git a/nuklear/demo/sdl_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c
index 9959d8a..9959d8a 100644
--- a/nuklear/demo/sdl_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/main.c
diff --git a/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
index eb53ebb..eb53ebb 100644
--- a/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
diff --git a/nuklear/demo/sdl_opengles2/Makefile b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile
index 7385305..7385305 100644
--- a/nuklear/demo/sdl_opengles2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/Makefile
diff --git a/nuklear/demo/sdl_opengles2/main.c b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c
index 16a271a..16a271a 100644
--- a/nuklear/demo/sdl_opengles2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/main.c
diff --git a/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
index f96e610..f96e610 100644
--- a/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h
diff --git a/nuklear/demo/sfml_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile
index 28848a4..28848a4 100644
--- a/nuklear/demo/sfml_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Makefile
diff --git a/nuklear/demo/sfml_opengl2/Readme.md b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md
index e3879c0..e3879c0 100644
--- a/nuklear/demo/sfml_opengl2/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/Readme.md
diff --git a/nuklear/demo/sfml_opengl2/main.cpp b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp
index 805c1f8..805c1f8 100644
--- a/nuklear/demo/sfml_opengl2/main.cpp
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/main.cpp
diff --git a/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
index 9649ec0..9649ec0 100644
--- a/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h
diff --git a/nuklear/demo/sfml_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile
index b30bf28..b30bf28 100644
--- a/nuklear/demo/sfml_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Makefile
diff --git a/nuklear/demo/sfml_opengl3/Readme.md b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md
index ac03e75..ac03e75 100644
--- a/nuklear/demo/sfml_opengl3/Readme.md
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/Readme.md
diff --git a/nuklear/demo/sfml_opengl3/main.cpp b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp
index 5dc2a6e..5dc2a6e 100644
--- a/nuklear/demo/sfml_opengl3/main.cpp
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/main.cpp
diff --git a/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
index 40b390e..40b390e 100644
--- a/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h
diff --git a/nuklear/demo/style.c b/subprojects/nk_pugl/nuklear/demo/style.c
index 17c48fe..17c48fe 100644
--- a/nuklear/demo/style.c
+++ b/subprojects/nk_pugl/nuklear/demo/style.c
diff --git a/nuklear/demo/x11/Makefile b/subprojects/nk_pugl/nuklear/demo/x11/Makefile
index 9057c7b..9057c7b 100644
--- a/nuklear/demo/x11/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11/Makefile
diff --git a/nuklear/demo/x11/main.c b/subprojects/nk_pugl/nuklear/demo/x11/main.c
index 791653d..791653d 100644
--- a/nuklear/demo/x11/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11/main.c
diff --git a/nuklear/demo/x11/nuklear_xlib.h b/subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h
index 8b6b4ff..8b6b4ff 100644
--- a/nuklear/demo/x11/nuklear_xlib.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11/nuklear_xlib.h
diff --git a/nuklear/demo/x11_opengl2/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile
index a3d6d32..a3d6d32 100644
--- a/nuklear/demo/x11_opengl2/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/Makefile
diff --git a/nuklear/demo/x11_opengl2/main.c b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c
index 12cbed1..12cbed1 100644
--- a/nuklear/demo/x11_opengl2/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/main.c
diff --git a/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
index ab36eb6..ab36eb6 100644
--- a/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
diff --git a/nuklear/demo/x11_opengl3/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile
index a3d6d32..a3d6d32 100644
--- a/nuklear/demo/x11_opengl3/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/Makefile
diff --git a/nuklear/demo/x11_opengl3/main.c b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c
index 0191f98..0191f98 100644
--- a/nuklear/demo/x11_opengl3/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/main.c
diff --git a/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
index 487dbc7..487dbc7 100644
--- a/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
diff --git a/nuklear/demo/x11_rawfb/Makefile b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile
index c1a178a..c1a178a 100644
--- a/nuklear/demo/x11_rawfb/Makefile
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/Makefile
diff --git a/nuklear/demo/x11_rawfb/main.c b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c
index c70b024..c70b024 100644
--- a/nuklear/demo/x11_rawfb/main.c
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/main.c
diff --git a/nuklear/demo/x11_rawfb/nuklear_rawfb.h b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h
index e518780..e518780 100644
--- a/nuklear/demo/x11_rawfb/nuklear_rawfb.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_rawfb.h
diff --git a/nuklear/demo/x11_rawfb/nuklear_xlib.h b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h
index 068112f..068112f 100644
--- a/nuklear/demo/x11_rawfb/nuklear_xlib.h
+++ b/subprojects/nk_pugl/nuklear/demo/x11_rawfb/nuklear_xlib.h
diff --git a/nuklear/doc/Makefile b/subprojects/nk_pugl/nuklear/doc/Makefile
index 72a598b..72a598b 100644
--- a/nuklear/doc/Makefile
+++ b/subprojects/nk_pugl/nuklear/doc/Makefile
diff --git a/nuklear/doc/build.sh b/subprojects/nk_pugl/nuklear/doc/build.sh
index 560dd59..560dd59 100755
--- a/nuklear/doc/build.sh
+++ b/subprojects/nk_pugl/nuklear/doc/build.sh
diff --git a/nuklear/doc/nuklear.html b/subprojects/nk_pugl/nuklear/doc/nuklear.html
index ad0d63b..ad0d63b 100644
--- a/nuklear/doc/nuklear.html
+++ b/subprojects/nk_pugl/nuklear/doc/nuklear.html
diff --git a/nuklear/doc/stddoc.c b/subprojects/nk_pugl/nuklear/doc/stddoc.c
index 461ddc1..461ddc1 100644
--- a/nuklear/doc/stddoc.c
+++ b/subprojects/nk_pugl/nuklear/doc/stddoc.c
diff --git a/nuklear/example/Makefile b/subprojects/nk_pugl/nuklear/example/Makefile
index a3ae6f0..a3ae6f0 100644
--- a/nuklear/example/Makefile
+++ b/subprojects/nk_pugl/nuklear/example/Makefile
diff --git a/nuklear/example/canvas.c b/subprojects/nk_pugl/nuklear/example/canvas.c
index 2a7f7a2..2a7f7a2 100644
--- a/nuklear/example/canvas.c
+++ b/subprojects/nk_pugl/nuklear/example/canvas.c
diff --git a/nuklear/example/extended.c b/subprojects/nk_pugl/nuklear/example/extended.c
index 003adf3..003adf3 100644
--- a/nuklear/example/extended.c
+++ b/subprojects/nk_pugl/nuklear/example/extended.c
diff --git a/nuklear/example/file_browser.c b/subprojects/nk_pugl/nuklear/example/file_browser.c
index 1945ff5..1945ff5 100644
--- a/nuklear/example/file_browser.c
+++ b/subprojects/nk_pugl/nuklear/example/file_browser.c
diff --git a/nuklear/example/icon/checked.png b/subprojects/nk_pugl/nuklear/example/icon/checked.png
index e4e05b2..e4e05b2 100644
--- a/nuklear/example/icon/checked.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/checked.png
Binary files differ
diff --git a/nuklear/example/icon/cloud.png b/subprojects/nk_pugl/nuklear/example/icon/cloud.png
index ecc5791..ecc5791 100644
--- a/nuklear/example/icon/cloud.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/cloud.png
Binary files differ
diff --git a/nuklear/example/icon/computer.png b/subprojects/nk_pugl/nuklear/example/icon/computer.png
index 29db8fc..29db8fc 100644
--- a/nuklear/example/icon/computer.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/computer.png
Binary files differ
diff --git a/nuklear/example/icon/copy.png b/subprojects/nk_pugl/nuklear/example/icon/copy.png
index 0a6e979..0a6e979 100644
--- a/nuklear/example/icon/copy.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/copy.png
Binary files differ
diff --git a/nuklear/example/icon/default.png b/subprojects/nk_pugl/nuklear/example/icon/default.png
index c11145a..c11145a 100644
--- a/nuklear/example/icon/default.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/default.png
Binary files differ
diff --git a/nuklear/example/icon/delete.png b/subprojects/nk_pugl/nuklear/example/icon/delete.png
index 7bc6dde..7bc6dde 100644
--- a/nuklear/example/icon/delete.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/delete.png
Binary files differ
diff --git a/nuklear/example/icon/desktop.png b/subprojects/nk_pugl/nuklear/example/icon/desktop.png
index b4abcfd..b4abcfd 100644
--- a/nuklear/example/icon/desktop.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/desktop.png
Binary files differ
diff --git a/nuklear/example/icon/directory.png b/subprojects/nk_pugl/nuklear/example/icon/directory.png
index 4c73d37..4c73d37 100644
--- a/nuklear/example/icon/directory.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/directory.png
Binary files differ
diff --git a/nuklear/example/icon/edit.png b/subprojects/nk_pugl/nuklear/example/icon/edit.png
index 62ce0b4..62ce0b4 100644
--- a/nuklear/example/icon/edit.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/edit.png
Binary files differ
diff --git a/nuklear/example/icon/export.png b/subprojects/nk_pugl/nuklear/example/icon/export.png
index ff6b5aa..ff6b5aa 100644
--- a/nuklear/example/icon/export.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/export.png
Binary files differ
diff --git a/nuklear/example/icon/font.png b/subprojects/nk_pugl/nuklear/example/icon/font.png
index 918e9bf..918e9bf 100644
--- a/nuklear/example/icon/font.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/font.png
Binary files differ
diff --git a/nuklear/example/icon/home.png b/subprojects/nk_pugl/nuklear/example/icon/home.png
index 8560626..8560626 100644
--- a/nuklear/example/icon/home.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/home.png
Binary files differ
diff --git a/nuklear/example/icon/img.png b/subprojects/nk_pugl/nuklear/example/icon/img.png
index 1985957..1985957 100644
--- a/nuklear/example/icon/img.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/img.png
Binary files differ
diff --git a/nuklear/example/icon/movie.png b/subprojects/nk_pugl/nuklear/example/icon/movie.png
index 5227883..5227883 100644
--- a/nuklear/example/icon/movie.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/movie.png
Binary files differ
diff --git a/nuklear/example/icon/music.png b/subprojects/nk_pugl/nuklear/example/icon/music.png
index 0f1415c..0f1415c 100644
--- a/nuklear/example/icon/music.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/music.png
Binary files differ
diff --git a/nuklear/example/icon/next.png b/subprojects/nk_pugl/nuklear/example/icon/next.png
index af0b98d..af0b98d 100644
--- a/nuklear/example/icon/next.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/next.png
Binary files differ
diff --git a/nuklear/example/icon/pause.png b/subprojects/nk_pugl/nuklear/example/icon/pause.png
index 7d6367e..7d6367e 100644
--- a/nuklear/example/icon/pause.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/pause.png
Binary files differ
diff --git a/nuklear/example/icon/pen.png b/subprojects/nk_pugl/nuklear/example/icon/pen.png
index 10c851c..10c851c 100644
--- a/nuklear/example/icon/pen.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/pen.png
Binary files differ
diff --git a/nuklear/example/icon/phone.png b/subprojects/nk_pugl/nuklear/example/icon/phone.png
index 5e6f613..5e6f613 100644
--- a/nuklear/example/icon/phone.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/phone.png
Binary files differ
diff --git a/nuklear/example/icon/plane.png b/subprojects/nk_pugl/nuklear/example/icon/plane.png
index 3a98489..3a98489 100644
--- a/nuklear/example/icon/plane.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/plane.png
Binary files differ
diff --git a/nuklear/example/icon/play.png b/subprojects/nk_pugl/nuklear/example/icon/play.png
index 9c9e8f0..9c9e8f0 100644
--- a/nuklear/example/icon/play.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/play.png
Binary files differ
diff --git a/nuklear/example/icon/prev.png b/subprojects/nk_pugl/nuklear/example/icon/prev.png
index 0eecc2e..0eecc2e 100644
--- a/nuklear/example/icon/prev.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/prev.png
Binary files differ
diff --git a/nuklear/example/icon/rocket.png b/subprojects/nk_pugl/nuklear/example/icon/rocket.png
index ea8e187..ea8e187 100644
--- a/nuklear/example/icon/rocket.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/rocket.png
Binary files differ
diff --git a/nuklear/example/icon/settings.png b/subprojects/nk_pugl/nuklear/example/icon/settings.png
index e6e13f8..e6e13f8 100644
--- a/nuklear/example/icon/settings.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/settings.png
Binary files differ
diff --git a/nuklear/example/icon/stop.png b/subprojects/nk_pugl/nuklear/example/icon/stop.png
index 6742baf..6742baf 100644
--- a/nuklear/example/icon/stop.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/stop.png
Binary files differ
diff --git a/nuklear/example/icon/text.png b/subprojects/nk_pugl/nuklear/example/icon/text.png
index 136e534..136e534 100644
--- a/nuklear/example/icon/text.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/text.png
Binary files differ
diff --git a/nuklear/example/icon/tools.png b/subprojects/nk_pugl/nuklear/example/icon/tools.png
index 412ff85..412ff85 100644
--- a/nuklear/example/icon/tools.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/tools.png
Binary files differ
diff --git a/nuklear/example/icon/unchecked.png b/subprojects/nk_pugl/nuklear/example/icon/unchecked.png
index fca94d2..fca94d2 100644
--- a/nuklear/example/icon/unchecked.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/unchecked.png
Binary files differ
diff --git a/nuklear/example/icon/volume.png b/subprojects/nk_pugl/nuklear/example/icon/volume.png
index 8e86fa9..8e86fa9 100644
--- a/nuklear/example/icon/volume.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/volume.png
Binary files differ
diff --git a/nuklear/example/icon/wifi.png b/subprojects/nk_pugl/nuklear/example/icon/wifi.png
index 270d55d..270d55d 100644
--- a/nuklear/example/icon/wifi.png
+++ b/subprojects/nk_pugl/nuklear/example/icon/wifi.png
Binary files differ
diff --git a/nuklear/example/images/image1.png b/subprojects/nk_pugl/nuklear/example/images/image1.png
index 66a2b63..66a2b63 100644
--- a/nuklear/example/images/image1.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image1.png
Binary files differ
diff --git a/nuklear/example/images/image2.png b/subprojects/nk_pugl/nuklear/example/images/image2.png
index 4acafe5..4acafe5 100644
--- a/nuklear/example/images/image2.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image2.png
Binary files differ
diff --git a/nuklear/example/images/image3.png b/subprojects/nk_pugl/nuklear/example/images/image3.png
index 4dfe664..4dfe664 100644
--- a/nuklear/example/images/image3.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image3.png
Binary files differ
diff --git a/nuklear/example/images/image4.png b/subprojects/nk_pugl/nuklear/example/images/image4.png
index d2f16d0..d2f16d0 100644
--- a/nuklear/example/images/image4.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image4.png
Binary files differ
diff --git a/nuklear/example/images/image5.png b/subprojects/nk_pugl/nuklear/example/images/image5.png
index 852fd70..852fd70 100644
--- a/nuklear/example/images/image5.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image5.png
Binary files differ
diff --git a/nuklear/example/images/image6.png b/subprojects/nk_pugl/nuklear/example/images/image6.png
index 0e261cb..0e261cb 100644
--- a/nuklear/example/images/image6.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image6.png
Binary files differ
diff --git a/nuklear/example/images/image7.png b/subprojects/nk_pugl/nuklear/example/images/image7.png
index f61325b..f61325b 100644
--- a/nuklear/example/images/image7.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image7.png
Binary files differ
diff --git a/nuklear/example/images/image8.png b/subprojects/nk_pugl/nuklear/example/images/image8.png
index 6b27cb8..6b27cb8 100644
--- a/nuklear/example/images/image8.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image8.png
Binary files differ
diff --git a/nuklear/example/images/image9.png b/subprojects/nk_pugl/nuklear/example/images/image9.png
index 516929e..516929e 100644
--- a/nuklear/example/images/image9.png
+++ b/subprojects/nk_pugl/nuklear/example/images/image9.png
Binary files differ
diff --git a/nuklear/example/skinning.c b/subprojects/nk_pugl/nuklear/example/skinning.c
index 4020aec..4020aec 100644
--- a/nuklear/example/skinning.c
+++ b/subprojects/nk_pugl/nuklear/example/skinning.c
diff --git a/nuklear/example/skins/gwen.png b/subprojects/nk_pugl/nuklear/example/skins/gwen.png
index 40956c9..40956c9 100644
--- a/nuklear/example/skins/gwen.png
+++ b/subprojects/nk_pugl/nuklear/example/skins/gwen.png
Binary files differ
diff --git a/nuklear/example/stb_image.h b/subprojects/nk_pugl/nuklear/example/stb_image.h
index 0a9de39..0a9de39 100644
--- a/nuklear/example/stb_image.h
+++ b/subprojects/nk_pugl/nuklear/example/stb_image.h
diff --git a/nuklear/extra_font/Cousine-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf
index 70a0bf9..70a0bf9 100644
--- a/nuklear/extra_font/Cousine-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Cousine-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/DroidSans.ttf b/subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf
index 767c63a..767c63a 100644
--- a/nuklear/extra_font/DroidSans.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/DroidSans.ttf
Binary files differ
diff --git a/nuklear/extra_font/Karla-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf
index 81b3de6..81b3de6 100644
--- a/nuklear/extra_font/Karla-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Karla-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/ProggyClean.ttf b/subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf
index 0270cdf..0270cdf 100644
--- a/nuklear/extra_font/ProggyClean.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/ProggyClean.ttf
Binary files differ
diff --git a/nuklear/extra_font/ProggyTiny.ttf b/subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf
index 1c4312c..1c4312c 100644
--- a/nuklear/extra_font/ProggyTiny.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/ProggyTiny.ttf
Binary files differ
diff --git a/nuklear/extra_font/Raleway-Bold.ttf b/subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf
index 7aa37f0..7aa37f0 100644
--- a/nuklear/extra_font/Raleway-Bold.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Raleway-Bold.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Bold.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf
index aaf374d..aaf374d 100644
--- a/nuklear/extra_font/Roboto-Bold.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Bold.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Light.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf
index 664e1b2..664e1b2 100644
--- a/nuklear/extra_font/Roboto-Light.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Light.ttf
Binary files differ
diff --git a/nuklear/extra_font/Roboto-Regular.ttf b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf
index 3e6e2e7..3e6e2e7 100644
--- a/nuklear/extra_font/Roboto-Regular.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/Roboto-Regular.ttf
Binary files differ
diff --git a/nuklear/extra_font/kenvector_future.ttf b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf
index 39ebdfa..39ebdfa 100644
--- a/nuklear/extra_font/kenvector_future.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future.ttf
Binary files differ
diff --git a/nuklear/extra_font/kenvector_future_thin.ttf b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf
index 9f4b4fa..9f4b4fa 100644
--- a/nuklear/extra_font/kenvector_future_thin.ttf
+++ b/subprojects/nk_pugl/nuklear/extra_font/kenvector_future_thin.ttf
Binary files differ
diff --git a/nuklear/nuklear.h b/subprojects/nk_pugl/nuklear/nuklear.h
index dd423b9..dd423b9 100644
--- a/nuklear/nuklear.h
+++ b/subprojects/nk_pugl/nuklear/nuklear.h
diff --git a/nuklear/package.json b/subprojects/nk_pugl/nuklear/package.json
index edff924..edff924 100644
--- a/nuklear/package.json
+++ b/subprojects/nk_pugl/nuklear/package.json
diff --git a/pugl/.clang-format b/subprojects/nk_pugl/pugl/.clang-format
index 7b30bd2..7b30bd2 100644
--- a/pugl/.clang-format
+++ b/subprojects/nk_pugl/pugl/.clang-format
diff --git a/pugl/.clang-tidy b/subprojects/nk_pugl/pugl/.clang-tidy
index 1e40901..1e40901 100644
--- a/pugl/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/.clang-tidy
diff --git a/pugl/.clant.json b/subprojects/nk_pugl/pugl/.clant.json
index 6f48901..6f48901 100644
--- a/pugl/.clant.json
+++ b/subprojects/nk_pugl/pugl/.clant.json
diff --git a/pugl/.editorconfig b/subprojects/nk_pugl/pugl/.editorconfig
index c2d35dd..c2d35dd 100644
--- a/pugl/.editorconfig
+++ b/subprojects/nk_pugl/pugl/.editorconfig
diff --git a/pugl/.gitattributes b/subprojects/nk_pugl/pugl/.gitattributes
index 32967c1..32967c1 100644
--- a/pugl/.gitattributes
+++ b/subprojects/nk_pugl/pugl/.gitattributes
diff --git a/pugl/.gitignore b/subprojects/nk_pugl/pugl/.gitignore
index dad71c3..dad71c3 100644
--- a/pugl/.gitignore
+++ b/subprojects/nk_pugl/pugl/.gitignore
diff --git a/pugl/.gitlab-ci.yml b/subprojects/nk_pugl/pugl/.gitlab-ci.yml
index c0d0330..c0d0330 100644
--- a/pugl/.gitlab-ci.yml
+++ b/subprojects/nk_pugl/pugl/.gitlab-ci.yml
diff --git a/pugl/.gitmodules b/subprojects/nk_pugl/pugl/.gitmodules
index cc8b569..cc8b569 100644
--- a/pugl/.gitmodules
+++ b/subprojects/nk_pugl/pugl/.gitmodules
diff --git a/pugl/.includes.imp b/subprojects/nk_pugl/pugl/.includes.imp
index 74a3105..74a3105 100644
--- a/pugl/.includes.imp
+++ b/subprojects/nk_pugl/pugl/.includes.imp
diff --git a/pugl/AUTHORS b/subprojects/nk_pugl/pugl/AUTHORS
index 99f6dac..99f6dac 100644
--- a/pugl/AUTHORS
+++ b/subprojects/nk_pugl/pugl/AUTHORS
diff --git a/pugl/COPYING b/subprojects/nk_pugl/pugl/COPYING
index 63e6829..63e6829 100644
--- a/pugl/COPYING
+++ b/subprojects/nk_pugl/pugl/COPYING
diff --git a/pugl/README.md b/subprojects/nk_pugl/pugl/README.md
index 97906a8..97906a8 100644
--- a/pugl/README.md
+++ b/subprojects/nk_pugl/pugl/README.md
diff --git a/pugl/bindings/cxx/include/.clang-tidy b/subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy
index 816223d..816223d 100644
--- a/pugl/bindings/cxx/include/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/.clang-tidy
diff --git a/pugl/bindings/cxx/include/pugl/cairo.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp
index 126bfe3..126bfe3 100644
--- a/pugl/bindings/cxx/include/pugl/cairo.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/cairo.hpp
diff --git a/pugl/bindings/cxx/include/pugl/gl.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp
index c845d80..c845d80 100644
--- a/pugl/bindings/cxx/include/pugl/gl.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/gl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/pugl.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp
index 9e65589..9e65589 100644
--- a/pugl/bindings/cxx/include/pugl/pugl.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/pugl.hpp
diff --git a/pugl/bindings/cxx/include/pugl/stub.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp
index 6946fe0..6946fe0 100644
--- a/pugl/bindings/cxx/include/pugl/stub.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/stub.hpp
diff --git a/pugl/bindings/cxx/include/pugl/vulkan.hpp b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp
index 2612578..2612578 100644
--- a/pugl/bindings/cxx/include/pugl/vulkan.hpp
+++ b/subprojects/nk_pugl/pugl/bindings/cxx/include/pugl/vulkan.hpp
diff --git a/pugl/doc/_static/custom.css b/subprojects/nk_pugl/pugl/doc/_static/custom.css
index 60aa759..60aa759 100644
--- a/pugl/doc/_static/custom.css
+++ b/subprojects/nk_pugl/pugl/doc/_static/custom.css
diff --git a/pugl/doc/_templates/about.html b/subprojects/nk_pugl/pugl/doc/_templates/about.html
index 5bbadbe..5bbadbe 100644
--- a/pugl/doc/_templates/about.html
+++ b/subprojects/nk_pugl/pugl/doc/_templates/about.html
diff --git a/pugl/doc/c/Doxyfile b/subprojects/nk_pugl/pugl/doc/c/Doxyfile
index bdc3a46..bdc3a46 100644
--- a/pugl/doc/c/Doxyfile
+++ b/subprojects/nk_pugl/pugl/doc/c/Doxyfile
diff --git a/pugl/doc/c/index.rst b/subprojects/nk_pugl/pugl/doc/c/index.rst
index 6f046de..6f046de 100644
--- a/pugl/doc/c/index.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/index.rst
diff --git a/pugl/doc/c/overview.rst b/subprojects/nk_pugl/pugl/doc/c/overview.rst
index 3ebe21e..3ebe21e 100644
--- a/pugl/doc/c/overview.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/overview.rst
diff --git a/pugl/doc/c/reference.rst b/subprojects/nk_pugl/pugl/doc/c/reference.rst
index 21a187f..21a187f 100644
--- a/pugl/doc/c/reference.rst
+++ b/subprojects/nk_pugl/pugl/doc/c/reference.rst
diff --git a/pugl/doc/c/wscript b/subprojects/nk_pugl/pugl/doc/c/wscript
index 4e0fbc9..4e0fbc9 100644
--- a/pugl/doc/c/wscript
+++ b/subprojects/nk_pugl/pugl/doc/c/wscript
diff --git a/pugl/doc/conf.py.in b/subprojects/nk_pugl/pugl/doc/conf.py.in
index 9ca7bfa..9ca7bfa 100644
--- a/pugl/doc/conf.py.in
+++ b/subprojects/nk_pugl/pugl/doc/conf.py.in
diff --git a/pugl/doc/cpp/Doxyfile b/subprojects/nk_pugl/pugl/doc/cpp/Doxyfile
index 0f5f636..0f5f636 100644
--- a/pugl/doc/cpp/Doxyfile
+++ b/subprojects/nk_pugl/pugl/doc/cpp/Doxyfile
diff --git a/pugl/doc/cpp/c-reference.rst b/subprojects/nk_pugl/pugl/doc/cpp/c-reference.rst
index 546e4d3..546e4d3 100644
--- a/pugl/doc/cpp/c-reference.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/c-reference.rst
diff --git a/pugl/doc/cpp/cpp-reference.rst b/subprojects/nk_pugl/pugl/doc/cpp/cpp-reference.rst
index 96c523c..96c523c 100644
--- a/pugl/doc/cpp/cpp-reference.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/cpp-reference.rst
diff --git a/pugl/doc/cpp/index.rst b/subprojects/nk_pugl/pugl/doc/cpp/index.rst
index c3d330e..c3d330e 100644
--- a/pugl/doc/cpp/index.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/index.rst
diff --git a/pugl/doc/cpp/overview.rst b/subprojects/nk_pugl/pugl/doc/cpp/overview.rst
index 5fffe37..5fffe37 100644
--- a/pugl/doc/cpp/overview.rst
+++ b/subprojects/nk_pugl/pugl/doc/cpp/overview.rst
diff --git a/pugl/doc/cpp/wscript b/subprojects/nk_pugl/pugl/doc/cpp/wscript
index a7aefc1..a7aefc1 100644
--- a/pugl/doc/cpp/wscript
+++ b/subprojects/nk_pugl/pugl/doc/cpp/wscript
diff --git a/pugl/doc/deployment.rst b/subprojects/nk_pugl/pugl/doc/deployment.rst
index ffbeae9..ffbeae9 100644
--- a/pugl/doc/deployment.rst
+++ b/subprojects/nk_pugl/pugl/doc/deployment.rst
diff --git a/pugl/doc/mainpage.md b/subprojects/nk_pugl/pugl/doc/mainpage.md
index 92f7409..92f7409 100644
--- a/pugl/doc/mainpage.md
+++ b/subprojects/nk_pugl/pugl/doc/mainpage.md
diff --git a/pugl/doc/pugl.rst b/subprojects/nk_pugl/pugl/doc/pugl.rst
index c48021b..c48021b 100644
--- a/pugl/doc/pugl.rst
+++ b/subprojects/nk_pugl/pugl/doc/pugl.rst
diff --git a/pugl/examples/.clang-tidy b/subprojects/nk_pugl/pugl/examples/.clang-tidy
index fdfa4ea..fdfa4ea 100644
--- a/pugl/examples/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/examples/.clang-tidy
diff --git a/pugl/examples/cube_view.h b/subprojects/nk_pugl/pugl/examples/cube_view.h
index 71ae88d..71ae88d 100644
--- a/pugl/examples/cube_view.h
+++ b/subprojects/nk_pugl/pugl/examples/cube_view.h
diff --git a/pugl/examples/demo_utils.h b/subprojects/nk_pugl/pugl/examples/demo_utils.h
index 2dda756..2dda756 100644
--- a/pugl/examples/demo_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/demo_utils.h
diff --git a/pugl/examples/file_utils.c b/subprojects/nk_pugl/pugl/examples/file_utils.c
index 8ecbca4..8ecbca4 100644
--- a/pugl/examples/file_utils.c
+++ b/subprojects/nk_pugl/pugl/examples/file_utils.c
diff --git a/pugl/examples/file_utils.h b/subprojects/nk_pugl/pugl/examples/file_utils.h
index 1530157..1530157 100644
--- a/pugl/examples/file_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/file_utils.h
diff --git a/pugl/examples/glad/glad.c b/subprojects/nk_pugl/pugl/examples/glad/glad.c
index 38f442c..38f442c 100644
--- a/pugl/examples/glad/glad.c
+++ b/subprojects/nk_pugl/pugl/examples/glad/glad.c
diff --git a/pugl/examples/glad/glad.h b/subprojects/nk_pugl/pugl/examples/glad/glad.h
index 9efb229..9efb229 100644
--- a/pugl/examples/glad/glad.h
+++ b/subprojects/nk_pugl/pugl/examples/glad/glad.h
diff --git a/pugl/examples/glad/khrplatform.h b/subprojects/nk_pugl/pugl/examples/glad/khrplatform.h
index 5b55ea2..5b55ea2 100644
--- a/pugl/examples/glad/khrplatform.h
+++ b/subprojects/nk_pugl/pugl/examples/glad/khrplatform.h
diff --git a/pugl/examples/pugl_cairo_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c
index 4da0caf..4da0caf 100644
--- a/pugl/examples/pugl_cairo_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c
diff --git a/pugl/examples/pugl_cursor_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c
index 60ec3d3..60ec3d3 100644
--- a/pugl/examples/pugl_cursor_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c
diff --git a/pugl/examples/pugl_cxx_demo.cpp b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp
index 4914724..4914724 100644
--- a/pugl/examples/pugl_cxx_demo.cpp
+++ b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp
diff --git a/pugl/examples/pugl_embed_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c
index 0e12ddb..0e12ddb 100644
--- a/pugl/examples/pugl_embed_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c
diff --git a/pugl/examples/pugl_print_events.c b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c
index dfa217e..dfa217e 100644
--- a/pugl/examples/pugl_print_events.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c
diff --git a/pugl/examples/pugl_shader_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c
index 087afc5..087afc5 100644
--- a/pugl/examples/pugl_shader_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c
diff --git a/pugl/examples/pugl_vulkan_cxx_demo.cpp b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp
index e816091..e816091 100644
--- a/pugl/examples/pugl_vulkan_cxx_demo.cpp
+++ b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_cxx_demo.cpp
diff --git a/pugl/examples/pugl_vulkan_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c
index 0dfbadd..0dfbadd 100644
--- a/pugl/examples/pugl_vulkan_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_vulkan_demo.c
diff --git a/pugl/examples/pugl_window_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c
index be320dd..be320dd 100644
--- a/pugl/examples/pugl_window_demo.c
+++ b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c
diff --git a/pugl/examples/rects.h b/subprojects/nk_pugl/pugl/examples/rects.h
index b99d9f0..b99d9f0 100644
--- a/pugl/examples/rects.h
+++ b/subprojects/nk_pugl/pugl/examples/rects.h
diff --git a/pugl/examples/shader_utils.h b/subprojects/nk_pugl/pugl/examples/shader_utils.h
index 2575f47..2575f47 100644
--- a/pugl/examples/shader_utils.h
+++ b/subprojects/nk_pugl/pugl/examples/shader_utils.h
diff --git a/pugl/examples/shaders/header_330.glsl b/subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl
index 59d5f6f..59d5f6f 100644
--- a/pugl/examples/shaders/header_330.glsl
+++ b/subprojects/nk_pugl/pugl/examples/shaders/header_330.glsl
diff --git a/pugl/examples/shaders/header_420.glsl b/subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl
index 2beaad0..2beaad0 100644
--- a/pugl/examples/shaders/header_420.glsl
+++ b/subprojects/nk_pugl/pugl/examples/shaders/header_420.glsl
diff --git a/pugl/examples/shaders/rect.frag b/subprojects/nk_pugl/pugl/examples/shaders/rect.frag
index 33bfbb2..33bfbb2 100644
--- a/pugl/examples/shaders/rect.frag
+++ b/subprojects/nk_pugl/pugl/examples/shaders/rect.frag
diff --git a/pugl/examples/shaders/rect.vert b/subprojects/nk_pugl/pugl/examples/shaders/rect.vert
index 2c7b5f1..2c7b5f1 100644
--- a/pugl/examples/shaders/rect.vert
+++ b/subprojects/nk_pugl/pugl/examples/shaders/rect.vert
diff --git a/pugl/examples/sybok.hpp b/subprojects/nk_pugl/pugl/examples/sybok.hpp
index 7740824..7740824 100644
--- a/pugl/examples/sybok.hpp
+++ b/subprojects/nk_pugl/pugl/examples/sybok.hpp
diff --git a/pugl/include/.clang-tidy b/subprojects/nk_pugl/pugl/include/.clang-tidy
index dd2fd47..dd2fd47 100644
--- a/pugl/include/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/include/.clang-tidy
diff --git a/pugl/include/pugl/cairo.h b/subprojects/nk_pugl/pugl/include/pugl/cairo.h
index 48e868e..48e868e 100644
--- a/pugl/include/pugl/cairo.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/cairo.h
diff --git a/pugl/include/pugl/gl.h b/subprojects/nk_pugl/pugl/include/pugl/gl.h
index 51c4a7d..51c4a7d 100644
--- a/pugl/include/pugl/gl.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/gl.h
diff --git a/pugl/include/pugl/pugl.h b/subprojects/nk_pugl/pugl/include/pugl/pugl.h
index 8a261c7..8a261c7 100644
--- a/pugl/include/pugl/pugl.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/pugl.h
diff --git a/pugl/include/pugl/stub.h b/subprojects/nk_pugl/pugl/include/pugl/stub.h
index d1a699a..d1a699a 100644
--- a/pugl/include/pugl/stub.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/stub.h
diff --git a/pugl/include/pugl/vulkan.h b/subprojects/nk_pugl/pugl/include/pugl/vulkan.h
index f12ad97..f12ad97 100644
--- a/pugl/include/pugl/vulkan.h
+++ b/subprojects/nk_pugl/pugl/include/pugl/vulkan.h
diff --git a/pugl/pugl.pc.in b/subprojects/nk_pugl/pugl/pugl.pc.in
index 2cfa644..2cfa644 100644
--- a/pugl/pugl.pc.in
+++ b/subprojects/nk_pugl/pugl/pugl.pc.in
diff --git a/pugl/resources/Info.plist.in b/subprojects/nk_pugl/pugl/resources/Info.plist.in
index a08dbd0..a08dbd0 100644
--- a/pugl/resources/Info.plist.in
+++ b/subprojects/nk_pugl/pugl/resources/Info.plist.in
diff --git a/pugl/resources/pugl.ipe b/subprojects/nk_pugl/pugl/resources/pugl.ipe
index 238c09c..238c09c 100644
--- a/pugl/resources/pugl.ipe
+++ b/subprojects/nk_pugl/pugl/resources/pugl.ipe
diff --git a/pugl/resources/pugl.png b/subprojects/nk_pugl/pugl/resources/pugl.png
index 4641660..4641660 100644
--- a/pugl/resources/pugl.png
+++ b/subprojects/nk_pugl/pugl/resources/pugl.png
Binary files differ
diff --git a/pugl/resources/pugl.svg b/subprojects/nk_pugl/pugl/resources/pugl.svg
index 93189f4..93189f4 100644
--- a/pugl/resources/pugl.svg
+++ b/subprojects/nk_pugl/pugl/resources/pugl.svg
diff --git a/pugl/scripts/dox_to_sphinx.py b/subprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py
index b4eee9f..b4eee9f 100755
--- a/pugl/scripts/dox_to_sphinx.py
+++ b/subprojects/nk_pugl/pugl/scripts/dox_to_sphinx.py
diff --git a/pugl/src/.clang-tidy b/subprojects/nk_pugl/pugl/src/.clang-tidy
index 11b620e..11b620e 100644
--- a/pugl/src/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/src/.clang-tidy
diff --git a/pugl/src/implementation.c b/subprojects/nk_pugl/pugl/src/implementation.c
index 47b52b8..47b52b8 100644
--- a/pugl/src/implementation.c
+++ b/subprojects/nk_pugl/pugl/src/implementation.c
diff --git a/pugl/src/implementation.h b/subprojects/nk_pugl/pugl/src/implementation.h
index 8c5398c..8c5398c 100644
--- a/pugl/src/implementation.h
+++ b/subprojects/nk_pugl/pugl/src/implementation.h
diff --git a/pugl/src/mac.h b/subprojects/nk_pugl/pugl/src/mac.h
index 35e6e0d..35e6e0d 100644
--- a/pugl/src/mac.h
+++ b/subprojects/nk_pugl/pugl/src/mac.h
diff --git a/pugl/src/mac.m b/subprojects/nk_pugl/pugl/src/mac.m
index 58f16c7..58f16c7 100644
--- a/pugl/src/mac.m
+++ b/subprojects/nk_pugl/pugl/src/mac.m
diff --git a/pugl/src/mac_cairo.m b/subprojects/nk_pugl/pugl/src/mac_cairo.m
index 1c564a0..1c564a0 100644
--- a/pugl/src/mac_cairo.m
+++ b/subprojects/nk_pugl/pugl/src/mac_cairo.m
diff --git a/pugl/src/mac_gl.m b/subprojects/nk_pugl/pugl/src/mac_gl.m
index dd06cc0..dd06cc0 100644
--- a/pugl/src/mac_gl.m
+++ b/subprojects/nk_pugl/pugl/src/mac_gl.m
diff --git a/pugl/src/mac_stub.m b/subprojects/nk_pugl/pugl/src/mac_stub.m
index ac7bfcc..ac7bfcc 100644
--- a/pugl/src/mac_stub.m
+++ b/subprojects/nk_pugl/pugl/src/mac_stub.m
diff --git a/pugl/src/mac_vulkan.m b/subprojects/nk_pugl/pugl/src/mac_vulkan.m
index 22fff10..22fff10 100644
--- a/pugl/src/mac_vulkan.m
+++ b/subprojects/nk_pugl/pugl/src/mac_vulkan.m
diff --git a/pugl/src/stub.h b/subprojects/nk_pugl/pugl/src/stub.h
index c816679..c816679 100644
--- a/pugl/src/stub.h
+++ b/subprojects/nk_pugl/pugl/src/stub.h
diff --git a/pugl/src/types.h b/subprojects/nk_pugl/pugl/src/types.h
index 6fa658f..6fa658f 100644
--- a/pugl/src/types.h
+++ b/subprojects/nk_pugl/pugl/src/types.h
diff --git a/pugl/src/win.c b/subprojects/nk_pugl/pugl/src/win.c
index 1e11520..1e11520 100644
--- a/pugl/src/win.c
+++ b/subprojects/nk_pugl/pugl/src/win.c
diff --git a/pugl/src/win.h b/subprojects/nk_pugl/pugl/src/win.h
index ccab36a..ccab36a 100644
--- a/pugl/src/win.h
+++ b/subprojects/nk_pugl/pugl/src/win.h
diff --git a/pugl/src/win_cairo.c b/subprojects/nk_pugl/pugl/src/win_cairo.c
index 9dc5ce0..9dc5ce0 100644
--- a/pugl/src/win_cairo.c
+++ b/subprojects/nk_pugl/pugl/src/win_cairo.c
diff --git a/pugl/src/win_gl.c b/subprojects/nk_pugl/pugl/src/win_gl.c
index 4abd5ab..4abd5ab 100644
--- a/pugl/src/win_gl.c
+++ b/subprojects/nk_pugl/pugl/src/win_gl.c
diff --git a/pugl/src/win_stub.c b/subprojects/nk_pugl/pugl/src/win_stub.c
index cf86390..cf86390 100644
--- a/pugl/src/win_stub.c
+++ b/subprojects/nk_pugl/pugl/src/win_stub.c
diff --git a/pugl/src/win_vulkan.c b/subprojects/nk_pugl/pugl/src/win_vulkan.c
index a892a16..a892a16 100644
--- a/pugl/src/win_vulkan.c
+++ b/subprojects/nk_pugl/pugl/src/win_vulkan.c
diff --git a/pugl/src/x11.c b/subprojects/nk_pugl/pugl/src/x11.c
index 24054f9..24054f9 100644
--- a/pugl/src/x11.c
+++ b/subprojects/nk_pugl/pugl/src/x11.c
diff --git a/pugl/src/x11.h b/subprojects/nk_pugl/pugl/src/x11.h
index 778e5ec..778e5ec 100644
--- a/pugl/src/x11.h
+++ b/subprojects/nk_pugl/pugl/src/x11.h
diff --git a/pugl/src/x11_cairo.c b/subprojects/nk_pugl/pugl/src/x11_cairo.c
index a0e7d08..a0e7d08 100644
--- a/pugl/src/x11_cairo.c
+++ b/subprojects/nk_pugl/pugl/src/x11_cairo.c
diff --git a/pugl/src/x11_gl.c b/subprojects/nk_pugl/pugl/src/x11_gl.c
index 34152de..34152de 100644
--- a/pugl/src/x11_gl.c
+++ b/subprojects/nk_pugl/pugl/src/x11_gl.c
diff --git a/pugl/src/x11_stub.c b/subprojects/nk_pugl/pugl/src/x11_stub.c
index de89a86..de89a86 100644
--- a/pugl/src/x11_stub.c
+++ b/subprojects/nk_pugl/pugl/src/x11_stub.c
diff --git a/pugl/src/x11_vulkan.c b/subprojects/nk_pugl/pugl/src/x11_vulkan.c
index 1ff5759..1ff5759 100644
--- a/pugl/src/x11_vulkan.c
+++ b/subprojects/nk_pugl/pugl/src/x11_vulkan.c
diff --git a/pugl/test/.clang-tidy b/subprojects/nk_pugl/pugl/test/.clang-tidy
index 8014e6a..8014e6a 100644
--- a/pugl/test/.clang-tidy
+++ b/subprojects/nk_pugl/pugl/test/.clang-tidy
diff --git a/pugl/test/test_build.c b/subprojects/nk_pugl/pugl/test/test_build.c
index 957e0bd..957e0bd 100644
--- a/pugl/test/test_build.c
+++ b/subprojects/nk_pugl/pugl/test/test_build.c
diff --git a/pugl/test/test_build.cpp b/subprojects/nk_pugl/pugl/test/test_build.cpp
index 20235e8..20235e8 100644
--- a/pugl/test/test_build.cpp
+++ b/subprojects/nk_pugl/pugl/test/test_build.cpp
diff --git a/pugl/test/test_clipboard.c b/subprojects/nk_pugl/pugl/test/test_clipboard.c
index a458d00..a458d00 100644
--- a/pugl/test/test_clipboard.c
+++ b/subprojects/nk_pugl/pugl/test/test_clipboard.c
diff --git a/pugl/test/test_gl_hints.c b/subprojects/nk_pugl/pugl/test/test_gl_hints.c
index 3be3651..3be3651 100644
--- a/pugl/test/test_gl_hints.c
+++ b/subprojects/nk_pugl/pugl/test/test_gl_hints.c
diff --git a/pugl/test/test_realize.c b/subprojects/nk_pugl/pugl/test/test_realize.c
index 3c4e09a..3c4e09a 100644
--- a/pugl/test/test_realize.c
+++ b/subprojects/nk_pugl/pugl/test/test_realize.c
diff --git a/pugl/test/test_redisplay.c b/subprojects/nk_pugl/pugl/test/test_redisplay.c
index c5b9887..c5b9887 100644
--- a/pugl/test/test_redisplay.c
+++ b/subprojects/nk_pugl/pugl/test/test_redisplay.c
diff --git a/pugl/test/test_show_hide.c b/subprojects/nk_pugl/pugl/test/test_show_hide.c
index 584448c..584448c 100644
--- a/pugl/test/test_show_hide.c
+++ b/subprojects/nk_pugl/pugl/test/test_show_hide.c
diff --git a/pugl/test/test_stub_hints.c b/subprojects/nk_pugl/pugl/test/test_stub_hints.c
index 1cc1180..1cc1180 100644
--- a/pugl/test/test_stub_hints.c
+++ b/subprojects/nk_pugl/pugl/test/test_stub_hints.c
diff --git a/pugl/test/test_timer.c b/subprojects/nk_pugl/pugl/test/test_timer.c
index 18518ec..18518ec 100644
--- a/pugl/test/test_timer.c
+++ b/subprojects/nk_pugl/pugl/test/test_timer.c
diff --git a/pugl/test/test_update.c b/subprojects/nk_pugl/pugl/test/test_update.c
index 89de0b6..89de0b6 100644
--- a/pugl/test/test_update.c
+++ b/subprojects/nk_pugl/pugl/test/test_update.c
diff --git a/pugl/test/test_utils.h b/subprojects/nk_pugl/pugl/test/test_utils.h
index 2464737..2464737 100644
--- a/pugl/test/test_utils.h
+++ b/subprojects/nk_pugl/pugl/test/test_utils.h
diff --git a/pugl/waf b/subprojects/nk_pugl/pugl/waf
index 58d14c3..58d14c3 100755
--- a/pugl/waf
+++ b/subprojects/nk_pugl/pugl/waf
diff --git a/pugl/wscript b/subprojects/nk_pugl/pugl/wscript
index 1dc95c1..1dc95c1 100644
--- a/pugl/wscript
+++ b/subprojects/nk_pugl/pugl/wscript