aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--.gitlab-ci.yml70
-rw-r--r--API.md119
-rw-r--r--COPYING214
-rw-r--r--README.md438
-rw-r--r--VERSION1
-rw-r--r--app/meson.build15
-rw-r--r--app/synthpod_app.c1494
-rw-r--r--app/synthpod_app_mod.c1171
-rw-r--r--app/synthpod_app_port.c1179
-rw-r--r--app/synthpod_app_private.h771
-rw-r--r--app/synthpod_app_state.c1822
-rw-r--r--app/synthpod_app_ui.c1875
-rw-r--r--app/synthpod_app_worker.c550
-rw-r--r--ardour.lv2/lv2_extensions.h174
-rw-r--r--bin/GPLv3674
-rw-r--r--bin/meson.build139
-rw-r--r--bin/pcmi.cpp160
-rw-r--r--bin/pcmi.h78
-rw-r--r--bin/synthpod_alsa.1183
-rw-r--r--bin/synthpod_alsa.c1141
-rw-r--r--bin/synthpod_bin.c812
-rw-r--r--bin/synthpod_bin.h177
-rw-r--r--bin/synthpod_dummy.1133
-rw-r--r--bin/synthpod_dummy.c650
-rw-r--r--bin/synthpod_jack.1123
-rw-r--r--bin/synthpod_jack.c1250
-rw-r--r--bin/synthpod_sandbox.172
-rw-r--r--bin/synthpod_sandbox_gtk.c183
-rw-r--r--bin/synthpod_sandbox_kx.c144
-rw-r--r--bin/synthpod_sandbox_qt.cpp185
-rw-r--r--bin/synthpod_sandbox_show.c134
-rw-r--r--bin/synthpod_sandbox_x11.c269
-rw-r--r--bin/synthpod_ui30
-rw-r--r--bundle/manifest.ttl.in94
-rw-r--r--bundle/meson.build20
-rw-r--r--bundle/synthpod_bundle.c209
-rw-r--r--bundle/synthpod_bundle.h52
-rw-r--r--bundle/synthpod_bundle.ttl593
-rw-r--r--canvas.lv2/COPYING201
-rw-r--r--canvas.lv2/README.md18
-rw-r--r--canvas.lv2/canvas.lv2/canvas.h195
-rw-r--r--canvas.lv2/canvas.lv2/forge.h326
-rw-r--r--canvas.lv2/canvas.lv2/render.h87
-rw-r--r--canvas.lv2/canvas.lv2/render_cairo.h568
-rw-r--r--canvas.lv2/canvas.lv2/render_nanovg.h586
-rw-r--r--cross_clock/.gitignore1
-rw-r--r--cross_clock/COPYING201
-rw-r--r--cross_clock/README.md20
-rw-r--r--cross_clock/cross_clock/cross_clock.h159
-rw-r--r--cross_clock/test/Makefile14
-rw-r--r--cross_clock/test/cross_clock_test.c42
-rw-r--r--data/font/Abel-Regular.ttfbin0 -> 35220 bytes
-rw-r--r--data/pix/COPYING1
-rw-r--r--data/pix/atom.pngbin0 -> 790 bytes
-rw-r--r--data/pix/atom_inverted.pngbin0 -> 829 bytes
-rw-r--r--data/pix/audio.pngbin0 -> 544 bytes
-rw-r--r--data/pix/automaton.pngbin0 -> 2667 bytes
-rw-r--r--data/pix/border.pngbin0 -> 186 bytes
-rw-r--r--data/pix/control.pngbin0 -> 204 bytes
-rw-r--r--data/pix/cv.pngbin0 -> 600 bytes
-rw-r--r--data/pix/event.pngbin0 -> 615 bytes
-rw-r--r--data/pix/event_inverted.pngbin0 -> 278 bytes
-rw-r--r--data/pix/midi.pngbin0 -> 1011 bytes
-rw-r--r--data/pix/omk_logo_256x256.pngbin0 -> 6916 bytes
-rw-r--r--data/pix/osc.pngbin0 -> 382 bytes
-rw-r--r--data/pix/patch.pngbin0 -> 495 bytes
-rw-r--r--data/pix/sink.pngbin0 -> 1017 bytes
-rw-r--r--data/pix/source.pngbin0 -> 1017 bytes
-rw-r--r--data/pix/synthpod.pngbin0 -> 12927 bytes
-rw-r--r--data/pix/time.pngbin0 -> 755 bytes
-rw-r--r--data/pix/xpress.pngbin0 -> 565 bytes
-rw-r--r--data/png/COPYING6
-rw-r--r--data/png/bell.pngbin0 -> 3254 bytes
-rw-r--r--data/png/cancel-1.pngbin0 -> 3804 bytes
-rw-r--r--data/png/cancel.pngbin0 -> 2827 bytes
-rw-r--r--data/png/checked.pngbin0 -> 3186 bytes
-rw-r--r--data/png/download.pngbin0 -> 2699 bytes
-rw-r--r--data/png/envelope.pngbin0 -> 2847 bytes
-rw-r--r--data/png/house.pngbin0 -> 2914 bytes
-rw-r--r--data/png/layers.pngbin0 -> 3788 bytes
-rw-r--r--data/png/menu.pngbin0 -> 2478 bytes
-rw-r--r--data/png/next.pngbin0 -> 2548 bytes
-rw-r--r--data/png/pencil.pngbin0 -> 2663 bytes
-rw-r--r--data/png/plus.pngbin0 -> 2421 bytes
-rw-r--r--data/png/question.pngbin0 -> 3339 bytes
-rw-r--r--data/png/reload.pngbin0 -> 3653 bytes
-rw-r--r--data/png/screen.pngbin0 -> 2506 bytes
-rw-r--r--data/png/settings.pngbin0 -> 3570 bytes
-rw-r--r--data/png/sort.pngbin0 -> 2648 bytes
-rw-r--r--data/png/upload.pngbin0 -> 2716 bytes
-rw-r--r--data/png/user.pngbin0 -> 3636 bytes
-rw-r--r--ext_ui.lv2/lv2_external_ui.h109
-rw-r--r--include/synthpod_app.h201
-rw-r--r--include/synthpod_common.h107
-rw-r--r--include/synthpod_patcher.h481
-rw-r--r--include/synthpod_private.h2036
-rw-r--r--include/synthpod_ui.h132
-rw-r--r--jackey/LICENSE21
-rw-r--r--jackey/README.md18
-rw-r--r--jackey/jackey.h72
-rw-r--r--lfrtm/.gitlab-ci.yml63
-rw-r--r--lfrtm/COPYING201
-rw-r--r--lfrtm/README.md40
-rw-r--r--lfrtm/VERSION1
-rw-r--r--lfrtm/lfrtm/lfrtm.h227
-rw-r--r--lfrtm/meson.build59
-rw-r--r--lfrtm/test/dummy_calloc.c23
-rw-r--r--lfrtm/test/lfrtm_test.c118
-rw-r--r--mapper.lv2/.gitlab-ci.yml63
-rw-r--r--mapper.lv2/COPYING201
-rw-r--r--mapper.lv2/README.md52
-rw-r--r--mapper.lv2/VERSION1
-rw-r--r--mapper.lv2/mapper.lv2/mapper.h354
-rw-r--r--mapper.lv2/mapper.lv2/mum.h405
-rw-r--r--mapper.lv2/meson.build50
-rw-r--r--mapper.lv2/test/mapper_test.c397
-rw-r--r--mapper.lv2/test/random.c142
-rw-r--r--meson.build160
-rw-r--r--meson_options.txt9
-rw-r--r--netatom.lv2/.gitlab-ci.yml62
-rw-r--r--netatom.lv2/COPYING201
-rw-r--r--netatom.lv2/README.md33
-rw-r--r--netatom.lv2/VERSION1
-rw-r--r--netatom.lv2/meson.build38
-rw-r--r--netatom.lv2/netatom.lv2/endian.h120
-rw-r--r--netatom.lv2/netatom.lv2/netatom.h535
-rw-r--r--netatom.lv2/test/netatom_test.c289
-rw-r--r--nk_pugl/COPYING201
-rw-r--r--nk_pugl/nk_pugl.h1371
-rw-r--r--nsmc/nsmc.h597
-rw-r--r--nuklear/.gitattributes3
-rw-r--r--nuklear/.gitignore8
-rw-r--r--nuklear/.gitmodules0
-rw-r--r--nuklear/.travis.yml16
-rw-r--r--nuklear/Readme.md165
-rw-r--r--nuklear/demo/allegro5/KeyboardHandleriOS.h10
-rw-r--r--nuklear/demo/allegro5/KeyboardHandleriOS.m120
-rw-r--r--nuklear/demo/allegro5/Makefile22
-rw-r--r--nuklear/demo/allegro5/Readme.md35
-rw-r--r--nuklear/demo/allegro5/main.c165
-rw-r--r--nuklear/demo/allegro5/nuklear_allegro5.h459
-rw-r--r--nuklear/demo/calculator.c64
-rw-r--r--nuklear/demo/d3d11/build.bat9
-rw-r--r--nuklear/demo/d3d11/main.c299
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11.h622
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11.hlsl36
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h179
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h350
-rw-r--r--nuklear/demo/d3d9/build.bat6
-rw-r--r--nuklear/demo/d3d9/main.c313
-rw-r--r--nuklear/demo/d3d9/nuklear_d3d9.h543
-rw-r--r--nuklear/demo/gdi/build.bat6
-rw-r--r--nuklear/demo/gdi/main.c184
-rw-r--r--nuklear/demo/gdi/nuklear_gdi.h842
-rw-r--r--nuklear/demo/gdip/build.bat5
-rw-r--r--nuklear/demo/gdip/main.c178
-rw-r--r--nuklear/demo/gdip/nuklear_gdip.h1175
-rw-r--r--nuklear/demo/glfw_opengl2/Makefile25
-rw-r--r--nuklear/demo/glfw_opengl2/main.c182
-rw-r--r--nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h381
-rw-r--r--nuklear/demo/glfw_opengl3/Makefile26
-rw-r--r--nuklear/demo/glfw_opengl3/main.c200
-rw-r--r--nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h492
-rw-r--r--nuklear/demo/node_editor.c343
-rw-r--r--nuklear/demo/overview.c1249
-rw-r--r--nuklear/demo/sdl_opengl2/Makefile25
-rw-r--r--nuklear/demo/sdl_opengl2/main.c198
-rw-r--r--nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h346
-rw-r--r--nuklear/demo/sdl_opengl3/Makefile25
-rw-r--r--nuklear/demo/sdl_opengl3/main.c208
-rw-r--r--nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h442
-rw-r--r--nuklear/demo/sdl_opengles2/Makefile25
-rw-r--r--nuklear/demo/sdl_opengles2/main.c191
-rw-r--r--nuklear/demo/sdl_opengles2/nuklear_sdl_gles2.h443
-rw-r--r--nuklear/demo/sfml_opengl2/Makefile33
-rw-r--r--nuklear/demo/sfml_opengl2/Readme.md9
-rw-r--r--nuklear/demo/sfml_opengl2/main.cpp181
-rw-r--r--nuklear/demo/sfml_opengl2/nuklear_sfml_gl2.h359
-rw-r--r--nuklear/demo/sfml_opengl3/Makefile37
-rw-r--r--nuklear/demo/sfml_opengl3/Readme.md11
-rw-r--r--nuklear/demo/sfml_opengl3/main.cpp189
-rw-r--r--nuklear/demo/sfml_opengl3/nuklear_sfml_gl3.h463
-rw-r--r--nuklear/demo/style.c132
-rw-r--r--nuklear/demo/x11/Makefile13
-rw-r--r--nuklear/demo/x11/main.c224
-rw-r--r--nuklear/demo/x11/nuklear_xlib.h957
-rw-r--r--nuklear/demo/x11_opengl2/Makefile26
-rw-r--r--nuklear/demo/x11_opengl2/main.c345
-rw-r--r--nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h376
-rw-r--r--nuklear/demo/x11_opengl3/Makefile26
-rw-r--r--nuklear/demo/x11_opengl3/main.c342
-rw-r--r--nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h743
-rw-r--r--nuklear/demo/x11_rawfb/Makefile13
-rw-r--r--nuklear/demo/x11_rawfb/main.c263
-rw-r--r--nuklear/demo/x11_rawfb/nuklear_rawfb.h986
-rw-r--r--nuklear/demo/x11_rawfb/nuklear_xlib.h283
-rw-r--r--nuklear/doc/Makefile24
-rwxr-xr-xnuklear/doc/build.sh4
-rw-r--r--nuklear/doc/nuklear.html2533
-rw-r--r--nuklear/doc/stddoc.c141
-rw-r--r--nuklear/example/Makefile42
-rw-r--r--nuklear/example/canvas.c489
-rw-r--r--nuklear/example/extended.c906
-rw-r--r--nuklear/example/file_browser.c910
-rw-r--r--nuklear/example/icon/checked.pngbin0 -> 1813 bytes
-rw-r--r--nuklear/example/icon/cloud.pngbin0 -> 7509 bytes
-rw-r--r--nuklear/example/icon/computer.pngbin0 -> 620 bytes
-rw-r--r--nuklear/example/icon/copy.pngbin0 -> 655 bytes
-rw-r--r--nuklear/example/icon/default.pngbin0 -> 460 bytes
-rw-r--r--nuklear/example/icon/delete.pngbin0 -> 11040 bytes
-rw-r--r--nuklear/example/icon/desktop.pngbin0 -> 583 bytes
-rw-r--r--nuklear/example/icon/directory.pngbin0 -> 533 bytes
-rw-r--r--nuklear/example/icon/edit.pngbin0 -> 14998 bytes
-rw-r--r--nuklear/example/icon/export.pngbin0 -> 13336 bytes
-rw-r--r--nuklear/example/icon/font.pngbin0 -> 561 bytes
-rw-r--r--nuklear/example/icon/home.pngbin0 -> 819 bytes
-rw-r--r--nuklear/example/icon/img.pngbin0 -> 648 bytes
-rw-r--r--nuklear/example/icon/movie.pngbin0 -> 626 bytes
-rw-r--r--nuklear/example/icon/music.pngbin0 -> 610 bytes
-rw-r--r--nuklear/example/icon/next.pngbin0 -> 703 bytes
-rw-r--r--nuklear/example/icon/pause.pngbin0 -> 1338 bytes
-rw-r--r--nuklear/example/icon/pen.pngbin0 -> 5949 bytes
-rw-r--r--nuklear/example/icon/phone.pngbin0 -> 15778 bytes
-rw-r--r--nuklear/example/icon/plane.pngbin0 -> 13546 bytes
-rw-r--r--nuklear/example/icon/play.pngbin0 -> 566 bytes
-rw-r--r--nuklear/example/icon/prev.pngbin0 -> 701 bytes
-rw-r--r--nuklear/example/icon/rocket.pngbin0 -> 1121 bytes
-rw-r--r--nuklear/example/icon/settings.pngbin0 -> 15671 bytes
-rw-r--r--nuklear/example/icon/stop.pngbin0 -> 520 bytes
-rw-r--r--nuklear/example/icon/text.pngbin0 -> 601 bytes
-rw-r--r--nuklear/example/icon/tools.pngbin0 -> 24483 bytes
-rw-r--r--nuklear/example/icon/unchecked.pngbin0 -> 1044 bytes
-rw-r--r--nuklear/example/icon/volume.pngbin0 -> 25438 bytes
-rw-r--r--nuklear/example/icon/wifi.pngbin0 -> 18857 bytes
-rw-r--r--nuklear/example/images/image1.pngbin0 -> 42882 bytes
-rw-r--r--nuklear/example/images/image2.pngbin0 -> 5671 bytes
-rw-r--r--nuklear/example/images/image3.pngbin0 -> 131502 bytes
-rw-r--r--nuklear/example/images/image4.pngbin0 -> 185821 bytes
-rw-r--r--nuklear/example/images/image5.pngbin0 -> 98475 bytes
-rw-r--r--nuklear/example/images/image6.pngbin0 -> 35633 bytes
-rw-r--r--nuklear/example/images/image7.pngbin0 -> 13960 bytes
-rw-r--r--nuklear/example/images/image8.pngbin0 -> 45987 bytes
-rw-r--r--nuklear/example/images/image9.pngbin0 -> 30759 bytes
-rw-r--r--nuklear/example/skinning.c824
-rw-r--r--nuklear/example/skins/gwen.pngbin0 -> 24565 bytes
-rw-r--r--nuklear/example/stb_image.h6509
-rw-r--r--nuklear/extra_font/Cousine-Regular.ttfbin0 -> 43912 bytes
-rw-r--r--nuklear/extra_font/DroidSans.ttfbin0 -> 190044 bytes
-rw-r--r--nuklear/extra_font/Karla-Regular.ttfbin0 -> 16848 bytes
-rw-r--r--nuklear/extra_font/ProggyClean.ttfbin0 -> 41208 bytes
-rw-r--r--nuklear/extra_font/ProggyTiny.ttfbin0 -> 35656 bytes
-rw-r--r--nuklear/extra_font/Raleway-Bold.ttfbin0 -> 176280 bytes
-rw-r--r--nuklear/extra_font/Roboto-Bold.ttfbin0 -> 135820 bytes
-rw-r--r--nuklear/extra_font/Roboto-Light.ttfbin0 -> 140276 bytes
-rw-r--r--nuklear/extra_font/Roboto-Regular.ttfbin0 -> 145348 bytes
-rw-r--r--nuklear/extra_font/kenvector_future.ttfbin0 -> 34136 bytes
-rw-r--r--nuklear/extra_font/kenvector_future_thin.ttfbin0 -> 34100 bytes
-rw-r--r--nuklear/nuklear.h25596
-rw-r--r--nuklear/package.json8
-rw-r--r--osc.lv2/.gitlab-ci.yml62
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md33
-rw-r--r--osc.lv2/VERSION1
-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.h571
-rw-r--r--osc.lv2/osc.lv2/stream.h1379
-rw-r--r--osc.lv2/osc.lv2/util.h505
-rw-r--r--osc.lv2/osc.lv2/writer.h579
-rw-r--r--osc.lv2/osc.ttl42
-rw-r--r--osc.lv2/test/osc_test.c968
-rw-r--r--plugins/manifest.ttl.in111
-rw-r--r--plugins/meson.build105
-rw-r--r--plugins/synthpod.ttl1056
-rw-r--r--plugins/synthpod_common_nk.c9423
-rw-r--r--plugins/synthpod_control2cv.c135
-rw-r--r--plugins/synthpod_cv2control.c186
-rw-r--r--plugins/synthpod_heavyload.c110
-rw-r--r--plugins/synthpod_keyboard.c297
-rw-r--r--plugins/synthpod_keyboard_nk.c370
-rw-r--r--plugins/synthpod_lv2.c52
-rw-r--r--plugins/synthpod_lv2.h73
-rw-r--r--plugins/synthpod_lv2_nk.c46
-rw-r--r--plugins/synthpod_lv2_ui.c55
-rw-r--r--plugins/synthpod_midisplitter.c149
-rw-r--r--plugins/synthpod_panic.c278
-rw-r--r--plugins/synthpod_placeholder.c179
-rw-r--r--plugins/synthpod_stereo.c1007
-rw-r--r--plugins/synthpod_ui.ttl55
-rw-r--r--props.lv2/.gitlab-ci.yml72
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/VERSION1
-rw-r--r--props.lv2/meson.build59
-rw-r--r--props.lv2/props.h1012
-rw-r--r--props.lv2/test/chunk.binbin0 -> 16 bytes
-rw-r--r--props.lv2/test/manifest.ttl.in28
-rw-r--r--props.lv2/test/props.c323
-rw-r--r--props.lv2/test/props.ttl152
-rw-r--r--pugl/.gitignore3
-rw-r--r--pugl/AUTHORS (renamed from AUTHORS)0
-rw-r--r--pugl/COPYING13
-rw-r--r--pugl/Doxyfile.in (renamed from Doxyfile.in)0
-rw-r--r--pugl/README.md28
-rw-r--r--pugl/pugl.pc.in (renamed from pugl.pc.in)0
-rw-r--r--pugl/pugl/cairo_gl.h (renamed from pugl/cairo_gl.h)0
-rw-r--r--pugl/pugl/gl.h (renamed from pugl/gl.h)0
-rw-r--r--pugl/pugl/glew.h (renamed from pugl/glew.h)0
-rw-r--r--pugl/pugl/glu.h (renamed from pugl/glu.h)0
-rw-r--r--pugl/pugl/pugl.h (renamed from pugl/pugl.h)0
-rw-r--r--pugl/pugl/pugl.hpp (renamed from pugl/pugl.hpp)0
-rw-r--r--pugl/pugl/pugl_internal.h (renamed from pugl/pugl_internal.h)0
-rw-r--r--pugl/pugl/pugl_osx.m (renamed from pugl/pugl_osx.m)0
-rw-r--r--pugl/pugl/pugl_win.cpp (renamed from pugl/pugl_win.cpp)0
-rw-r--r--pugl/pugl/pugl_x11.c (renamed from pugl/pugl_x11.c)0
-rw-r--r--pugl/pugl_cairo_test.c (renamed from pugl_cairo_test.c)0
-rw-r--r--pugl/pugl_test.c (renamed from pugl_test.c)0
-rwxr-xr-xpugl/waf (renamed from waf)bin97489 -> 97489 bytes
-rw-r--r--pugl/wscript (renamed from wscript)0
-rw-r--r--sandbox_ui.lv2/COPYING201
-rw-r--r--sandbox_ui.lv2/README.md18
-rw-r--r--sandbox_ui.lv2/lv2_external_ui.h109
-rw-r--r--sandbox_ui.lv2/meson.build9
-rw-r--r--sandbox_ui.lv2/sandbox_io.h541
-rw-r--r--sandbox_ui.lv2/sandbox_master.c108
-rw-r--r--sandbox_ui.lv2/sandbox_master.h71
-rw-r--r--sandbox_ui.lv2/sandbox_slave.c805
-rw-r--r--sandbox_ui.lv2/sandbox_slave.h77
-rw-r--r--sandbox_ui.lv2/sandbox_ui.h47
-rw-r--r--screenshots/screenshot_1.pngbin0 -> 195689 bytes
-rw-r--r--stoat.whitelist28
-rw-r--r--varchunk/.gitlab-ci.yml63
-rw-r--r--varchunk/COPYING201
-rw-r--r--varchunk/README.md112
-rw-r--r--varchunk/VERSION1
-rw-r--r--varchunk/meson.build30
-rw-r--r--varchunk/test_varchunk.c254
-rw-r--r--varchunk/varchunk.h384
-rw-r--r--xpress.lv2/.gitlab-ci.yml69
-rw-r--r--xpress.lv2/COPYING201
-rw-r--r--xpress.lv2/README.md20
-rw-r--r--xpress.lv2/VERSION1
-rw-r--r--xpress.lv2/meson.build81
-rw-r--r--xpress.lv2/test/manifest.ttl.in28
-rw-r--r--xpress.lv2/test/xpress.c285
-rw-r--r--xpress.lv2/test/xpress.ttl82
-rw-r--r--xpress.lv2/test/xpress_test.c206
-rw-r--r--xpress.lv2/xpress.lv2/xpress.h839
353 files changed, 109292 insertions, 35 deletions
diff --git a/.gitignore b/.gitignore
index e5116427..57006939 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
-.waf*/
-build/
-.lock-waf*
+tags
+*.taghl
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..312d05c4
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,70 @@
+stages:
+ - build
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "synthpod"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - meson --prefix="/" --libdir="lib" --cross-file "${CI_BUILD_NAME}" -Duse-qt4=false -Duse-qt5=false -Duse-gtk2=false -Duse-gtk3=false build
+ - ninja -C build
+ - DESTDIR="${CI_PROJECT_DIR}/${BASE_NAME}-$(cat VERSION)/${CI_BUILD_NAME}" ninja -C build install
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *build_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *build_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ before_script:
+ - apt-get install -y libjack-dev
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+#x86_64-w64-mingw32:
+# <<: *universal_w64_definition
+#
+#i686-w64-mingw32:
+# <<: *universal_w64_definition
+#
+#universal-apple-darwin:
+# <<: *universal_apple_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/API.md b/API.md
new file mode 100644
index 00000000..472312bb
--- /dev/null
+++ b/API.md
@@ -0,0 +1,119 @@
+# Get list of plugins (ui -> dsp)
+ a patch:Get ;
+ patch:subject spod:stereo ;
+ patch:property spod:pluginList .
+
+# Set list of plugins (dsp -> ui)
+ a patch:Set ;
+ patch:subject spod:stereo ;
+ patch:property spod:pluginList ;
+ patch:value [
+ a atom:Tuple ;
+ rdf:value (
+ <URI>
+ <URI>
+ <URI>
+ <URI>
+ <URI>
+ )
+ ] .
+
+
+# Get plugin properties (ui -> dsp)
+ a patch:Get ;
+ patch:subject <URI> .
+
+# Set plugin properties (dsp -> ui)
+ a patch:Put ;
+ patch:subject <URI> ;
+ patch:body [
+ lv2:name "NAME" ;
+ rdfs:comment "COMMENT" ;
+ ] .
+
+
+# Get list of modules (ui -> dsp)
+ a patch:Get ;
+ patch:subject spod:stereo ;
+ patch:property spod:moduleList .
+
+# Set list of modules (dsp -> ui)
+ a patch:Set ;
+ patch:subject spod:stereo ;
+ patch:property spod:moduleList ;
+ patch:value [
+ a atom:Tuple ;
+ rdf:value (
+ spod:module#1
+ spod:module#3
+ spod:module#2
+ )
+ ] .
+
+
+# Add module (ui -> dsp)
+ a patch:Insert ;
+ patch:subject spod:stereo ;
+ patch:body [
+ lv2:Plugin <URI> ;
+ ] .
+
+
+# Get all module properties (ui -> dsp)
+ a patch:Get ;
+ patch:subject spod:module#1 .
+
+# Set all module properties (dsp ->ui)
+ a patch:Put ;
+ patch:subject spod:module#1
+ patch:body [
+ lv2:Plugin <URI> ;
+ spod:enabled true ;
+ spod:visible true ;
+ lv2:Port [
+ a atom:Tuple ;
+ rdf:value (
+ spod:module1#symbol
+ spod:module1#symbol
+ )
+ ] ;
+ ] .
+
+
+# Get individual module properties (ui -> dsp)
+ a patch:Get ;
+ patch:subject spod:module#1 ;
+ patch:property rdfs:label .
+
+# Set individual module properties (dsp -> ui)
+ a patch:Set ;
+ patch:subject spod:module#1 ;
+ patch:property rdfs:label ;
+ patch:value "LABEL" .
+
+
+# Get port properties (ui -> dsp)
+ a patch:Get ;
+ patch:subject spod:module#1#symbol .
+
+# Set port properties (dsp -> ui)
+ a patch:Put ;
+ patch:subject spod:module#1#symbol ;
+ patch:body [
+ rdf.value 0.5 ;
+ spod:enabled true ;
+ spod:visible true ;
+ spod:sources [
+ a atom:Tuple ;
+ rdf:value (
+ spod:module#3#symbol ;
+ spod:module#3#symbol ;
+ )
+ ]
+ ] .
+
+# Set port value (ui <-> ui)
+ a patch:Set ;
+ patch:subject spod:module#1#symbol ;
+ patch:property rdf.value ;
+ patch:value 0.2 .
diff --git a/COPYING b/COPYING
index e1e203dd..ddb9a463 100644
--- a/COPYING
+++ b/COPYING
@@ -1,13 +1,201 @@
-Copyright 2011-2014 David Robillard <http://drobilla.net>
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 77809d80..8816163b 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,428 @@
-PUGL
-====
+## Synthpod
-Pugl is a minimal portable API for GUIs which supports embedding and is
-suitable for use in plugins. It works on X11, Mac OS X, and Windows. GUIs can
-be drawn with OpenGL or Cairo.
+### Lightweight Nonlinear LV2 Plugin Container
-Pugl is vaguely similar to GLUT, but with some significant distinctions:
+#### Build status
- * Minimal in scope, providing only what is necessary to draw and receive
- keyboard and mouse input.
+[![build status](https://gitlab.com/OpenMusicKontrollers/synthpod/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/synthpod/commits/master)
- * No reliance on static data whatsoever, so the API can be used in plugins or
- multiple independent parts of a program.
+### Binaries
- * Single implementation, which is small, liberally licensed Free / Open Source
- Software, and suitable for direct inclusion in programs if avoiding a
- library dependency is desired.
+For GNU/Linux (64-bit, 32-bit, armv7), Windows (64-bit, 32-bit) and MacOS
+(64/32-bit univeral).
- * Support for embedding in other windows, so Pugl code can draw to a widget
- inside a larger GUI.
+To install the plugin bundle on your system, simply copy the __synthpod__
+folder out of the platform folder of the downloaded package into your
+[LV2 path](http://lv2plug.in/pages/filesystem-hierarchy-standard.html).
- * More complete support for keyboard input, including additional "special"
- keys, modifiers, and support for detecting individual modifier key presses.
+<!--
+#### Stable release
-For more information, see <http://drobilla.net/software/pugl>.
+* [synthpod-0.16.0.zip](https://dl.open-music-kontrollers.ch/synthpod/stable/synthpod-0.16.0.zip) ([sig](https://dl.open-music-kontrollers.ch/synthpod/stable/synthpod-0.16.0.zip.sig))
+-->
- -- David Robillard <d@drobilla.net>
+#### Unstable (nightly) release
+
+* [synthpod-latest-unstable.zip](https://dl.open-music-kontrollers.ch/synthpod/unstable/synthpod-latest-unstable.zip) ([sig](https://dl.open-music-kontrollers.ch/synthpod/unstable/synthpod-latest-unstable.zip.sig))
+
+### Sources
+
+<!--
+#### Stable release
+
+* [synthpod-0.16.0.tar.xz](https://git.open-music-kontrollers.ch/lv2/synthpod/snapshot/synthpod-0.16.0.tar.xz)
+-->
+
+#### Git repository
+
+* <https://git.open-music-kontrollers.ch/lv2/synthpod>
+
+### Packages
+
+* [ArchLinux](https://aur.archlinux.org/packages/synthpod-git/)
+
+### Bugs and feature requests
+
+* [Gitlab](https://gitlab.com/OpenMusicKontrollers/synthpod)
+* [Github](https://github.com/OpenMusicKontrollers/synthpod)
+
+### About
+
+Synthpod is an LV2 host. It can be run as a standalone app
+and be used as a tool for live performances or general audio and event filtering.
+
+It was conceptualized to fill the gap between pure textual
+(e.g. [SuperCollider](http://supercollider.github.io)) and
+pure visual flow (e.g. [Pure Data](http://puredata.info))
+audio programming paradigms.
+
+Potential fields of application may include:
+
+* Live audio synthesis
+* Real-time event scripting
+* Non-linear signal routing
+* Advanced control automation
+* Advanced event filtering
+* Live mixing
+* Live coding
+* Algorithmic composition
+* Interfacing to expressive controllers
+
+The standalone host saves its state in the same format as an LV2 plugin instance,
+
+It may be run on top of an audio system (JACK or ALSA) and
+on top of an event system (MIDI and OSC). It can be run with a GUI or
+headless. You can e.g. prepare a patch on your desktop machine and then
+transfer it to a wearable synth.
+
+Synthpod takes a totally modular approach whereby it provides only the
+minimal necessary host infrastructure expected by a given plugin.
+
+All additional, non strictly necessary glue shall be implemented with
+plugins. Synthpod e.g. can be extended with [OSC](http://opensoundcontrol.org)
+via [Eteroj](/lv2/eteroj/#). Sequencing and looping may be added via
+plugins from the [Orbit](/lv2/orbit/#) bundle.
+When paired with realtime scripting via [Moony](/lv2/moony/#),
+it turns Synthpod into a versatile realtime programmable, remote controllable
+LV2 host framework.
+
+![Synthpod screenshot](https://git.open-music-kontrollers.ch/lv2/synthpod/plain/screenshots/screenshot_1.png)
+
+### LV2 specifications support status
+
+As Synthpod tries to be a lightweight LV2 host, it may not (fully) support
+the more exotic extensions.
+Get an up-to-date overview of current extensions support for Synthpod
+in the table below.
+
+The full LV2 specification is located at <http://lv2plug.in/ns/>.
+
+<table>
+ <tr>
+ <th>Specification</th>
+ <th>API</th>
+ <th>Description</th>
+ <th>Support status</th>
+ <th>Notes</th>
+ </tr><tr>
+ <td>Atom</td>
+ <td>atom</td>
+ <td>A generic value container and several data types.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Buf Size</td>
+ <td>buf-size </td>
+ <td>Access to, and restrictions on, buffer sizes.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Data Access</td>
+ <td>data-access</td>
+ <td>Provides access to LV2_Descriptor::extension_data().</td>
+ <td style="color:#b00;">No</td>
+ <td>won't, ever</td>
+ </tr><tr>
+ <td>Dynamic Manifest</td>
+ <td>dynmanifest</td>
+ <td>Support for dynamic data generation.</td>
+ <td style="color:#b00;">No</td>
+ <td></td>
+ </tr><tr>
+ <td>Event</td>
+ <td>event</td>
+ <td>A port-based real-time generic event interface.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Instance Access</td>
+ <td>instance-access</td>
+ <td>Provides access to the LV2_Handle of a plugin.</td>
+ <td style="color:#b00;">No</td>
+ <td>won't, ever</td>
+ </tr><tr>
+ <td>Log</td>
+ <td>log</td>
+ <td>A feature for writing log messages.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>LV2</td>
+ <td>lv2core</td>
+ <td>An audio plugin interface specification.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>MIDI</td>
+ <td>midi</td>
+ <td>A normalised definition of raw MIDI.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Morph</td>
+ <td>morph</td>
+ <td>Ports that can dynamically change type.</td>
+ <td style="color:#b00;">No</td>
+ <td></td>
+ </tr><tr>
+ <td>Options</td>
+ <td>options</td>
+ <td>Instantiation time options.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Parameters</td>
+ <td>parameters</td>
+ <td>Common parameters for audio processing.</td>
+ <td style="color:#b00;"></td>
+ <td>data-only</td>
+ </tr><tr>
+ <td>Patch</td>
+ <td>patch</td>
+ <td>Messages for accessing and manipulating properties.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Port Groups</td>
+ <td>port-groups</td>
+ <td>Multi-channel groups of LV2 ports.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Port Properties</td>
+ <td>port-props</td>
+ <td>Various port properties.</td>
+ <td style="color:#b00;"></td>
+ <td>data-only</td>
+ </tr><tr>
+ <td>Presets</td>
+ <td>presets</td>
+ <td>Presets for LV2 plugins.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Resize Port</td>
+ <td>resize-port</td>
+ <td>Dynamically sized LV2 port buffers.</td>
+ <td style="color:#00b;">Partial</td>
+ <td>no dynamic resize</td>
+ </tr><tr>
+ <td>State</td>
+ <td>state</td>
+ <td>An interface for LV2 plugins to save and restore state.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Time</td>
+ <td>time</td>
+ <td>Properties for describing time.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>UI</td>
+ <td>ui</td>
+ <td>LV2 plugin UIs of any type.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td>X11UI, Gt2kUI, Gtk3UI, Qt4UI, Qt5UI, Show/Idle-Interface, external-ui</td>
+ </tr><tr>
+ <td>Units</td>
+ <td>units</td>
+ <td>Units for LV2 values.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>URI Map</td>
+ <td>uri-map</td>
+ <td>A feature for mapping URIs to integers.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>URID</td>
+ <td>urid</td>
+ <td>Features for mapping URIs to and from integers.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr><tr>
+ <td>Worker</td>
+ <td>worker</td>
+ <td>Support for a non-realtime plugin worker method.</td>
+ <td style="color:#0b0;">Yes</td>
+ <td></td>
+ </tr>
+</table>
+
+### Hosts
+
+Currently the following hosts are contained in this software bundle:
+
+* JACK
+* ALSA
+* DUMMY
+
+#### JACK
+
+Synthpod as host built on top of [JACK](http://jackaudio.org)
+with support for native JACK audio, MIDI, OSC and CV in/out ports.
+The right choice on GNU/Linux for modular setups.
+
+This standalone host supports
+[NON session management](http://non.tuxfamily.org/nsm/API.html) and
+[JACK session management](http://jackaudio.org/files/docs/html/group__SessionClientFunctions.html)
+to neatly integrate into modular setups.
+
+#### ALSA
+
+Synthpod as host built on top of [ALSA](http://alsa-project.org)
+with support for native ALSA audio and MIDI sequencer in/out ports.
+The right choice on GNU/Linux for live setups, embedded devices or
+when you don't need audio routing to other apps.
+
+This standalone host supports
+[NON session management](http://non.tuxfamily.org/nsm/API.html)
+to neatly integrate into modular setups.
+
+#### DUMMY
+
+Synthpod as host built on top of a dummy driver, mainly useful for
+debugging purposes.
+
+This standalone host supports
+[NON session management](http://non.tuxfamily.org/nsm/API.html)
+to neatly integrate into modular setups.
+
+### Plugins
+
+#### Control to CV
+
+Convert between Control Voltage and control ports.
+
+#### CV to Control
+
+Convert between Control Voltage and control ports.
+
+#### Heavyload
+
+Just burn CPU cycles away for debugging.
+
+#### Keyboard
+
+A rudimentary graphical keyboard with a 2 octave range, mainly meant for
+simple test cases.
+
+#### MIDI splitter
+
+Split MIDI events based on their channel.
+
+#### Panic
+
+Silence MIDI downstream plugins upon panic.
+
+#### Stereo
+
+The Synthpod LV2 non-linear plugin container run as a plugin itself in an
+other host or itself. It features stereo audio in/out ports, atom event
+in/out ports and 4 control in/out ports.
+
+Use this to add support for non-linear plugin routing in a strictly
+linear host.
+
+### Usage
+
+#### Server - client
+
+Synthpod comes as server - client combo, e.g. the server doing the DSP side of things runs in its own
+process and the client showing the GUI side of things runs in its own process.
+
+By default, synthpod just runs the server. There's a command line argument to automatically run the GUI,
+if you want. Please consult the manual page to find out more.
+
+#### GUI
+
+##### Mouse actions
+
+* Plugin actions:
+ * Mouse-over: show connections
+ * Right-click: toggle selection
+ * Left-click-down: start connecting
+ * Left-click-up: end connecting
+
+* Connection matrix actions:
+ * Mouse-over: show connections
+ * Right-click: toggle selection
+ * Left-click: toggle connection
+ * Mouse-wheel: toggle connection
+
+#### Key actions
+
+* a: (de)select all nodes
+* b: start drawing selection box
+* g: start moving selected nodes
+* v: toggle plugin GUIs of selected nodes
+* x: remove selected nodes
+* i: reinstantiate selected nodes
+
+### Mandatory dependencies
+
+* [LV2](http://lv2plug.in) (LV2 plugin specification)
+* [lilv](http://drobilla.net/software/lilv/) (LV2 plugin host library)
+* [sratom](http://drobilla.net/software/sratom/) (LV2 atom serialization library)
+
+### Optional dependencies for JACK backend
+
+* [JACK](http://jackaudio.org/) (JACK audio connection kit)
+
+### Optional dependencies for ALSA backend
+
+* [ALSA](http://alsa-project.org) (Advanced Linux Sound Architecture)
+* [zita-alsa-pcmi](http://kokkinizita.linuxaudio.org/linuxaudio/) (ALSA PCM high-level API)
+
+### Optional dependencies for plugin UIs
+
+* [libxcb](https://xcb.freedesktop.org/) (X protocol C-language Binding)
+* [Gtk2](http://www.gtk.org/) (cross-platform UI toolkit)
+* [Gtk3](http://www.gtk.org/) (cross-platform UI toolkit)
+* [Qt4](https://www.qt.io/) (cross-platform UI toolkit)
+* [Qt5](https://www.qt.io/) (cross-platform UI toolkit)
+
+### Build / install
+
+ git clone https://git.open-music-kontrollers.ch/lv2/synthpod
+ cd synthpod
+ meson build
+ cd build
+ ninja -j4
+ sudo ninja install
+
+### License (everything but synthpod\_alsa)
+
+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>.
+
+### License (synthpod\_alsa only)
+
+Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program 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
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/VERSION b/VERSION
new file mode 100644
index 00000000..c99ea083
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.5927
diff --git a/app/meson.build b/app/meson.build
new file mode 100644
index 00000000..f3692fd0
--- /dev/null
+++ b/app/meson.build
@@ -0,0 +1,15 @@
+srcs = ['synthpod_app.c',
+ 'synthpod_app_mod.c',
+ 'synthpod_app_port.c',
+ 'synthpod_app_state.c',
+ 'synthpod_app_ui.c',
+ 'synthpod_app_worker.c'
+]
+
+incs = [inc_incs, app_incs, xpress_incs, osc_incs, extui_incs, ardour_incs, varchunk_incs, crossclock_incs]
+deps = [m_dep, rt_dep, lv2_dep, thread_dep, lilv_dep]
+
+app = static_library('synthpod_app', srcs,
+ include_directories : incs,
+ c_args : c_args,
+ dependencies : deps)
diff --git a/app/synthpod_app.c b/app/synthpod_app.c
new file mode 100644
index 00000000..0779582c
--- /dev/null
+++ b/app/synthpod_app.c
@@ -0,0 +1,1494 @@
+/*
+ * 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.
+ */
+
+#include <synthpod_app_private.h>
+#include <synthpod_patcher.h>
+
+#include <osc.lv2/util.h>
+#include <osc.lv2/forge.h>
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+# include <pthread_np.h>
+typedef cpuset_t cpu_set_t;
+#endif
+
+// non-rt
+void
+sp_app_activate(sp_app_t *app)
+{
+ //TODO
+}
+
+static inline void
+_sp_app_update_system_sources(sp_app_t *app)
+{
+ int num_system_sources = 0;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(!mod->system_ports) // has system ports?
+ continue; // skip
+
+ for(unsigned p=0; p<mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(port->sys.type == SYSTEM_PORT_NONE)
+ continue; // skip
+
+ if(port->direction == PORT_DIRECTION_OUTPUT)
+ {
+ app->system_sources[num_system_sources].type = port->sys.type;
+ app->system_sources[num_system_sources].buf = PORT_BASE_ALIGNED(port);
+ app->system_sources[num_system_sources].sys_port = port->sys.data;
+ num_system_sources += 1;
+ }
+ }
+ }
+
+ // sentinel
+ app->system_sources[num_system_sources].type = SYSTEM_PORT_NONE;
+ app->system_sources[num_system_sources].buf = NULL;
+ app->system_sources[num_system_sources].sys_port = NULL;
+}
+
+static inline void
+_sp_app_update_system_sinks(sp_app_t *app)
+{
+ int num_system_sinks = 0;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(!mod->system_ports) // has system ports?
+ continue;
+
+ for(unsigned p=0; p<mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(port->sys.type == SYSTEM_PORT_NONE)
+ continue; // skip
+
+ if(port->direction == PORT_DIRECTION_INPUT)
+ {
+ app->system_sinks[num_system_sinks].type = port->sys.type;
+ app->system_sinks[num_system_sinks].buf = PORT_BASE_ALIGNED(port);
+ app->system_sinks[num_system_sinks].sys_port = port->sys.data;
+ num_system_sinks += 1;
+ }
+ }
+ }
+
+ // sentinel
+ app->system_sinks[num_system_sinks].type = SYSTEM_PORT_NONE;
+ app->system_sinks[num_system_sinks].buf = NULL;
+ app->system_sinks[num_system_sinks].sys_port = NULL;
+}
+
+const sp_app_system_source_t *
+sp_app_get_system_sources(sp_app_t *app)
+{
+ _sp_app_update_system_sources(app);
+
+ return app->system_sources;
+}
+
+const sp_app_system_sink_t *
+sp_app_get_system_sinks(sp_app_t *app)
+{
+ _sp_app_update_system_sinks(app);
+
+ return app->system_sinks;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+__non_realtime static uint32_t
+_uri_to_id(LV2_URI_Map_Callback_Data handle, const char *_, const char *uri)
+{
+ sp_app_t *app = handle;
+
+ LV2_URID_Map *map = app->driver->map;
+
+ return map->map(map->handle, uri);
+}
+#pragma GCC diagnostic pop
+
+__realtime static inline bool
+_sp_app_has_source_automations(mod_t *mod)
+{
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if( (automation->type != AUTO_TYPE_NONE)
+ && automation->src_enabled )
+ {
+ return true; // has automations
+ }
+ }
+
+ return false; // has no automations
+}
+
+__realtime static inline auto_t *
+_sp_app_find_automation_for_port(mod_t *mod, uint32_t index)
+{
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if(automation->type == AUTO_TYPE_NONE)
+ continue; // skip empty slot
+
+ if( (automation->property == 0) && (automation->index == index) )
+ return automation; // found match
+ }
+
+ return NULL;
+}
+
+__realtime static inline auto_t *
+_sp_app_find_automation_for_property(mod_t *mod, LV2_URID property)
+{
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if(automation->type == AUTO_TYPE_NONE)
+ continue; // skip empty slot
+
+ if(automation->property == property)
+ return automation; // found match
+ }
+
+ return NULL;
+}
+
+__realtime static inline LV2_Atom_Forge_Ref
+_sp_app_automation_out(sp_app_t *app, LV2_Atom_Forge *forge, auto_t *automation, uint32_t frames, double value)
+{
+ LV2_Atom_Forge_Ref ref = 0;
+
+ if(automation->type == AUTO_TYPE_MIDI)
+ {
+ midi_auto_t *mauto = &automation->midi;
+
+ const uint8_t channel = (mauto->channel >= 0)
+ ? mauto->channel
+ : 0;
+ const uint8_t controller = (mauto->controller >= 0)
+ ? mauto->controller
+ : 0;
+ const uint8_t msg [3] = {0xb0 | channel, controller, floor(value)};
+
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, 3, app->regs.port.midi.urid);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, msg, 3);
+ }
+ else if(automation->type == AUTO_TYPE_OSC)
+ {
+ osc_auto_t *oauto = &automation->osc;
+
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_osc_forge_message_vararg(forge, &app->osc_urid, oauto->path, "d", value); //FIXME what type should be used?
+ }
+
+ return ref;
+}
+
+__realtime static inline void
+_sp_app_process_single_run(mod_t *mod, uint32_t nsamples)
+{
+ sp_app_t *app = mod->app;
+
+ struct timespec mod_t1;
+ struct timespec mod_t2;
+ cross_clock_gettime(&app->clk_mono, &mod_t1);
+
+ // multiplex multiple sources to single sink where needed
+ for(int p=mod->num_ports-1; p>=0; p--)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(port->direction == PORT_DIRECTION_OUTPUT)
+ {
+ if( (port->type == PORT_TYPE_ATOM)
+ && (port->atom.buffer_type == PORT_BUFFER_TYPE_SEQUENCE)
+ && (!mod->system_ports) ) // don't overwrite source buffer events
+ {
+ LV2_Atom_Sequence *seq = PORT_BASE_ALIGNED(port);
+ seq->atom.size = port->size;
+ seq->atom.type = app->forge.Sequence;
+ seq->body.unit = 0;
+ seq->body.pad = 0;
+ }
+ }
+ else // PORT_DIRECTION_INPUT
+ {
+ if(port->driver->multiplex)
+ port->driver->multiplex(app, port, nsamples);
+ }
+ }
+
+ mod_worker_t *mod_worker = &mod->mod_worker;
+ if(mod_worker->app_from_worker)
+ {
+ const void *payload;
+ size_t size;
+ while((payload = varchunk_read_request(mod_worker->app_from_worker, &size)))
+ {
+ if(mod->worker.iface && mod->worker.iface->work_response)
+ {
+ mod->worker.iface->work_response(mod->handle, size, payload);
+ //TODO check return status
+ }
+
+ varchunk_read_advance(mod_worker->app_from_worker);
+ }
+
+ // handle end of work
+ if(mod->worker.iface && mod->worker.iface->end_run)
+ {
+ mod->worker.iface->end_run(mod->handle);
+ }
+ }
+
+ // is module currently loading a preset asynchronously?
+ if(!mod->bypassed)
+ {
+ // run plugin
+ if(!mod->disabled)
+ {
+ lilv_instance_run(mod->inst, nsamples);
+ }
+ }
+
+ //handle automation output
+ {
+ const unsigned ao = mod->num_ports - 1;
+ port_t *auto_port = &mod->ports[ao];
+ LV2_Atom_Sequence *seq = PORT_BASE_ALIGNED(auto_port);
+ //const uint32_t capacity = seq->atom.size;
+ const uint32_t capacity = PORT_SIZE(auto_port);
+ LV2_Atom_Forge_Frame frame;
+
+ LV2_Atom_Forge forge = app->forge; //FIXME do this only once
+ lv2_atom_forge_set_buffer(&forge, (uint8_t *)seq, capacity);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&forge, &frame, 0);
+
+ if(_sp_app_has_source_automations(mod))
+ {
+ uint32_t t0 = 0;
+
+ for(unsigned p=0; p<mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(port->type == PORT_TYPE_CONTROL)
+ {
+ const float *val = PORT_BASE_ALIGNED(port);
+
+ if( (*val != port->control.last)
+ || (port->control.auto_dirty) ) // has changed since last cycle
+ {
+ auto_t *automation = _sp_app_find_automation_for_port(mod, p);
+
+ if(automation && automation->src_enabled)
+ {
+ const double value = (*val - automation->add) / automation->mul;
+
+ if(ref)
+ ref = _sp_app_automation_out(app, &forge, automation, t0, value);
+ }
+
+ port->control.auto_dirty = false;
+ }
+ }
+ else if( (port->type == PORT_TYPE_ATOM)
+ && port->atom.patchable )
+ {
+ const LV2_Atom_Sequence *patch_seq = PORT_BASE_ALIGNED(port);
+
+ LV2_ATOM_SEQUENCE_FOREACH(patch_seq, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if( lv2_atom_forge_is_object_type(&forge, obj->atom.type)
+ && (obj->body.otype == app->regs.patch.set.urid) ) //FIXME also consider patch:Put
+ {
+ const LV2_Atom_URID *patch_property = NULL;
+ const LV2_Atom *patch_value = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.patch.property.urid, &patch_property,
+ app->regs.patch.value.urid, &patch_value,
+ 0);
+
+ if(!patch_property || (patch_property->atom.type != forge.URID) || !patch_value)
+ continue;
+
+ auto_t *automation = _sp_app_find_automation_for_property(mod, patch_property->body);
+ if(automation && automation->src_enabled && (patch_value->type == automation->range))
+ {
+ double val = 0.0;
+
+ if(patch_value->type == forge.Bool)
+ val = ((const LV2_Atom_Bool *)patch_value)->body;
+ else if(patch_value->type == forge.Int)
+ val = ((const LV2_Atom_Int *)patch_value)->body;
+ else if(patch_value->type == forge.Long)
+ val = ((const LV2_Atom_Long *)patch_value)->body;
+ else if(patch_value->type == forge.Float)
+ val = ((const LV2_Atom_Float *)patch_value)->body;
+ else if(patch_value->type == forge.Double)
+ val = ((const LV2_Atom_Double *)patch_value)->body;
+ //FIXME support more types
+
+ const double value = (val - automation->add) / automation->mul;
+
+ if(ref)
+ ref = _sp_app_automation_out(app, &forge, automation, ev->time.frames, value);
+
+ t0 = ev->time.frames;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(&forge, &frame);
+ else
+ {
+ lv2_atom_sequence_clear(seq);
+ sp_app_log_trace(app, "%s: automation out buffer overflow\n", __func__);
+ }
+ }
+
+ cross_clock_gettime(&app->clk_mono, &mod_t2);
+
+ // profiling
+ const unsigned run_time = (mod_t2.tv_sec - mod_t1.tv_sec)*1000000000
+ + mod_t2.tv_nsec - mod_t1.tv_nsec;
+ mod->prof.sum += run_time;
+
+ if(run_time < mod->prof.min)
+ mod->prof.min = run_time;
+ else if(run_time > mod->prof.max)
+ mod->prof.max = run_time;
+}
+
+__realtime static void
+_sync_midi_automation_to_ui(sp_app_t *app, mod_t *mod, auto_t *automation)
+{
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const LV2_URID subj = 0; //FIXME
+ const int32_t sn = 0; //FIXME
+ const LV2_URID prop = app->regs.synthpod.automation_list.urid;
+ port_t *port = &mod->ports[automation->index]; //FIXME handle prop
+
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_add_object(
+ &app->regs, &app->forge, &frame[0], subj, sn, prop);
+
+ if(ref)
+ ref = _sp_app_forge_midi_automation(app, &frame[2], mod, port, automation);
+
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+}
+
+__realtime static void
+_sync_osc_automation_to_ui(sp_app_t *app, mod_t *mod, auto_t *automation)
+{
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const LV2_URID subj = 0; //FIXME
+ const int32_t sn = 0; //FIXME
+ const LV2_URID prop = app->regs.synthpod.automation_list.urid;
+ port_t *port = &mod->ports[automation->index]; //FIXME handle prop
+
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_add_object(
+ &app->regs, &app->forge, &frame[0], subj, sn, prop);
+
+ if(ref)
+ ref = _sp_app_forge_osc_automation(app, &frame[2], mod, port, automation);
+
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+}
+
+__realtime static inline void
+_sp_app_process_single_post(mod_t *mod, uint32_t nsamples, bool sparse_update_timeout)
+{
+ sp_app_t *app = mod->app;
+
+ // handle mod ui post
+ for(unsigned i=0; i<mod->num_ports; i++)
+ {
+ port_t *port = &mod->ports[i];
+
+ // no notification/subscription and no support for patch:Message
+ const bool subscribed = port->subscriptions != 0;
+ if(!subscribed)
+ continue; // skip this port
+ if( (port->type == PORT_TYPE_ATOM) && !port->atom.patchable)
+ continue; // skip this port
+
+ if(port->driver->transfer && (port->driver->sparse_update ? sparse_update_timeout : true))
+ port->driver->transfer(app, port, nsamples);
+ }
+
+ // handle inline display
+ if(mod->idisp.iface)
+ {
+ mod->idisp.counter += nsamples;
+
+ // trylock
+ if(!atomic_flag_test_and_set(&mod->idisp.lock))
+ {
+ const LV2_Inline_Display_Image_Surface *surf= mod->idisp.surf;
+ if(surf)
+ {
+ // to nk
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [3];
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(&app->regs, &app->forge, &frame[0],
+ mod->urn, 0, app->regs.idisp.surface.urid); //TODO seqn
+ if(ref)
+ ref = lv2_atom_forge_tuple(&app->forge, &frame[1]);
+ if(ref)
+ ref = lv2_atom_forge_int(&app->forge, surf->width);
+ if(ref)
+ ref = lv2_atom_forge_int(&app->forge, surf->height);
+ if(ref)
+ ref = lv2_atom_forge_vector_head(&app->forge, &frame[2], sizeof(int32_t), app->forge.Int);
+ if(surf->stride == surf->width * sizeof(uint32_t))
+ {
+ if(ref)
+ ref = lv2_atom_forge_write(&app->forge, surf->data, surf->height * surf->stride);
+ }
+ else
+ {
+ for(int h = 0; h < surf->height; h++)
+ {
+ const uint8_t *row = &surf->data[surf->stride * h];
+
+ if(ref)
+ ref = lv2_atom_forge_raw(&app->forge, row, surf->width * sizeof(uint32_t));
+ }
+ if(ref)
+ lv2_atom_forge_pad(&app->forge, surf->height * surf->width * sizeof(uint32_t));
+ }
+
+ if(ref)
+ synthpod_patcher_pop(&app->forge, frame, 3);
+
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ mod->idisp.surf = NULL; // invalidate
+ }
+
+ // unlock
+ atomic_flag_clear(&mod->idisp.lock);
+ }
+ }
+
+ // handle automation learn
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if(automation->sync)
+ {
+ if(automation->type == AUTO_TYPE_MIDI)
+ {
+ _sync_midi_automation_to_ui(app, mod, automation);
+ }
+ else if(automation->type == AUTO_TYPE_OSC)
+ {
+ _sync_osc_automation_to_ui(app, mod, automation);
+ }
+
+ automation->sync = false;
+ }
+ }
+}
+
+__realtime static inline int
+_dsp_slave_fetch(dsp_master_t *dsp_master, int head)
+{
+ sp_app_t *app = (void *)dsp_master - offsetof(sp_app_t, dsp_master);
+
+ const unsigned M = head;
+ head = -1; // assume no more work left
+
+ for(unsigned m=M; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ int expected = 0;
+ const int desired = -1; // mark as done
+ const bool match = atomic_compare_exchange_weak(&dsp_client->ref_count,
+ &expected, desired);
+ if(match) // needs to run now
+ {
+ _sp_app_process_single_run(mod, dsp_master->nsamples);
+
+ for(unsigned j=0; j<dsp_client->num_sinks; j++)
+ {
+ dsp_client_t *sink = dsp_client->sinks[j];
+ const int32_t ref_count = atomic_fetch_sub(&sink->ref_count, 1);
+ assert(ref_count >= 0);
+ }
+ }
+ else if(expected >= 0) // needs to run later
+ {
+ if(head == -1) // only set heading position once per loop
+ {
+ head = m;
+ }
+ }
+ }
+
+ return head;
+}
+
+__realtime static inline void
+_dsp_slave_spin(sp_app_t *app, dsp_master_t *dsp_master, bool post)
+{
+ int head = 0;
+
+ while(!atomic_load(&dsp_master->emergency_exit))
+ {
+ head = _dsp_slave_fetch(dsp_master, head);
+ if(head == -1) // no more work left
+ {
+ break;
+ }
+ }
+
+ if(post)
+ {
+ sem_post(&dsp_master->sem);
+ }
+}
+
+__non_realtime static void *
+_dsp_slave_thread(void *data)
+{
+ dsp_slave_t *dsp_slave = data;
+ dsp_master_t *dsp_master = dsp_slave->dsp_master;
+ sp_app_t *app = (void *)dsp_master - offsetof(sp_app_t, dsp_master);
+ const int num = dsp_slave - dsp_master->dsp_slaves + 1;
+ //printf("thread: %i\n", num);
+
+ struct sched_param schedp;
+ memset(&schedp, 0, sizeof(struct sched_param));
+ schedp.sched_priority = app->driver->audio_prio - 1;
+
+ const pthread_t self = pthread_self();
+ if(pthread_setschedparam(self, SCHED_FIFO, &schedp))
+ sp_app_log_error(app, "%s: pthread_setschedparam error\n", __func__);
+
+ if(app->driver->cpu_affinity)
+ {
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(num, &cpuset);
+ if(pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset))
+ sp_app_log_error(app, "%s: pthread_setaffinity_np error\n", __func__);
+ }
+
+ while(true)
+ {
+ sem_wait(&dsp_slave->sem);
+
+ _dsp_slave_spin(app, dsp_master, true);
+
+ if(atomic_load(&dsp_master->kill))
+ break;
+
+ //sched_yield();
+ }
+
+ return NULL;
+}
+
+__realtime static inline void
+_dsp_master_post(dsp_master_t *dsp_master, unsigned num)
+{
+ for(unsigned i=0; i<num; i++)
+ {
+ dsp_slave_t *dsp_slave = &dsp_master->dsp_slaves[i];
+
+ sem_post(&dsp_slave->sem);
+ }
+}
+
+__realtime static inline void
+_dsp_master_wait(sp_app_t *app, dsp_master_t *dsp_master, unsigned num)
+{
+ // derive timeout
+ struct timespec to;
+ cross_clock_gettime(&app->clk_real, &to);
+ to.tv_sec += 1; // if workers have not finished in due 1s, do emergency exit!
+
+ // wait for worker threads to have finished
+ for(unsigned c = 0; c < num; )
+ {
+ if(sem_timedwait(&dsp_master->sem, &to) == -1)
+ {
+ switch(errno)
+ {
+ case ETIMEDOUT:
+ {
+ fprintf(stderr, "%s: taking emergency exit\n", __func__);
+ atomic_store(&dsp_master->emergency_exit, true);
+ } continue;
+ case EINTR:
+ {
+ // nothing
+ } continue;
+ }
+ }
+
+ c++;
+ }
+}
+
+__realtime static inline void
+_dsp_master_process(sp_app_t *app, dsp_master_t *dsp_master, unsigned nsamples)
+{
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ atomic_store(&dsp_client->ref_count, dsp_client->num_sources);
+ }
+
+ dsp_master->nsamples = nsamples;
+
+ unsigned num_slaves = dsp_master->concurrent - 1;
+ if(num_slaves > dsp_master->num_slaves)
+ num_slaves = dsp_master->num_slaves;
+
+ _dsp_master_post(dsp_master, num_slaves); // wake up other slaves
+ _dsp_slave_spin(app, dsp_master, false); // runs jobs itself
+ _dsp_master_wait(app, dsp_master, num_slaves);
+}
+
+void
+_sp_app_reset(sp_app_t *app)
+{
+ // remove existing modules
+ int num_mods = app->num_mods;
+
+ app->num_mods = 0;
+
+ for(int m=0; m<num_mods; m++)
+ _sp_app_mod_del(app, app->mods[m]);
+}
+
+void
+_sp_app_populate(sp_app_t *app)
+{
+ const char *uri_str;
+ mod_t *mod;
+
+ // inject source mod
+ uri_str = SYNTHPOD_PREFIX"source";
+ mod = _sp_app_mod_add(app, uri_str, 0);
+ if(mod)
+ {
+ app->mods[app->num_mods] = mod;
+ app->num_mods += 1;
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: failed to create system source\n", __func__);
+ }
+
+ // inject sink mod
+ uri_str = SYNTHPOD_PREFIX"sink";
+ mod = _sp_app_mod_add(app, uri_str, 0);
+ if(mod)
+ {
+ app->mods[app->num_mods] = mod;
+ app->num_mods += 1;
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: failed to create system sink\n", __func__);
+ }
+}
+
+sp_app_t *
+sp_app_new(const LilvWorld *world, sp_app_driver_t *driver, void *data)
+{
+ if(!driver || !data)
+ return NULL;
+
+ srand(time(NULL)); // seed random number generator for UUID generator
+
+ sp_app_t *app = calloc(1, sizeof(sp_app_t));
+ if(!app)
+ return NULL;
+
+ atomic_init(&app->dirty, false);
+
+ app->dir.home = getenv("HOME");
+
+ //printf("%s %s %s\n", app->dir.home, app->dir.config, app->dir.data);
+
+ app->driver = driver;
+ app->data = data;
+
+ if(world)
+ {
+ app->world = (LilvWorld *)world;
+ app->embedded = 1;
+ }
+ else
+ {
+ app->world = lilv_world_new();
+ if(!app->world)
+ {
+ free(app);
+ return NULL;
+ }
+ LilvNode *node_false = lilv_new_bool(app->world, false);
+ if(node_false)
+ {
+ lilv_world_set_option(app->world, LILV_OPTION_DYN_MANIFEST, node_false);
+ lilv_node_free(node_false);
+ }
+ lilv_world_load_all(app->world);
+ LilvNode *synthpod_bundle = lilv_new_file_uri(app->world, NULL, SYNTHPOD_BUNDLE_DIR);
+ if(synthpod_bundle)
+ {
+ lilv_world_load_bundle(app->world, synthpod_bundle);
+ lilv_node_free(synthpod_bundle);
+ }
+ }
+ app->plugs = lilv_world_get_all_plugins(app->world);
+
+ lv2_atom_forge_init(&app->forge, app->driver->map);
+ sp_regs_init(&app->regs, app->world, app->driver->map);
+
+ _sp_app_populate(app);
+
+ app->fps.bound = driver->sample_rate / driver->update_rate;
+ app->fps.counter = 0;
+
+ app->ramp_samples = driver->sample_rate / 10; // ramp over 0.1s FIXME make this configurable
+
+ // populate uri_to_id
+ app->uri_to_id.callback_data = app;
+ app->uri_to_id.uri_to_id = _uri_to_id;
+
+ app->sratom = sratom_new(app->driver->map);
+ if(app->sratom)
+ sratom_set_pretty_numbers(app->sratom, false);
+
+ // initialize DSP load profiler
+ cross_clock_init(&app->clk_mono, CROSS_CLOCK_MONOTONIC);
+ cross_clock_init(&app->clk_real, CROSS_CLOCK_REALTIME);
+ cross_clock_gettime(&app->clk_mono, &app->prof.t0);
+ app->prof.min = UINT_MAX;
+ app->prof.max = 0;
+ app->prof.sum = 0;
+ app->prof.count = 0;
+
+ // initialize grid dimensions
+ app->ncols = 3;
+ app->nrows = 2;
+ app->nleft = 0.2;
+
+ // initialize parallel processing
+ dsp_master_t *dsp_master = &app->dsp_master;
+ atomic_init(&dsp_master->kill, false);
+ atomic_init(&dsp_master->emergency_exit, false);
+ sem_init(&dsp_master->sem, 0, 0);
+ dsp_master->num_slaves = driver->num_slaves;
+ dsp_master->concurrent = dsp_master->num_slaves; // this is a safe fallback
+ for(unsigned i=0; i<dsp_master->num_slaves; i++)
+ {
+ dsp_slave_t *dsp_slave = &dsp_master->dsp_slaves[i];
+
+ dsp_slave->dsp_master = dsp_master;
+ sem_init(&dsp_slave->sem, 0, 0);
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_create(&dsp_slave->thread, &attr, _dsp_slave_thread, dsp_slave);
+ }
+
+ lv2_osc_urid_init(&app->osc_urid, driver->map);
+
+ return app;
+}
+
+void
+sp_app_run_pre(sp_app_t *app, uint32_t nsamples)
+{
+ mod_t *del_me = NULL;
+
+ cross_clock_gettime(&app->clk_mono, &app->prof.t1);
+
+ // iterate over all modules
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->delete_request && !del_me) // only delete 1 module at once
+ {
+ del_me = mod;
+ mod->delete_request = false;
+ }
+
+ for(unsigned p=0; p<mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ // stash control port values
+ if( (port->type == PORT_TYPE_CONTROL) && port->control.stashing)
+ {
+ port->control.stashing = false;
+ _sp_app_port_control_stash(port);
+ }
+
+ if(port->direction == PORT_DIRECTION_OUTPUT)
+ continue; // ignore output ports
+
+ // clear atom sequence input buffers
+ if( (port->type == PORT_TYPE_ATOM)
+ && (port->atom.buffer_type == PORT_BUFFER_TYPE_SEQUENCE) )
+ {
+ LV2_Atom_Sequence *seq = PORT_BASE_ALIGNED(port);
+ seq->atom.size = sizeof(LV2_Atom_Sequence_Body); // empty sequence
+ seq->atom.type = app->regs.port.sequence.urid;
+ seq->body.unit = 0;
+ seq->body.pad = 0;
+ }
+ }
+ }
+
+ if(del_me)
+ _sp_app_mod_eject(app, del_me);
+}
+
+static inline void
+_sp_app_process_serial(sp_app_t *app, uint32_t nsamples, bool sparse_update_timeout)
+{
+ // iterate over all modules
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ _sp_app_process_single_run(mod, nsamples);
+ _sp_app_process_single_post(mod, nsamples, sparse_update_timeout);
+ }
+}
+
+static inline void
+_sp_app_process_parallel(sp_app_t *app, uint32_t nsamples, bool sparse_update_timeout)
+{
+ _dsp_master_process(app, &app->dsp_master, nsamples);
+
+ // iterate over all modules
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ _sp_app_process_single_post(mod, nsamples, sparse_update_timeout);
+ }
+}
+
+void
+sp_app_run_post(sp_app_t *app, uint32_t nsamples)
+{
+ bool sparse_update_timeout = false;
+
+ app->fps.counter += nsamples; // increase sample counter
+ app->fps.period_cnt += 1; // increase period counter
+ if(app->fps.counter >= app->fps.bound) // check whether we reached boundary
+ {
+ sparse_update_timeout = true;
+ app->fps.counter -= app->fps.bound; // reset sample counter
+ }
+
+ dsp_master_t *dsp_master = &app->dsp_master;
+ if( (dsp_master->num_slaves > 0) && (dsp_master->concurrent > 1) ) // parallel processing makes sense here
+ _sp_app_process_parallel(app, nsamples, sparse_update_timeout);
+ else
+ _sp_app_process_serial(app, nsamples, sparse_update_timeout);
+
+ if(atomic_exchange(&dsp_master->emergency_exit, false))
+ {
+ app->dsp_master.concurrent = dsp_master->num_slaves; // spin up all cores
+ sp_app_log_trace(app, "%s: had to take emergency exit\n", __func__);
+ }
+
+ // profiling
+ struct timespec app_t2;
+ cross_clock_gettime(&app->clk_mono, &app_t2);
+
+ const unsigned run_time = (app_t2.tv_sec - app->prof.t1.tv_sec)*1000000000
+ + app_t2.tv_nsec - app->prof.t1.tv_nsec;
+ app->prof.sum += run_time;
+ app->prof.count += 1;
+
+ if(run_time < app->prof.min)
+ app->prof.min = run_time;
+ else if(run_time > app->prof.max)
+ app->prof.max = run_time;
+
+ if(app_t2.tv_sec > app->prof.t0.tv_sec) // a second has passed
+ {
+ const unsigned tot_time = (app_t2.tv_sec - app->prof.t0.tv_sec)*1000000000
+ + app_t2.tv_nsec - app->prof.t0.tv_nsec;
+ const float tot_time_1 = 100.f / tot_time;
+
+#if defined(USE_DYNAMIC_PARALLELIZER)
+ // reset DAG weights
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ dsp_client->weight = 0;
+ }
+
+ unsigned T1 = 0;
+ unsigned Tinf = 0;
+
+ // calculate DAG weights
+ for(unsigned m1=0; m1<app->num_mods; m1++)
+ {
+ mod_t *mod1 = app->mods[m1];
+ dsp_client_t *dsp_client1 = &mod1->dsp_client;
+
+ unsigned gsw = 0; // greatest sink weight
+
+ for(unsigned m2=0; m2<m1; m2++)
+ {
+ mod_t *mod2 = app->mods[m2];
+ dsp_client_t *dsp_client2 = &mod2->dsp_client;
+
+ for(unsigned s=0; s<dsp_client2->num_sinks; s++)
+ {
+ dsp_client_t *dsp_client3 = dsp_client2->sinks[s];
+
+ if(dsp_client3 == dsp_client1) // mod2 is source of mod1
+ {
+ if(dsp_client2->weight > gsw)
+ gsw = dsp_client2->weight;
+
+ break;
+ }
+ }
+ }
+
+ const unsigned w1 = mod1->prof.sum;
+
+ T1 += w1;
+ dsp_client1->weight = gsw + w1;
+
+ if(dsp_client1->weight > Tinf)
+ Tinf = dsp_client1->weight;
+ }
+
+ // derive average parallelism
+ const float parallelism = (float)T1 / Tinf; //TODO add some head-room?
+ app->dsp_master.concurrent = ceilf(parallelism);
+
+ // to nk
+ {
+ LV2_Atom *answer;
+ answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t cpus_used = (app->dsp_master.concurrent > app->dsp_master.num_slaves + 1)
+ ? app->dsp_master.num_slaves + 1
+ : app->dsp_master.concurrent;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, 0, 0, app->regs.synthpod.cpus_used.urid,
+ sizeof(int32_t), app->forge.Int, &cpus_used); //TODO subj, seqn
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+#endif
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ const float mod_min = mod->prof.min * app->prof.count * tot_time_1;
+ const float mod_avg = mod->prof.sum * tot_time_1;
+ const float mod_max = mod->prof.max * app->prof.count * tot_time_1;
+
+ // to nk
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const float vec [] = {
+ mod_min, mod_avg, mod_max
+ };
+
+ LV2_Atom_Forge_Frame frame [1];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], mod->urn, 0, app->regs.synthpod.module_profiling.urid); //TODO seqn
+ if(ref)
+ ref = lv2_atom_forge_vector(&app->forge, sizeof(float), app->forge.Float, 3, vec);
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 1);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ mod->prof.min = UINT_MAX;
+ mod->prof.max = 0;
+ mod->prof.sum = 0;
+ }
+
+ {
+ const float app_min = app->prof.min * app->prof.count * tot_time_1;
+ const float app_avg = app->prof.sum * tot_time_1;
+ const float app_max = app->prof.max * app->prof.count * tot_time_1;
+
+ // to nk
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const float vec [] = {
+ app_min, app_avg, app_max
+ };
+
+ LV2_Atom_Forge_Frame frame [1];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], 0, 0, app->regs.synthpod.dsp_profiling.urid); //TODO subj, seqn
+ if(ref)
+ ref = lv2_atom_forge_vector(&app->forge, sizeof(float), app->forge.Float, 3, vec);
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 1);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ app->prof.t0.tv_sec = app_t2.tv_sec;
+ app->prof.t0.tv_nsec = app_t2.tv_nsec;
+ app->prof.min = UINT_MAX;
+ app->prof.max = 0;
+ app->prof.sum = 0;
+ app->prof.count = 0;
+ }
+ }
+
+ // handle app ui post
+ bool expected = true;
+ const bool desired = false;
+ if(atomic_compare_exchange_weak(&app->dirty, &expected, desired))
+ {
+ // to nk
+ _sp_app_ui_set_modlist(app, 0, 0); //FIXME subj, seqn
+
+ // recalculate concurrency
+ _dsp_master_reorder(app);
+ //printf("concurrency: %i\n", app->dsp_master.concurrent);
+ }
+}
+
+void
+sp_app_deactivate(sp_app_t *app)
+{
+ //TODO
+}
+
+void
+sp_app_free(sp_app_t *app)
+{
+ if(!app)
+ return;
+
+ // deinit parallel processing
+ dsp_master_t *dsp_master = &app->dsp_master;
+ atomic_store(&dsp_master->kill, true);
+ //printf("finish\n");
+ _dsp_master_post(dsp_master, dsp_master->num_slaves);
+ _dsp_master_wait(app, dsp_master, dsp_master->num_slaves);
+
+ for(unsigned i=0; i<dsp_master->num_slaves; i++)
+ {
+ dsp_slave_t *dsp_slave = &dsp_master->dsp_slaves[i];
+
+ void *ret;
+ pthread_join(dsp_slave->thread, &ret);
+ sem_destroy(&dsp_slave->sem);
+ }
+ sem_destroy(&dsp_master->sem);
+
+ // free mods
+ for(unsigned m=0; m<app->num_mods; m++)
+ _sp_app_mod_del(app, app->mods[m]);
+
+ sp_regs_deinit(&app->regs);
+
+ if(!app->embedded)
+ lilv_world_free(app->world);
+
+ if(app->bundle_path)
+ free(app->bundle_path);
+ if(app->bundle_filename)
+ free(app->bundle_filename);
+
+ if(app->sratom)
+ sratom_free(app->sratom);
+
+ cross_clock_deinit(&app->clk_mono);
+ cross_clock_deinit(&app->clk_real);
+
+ free(app);
+}
+
+bool
+sp_app_bypassed(sp_app_t *app)
+{
+ return app->load_bundle && (app->block_state == BLOCKING_STATE_WAIT);
+}
+
+__realtime uint32_t
+sp_app_options_set(sp_app_t *app, const LV2_Options_Option *options)
+{
+ LV2_Options_Status status = LV2_OPTIONS_SUCCESS;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->opts.iface && mod->opts.iface->set)
+ status |= mod->opts.iface->set(mod->handle, options);
+ }
+
+ return status;
+}
+
+static void
+_sp_app_reinitialize(sp_app_t *app)
+{
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ _sp_app_mod_reinitialize(mod);
+ }
+
+ // refresh all connections
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ for(unsigned i=0; i<mod->num_ports - 2; i++)
+ {
+ port_t *tar = &mod->ports[i];
+
+ // set port buffer
+ lilv_instance_connect_port(mod->inst, i, tar->base);
+ }
+
+ lilv_instance_activate(mod->inst);
+ }
+}
+
+int
+sp_app_nominal_block_length(sp_app_t *app, uint32_t nsamples)
+{
+ if(nsamples <= app->driver->max_block_size)
+ {
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->opts.iface && mod->opts.iface->set)
+ {
+ if(nsamples < app->driver->min_block_size)
+ {
+ // update driver struct
+ app->driver->min_block_size = nsamples;
+
+ const LV2_Options_Option options [2] = {{
+ .context = LV2_OPTIONS_INSTANCE,
+ .subject = 0, // is ignored
+ .key = app->regs.bufsz.min_block_length.urid,
+ .size = sizeof(int32_t),
+ .type = app->forge.Int,
+ .value = &app->driver->min_block_size
+ }, {
+ .key = 0, // sentinel
+ .value =NULL // sentinel
+ }};
+
+ // notify new minimalBlockLength
+ if(mod->opts.iface->set(mod->handle, options) != LV2_OPTIONS_SUCCESS)
+ sp_app_log_error(app, "%s:setting of minBlockSize failed\n", __func__);
+ }
+
+ const int32_t nominal_block_length = nsamples;
+
+ const LV2_Options_Option options [2] = {{
+ .context = LV2_OPTIONS_INSTANCE,
+ .subject = 0, // is ignored
+ .key = app->regs.bufsz.nominal_block_length.urid,
+ .size = sizeof(int32_t),
+ .type = app->forge.Int,
+ .value = &nominal_block_length
+ }, {
+ .key = 0, // sentinel
+ .value =NULL // sentinel
+ }};
+
+ // notify new nominalBlockLength
+ if(mod->opts.iface->set(mod->handle, options) != LV2_OPTIONS_SUCCESS)
+ sp_app_log_error(app, "%s:setting of nominalblockSize failed\n", __func__);
+ }
+ }
+ }
+ else // nsamples > max_block_size
+ {
+ // update driver struct
+ app->driver->max_block_size = nsamples;
+
+ _sp_app_reinitialize(app);
+ }
+
+ return 0;
+}
+
+int
+sp_app_com_event(sp_app_t *app, LV2_URID otype)
+{
+ // it is a com event, if it is not an official port protocol
+ if( (otype == app->regs.port.float_protocol.urid)
+ || (otype == app->regs.port.peak_protocol.urid)
+ || (otype == app->regs.port.atom_transfer.urid)
+ || (otype == app->regs.port.event_transfer.urid) )
+ return 0;
+
+ return 1;
+}
+
+// sort according to position
+__realtime static void
+_sp_app_mod_qsort(mod_t **A, int n)
+{
+ if(n < 2)
+ return;
+
+ const mod_t *p = A[0];
+
+ int i = -1;
+ int j = n;
+
+ while(true)
+ {
+ do {
+ i += 1;
+ } while( (A[i]->pos.x < p->pos.x) || ( (A[i]->pos.x == p->pos.x) && (A[i]->pos.y < p->pos.y) ) );
+
+ do {
+ j -= 1;
+ } while( (A[j]->pos.x > p->pos.x) || ( (A[j]->pos.x == p->pos.x) && (A[j]->pos.y > p->pos.y) ) );
+
+ if(i >= j)
+ break;
+
+ mod_t *tmp = A[i];
+ A[i] = A[j];
+ A[j] = tmp;
+ }
+
+ _sp_app_mod_qsort(A, j + 1);
+ _sp_app_mod_qsort(A + j + 1, n - j - 1);
+}
+
+/*
+__non_realtime static void
+_sp_app_order_dump(sp_app_t *app)
+{
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ printf("%u: %u\n", m, mod->uid);
+ }
+ printf("\n");
+}
+*/
+
+__realtime void
+_sp_app_order(sp_app_t *app)
+{
+ //_sp_app_order_dump(app);
+ _sp_app_mod_qsort(app->mods, app->num_mods);
+ //_sp_app_order_dump(app);
+
+ _dsp_master_reorder(app);
+}
+
+__non_realtime int
+sp_app_log_error(sp_app_t *app, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = app->driver->log->vprintf(app->driver->log->handle, app->regs.log.error.urid, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__non_realtime int
+sp_app_log_note(sp_app_t *app, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = app->driver->log->vprintf(app->driver->log->handle, app->regs.log.note.urid, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__non_realtime int
+sp_app_log_warning(sp_app_t *app, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = app->driver->log->vprintf(app->driver->log->handle, app->regs.log.warning.urid, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__realtime int
+sp_app_log_trace(sp_app_t *app, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = app->driver->log->vprintf(app->driver->log->handle, app->regs.log.trace.urid, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+void
+sp_app_set_bundle_path(sp_app_t *app, const char *bundle_path)
+{
+ if(app->bundle_path)
+ free(app->bundle_path);
+
+ app->bundle_path = strdup(bundle_path);
+}
diff --git a/app/synthpod_app_mod.c b/app/synthpod_app_mod.c
new file mode 100644
index 00000000..5c331ed0
--- /dev/null
+++ b/app/synthpod_app_mod.c
@@ -0,0 +1,1171 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <synthpod_app_private.h>
+
+#define ANSI_COLOR_BOLD "\x1b[1m"
+#define ANSI_COLOR_RESET "\x1b[0m"
+
+//tools.ietf.org/html/rfc4122 version 4
+static void
+urn_uuid_unparse_random(urn_uuid_t urn_uuid)
+{
+ uint8_t bytes [0x10];
+
+ for(unsigned i=0x0; i<0x10; i++)
+ bytes[i] = rand() & 0xff;
+
+ bytes[6] = (bytes[6] & 0b00001111) | 0b01000000; // set four most significant bits of 7th byte to 0b0100
+ bytes[8] = (bytes[8] & 0b00111111) | 0b10000000; // set two most significant bits of 9th byte to 0b10
+
+ snprintf(urn_uuid, URN_UUID_LENGTH, "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ bytes[0x0], bytes[0x1], bytes[0x2], bytes[0x3],
+ bytes[0x4], bytes[0x5],
+ bytes[0x6], bytes[0x7],
+ bytes[0x8], bytes[0x9],
+ bytes[0xa], bytes[0xb], bytes[0xc], bytes[0xd], bytes[0xe], bytes[0xf]);
+}
+
+#if defined(_WIN32)
+static inline char *
+strsep(char **sp, char *sep)
+{
+ char *p, *s;
+ if(sp == NULL || *sp == NULL || **sp == '\0')
+ return(NULL);
+ s = *sp;
+ p = s + strcspn(s, sep);
+ if(*p != '\0')
+ *p++ = '\0';
+ *sp = p;
+ return(s);
+}
+#endif
+
+//FIXME is actually __realtime
+__non_realtime static int
+_log_vprintf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, va_list args)
+{
+ mod_t *mod = handle;
+ sp_app_t *app = mod->app;
+
+ char prefix [128]; //TODO how big?
+ char buf [1024]; //TODO how big?
+
+ if(isatty(STDERR_FILENO))
+ snprintf(prefix, sizeof(prefix), "{"ANSI_COLOR_BOLD"%s"ANSI_COLOR_RESET"} ", mod->urn_uri);
+ else
+ snprintf(prefix, sizeof(prefix), "{%s} ", mod->urn_uri);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+
+ const char *sep = "\n";
+ for(char *bufp = buf, *pch = strsep(&bufp, sep);
+ pch;
+ pch = strsep(&bufp, sep) )
+ {
+ if(strlen(pch) && app->driver->log)
+ app->driver->log->printf(app->driver->log->handle, type, "%s%s\n", prefix, pch);
+ }
+
+ return 0;
+}
+
+//FIXME is actually __realtime
+__non_realtime static int __attribute__((format(printf, 3, 4)))
+_log_printf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(handle, type, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__realtime static LV2_Worker_Status
+_schedule_work(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data)
+{
+ mod_t *mod = handle;
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ void *target;
+ if((target = varchunk_write_request(mod_worker->app_to_worker, size)))
+ {
+ memcpy(target, data, size);
+ varchunk_write_advance(mod_worker->app_to_worker, size);
+ sem_post(&mod_worker->sem);
+
+ return LV2_WORKER_SUCCESS;
+ }
+
+ sp_app_log_trace(mod->app, "%s: failed to request buffer\n", __func__);
+
+ return LV2_WORKER_ERR_NO_SPACE;
+}
+
+__non_realtime static char *
+_mod_make_path(LV2_State_Make_Path_Handle instance, const char *abstract_path)
+{
+ mod_t *mod = instance;
+ sp_app_t *app = mod->app;
+
+ char *absolute_path = NULL;
+ asprintf(&absolute_path, "%s/%s/%s", app->bundle_path, mod->urn_uri, abstract_path);
+
+ // create leading directory tree, e.g. up to last '/'
+ if(absolute_path)
+ {
+ const char *end = strrchr(absolute_path, '/');
+ if(end)
+ {
+ char *path = strndup(absolute_path, end - absolute_path);
+ if(path)
+ {
+ mkpath(path);
+
+ free(path);
+ }
+ }
+ }
+
+ return absolute_path;
+}
+
+static inline int
+_sp_app_mod_alloc_pool(pool_t *pool)
+{
+#if defined(_WIN32)
+ pool->buf = _aligned_malloc(pool->size, 8);
+#else
+ posix_memalign(&pool->buf, 8, pool->size);
+#endif
+ if(pool->buf)
+ {
+ memset(pool->buf, 0x0, pool->size);
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static inline void
+_sp_app_mod_free_pool(pool_t *pool)
+{
+ if(pool->buf)
+ {
+ free(pool->buf);
+ pool->buf = NULL;
+ }
+}
+
+static inline void
+_sp_app_mod_slice_pool(mod_t *mod, port_type_t type)
+{
+ // set ptr to pool buffer
+ void *ptr = mod->pools[type].buf;
+
+ for(port_direction_t dir=0; dir<PORT_DIRECTION_NUM; dir++)
+ {
+ for(unsigned i=0; i<mod->num_ports; i++)
+ {
+ port_t *tar = &mod->ports[i];
+
+ if( (tar->type != type) || (tar->direction != dir) )
+ continue; //skip
+
+ // define buffer slice
+ tar->base = ptr;
+
+ // initialize control buffers to default value
+ if(tar->type == PORT_TYPE_CONTROL)
+ {
+ control_port_t *control = &tar->control;
+
+ float *buf_ptr = PORT_BASE_ALIGNED(tar);
+ *buf_ptr = control->dflt;
+ control->stash = control->dflt;
+ control->last = control->dflt;
+ control->auto_dirty = true;
+ }
+
+ ptr += lv2_atom_pad_size(tar->size);
+ }
+ }
+}
+
+void
+_sp_app_mod_reinitialize(mod_t *mod)
+{
+ sp_app_t *app = mod->app;
+
+ // reinitialize all modules,
+ lilv_instance_deactivate(mod->inst);
+ lilv_instance_free(mod->inst);
+ mod->inst = NULL;
+ mod->handle = NULL;
+
+ // mod->features should be up-to-date
+ mod->inst = lilv_plugin_instantiate(mod->plug, app->driver->sample_rate, mod->features);
+ mod->handle = lilv_instance_get_handle(mod->inst);
+
+ //TODO should we re-get extension_data?
+
+ // resize sample based buffers only (e.g. AUDIO and CV)
+ _sp_app_mod_free_pool(&mod->pools[PORT_TYPE_AUDIO]);
+ _sp_app_mod_free_pool(&mod->pools[PORT_TYPE_CV]);
+
+ mod->pools[PORT_TYPE_AUDIO].size = 0;
+ mod->pools[PORT_TYPE_CV].size = 0;
+
+ for(unsigned i=0; i<mod->num_ports; i++)
+ {
+ port_t *tar = &mod->ports[i];
+
+ if( (tar->type == PORT_TYPE_AUDIO)
+ || (tar->type == PORT_TYPE_CV) )
+ {
+ tar->size = app->driver->max_block_size * sizeof(float);
+ mod->pools[tar->type].size += lv2_atom_pad_size(tar->size);
+ }
+ }
+
+ _sp_app_mod_alloc_pool(&mod->pools[PORT_TYPE_AUDIO]);
+ _sp_app_mod_alloc_pool(&mod->pools[PORT_TYPE_CV]);
+
+ _sp_app_mod_slice_pool(mod, PORT_TYPE_AUDIO);
+ _sp_app_mod_slice_pool(mod, PORT_TYPE_CV);
+}
+
+static inline int
+_sp_app_mod_features_populate(sp_app_t *app, mod_t *mod)
+{
+ // populate feature list
+ int nfeatures = 0;
+ mod->feature_list[nfeatures].URI = LV2_URID__map;
+ mod->feature_list[nfeatures++].data = app->driver->map;
+
+ mod->feature_list[nfeatures].URI = LV2_URID__unmap;
+ mod->feature_list[nfeatures++].data = app->driver->unmap;
+
+ mod->feature_list[nfeatures].URI = XPRESS__voiceMap;
+ mod->feature_list[nfeatures++].data = app->driver->xmap;
+
+ mod->feature_list[nfeatures].URI = LV2_WORKER__schedule;
+ mod->feature_list[nfeatures++].data = &mod->worker.schedule;
+
+ mod->feature_list[nfeatures].URI = LV2_LOG__log;
+ mod->feature_list[nfeatures++].data = &mod->log;
+
+ mod->feature_list[nfeatures].URI = LV2_STATE__makePath;
+ mod->feature_list[nfeatures++].data = &mod->make_path;
+
+ mod->feature_list[nfeatures].URI = LV2_BUF_SIZE__boundedBlockLength;
+ mod->feature_list[nfeatures++].data = NULL;
+
+ mod->feature_list[nfeatures].URI = LV2_OPTIONS__options;
+ mod->feature_list[nfeatures++].data = mod->opts.options;
+
+ /* TODO support
+ mod->feature_list[nfeatures].URI = LV2_PORT_PROPS__supportsStrictBounds;
+ mod->feature_list[nfeatures++].data = NULL;
+ */
+
+ /* TODO support
+ mod->feature_list[nfeatures].URI = LV2_RESIZE_PORT__resize;
+ mod->feature_list[nfeatures++].data = NULL;
+ */
+
+ mod->feature_list[nfeatures].URI = LV2_STATE__loadDefaultState;
+ mod->feature_list[nfeatures++].data = NULL;
+
+ if(app->driver->system_port_add && app->driver->system_port_del)
+ {
+ mod->feature_list[nfeatures].URI = SYNTHPOD_PREFIX"systemPorts";
+ mod->feature_list[nfeatures++].data = NULL;
+ }
+
+ if(app->driver->osc_sched)
+ {
+ mod->feature_list[nfeatures].URI = LV2_OSC__schedule;
+ mod->feature_list[nfeatures++].data = app->driver->osc_sched;
+ }
+
+ if(app->driver->features & SP_APP_FEATURE_FIXED_BLOCK_LENGTH)
+ {
+ mod->feature_list[nfeatures].URI = LV2_BUF_SIZE__fixedBlockLength;
+ mod->feature_list[nfeatures++].data = NULL;
+ }
+
+ if(app->driver->features & SP_APP_FEATURE_POWER_OF_2_BLOCK_LENGTH)
+ {
+ mod->feature_list[nfeatures].URI = LV2_BUF_SIZE__powerOf2BlockLength;
+ mod->feature_list[nfeatures++].data = NULL;
+ }
+
+ mod->feature_list[nfeatures].URI = LV2_URI_MAP_URI;
+ mod->feature_list[nfeatures++].data = &app->uri_to_id;
+
+ mod->feature_list[nfeatures].URI = LV2_CORE__inPlaceBroken;
+ mod->feature_list[nfeatures++].data = NULL;
+
+ mod->feature_list[nfeatures].URI = LV2_INLINEDISPLAY__queue_draw;
+ mod->feature_list[nfeatures++].data = &mod->idisp.queue_draw;
+
+ mod->feature_list[nfeatures].URI = LV2_STATE__threadSafeRestore;
+ mod->feature_list[nfeatures++].data = NULL;
+
+ assert(nfeatures <= NUM_FEATURES);
+
+ for(int i=0; i<nfeatures; i++)
+ mod->features[i] = &mod->feature_list[i];
+ mod->features[nfeatures] = NULL; // sentinel
+
+ return nfeatures;
+}
+
+static const LilvPlugin *
+_sp_app_mod_is_supported(sp_app_t *app, const char *uri)
+{
+ LilvNode *uri_node = lilv_new_uri(app->world, uri);
+ if(!uri_node)
+ {
+ sp_app_log_trace(app, "%s: failed to create URI\n", __func__);
+ return NULL;
+ }
+
+ const LilvPlugin *plug = lilv_plugins_get_by_uri(app->plugs, uri_node);
+ lilv_node_free(uri_node);
+
+ if(!plug)
+ {
+ sp_app_log_trace(app, "%s: failed to get plugin\n", __func__);
+ return NULL;
+ }
+
+ const LilvNode *library_uri= lilv_plugin_get_library_uri(plug);
+ if(!library_uri)
+ {
+ sp_app_log_trace(app, "%s: failed to get library URI\n", __func__);
+ return NULL;
+ }
+
+ if(!app->driver->bad_plugins)
+ {
+ // check whether DSP and UI code is mixed into same binary
+ bool mixed_binary = false;
+ LilvUIs *all_uis = lilv_plugin_get_uis(plug);
+ if(all_uis)
+ {
+ LILV_FOREACH(uis, ptr, all_uis)
+ {
+ const LilvUI *ui = lilv_uis_get(all_uis, ptr);
+ if(!ui)
+ continue;
+
+ const LilvNode *ui_uri_node = lilv_ui_get_uri(ui);
+ if(!ui_uri_node)
+ continue;
+
+ // nedded if ui ttl referenced via rdfs#seeAlso
+ lilv_world_load_resource(app->world, ui_uri_node);
+
+ const LilvNode *ui_library_uri= lilv_ui_get_binary_uri(ui);
+ if(ui_library_uri && lilv_node_equals(library_uri, ui_library_uri))
+ mixed_binary = true; // this is bad, we don't support that
+
+ lilv_world_unload_resource(app->world, ui_uri_node);
+ }
+
+ lilv_uis_free(all_uis);
+ }
+
+ if(mixed_binary)
+ {
+ sp_app_log_error(app, "%s: <%s> NOT supported: mixes DSP and UI code in same binary.\n", __func__, uri);
+ return NULL;
+ }
+ }
+
+ // populate feature list in dummy mod structure
+ mod_t mod;
+ const int nfeatures = _sp_app_mod_features_populate(app, &mod);
+
+ // check for missing features
+ int missing_required_feature = 0;
+ LilvNodes *required_features = lilv_plugin_get_required_features(plug);
+ if(required_features)
+ {
+ LILV_FOREACH(nodes, i, required_features)
+ {
+ const LilvNode* required_feature = lilv_nodes_get(required_features, i);
+ const char *required_feature_uri = lilv_node_as_uri(required_feature);
+ missing_required_feature = 1;
+
+ for(int f=0; f<nfeatures; f++)
+ {
+ if(!strcmp(mod.feature_list[f].URI, required_feature_uri))
+ {
+ missing_required_feature = 0;
+ break;
+ }
+ }
+
+ if(missing_required_feature)
+ {
+ sp_app_log_error(app, "%s: <%s> NOT supported: requires feature <%s>\n",
+ __func__, uri, required_feature_uri);
+ break;
+ }
+ }
+ lilv_nodes_free(required_features);
+ }
+
+ if(missing_required_feature)
+ return NULL;
+
+ return plug;
+}
+
+__non_realtime static LV2_Worker_Status
+_sp_worker_respond_async(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data)
+{
+ mod_t *mod = handle;
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ void *payload;
+ if((payload = varchunk_write_request(mod_worker->app_from_worker, size)))
+ {
+ memcpy(payload, data, size);
+ varchunk_write_advance(mod_worker->app_from_worker, size);
+ return LV2_WORKER_SUCCESS;
+ }
+
+ sp_app_log_error(mod->app, "%s: failed to request buffer\n", __func__);
+
+ return LV2_WORKER_ERR_NO_SPACE;
+}
+
+__non_realtime static LV2_Worker_Status
+_sp_worker_respond_sync(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data)
+{
+ mod_t *mod = handle;
+
+ if(mod->worker.iface && mod->worker.iface->work_response)
+ return mod->worker.iface->work_response(mod->handle, size, data);
+
+ sp_app_log_error(mod->app, "%s: failed to call work:response\n", __func__);
+
+ return LV2_WORKER_ERR_NO_SPACE;
+}
+
+__non_realtime LV2_Worker_Status
+_sp_app_mod_worker_work_sync(mod_t *mod, size_t size, const void *payload)
+{
+ if(mod->worker.iface && mod->worker.iface->work)
+ {
+ return mod->worker.iface->work(mod->handle, _sp_worker_respond_sync, mod,
+ size, payload);
+ }
+
+ sp_app_log_error(mod->app, "%s: failed to call work:work\n", __func__);
+
+ return LV2_WORKER_ERR_NO_SPACE;
+}
+
+__non_realtime static void
+_sp_app_mod_worker_work_async(mod_t *mod, size_t size, const void *payload)
+{
+ //printf("_mod_worker_work: %s, %zu\n", mod->urn_uri, size);
+
+ if(mod->worker.iface && mod->worker.iface->work)
+ {
+ mod->worker.iface->work(mod->handle, _sp_worker_respond_async, mod,
+ size, payload);
+ //TODO check return status
+ }
+ else
+ {
+ sp_app_log_error(mod->app, "%s: failed to call work:work\n", __func__);
+ }
+}
+
+__non_realtime static void *
+_mod_worker_thread(void *data)
+{
+ mod_t *mod = data;
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ // will inherit thread priority from main worker thread
+
+ while(!atomic_load_explicit(&mod_worker->kill, memory_order_acquire))
+ {
+ sem_wait(&mod_worker->sem);
+
+ const void *payload;
+ size_t size;
+ while((payload = varchunk_read_request(mod_worker->app_to_worker, &size)))
+ {
+ _sp_app_mod_worker_work_async(mod, size, payload);
+
+ varchunk_read_advance(mod_worker->app_to_worker);
+ }
+
+ while((payload = varchunk_read_request(mod_worker->state_to_worker, &size)))
+ {
+ _sp_app_mod_worker_work_async(mod, size, payload);
+
+ varchunk_read_advance(mod_worker->state_to_worker);
+ }
+
+ if(mod->idisp.iface && mod->idisp.iface->render)
+ {
+ if(atomic_exchange(&mod->idisp.draw_queued, false))
+ {
+ const uint32_t w = 256; //FIXME
+ const uint32_t h = 256; //FIXME
+
+ // lock surface
+ while(atomic_flag_test_and_set(&mod->idisp.lock))
+ {
+ // spin
+ }
+
+ mod->idisp.surf = mod->idisp.iface->render(mod->handle, w, h);
+
+ // unlock surface
+ atomic_flag_clear(&mod->idisp.lock);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+__realtime void
+_sp_app_mod_queue_draw(mod_t *mod)
+{
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ if(mod->idisp.iface && mod->idisp.subscribed)
+ {
+ if(mod->idisp.counter >= mod->idisp.threshold)
+ {
+ mod->idisp.counter = 0;
+
+ atomic_store(&mod->idisp.draw_queued, true);
+ sem_post(&mod_worker->sem);
+ }
+ }
+}
+
+__realtime static void
+_mod_queue_draw(void *data)
+{
+ mod_t *mod = data;
+
+ _sp_app_mod_queue_draw(mod);
+}
+
+mod_t *
+_sp_app_mod_add(sp_app_t *app, const char *uri, LV2_URID urn)
+{
+ const LilvPlugin *plug;
+
+ if(!(plug = _sp_app_mod_is_supported(app, uri)))
+ {
+ sp_app_log_error(app, "%s: plugin is not supported\n", __func__);
+ return NULL;
+ }
+
+ mod_t *mod = calloc(1, sizeof(mod_t));
+ if(!mod)
+ {
+ sp_app_log_error(app, "%s: allocation failed\n", __func__);
+ return NULL;
+ }
+
+ mod->needs_bypassing = false; // plugins with control ports only need no bypassing upon preset load
+ mod->bypassed = false;
+ atomic_init(&mod->dsp_client.ref_count, 0);
+
+ // populate worker schedule
+ mod->worker.schedule.handle = mod;
+ mod->worker.schedule.schedule_work = _schedule_work;
+
+ // populate log
+ mod->log.handle = mod;
+ mod->log.printf = _log_printf;
+ mod->log.vprintf = _log_vprintf;
+
+ mod->make_path.handle = mod;
+ mod->make_path.path = _mod_make_path;
+
+ mod->idisp.queue_draw.handle = mod;
+ mod->idisp.queue_draw.queue_draw = _mod_queue_draw;
+ atomic_init(&mod->idisp.draw_queued, false);
+ mod->idisp.lock = (atomic_flag)ATOMIC_FLAG_INIT;
+ mod->idisp.threshold = app->driver->sample_rate / app->driver->update_rate;
+
+ // populate options
+ mod->opts.options[0].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[0].subject = 0;
+ mod->opts.options[0].key = app->regs.bufsz.max_block_length.urid;
+ mod->opts.options[0].size = sizeof(int32_t);
+ mod->opts.options[0].type = app->forge.Int;
+ mod->opts.options[0].value = &app->driver->max_block_size;
+
+ mod->opts.options[1].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[1].subject = 0;
+ mod->opts.options[1].key = app->regs.bufsz.min_block_length.urid;
+ mod->opts.options[1].size = sizeof(int32_t);
+ mod->opts.options[1].type = app->forge.Int;
+ mod->opts.options[1].value = &app->driver->min_block_size;
+
+ mod->opts.options[2].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[2].subject = 0;
+ mod->opts.options[2].key = app->regs.bufsz.sequence_size.urid;
+ mod->opts.options[2].size = sizeof(int32_t);
+ mod->opts.options[2].type = app->forge.Int;
+ mod->opts.options[2].value = &app->driver->seq_size;
+
+ mod->opts.options[3].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[3].subject = 0;
+ mod->opts.options[3].key = app->regs.bufsz.nominal_block_length.urid;
+ mod->opts.options[3].size = sizeof(int32_t);
+ mod->opts.options[3].type = app->forge.Int;
+ mod->opts.options[3].value = &app->driver->max_block_size; // set to max by default
+
+ mod->opts.options[4].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[4].subject = 0;
+ mod->opts.options[4].key = app->regs.param.sample_rate.urid;
+ mod->opts.options[4].size = sizeof(float);
+ mod->opts.options[4].type = app->forge.Float;
+ mod->opts.options[4].value = &app->driver->sample_rate;
+
+ mod->opts.options[5].context = LV2_OPTIONS_INSTANCE;
+ mod->opts.options[5].subject = 0;
+ mod->opts.options[5].key = app->regs.ui.update_rate.urid;
+ mod->opts.options[5].size = sizeof(float);
+ mod->opts.options[5].type = app->forge.Float;
+ mod->opts.options[5].value = &app->driver->update_rate;
+
+ mod->opts.options[6].key = 0; // sentinel
+ mod->opts.options[6].value = NULL; // sentinel
+
+ _sp_app_mod_features_populate(app, mod);
+
+ mod->app = app;
+ if(urn == 0)
+ {
+ urn_uuid_unparse_random(mod->urn_uri);
+ urn = app->driver->map->map(app->driver->map->handle, mod->urn_uri);
+ }
+ else
+ {
+ const char *urn_uri = app->driver->unmap->unmap(app->driver->unmap->handle, urn);
+ strcpy(mod->urn_uri, urn_uri);
+ }
+ //printf("urn: %s\n", mod->urn_uri);
+ mod->urn = urn;
+ mod->plug = plug;
+ mod->plug_urid = app->driver->map->map(app->driver->map->handle, uri);
+ mod->num_ports = lilv_plugin_get_num_ports(plug) + 2; // + automation ports
+ mod->inst = lilv_plugin_instantiate(plug, app->driver->sample_rate, mod->features);
+ if(!mod->inst)
+ {
+ sp_app_log_error(app, "%s: instantiation failed\n", __func__);
+ free(mod);
+ return NULL;
+ }
+ mod->uri_str = strdup(uri); //TODO check
+ mod->handle = lilv_instance_get_handle(mod->inst);
+ mod->worker.iface = lilv_instance_get_extension_data(mod->inst,
+ LV2_WORKER__interface);
+ mod->opts.iface = lilv_instance_get_extension_data(mod->inst,
+ LV2_OPTIONS__interface);
+ mod->idisp.iface = lilv_instance_get_extension_data(mod->inst,
+ LV2_INLINEDISPLAY__interface);
+ mod->state.iface = lilv_instance_get_extension_data(mod->inst,
+ LV2_STATE__interface);
+ mod->system_ports = lilv_plugin_has_feature(plug, app->regs.synthpod.system_ports.node);
+ const bool load_default_state = lilv_plugin_has_feature(plug, app->regs.state.load_default_state.node);
+ const bool thread_safe_restore = lilv_plugin_has_feature(plug, app->regs.state.thread_safe_restore.node);
+
+ if(mod->state.iface) // plugins with state:interface need bypassing upon preset load
+ mod->needs_bypassing = true;
+
+ if(thread_safe_restore) // plugins with state:threadSafeRestore need no bypassing upon preset load
+ mod->needs_bypassing = false;
+
+ // clear pool sizes
+ for(port_type_t pool=0; pool<PORT_TYPE_NUM; pool++)
+ mod->pools[pool].size = 0;
+
+ mod->ports = calloc(mod->num_ports, sizeof(port_t));
+ if(!mod->ports)
+ {
+ sp_app_log_error(app, "%s: pool allocation failed\n", __func__);
+ free(mod);
+ return NULL; // failed to alloc ports
+ }
+
+ for(unsigned i=0; i<mod->num_ports - 2; i++) // - automation ports
+ {
+ port_t *tar = &mod->ports[i];
+ const LilvPort *port = lilv_plugin_get_port_by_index(plug, i);
+
+ tar->size = 0;
+ tar->mod = mod;
+ tar->index = i;
+ tar->symbol = lilv_node_as_string(lilv_port_get_symbol(plug, port));
+ tar->direction = lilv_port_is_a(plug, port, app->regs.port.input.node)
+ ? PORT_DIRECTION_INPUT
+ : PORT_DIRECTION_OUTPUT;
+
+ // register system ports
+ if(mod->system_ports)
+ {
+ if(lilv_port_is_a(plug, port, app->regs.synthpod.control_port.node))
+ tar->sys.type = SYSTEM_PORT_CONTROL;
+ else if(lilv_port_is_a(plug, port, app->regs.synthpod.audio_port.node))
+ tar->sys.type = SYSTEM_PORT_AUDIO;
+ else if(lilv_port_is_a(plug, port, app->regs.synthpod.cv_port.node))
+ tar->sys.type = SYSTEM_PORT_CV;
+ else if(lilv_port_is_a(plug, port, app->regs.synthpod.midi_port.node))
+ tar->sys.type = SYSTEM_PORT_MIDI;
+ else if(lilv_port_is_a(plug, port, app->regs.synthpod.osc_port.node))
+ tar->sys.type = SYSTEM_PORT_OSC;
+ else if(lilv_port_is_a(plug, port, app->regs.synthpod.com_port.node))
+ tar->sys.type = SYSTEM_PORT_COM;
+ else
+ tar->sys.type = SYSTEM_PORT_NONE;
+
+ if(app->driver->system_port_add)
+ {
+ //FIXME check lilv returns
+ char *short_name = NULL;
+ char *pretty_name = NULL;
+ const char *designation = NULL;
+ const LilvNode *port_symbol_node = lilv_port_get_symbol(plug, port);
+ LilvNode *port_name_node = lilv_port_get_name(plug, port);
+ LilvNode *port_designation= lilv_port_get(plug, port, app->regs.core.designation.node);
+
+ asprintf(&short_name, "#%"PRIu32"_%s",
+ mod->urn, lilv_node_as_string(port_symbol_node));
+ asprintf(&pretty_name, "#%"PRIu32" - %s",
+ mod->urn, lilv_node_as_string(port_name_node));
+ designation = port_designation ? lilv_node_as_string(port_designation) : NULL;
+ const uint32_t order = (mod->urn << 16) | tar->index;
+
+ tar->sys.data = app->driver->system_port_add(app->data, tar->sys.type,
+ short_name, pretty_name, designation,
+ tar->direction == PORT_DIRECTION_OUTPUT, order);
+
+ lilv_node_free(port_designation);
+ lilv_node_free(port_name_node);
+ free(short_name);
+ free(pretty_name);
+ }
+ }
+ else
+ {
+ tar->sys.type = SYSTEM_PORT_NONE;
+ tar->sys.data = NULL;
+ }
+
+ if(lilv_port_is_a(plug, port, app->regs.port.audio.node))
+ {
+ tar->size = app->driver->max_block_size * sizeof(float);
+ tar->type = PORT_TYPE_AUDIO;
+ tar->protocol = app->regs.port.peak_protocol.urid;
+ tar->driver = &audio_port_driver;
+ }
+ else if(lilv_port_is_a(plug, port, app->regs.port.cv.node))
+ {
+ tar->size = app->driver->max_block_size * sizeof(float);
+ tar->type = PORT_TYPE_CV;
+ tar->protocol = app->regs.port.peak_protocol.urid;
+ tar->driver = &cv_port_driver;
+ }
+ else if(lilv_port_is_a(plug, port, app->regs.port.control.node))
+ {
+ tar->size = sizeof(float);
+ tar->type = PORT_TYPE_CONTROL;
+ tar->protocol = app->regs.port.float_protocol.urid;
+ tar->driver = &control_port_driver;
+
+ control_port_t *control = &tar->control;
+ control->is_integer = lilv_port_has_property(plug, port, app->regs.port.integer.node);
+ control->is_toggled = lilv_port_has_property(plug, port, app->regs.port.toggled.node);
+ control->lock = (atomic_flag)ATOMIC_FLAG_INIT;
+
+ LilvNode *dflt_node;
+ LilvNode *min_node;
+ LilvNode *max_node;
+ lilv_port_get_range(plug, port, &dflt_node, &min_node, &max_node);
+ control->dflt = dflt_node ? lilv_node_as_float(dflt_node) : 0.f; //FIXME int, bool
+ control->min = min_node ? lilv_node_as_float(min_node) : 0.f; //FIXME int, bool
+ control->max = max_node ? lilv_node_as_float(max_node) : 1.f; //FIXME int, bool
+ control->range = control->max - control->min;
+ control->range_1 = 1.f / control->range;
+ lilv_node_free(dflt_node);
+ lilv_node_free(min_node);
+ lilv_node_free(max_node);
+ }
+ else if(lilv_port_is_a(plug, port, app->regs.port.atom.node))
+ {
+ tar->size = app->driver->seq_size;
+ tar->type = PORT_TYPE_ATOM;
+ tar->protocol = app->regs.port.event_transfer.urid; //FIXME handle atom_transfer
+ tar->driver = &seq_port_driver; // FIXME handle atom_port_driver
+
+ tar->atom.buffer_type = PORT_BUFFER_TYPE_SEQUENCE; //FIXME properly discover this
+
+ // does this port support patch:Message?
+ tar->atom.patchable = lilv_port_supports_event(plug, port, app->regs.patch.message.node);
+
+ // check whether this is a control port
+ const LilvPort *control_port = lilv_plugin_get_port_by_designation(plug,
+ tar->direction == PORT_DIRECTION_INPUT
+ ? app->regs.port.input.node
+ : app->regs.port.output.node
+ , app->regs.core.control.node);
+ (void)control_port; //TODO use this?
+ }
+ else
+ {
+ sp_app_log_warning(app, "%s: unknown port type\n", __func__); //FIXME plugin should fail to initialize here
+
+ free(mod->uri_str);
+ free(mod->ports);
+ free(mod);
+
+ return NULL;
+ }
+
+ // get minimum port size if specified
+ LilvNode *minsize = lilv_port_get(plug, port, app->regs.port.minimum_size.node);
+ if(minsize)
+ {
+ tar->size = lilv_node_as_int(minsize);
+ lilv_node_free(minsize);
+ }
+
+ // increase pool sizes
+ mod->pools[tar->type].size += lv2_atom_pad_size(tar->size);
+ }
+
+ // automation input port //FIXME check
+ {
+ const unsigned i = mod->num_ports - 2;
+ port_t *tar = &mod->ports[i];
+
+ tar->mod = mod;
+ tar->index = i;
+ tar->symbol = "__automation__in__";
+ tar->direction = PORT_DIRECTION_INPUT;
+
+ tar->size = app->driver->seq_size;
+ tar->type = PORT_TYPE_ATOM;
+ tar->protocol = app->regs.port.event_transfer.urid;
+ tar->driver = &seq_port_driver;
+
+ tar->atom.buffer_type = PORT_BUFFER_TYPE_SEQUENCE;
+
+ tar->sys.type = SYSTEM_PORT_NONE;
+ tar->sys.data = NULL;
+
+ // increase pool sizes
+ mod->pools[tar->type].size += lv2_atom_pad_size(tar->size);
+ }
+
+ // automation output port //FIXME check
+ {
+ const unsigned i = mod->num_ports - 1;
+ port_t *tar = &mod->ports[i];
+
+ tar->mod = mod;
+ tar->index = i;
+ tar->symbol = "__automation__out__";
+ tar->direction = PORT_DIRECTION_OUTPUT;
+
+ tar->size = app->driver->seq_size;
+ tar->type = PORT_TYPE_ATOM;
+ tar->protocol = app->regs.port.event_transfer.urid;
+ tar->driver = &seq_port_driver;
+
+ tar->atom.buffer_type = PORT_BUFFER_TYPE_SEQUENCE;
+
+ tar->sys.type = SYSTEM_PORT_NONE;
+ tar->sys.data = NULL;
+
+ // increase pool sizes
+ mod->pools[tar->type].size += lv2_atom_pad_size(tar->size);
+ }
+
+ // allocate 8-byte aligned buffer per plugin and port type pool
+ int alloc_failed = 0;
+ for(port_type_t pool=0; pool<PORT_TYPE_NUM; pool++)
+ {
+ if(_sp_app_mod_alloc_pool(&mod->pools[pool]))
+ {
+ alloc_failed = 1;
+ break;
+ }
+ }
+
+ if(alloc_failed)
+ {
+ sp_app_log_error(app, "%s: pool tiling failed\n", __func__);
+
+ for(port_type_t pool=0; pool<PORT_TYPE_NUM; pool++)
+ _sp_app_mod_free_pool(&mod->pools[pool]);
+
+ free(mod->uri_str);
+ free(mod->ports);
+ free(mod);
+
+ return NULL;
+ }
+
+ // slice plugin buffer into per-port-type-and-direction regions for
+ // efficient dereference in plugin instance
+ for(port_type_t pool=0; pool<PORT_TYPE_NUM; pool++)
+ _sp_app_mod_slice_pool(mod, pool);
+
+ for(unsigned i=0; i<mod->num_ports - 2; i++)
+ {
+ port_t *tar = &mod->ports[i];
+
+ // set port buffer
+ lilv_instance_connect_port(mod->inst, i, tar->base);
+ }
+
+ // load presets
+ mod->presets = lilv_plugin_get_related(plug, app->regs.pset.preset.node);
+
+ // spawn worker thread
+ if(mod->worker.iface || mod->idisp.iface)
+ {
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ sem_init(&mod_worker->sem, 0, 0);
+ atomic_init(&mod_worker->kill, false);
+ mod_worker->app_to_worker = varchunk_new(2048, true); //FIXME how big
+ mod_worker->state_to_worker = varchunk_new(2048, true); //FIXME how big
+ mod_worker->app_from_worker = varchunk_new(2048, true); //FIXME how big
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_create(&mod_worker->thread, &attr, _mod_worker_thread, mod);
+ }
+
+ // activate
+ lilv_instance_activate(mod->inst);
+
+ // load default state
+ if(load_default_state && _sp_app_state_preset_load(app, mod, uri, false))
+ sp_app_log_error(app, "%s: default state loading failed\n", __func__);
+
+ // initialize profiling reference time
+ mod->prof.sum = 0;
+
+ return mod;
+}
+
+int
+_sp_app_mod_del(sp_app_t *app, mod_t *mod)
+{
+ // deinit worker thread
+ if(mod->worker.iface || mod->idisp.iface)
+ {
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ atomic_store_explicit(&mod_worker->kill, true, memory_order_release);
+ sem_post(&mod_worker->sem);
+ void *ret;
+ pthread_join(mod_worker->thread, &ret);
+ varchunk_free(mod_worker->app_to_worker);
+ varchunk_free(mod_worker->state_to_worker);
+ varchunk_free(mod_worker->app_from_worker);
+ sem_destroy(&mod_worker->sem);
+ }
+
+ // deinit instance
+ lilv_nodes_free(mod->presets);
+ lilv_instance_deactivate(mod->inst);
+ lilv_instance_free(mod->inst);
+
+ // free memory
+ for(port_type_t pool=0; pool<PORT_TYPE_NUM; pool++)
+ _sp_app_mod_free_pool(&mod->pools[pool]);
+
+ // unregister system ports
+ for(unsigned i=0; i<mod->num_ports; i++)
+ {
+ port_t *port = &mod->ports[i];
+
+ if(port->sys.data && app->driver->system_port_del)
+ app->driver->system_port_del(app->data, port->sys.data);
+ }
+
+ // free ports
+ if(mod->ports)
+ {
+ free(mod->ports);
+ }
+
+ if(mod->uri_str)
+ free(mod->uri_str);
+
+ free(mod);
+
+ return 0; //success
+}
+
+mod_t *
+_sp_app_mod_get_by_uid(sp_app_t *app, int32_t uid)
+{
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->uid == uid)
+ return mod;
+ }
+
+ return NULL;
+}
+
+void
+_sp_app_mod_eject(sp_app_t *app, mod_t *mod)
+{
+ // eject module from graph
+ app->num_mods -= 1;
+ // remove mod from ->mods
+ for(unsigned m=0, offset=0; m<app->num_mods; m++)
+ {
+ if(app->mods[m] == mod)
+ offset += 1;
+ app->mods[m] = app->mods[m+offset];
+ }
+
+ // disconnect all ports
+ for(unsigned p1=0; p1<mod->num_ports; p1++)
+ {
+ port_t *port = &mod->ports[p1];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ // disconnect sources
+ for(int s=0; s<conn->num_sources; s++)
+ _sp_app_port_disconnect(app, conn->sources[s].port, port);
+ }
+
+ // disconnect sinks
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ for(unsigned p2=0; p2<app->mods[m]->num_ports; p2++)
+ _sp_app_port_disconnect(app, port, &app->mods[m]->ports[p2]);
+ }
+ }
+
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ job->request = JOB_TYPE_REQUEST_MODULE_DEL;
+ job->mod = mod;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: failed requesting buffer\n", __func__);
+ }
+
+ _sp_app_order(app);
+
+#if 0
+ // signal to ui
+ size = sizeof(transmit_module_del_t);
+ transmit_module_del_t *trans = _sp_app_to_ui_request(app, size);
+ if(trans)
+ {
+ _sp_transmit_module_del_fill(&app->regs, &app->forge, trans, size, mod->uid);
+ _sp_app_to_ui_advance(app, size);
+ }
+#endif
+}
+
+static void
+_sp_app_mod_reinitialize_soft(mod_t *mod)
+{
+ sp_app_t *app = mod->app;
+
+ // reinitialize all modules,
+ lilv_instance_deactivate(mod->inst);
+ lilv_instance_free(mod->inst);
+
+ mod->inst = NULL;
+ mod->handle = NULL;
+
+ // mod->features should be up-to-date
+ mod->inst = lilv_plugin_instantiate(mod->plug, app->driver->sample_rate, mod->features);
+ mod->handle = lilv_instance_get_handle(mod->inst);
+
+ // refresh all connections
+ for(unsigned i=0; i<mod->num_ports - 2; i++)
+ {
+ port_t *tar = &mod->ports[i];
+
+ // set port buffer
+ lilv_instance_connect_port(mod->inst, i, tar->base);
+ }
+}
+
+void
+_sp_app_mod_reinstantiate(sp_app_t *app, mod_t *mod)
+{
+ char *path;
+
+ if(asprintf(&path, "file:///tmp/%s.preset.lv2", mod->urn_uri) == -1)
+ {
+ sp_app_log_note(app, "%s: failed to create temporary path\n", __func__);
+ return;
+ }
+
+ LilvState *const state = _sp_app_state_preset_create(app, mod, path);
+ free(path);
+
+ if(state)
+ {
+ _sp_app_mod_reinitialize_soft(mod);
+
+ lilv_instance_activate(mod->inst);
+
+ _sp_app_state_preset_restore(app, mod, state, false);
+
+ lilv_state_free(state);
+ }
+}
diff --git a/app/synthpod_app_port.c b/app/synthpod_app_port.c
new file mode 100644
index 00000000..3ab48399
--- /dev/null
+++ b/app/synthpod_app_port.c
@@ -0,0 +1,1179 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+
+#include <synthpod_app_private.h>
+#include <synthpod_patcher.h>
+
+#include <osc.lv2/util.h>
+
+#if !defined(USE_DYNAMIC_PARALLELIZER)
+__realtime static inline void
+_dsp_master_concurrent(sp_app_t *app)
+{
+ dsp_master_t *dsp_master = &app->dsp_master;
+
+ dsp_master->concurrent = 0;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ dsp_client->count = dsp_client->num_sources;
+ dsp_client->mark = 0;
+ }
+
+ while(true)
+ {
+ bool done = true;
+ unsigned sum = 0;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ const int count = dsp_client->count;
+ if(count == 0)
+ sum += 1;
+ else if(count > 0)
+ done = false;
+ }
+
+ //printf("sum: %u, concurrent: %u\n", sum, dsp_master->concurrent);
+ if(sum > dsp_master->concurrent)
+ dsp_master->concurrent = sum;
+
+ if(done)
+ break;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ if(dsp_client->count == 0)
+ {
+ dsp_client->mark += 1;
+
+ for(unsigned j=0; j<dsp_client->num_sinks; j++)
+ {
+ dsp_client_t *sink = dsp_client->sinks[j];
+
+ sink->mark += 1;
+ }
+ }
+ }
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ if(dsp_client->mark > 0)
+ {
+ dsp_client->count -= dsp_client->mark;
+ dsp_client->mark = 0;
+ }
+ }
+ }
+
+ //printf("concurrent: %u\n", dsp_master->concurrent);
+}
+#endif
+
+__realtime void
+_dsp_master_reorder(sp_app_t *app)
+{
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ dsp_client_t *dsp_client = &mod->dsp_client;
+
+ dsp_client->num_sinks = 0;
+ dsp_client->num_sources = 0;
+ }
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod_sink = app->mods[m];
+
+ for(unsigned n=0; n<m; n++)
+ {
+ mod_t *mod_source = app->mods[n];
+ bool is_connected = false;
+
+ for(unsigned p=0; p<mod_sink->num_ports; p++)
+ {
+ port_t *port_sink = &mod_sink->ports[p];
+
+ connectable_t *conn = _sp_app_port_connectable(port_sink);
+ if(conn)
+ {
+ for(int s=0; s<conn->num_sources; s++)
+ {
+ source_t *source = &conn->sources[s];
+
+ if(source->port->mod == mod_source)
+ {
+ is_connected = true;
+ break;
+ }
+ }
+ }
+
+ if(is_connected)
+ {
+ break;
+ }
+ }
+
+ if(is_connected)
+ {
+ mod_source->dsp_client.sinks[mod_source->dsp_client.num_sinks] = &mod_sink->dsp_client;
+ mod_source->dsp_client.num_sinks += 1;
+ mod_sink->dsp_client.num_sources += 1;
+
+ //printf("%u -> %u\n", mod_source->uid, mod_sink->uid);
+ }
+ }
+ }
+
+ /*
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+ sp_app_log_trace(app, "%s: %u, %u\n",
+ app->driver->unmap->unmap(app->driver->unmap->handle, mod->plug_urid),
+ mod->dsp_client.num_sources, mod->dsp_client.num_sinks);
+ }
+ sp_app_log_trace(app, "\n");
+ */
+
+#if !defined(USE_DYNAMIC_PARALLELIZER)
+ _dsp_master_concurrent(app);
+
+ // to nk
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t cpus_used = (app->dsp_master.concurrent > app->dsp_master.num_slaves + 1)
+ ? app->dsp_master.num_slaves + 1
+ : app->dsp_master.concurrent;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, 0, 0, app->regs.synthpod.cpus_used.urid,
+ sizeof(int32_t), app->forge.Int, &cpus_used); //TODO subj, seqn
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+#endif
+}
+
+bool
+_sp_app_port_connected(port_t *src_port, port_t *snk_port, float gain)
+{
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(conn)
+ {
+ for(int s = 0; s < conn->num_sources; s++)
+ {
+ source_t *src = &conn->sources[s];
+
+ if(src->port == src_port)
+ {
+ src->gain = gain;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+int
+_sp_app_port_connect(sp_app_t *app, port_t *src_port, port_t *snk_port, float gain)
+{
+ if(_sp_app_port_connected(src_port, snk_port, gain))
+ return 0;
+
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(!conn)
+ {
+ sp_app_log_trace(app, "%s: invalid connectable\n", __func__);
+ return 0;
+ }
+
+ if(conn->num_sources >= MAX_SOURCES)
+ {
+ sp_app_log_trace(app, "%s: too many sources\n", __func__);
+ return 0;
+ }
+
+ source_t *source = &conn->sources[conn->num_sources];
+ source->port = src_port;;
+ source->gain = gain;
+ conn->num_sources += 1;
+
+ // only audio port connections need to be ramped to be clickless
+ if(snk_port->type == PORT_TYPE_AUDIO)
+ {
+ source->ramp.samples = app->ramp_samples;
+ source->ramp.state = RAMP_STATE_UP;
+ source->ramp.value = 0.f;
+ }
+
+ _dsp_master_reorder(app);
+ return 1;
+}
+
+void
+_sp_app_port_disconnect(sp_app_t *app, port_t *src_port, port_t *snk_port)
+{
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(!conn)
+ return;
+
+ // update sources list
+ bool connected = false;
+ for(int i=0, j=0; i<conn->num_sources; i++)
+ {
+ if(conn->sources[i].port == src_port)
+ {
+ connected = true;
+ continue;
+ }
+
+ conn->sources[j++].port = conn->sources[i].port;
+ }
+
+ if(!connected)
+ return;
+
+ conn->num_sources -= 1;
+
+ _dsp_master_reorder(app);
+}
+
+int
+_sp_app_port_disconnect_request(sp_app_t *app, port_t *src_port, port_t *snk_port,
+ ramp_state_t ramp_state)
+{
+ if( (src_port->direction == PORT_DIRECTION_OUTPUT)
+ && (snk_port->direction == PORT_DIRECTION_INPUT) )
+ {
+ source_t *source = NULL;
+
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(conn)
+ {
+ // find connection
+ for(int i=0; i<conn->num_sources; i++)
+ {
+ if(conn->sources[i].port == src_port)
+ {
+ source = &conn->sources[i];
+ break;
+ }
+ }
+ }
+
+ if(source)
+ {
+ if(src_port->type == PORT_TYPE_AUDIO)
+ {
+ // only audio output ports need to be ramped to be clickless
+
+ if(source->ramp.state == RAMP_STATE_NONE)
+ {
+ source->ramp.samples = app->ramp_samples;
+ source->ramp.state = ramp_state;
+ source->ramp.value = 1.f;
+ }
+
+ return 1; // needs ramping
+ }
+ else // !AUDIO
+ {
+ // disconnect immediately
+ _sp_app_port_disconnect(app, src_port, snk_port);
+ }
+ }
+ }
+
+ return 0; // not connected
+}
+
+int
+_sp_app_port_desilence(sp_app_t *app, port_t *src_port, port_t *snk_port)
+{
+ if( (src_port->direction == PORT_DIRECTION_OUTPUT)
+ && (snk_port->direction == PORT_DIRECTION_INPUT) )
+ {
+ source_t *source = NULL;
+
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(conn)
+ {
+ // find connection
+ for(int i=0; i<conn->num_sources; i++)
+ {
+ if(conn->sources[i].port == src_port)
+ {
+ source = &conn->sources[i];
+ break;
+ }
+ }
+ }
+
+ if(source)
+ {
+ if(src_port->type == PORT_TYPE_AUDIO)
+ {
+ // only audio output ports need to be ramped to be clickless
+ source->ramp.samples = app->ramp_samples;
+ source->ramp.state = RAMP_STATE_UP;
+ source->ramp.value = 0.f;
+
+ return 1; // needs ramping
+ }
+ }
+ }
+
+ return 0; // not connected
+}
+
+connectable_t *
+_sp_app_port_connectable(port_t *port)
+{
+ switch(port->type)
+ {
+ case PORT_TYPE_AUDIO:
+ return &port->audio.connectable;
+ case PORT_TYPE_CV:
+ return &port->cv.connectable;
+ case PORT_TYPE_ATOM:
+ return &port->atom.connectable;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+int
+_sp_app_port_silence_request(sp_app_t *app, port_t *src_port, port_t *snk_port,
+ ramp_state_t ramp_state)
+{
+ if( (src_port->direction == PORT_DIRECTION_OUTPUT)
+ && (snk_port->direction == PORT_DIRECTION_INPUT) )
+ {
+ source_t *source = NULL;
+
+ connectable_t *conn = _sp_app_port_connectable(snk_port);
+ if(conn)
+ {
+ // find connection
+ for(int i=0; i<conn->num_sources; i++)
+ {
+ if(conn->sources[i].port == src_port)
+ {
+ source = &conn->sources[i];
+ break;
+ }
+ }
+ }
+
+ if(source)
+ {
+ if(src_port->type == PORT_TYPE_AUDIO)
+ {
+ // only audio output ports need to be ramped to be clickless
+ source->ramp.samples = app->ramp_samples;
+ source->ramp.state = ramp_state;
+ source->ramp.value = 1.f;
+
+ return 1; // needs ramping
+ }
+ }
+ }
+
+ return 0; // not connected
+}
+
+static inline void
+_update_ramp(sp_app_t *app, source_t *source, port_t *port, uint32_t nsamples)
+{
+ // update ramp properties
+ source->ramp.samples -= nsamples; // update remaining samples to ramp over
+ if(source->ramp.samples <= 0)
+ {
+ if(source->ramp.state == RAMP_STATE_DOWN)
+ {
+ _sp_app_port_disconnect(app, source->port, port);
+ }
+ else if(source->ramp.state == RAMP_STATE_DOWN_DEL)
+ {
+ source->port->mod->delete_request = true; // mark module for removal
+ _sp_app_port_disconnect(app, source->port, port);
+ }
+ else if(source->ramp.state == RAMP_STATE_DOWN_DRAIN)
+ {
+ // fully silenced, continue with preset loading
+
+ app->silence_state = SILENCING_STATE_WAIT;
+ source->ramp.value = 0.f;
+ return; // stay in RAMP_STATE_DOWN_DRAIN
+ }
+ else if(source->ramp.state == RAMP_STATE_DOWN_DISABLE)
+ {
+ source->port->mod->disabled = true; // disable module in graph
+ source->ramp.value = 0.f;
+ return; // stay in RAMP_STATE_DOWN_DISABLE
+ //FIXME make this more efficient, e.g. without multiplexing while disabled
+ }
+ else if(source->ramp.state == RAMP_STATE_UP)
+ {
+ // nothing
+ }
+
+ source->ramp.state = RAMP_STATE_NONE; // ramp is complete
+ }
+ else
+ {
+ source->ramp.value = (float)source->ramp.samples / (float)app->ramp_samples;
+ if(source->ramp.state == RAMP_STATE_UP)
+ source->ramp.value = 1.f - source->ramp.value;
+ //printf("ramp: %u.%u %f\n", source->port->mod->uid, source->port->index, source->ramp.value);
+ }
+}
+
+__realtime void
+_sp_app_port_control_stash(port_t *port)
+{
+ control_port_t *control = &port->control;
+ void *buf = PORT_BASE_ALIGNED(port);
+
+ if(_sp_app_port_try_lock(control))
+ {
+ control->stash = *(float *)buf;
+
+ _sp_app_port_unlock(control);
+ }
+ else
+ {
+ control->stashing = true;
+ }
+}
+
+__realtime static inline void
+_port_audio_multiplex(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ float *val = PORT_BASE_ALIGNED(port);
+ memset(val, 0, nsamples * sizeof(float)); // init
+
+ connectable_t *conn = &port->audio.connectable;
+ for(int s=0; s<conn->num_sources; s++)
+ {
+ source_t *source = &conn->sources[s];
+
+ // ramp audio output ports
+ if(source->ramp.state != RAMP_STATE_NONE)
+ {
+ const float *src = PORT_BASE_ALIGNED(source->port);
+ const float gain = source->gain * source->ramp.value;
+
+ if(gain == 1.f)
+ {
+ for(uint32_t j=0; j<nsamples; j++)
+ val[j] += src[j];
+ }
+ else // gain != 1.f
+ {
+ for(uint32_t j=0; j<nsamples; j++)
+ val[j] += src[j] * gain;
+ }
+
+ _update_ramp(app, source, port, nsamples);
+ }
+ else // RAMP_STATE_NONE
+ {
+ const float *src = PORT_BASE_ALIGNED(source->port);
+ const float gain = source->gain;
+ if(gain == 1.f)
+ {
+ for(uint32_t j=0; j<nsamples; j++)
+ val[j] += src[j];
+ }
+ else // gain != 1.f
+ {
+ for(uint32_t j=0; j<nsamples; j++)
+ val[j] += src[j] * gain;
+ }
+ }
+ }
+}
+
+__realtime static inline void
+_port_cv_multiplex(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ float *val = PORT_BASE_ALIGNED(port);
+ memset(val, 0, nsamples * sizeof(float)); // init
+
+ connectable_t *conn = &port->cv.connectable;
+ for(int s=0; s<conn->num_sources; s++)
+ {
+ source_t *source = &conn->sources[s];
+
+ const float *src = PORT_BASE_ALIGNED(source->port);
+ for(uint32_t j=0; j<nsamples; j++)
+ val[j] += src[j];
+ }
+}
+
+__realtime static inline int
+_sp_app_automate(sp_app_t *app, mod_t *mod, auto_t *automation, double value,
+ int64_t frames, bool pre)
+{
+ int do_route = 0;
+
+ // linear mapping from MIDI to automation value
+ double f64 = value * automation->mul + automation->add;
+
+ // clip automation value to destination range
+ if(f64 < automation->c)
+ f64 = automation->c;
+ else if(f64 > automation->d)
+ f64 = automation->d;
+
+ port_t *port = &mod->ports[automation->index];
+ if(port->type == PORT_TYPE_CONTROL)
+ {
+ if(pre)
+ {
+ control_port_t *control = &port->control;
+
+ float *buf = PORT_BASE_ALIGNED(port);
+ *buf = control->is_integer
+ ? floor(f64)
+ : f64;
+ }
+ }
+ else if( (port->type == PORT_TYPE_ATOM) && automation->property )
+ {
+ if(!pre)
+ {
+ LV2_Atom_Sequence *control = PORT_BASE_ALIGNED(port);
+ LV2_Atom_Event *dst = lv2_atom_sequence_end(&control->body, control->atom.size);
+ LV2_Atom_Forge_Frame obj_frame;
+
+ lv2_atom_forge_set_buffer(&app->forge, (uint8_t *)dst, PORT_SIZE(port) - control->atom.size - sizeof(LV2_Atom));
+
+ LV2_Atom_Forge_Ref ref;
+ ref = lv2_atom_forge_frame_time(&app->forge, frames)
+ && lv2_atom_forge_object(&app->forge, &obj_frame, 0, app->regs.patch.set.urid)
+ && lv2_atom_forge_key(&app->forge, app->regs.patch.property.urid)
+ && lv2_atom_forge_urid(&app->forge, automation->property)
+ && lv2_atom_forge_key(&app->forge, app->regs.patch.value.urid);
+ if(ref)
+ {
+ if(automation->range == app->forge.Bool)
+ {
+ ref = lv2_atom_forge_bool(&app->forge, f64 != 0.0);
+ }
+ else if(automation->range == app->forge.Int)
+ {
+ ref = lv2_atom_forge_int(&app->forge, floor(f64));
+ }
+ else if(automation->range == app->forge.Long)
+ {
+ ref = lv2_atom_forge_long(&app->forge, floor(f64));
+ }
+ else if(automation->range == app->forge.Float)
+ {
+ ref = lv2_atom_forge_float(&app->forge, f64);
+ }
+ else if(automation->range == app->forge.Double)
+ {
+ ref = lv2_atom_forge_double(&app->forge, f64);
+ }
+ //FIXME support more types
+
+ if(ref)
+ lv2_atom_forge_pop(&app->forge, &obj_frame);
+
+ control->atom.size += sizeof(LV2_Atom_Event) + dst->body.size;
+ }
+ }
+
+ do_route += 1;
+ }
+ else if(port->type == PORT_TYPE_CV)
+ {
+ //FIXME does it make sense to make this automatable?
+ }
+
+ return do_route;
+}
+
+__realtime static inline int
+_sp_app_automate_event(sp_app_t *app, mod_t *mod, const LV2_Atom_Event *ev,
+ bool pre)
+{
+ const int64_t frames = ev->time.frames;
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ int do_route = 0;
+
+ //printf("got automation\n");
+ if( (atom->type == app->regs.port.midi.urid)
+ && (atom->size == 3) ) // we're only interested in controller events
+ {
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(atom);
+ const uint8_t cmd = msg[0] & 0xf0;
+
+ if(cmd == 0xb0) // Controller
+ {
+ const uint8_t channel = msg[0] & 0x0f;
+ const uint8_t controller = msg[1];
+ const uint8_t val = msg[2];
+
+ // iterate over automations
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if( (automation->type == AUTO_TYPE_MIDI)
+ && automation->snk_enabled )
+ {
+ midi_auto_t *mauto = &automation->midi;
+
+ if(pre && automation->learning)
+ {
+ if( (mauto->channel == -1) && (mauto->controller == -1) )
+ {
+ mauto->channel = channel;
+ mauto->controller = controller;
+
+ automation->a = val;
+ automation->b = val;
+ _automation_refresh_mul_add(automation);
+
+ automation->sync = true;
+ }
+ else
+ {
+ bool needs_refresh = false;
+
+ if(val < automation->a)
+ {
+ automation->a = val;
+ needs_refresh = true;
+ }
+ else if(val > automation->b)
+ {
+ automation->b = val;
+ needs_refresh = true;
+ }
+
+ if(needs_refresh)
+ {
+ _automation_refresh_mul_add(automation);
+ }
+
+ automation->sync = true;
+ }
+ }
+
+ if( ( (mauto->channel == -1) || (mauto->channel == channel) )
+ && ( (mauto->controller == -1) || (mauto->controller == controller) ) )
+ {
+ do_route += _sp_app_automate(app, mod, automation, msg[2], frames, pre);
+ }
+ }
+ }
+ }
+ }
+ else if(lv2_osc_is_message_type(&app->osc_urid, obj->body.otype)) //FIXME also consider bundles
+ {
+ const LV2_Atom_String *osc_path = NULL;
+ const LV2_Atom_Tuple *osc_args = NULL;
+
+ if(lv2_osc_message_get(&app->osc_urid, obj, &osc_path, &osc_args))
+ {
+ const char *path = LV2_ATOM_BODY_CONST(osc_path);
+ double val = 0.0;
+
+ LV2_ATOM_TUPLE_FOREACH(osc_args, item)
+ {
+ switch(lv2_osc_argument_type(&app->osc_urid, item))
+ {
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ {
+ val = 0.0;
+ } break;
+ case LV2_OSC_TRUE:
+ {
+ val = 1.0;
+ } break;
+ case LV2_OSC_IMPULSE:
+ {
+ val = HUGE_VAL;
+ } break;
+ case LV2_OSC_INT32:
+ {
+ int32_t i32;
+ lv2_osc_int32_get(&app->osc_urid, item, &i32);
+ val = i32;
+ } break;
+ case LV2_OSC_INT64:
+ {
+ int64_t i64;
+ lv2_osc_int64_get(&app->osc_urid, item, &i64);
+ val = i64;
+ } break;
+ case LV2_OSC_FLOAT:
+ {
+ float f32;
+ lv2_osc_float_get(&app->osc_urid, item, &f32);
+ val = f32;
+ } break;
+ case LV2_OSC_DOUBLE:
+ {
+ double f64;
+ lv2_osc_double_get(&app->osc_urid, item, &f64);
+ val = f64;
+ } break;
+
+ case LV2_OSC_SYMBOL:
+ case LV2_OSC_BLOB:
+ case LV2_OSC_CHAR:
+ case LV2_OSC_STRING:
+ case LV2_OSC_MIDI:
+ case LV2_OSC_RGBA:
+ case LV2_OSC_TIMETAG:
+ {
+ //FIXME handle other types, especially string, blob, symbol
+ } break;
+ }
+ }
+
+ // iterate over automations
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if( (automation->type == AUTO_TYPE_OSC)
+ && automation->snk_enabled )
+ {
+ osc_auto_t *oauto = &automation->osc;
+
+ if(pre && automation->learning)
+ {
+ if(oauto->path[0] == '\0')
+ {
+ strncpy(oauto->path, path, sizeof(oauto->path));
+
+ automation->a = val;
+ automation->b = val;
+ _automation_refresh_mul_add(automation);
+
+ automation->sync = true;
+ }
+ else
+ {
+ bool needs_refresh = false;
+
+ if(val < automation->a)
+ {
+ automation->a = val;
+ needs_refresh = true;
+ }
+ else if(val > automation->b)
+ {
+ automation->b = val;
+ needs_refresh = true;
+ }
+
+ if(needs_refresh)
+ {
+ _automation_refresh_mul_add(automation);
+ }
+
+ automation->sync = true;
+ }
+ }
+
+ if( (oauto->path[0] == '\0') || !strncmp(oauto->path, path, sizeof(oauto->path)) )
+ {
+ do_route += _sp_app_automate(app, mod, automation, val, frames, pre);
+ }
+ }
+ }
+ }
+ }
+ //FIXME handle other events
+
+ return do_route;
+}
+
+__realtime static inline void
+_port_seq_multiplex(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ mod_t *mod = port->mod;
+ const unsigned p = mod->num_ports - 2;
+ port_t *auto_port = &mod->ports[p];
+
+ // create forge to append to sequence (may contain events from UI)
+ const uint32_t capacity = PORT_SIZE(port);
+ LV2_Atom_Sequence *dst = PORT_BASE_ALIGNED(port);
+
+ connectable_t *conn = &port->atom.connectable;
+ const LV2_Atom_Sequence *seq [MAX_SOURCES];
+ const LV2_Atom_Event *itr [MAX_SOURCES];
+ for(int s=0; s<conn->num_sources; s++)
+ {
+ seq[s] = PORT_BASE_ALIGNED(conn->sources[s].port);
+ itr[s] = lv2_atom_sequence_begin(&seq[s]->body);
+ }
+
+ int num_sources = conn->num_sources;
+
+ // if destination port is patchable, also read events from automation input
+ if(port->atom.patchable && (port != auto_port) )
+ {
+ seq[num_sources] = PORT_BASE_ALIGNED(auto_port);
+ itr[num_sources] = lv2_atom_sequence_begin(&seq[num_sources]->body);
+
+ num_sources++;
+ }
+
+ while(true)
+ {
+ int nxt = -1;
+ int64_t frames = nsamples;
+
+ // search for next event in timeline accross source ports
+ for(int s=0; s<num_sources; s++)
+ {
+ if(lv2_atom_sequence_is_end(&seq[s]->body, seq[s]->atom.size, itr[s]))
+ continue; // reached sequence end
+
+ if(itr[s]->time.frames < frames)
+ {
+ frames = itr[s]->time.frames;
+ nxt = s;
+ }
+ }
+
+ if(nxt >= 0) // next event found
+ {
+ const LV2_Atom_Event *ev = itr[nxt];
+
+ if(nxt == conn->num_sources) // event from automation port
+ {
+ _sp_app_automate_event(app, mod, ev, false);
+ }
+ else
+ {
+ int do_route = 0;
+
+ if(port == auto_port)
+ {
+ // directly apply control automation, only route param automation
+ do_route += _sp_app_automate_event(app, mod, ev, true);
+ }
+ else
+ {
+ do_route += 1;
+ }
+
+ if(do_route)
+ {
+ LV2_Atom_Event *ev2 = lv2_atom_sequence_append_event(dst, capacity, ev);
+ if(!ev2)
+ {
+ sp_app_log_trace(app, "%s: failed to append\n", __func__);
+ }
+ }
+ }
+
+ // advance iterator
+ itr[nxt] = lv2_atom_sequence_next(ev);
+ }
+ else
+ break; // no more events to process
+ };
+}
+
+__realtime static LV2_Atom_Forge_Ref
+_patch_notification_internal(sp_app_t *app, port_t *source_port,
+ uint32_t size, LV2_URID type, const void *body)
+{
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, source_port->mod->urn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_symbol.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, source_port->symbol, strlen(source_port->symbol));
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.rdf.value.urid);
+ if(ref)
+ ref = lv2_atom_forge_atom(&app->forge, size, type);
+ if(ref)
+ ref = lv2_atom_forge_write(&app->forge, body, size);
+
+ return ref;
+}
+
+__realtime static void
+_patch_notification_add(sp_app_t *app, port_t *source_port,
+ LV2_URID proto, uint32_t size, LV2_URID type, const void *body)
+{
+ LV2_Atom_Forge_Frame frame [3];
+
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ if(synthpod_patcher_add_object(&app->regs, &app->forge, &frame[0],
+ 0, 0, app->regs.synthpod.notification_list.urid) //TODO subject
+ && lv2_atom_forge_object(&app->forge, &frame[2], 0, proto)
+ && _patch_notification_internal(app, source_port, size, type, body) )
+ {
+ synthpod_patcher_pop(&app->forge, frame, 3);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+}
+
+__realtime static inline void
+_port_float_protocol_update(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ bool needs_update = false;
+ float new_val = 0.f;
+
+ const float *val = PORT_BASE_ALIGNED(port);
+ new_val = *val;
+ needs_update = new_val != port->control.last;
+
+ if(needs_update) // update last value
+ port->control.last = new_val;
+
+ if(needs_update)
+ {
+ // for nk
+ _patch_notification_add(app, port, app->regs.port.float_protocol.urid,
+ sizeof(float), app->forge.Float, &new_val);
+ }
+}
+
+__realtime static inline void
+_port_peak_protocol_update(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ const float *vec = PORT_BASE_ALIGNED(port);
+
+ // find peak value in current period
+ float peak = 0.f;
+ for(uint32_t j=0; j<nsamples; j++)
+ {
+ float val = fabs(vec[j]);
+ if(val > peak)
+ peak = val;
+ }
+
+ if(fabs(peak - port->audio.last) >= 1e-3) //TODO make this configurable
+ {
+ // update last value
+ port->audio.last = peak;
+
+ const LV2UI_Peak_Data data = {
+ .period_start = app->fps.period_cnt,
+ .period_size = nsamples,
+ .peak = peak
+ };
+
+ // for nk
+ const struct {
+ LV2_Atom_Tuple header;
+ LV2_Atom_Int period_start;
+ int32_t space_1;
+ LV2_Atom_Int period_size;
+ int32_t space_2;
+ LV2_Atom_Float peak;
+ int32_t space_3;
+ } tup = {
+ .header = {
+ .atom = {
+ .size = 3*sizeof(LV2_Atom_Long),
+ .type = app->forge.Tuple
+ }
+ },
+ .period_start = {
+ .atom = {
+ .size = sizeof(int32_t),
+ .type = app->forge.Int
+ },
+ .body = data.period_start
+ },
+ .period_size = {
+ .atom = {
+ .size = sizeof(int32_t),
+ .type = app->forge.Int
+ },
+ .body = data.period_size
+ },
+ .peak = {
+ .atom = {
+ .size = sizeof(float),
+ .type = app->forge.Float
+ },
+ .body = data.peak
+ }
+ };
+ _patch_notification_add(app, port, app->regs.port.peak_protocol.urid,
+ tup.header.atom.size, tup.header.atom.type, &tup.period_start);
+ }
+}
+
+__realtime static inline void
+_port_atom_transfer_update(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ const LV2_Atom *atom = PORT_BASE_ALIGNED(port);
+
+ if(atom->size == 0) // empty atom
+ return;
+ else if( (port->atom.buffer_type == PORT_BUFFER_TYPE_SEQUENCE)
+ && (atom->size == sizeof(LV2_Atom_Sequence_Body)) ) // empty atom sequence
+ return;
+
+ // for nk
+ _patch_notification_add(app, port, app->regs.port.atom_transfer.urid,
+ atom->size, atom->type, LV2_ATOM_BODY_CONST(atom));
+}
+
+__realtime static inline void
+_port_event_transfer_update(sp_app_t *app, port_t *port, uint32_t nsamples)
+{
+ const LV2_Atom_Sequence *seq = PORT_BASE_ALIGNED(port);
+
+ if(seq->atom.size == sizeof(LV2_Atom_Sequence_Body)) // empty seq
+ return;
+
+ const bool subscribed = port->subscriptions != 0;
+
+ if(subscribed)
+ {
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ // for nk
+ _patch_notification_add(app, port, app->regs.port.event_transfer.urid,
+ atom->size, atom->type, LV2_ATOM_BODY_CONST(atom));
+ }
+ }
+ else // patched
+ {
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_atom_forge_is_object_type(&app->forge, obj->atom.type))
+ {
+ /*FIXME
+ const LV2_Atom_URID *destination = NULL;
+ lv2_atom_object_get(obj, app->regs.patch.destination.urid, &destination, NULL);
+ if(destination && (destination->atom.type == app->forge.URID)
+ && (destination->body == app->regs.core.plugin.urid) )
+ continue; // ignore feedback messages
+ */
+
+ if( (obj->body.otype == app->regs.patch.set.urid)
+ || (obj->body.otype == app->regs.patch.get.urid)
+ || (obj->body.otype == app->regs.patch.put.urid)
+ || (obj->body.otype == app->regs.patch.patch.urid)
+ || (obj->body.otype == app->regs.patch.insert.urid)
+ || (obj->body.otype == app->regs.patch.move.urid)
+ || (obj->body.otype == app->regs.patch.copy.urid)
+ || (obj->body.otype == app->regs.patch.delete.urid)
+ || (obj->body.otype == app->regs.patch.error.urid)
+ || (obj->body.otype == app->regs.patch.ack.urid) ) //TODO support more patch messages
+ {
+ // for nk
+ _patch_notification_add(app, port, app->regs.port.event_transfer.urid,
+ obj->atom.size, obj->atom.type, &obj->body);
+ }
+ }
+ }
+ }
+}
+
+const port_driver_t control_port_driver = {
+ .multiplex = NULL, // unsupported
+ .transfer = _port_float_protocol_update,
+ .sparse_update = true
+};
+
+const port_driver_t audio_port_driver = {
+ .multiplex = _port_audio_multiplex,
+ .transfer = _port_peak_protocol_update,
+ .sparse_update = true
+};
+
+const port_driver_t cv_port_driver = {
+ .multiplex = _port_cv_multiplex,
+ .transfer = _port_peak_protocol_update,
+ .sparse_update = true
+};
+
+//FIXME actually use this
+const port_driver_t atom_port_driver = {
+ .multiplex = NULL, // unsupported
+ .transfer = _port_atom_transfer_update,
+ .sparse_update = false
+};
+
+const port_driver_t seq_port_driver = {
+ .multiplex = _port_seq_multiplex,
+ .transfer = _port_event_transfer_update,
+ .sparse_update = false
+};
diff --git a/app/synthpod_app_private.h b/app/synthpod_app_private.h
new file mode 100644
index 00000000..7a703820
--- /dev/null
+++ b/app/synthpod_app_private.h
@@ -0,0 +1,771 @@
+/*
+ * 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 _SYNTHPOD_APP_PRIVATE_H
+#define _SYNTHPOD_APP_PRIVATE_H
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdatomic.h>
+#include <ctype.h> // isspace
+#include <math.h>
+#include <semaphore.h>
+#include <pthread.h>
+
+#include <synthpod_app.h>
+#include <synthpod_private.h>
+
+#include <sratom/sratom.h>
+#include <varchunk.h>
+
+#include <lv2_extensions.h> // ardour's inline display
+
+#define CROSS_CLOCK_IMPLEMENTATION
+#include <cross_clock/cross_clock.h>
+
+#define XSD_PREFIX "http://www.w3.org/2001/XMLSchema#"
+#define RDF_PREFIX "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define RDFS_PREFIX "http://www.w3.org/2000/01/rdf-schema#"
+#define SPOD_PREFIX "http://open-music-kontrollers.ch/lv2/synthpod#"
+
+#define URN_UUID_LENGTH 46
+
+#define NUM_FEATURES 17
+#define MAX_SOURCES 32 // TODO how many?
+#define MAX_MODS 512 // TODO how many?
+#define MAX_SLAVES 7 // e.g. 8-core machines
+#define MAX_AUTOMATIONS 64
+#define ALIAS_MAX 32
+
+typedef enum _job_type_request_t job_type_request_t;
+typedef enum _job_type_reply_t job_type_reply_t;
+typedef enum _blocking_state_t blocking_state_t;
+typedef enum _silencing_state_t silencing_state_t;
+typedef enum _ramp_state_t ramp_state_t;
+typedef enum _auto_type_t auto_type_t;
+
+typedef char urn_uuid_t [URN_UUID_LENGTH];
+typedef struct _dsp_slave_t dsp_slave_t;
+typedef struct _dsp_client_t dsp_client_t;
+typedef struct _dsp_master_t dsp_master_t;
+
+typedef struct _mod_worker_t mod_worker_t;
+typedef struct _midi_auto_t midi_auto_t;
+typedef struct _osc_auto_t osc_auto_t;
+typedef struct _auto_t auto_t;
+typedef struct _mod_t mod_t;
+typedef struct _port_t port_t;
+typedef struct _job_t job_t;
+typedef struct _source_t source_t;
+typedef struct _pool_t pool_t;
+typedef struct _port_driver_t port_driver_t;
+typedef struct _app_prof_t app_prof_t;
+typedef struct _mod_prof_t mod_prof_t;
+
+typedef void (*port_multiplex_cb_t) (sp_app_t *app, port_t *port, uint32_t nsamples);
+typedef void (*port_transfer_cb_t) (sp_app_t *app, port_t *port, uint32_t nsamples);
+
+enum _silencing_state_t {
+ SILENCING_STATE_RUN = 0,
+ SILENCING_STATE_BLOCK,
+ SILENCING_STATE_WAIT
+};
+
+enum _blocking_state_t {
+ BLOCKING_STATE_RUN = 0,
+ BLOCKING_STATE_DRAIN,
+ BLOCKING_STATE_BLOCK,
+ BLOCKING_STATE_WAIT,
+};
+
+static const bool advance_ui [] = {
+ [BLOCKING_STATE_RUN] = true,
+ [BLOCKING_STATE_DRAIN] = false,
+ [BLOCKING_STATE_BLOCK] = true,
+ [BLOCKING_STATE_WAIT] = false
+};
+
+static const bool advance_work [] = {
+ [BLOCKING_STATE_RUN] = true,
+ [BLOCKING_STATE_DRAIN] = true,
+ [BLOCKING_STATE_BLOCK] = true,
+ [BLOCKING_STATE_WAIT] = true
+};
+
+enum _ramp_state_t {
+ RAMP_STATE_NONE = 0,
+ RAMP_STATE_UP,
+ RAMP_STATE_DOWN,
+ RAMP_STATE_DOWN_DEL,
+ RAMP_STATE_DOWN_DRAIN,
+ RAMP_STATE_DOWN_DISABLE,
+};
+
+enum _job_type_request_t {
+ JOB_TYPE_REQUEST_MODULE_SUPPORTED,
+ JOB_TYPE_REQUEST_MODULE_ADD,
+ JOB_TYPE_REQUEST_MODULE_DEL,
+ JOB_TYPE_REQUEST_MODULE_REINSTANTIATE,
+ JOB_TYPE_REQUEST_PRESET_LOAD,
+ JOB_TYPE_REQUEST_PRESET_SAVE,
+ JOB_TYPE_REQUEST_BUNDLE_LOAD,
+ JOB_TYPE_REQUEST_BUNDLE_SAVE,
+ JOB_TYPE_REQUEST_DRAIN
+};
+
+enum _job_type_reply_t {
+ JOB_TYPE_REPLY_MODULE_SUPPORTED,
+ JOB_TYPE_REPLY_MODULE_ADD,
+ JOB_TYPE_REPLY_MODULE_DEL,
+ JOB_TYPE_REPLY_MODULE_REINSTANTIATE,
+ JOB_TYPE_REPLY_PRESET_LOAD,
+ JOB_TYPE_REPLY_PRESET_SAVE,
+ JOB_TYPE_REPLY_BUNDLE_LOAD,
+ JOB_TYPE_REPLY_BUNDLE_SAVE,
+ JOB_TYPE_REPLY_DRAIN
+};
+
+struct _dsp_slave_t {
+ dsp_master_t *dsp_master;
+ sem_t sem;
+ pthread_t thread;
+};
+
+struct _dsp_client_t {
+ atomic_int ref_count;
+ unsigned num_sinks;
+ unsigned num_sources;
+ dsp_client_t *sinks [64]; //FIXME
+
+#if defined(USE_DYNAMIC_PARALLELIZER)
+ unsigned weight;
+#else
+ int count;
+ unsigned mark;
+#endif
+};
+
+struct _dsp_master_t {
+ dsp_slave_t dsp_slaves [MAX_SLAVES];
+ atomic_bool kill;
+ atomic_bool emergency_exit;
+ sem_t sem;
+ unsigned concurrent;
+ unsigned num_slaves;
+ uint32_t nsamples;
+};
+
+struct _job_t {
+ union {
+ job_type_request_t request;
+ job_type_reply_t reply;
+ };
+ union {
+ mod_t *mod;
+ int32_t status;
+ };
+ LV2_URID urn;
+};
+
+struct _pool_t {
+ size_t size;
+ void *buf;
+};
+
+struct _app_prof_t {
+ struct timespec t0;
+ struct timespec t1;
+ unsigned sum;
+ unsigned min;
+ unsigned max;
+ unsigned count;
+};
+
+struct _mod_prof_t {
+ unsigned sum;
+ unsigned min;
+ unsigned max;
+};
+
+struct _mod_worker_t {
+ sem_t sem;
+ pthread_t thread;
+ atomic_bool kill;
+ varchunk_t *app_to_worker;
+ varchunk_t *state_to_worker;
+ varchunk_t *app_from_worker;
+};
+
+enum _auto_type_t {
+ AUTO_TYPE_NONE = 0,
+ AUTO_TYPE_MIDI,
+ AUTO_TYPE_OSC
+};
+
+struct _midi_auto_t {
+ int8_t channel;
+ int8_t controller;
+};
+
+struct _osc_auto_t {
+ char path [128]; //TODO how big?
+};
+
+struct _auto_t {
+ auto_type_t type;
+ uint32_t index;
+ LV2_URID property;
+ LV2_URID range;
+
+ int src_enabled;
+ int snk_enabled;
+
+ double a;
+ double b;
+ double c;
+ double d;
+
+ double mul;
+ double add;
+
+ bool sync;
+ bool learning;
+
+ union {
+ midi_auto_t midi;
+ osc_auto_t osc;
+ };
+};
+
+struct _mod_t {
+ sp_app_t *app;
+ int32_t uid;
+ urn_uuid_t urn_uri;
+ LV2_URID urn;
+ LV2_URID visible;
+ bool disabled;
+
+ bool delete_request;
+ bool needs_bypassing;
+ bool bypassed;
+
+ // worker
+ struct {
+ const LV2_Worker_Interface *iface;
+ LV2_Worker_Schedule schedule;
+ } worker;
+
+ mod_worker_t mod_worker;
+
+ LV2_Worker_Schedule state_worker;
+ LV2_Feature state_feature_list [1];
+ LV2_Feature *state_features [2];
+
+ // system_port
+ bool system_ports;
+
+ // log
+ LV2_Log_Log log;
+
+ // make_path
+ LV2_State_Make_Path make_path;
+
+ struct {
+ const LV2_Inline_Display_Interface *iface;
+ const LV2_Inline_Display_Image_Surface *surf;
+ LV2_Inline_Display queue_draw;
+ atomic_bool draw_queued;
+ atomic_flag lock;
+ bool subscribed;
+ uint32_t counter;
+ uint32_t threshold;
+ } idisp;
+
+ // opts
+ struct {
+ LV2_Options_Option options [7];
+ const LV2_Options_Interface *iface;
+ } opts;
+
+ // state
+ struct {
+ const LV2_State_Interface *iface;
+ } state;
+
+ // features
+ LV2_Feature feature_list [NUM_FEATURES];
+ const LV2_Feature *features [NUM_FEATURES + 1];
+
+ // self
+ const LilvPlugin *plug;
+ LV2_URID plug_urid;
+ LilvInstance *inst;
+ LV2_Handle handle;
+ LilvNodes *presets;
+ char *uri_str;
+
+ // ports
+ unsigned num_ports;
+ port_t *ports;
+
+ pool_t pools [PORT_TYPE_NUM];
+ mod_prof_t prof;
+
+ dsp_client_t dsp_client;
+
+ struct {
+ float x;
+ float y;
+ } pos;
+
+ char alias [ALIAS_MAX];
+ LV2_URID ui;
+ auto_t automations [MAX_AUTOMATIONS];
+};
+
+struct _port_driver_t {
+ port_multiplex_cb_t multiplex;
+ port_transfer_cb_t transfer;
+ bool sparse_update;
+};
+
+struct _source_t {
+ port_t *port;
+ float gain;
+ struct {
+ float x;
+ float y;
+ } pos;
+
+ // ramping
+ struct {
+ int can;
+ int samples;
+ ramp_state_t state;
+ float value;
+ } ramp;
+};
+
+typedef struct _connectable_t connectable_t;
+typedef struct _control_port_t control_port_t;
+typedef struct _audio_port_t audio_port_t;
+typedef struct _cv_port_t cv_port_t;
+typedef struct _atom_port_t atom_port_t;
+
+struct _connectable_t {
+ int num_sources;
+ source_t sources [MAX_SOURCES];
+};
+
+struct _control_port_t {
+ bool is_integer;
+ bool is_toggled;
+
+ float dflt;
+ float min;
+ float max;
+ float range;
+ float range_1;
+ float last;
+ int32_t i32;
+ float f32;
+ bool auto_dirty;
+
+ float stash;
+ bool stashing;
+ atomic_flag lock;
+};
+
+struct _audio_port_t {
+ connectable_t connectable;
+ float last;
+};
+
+struct _cv_port_t {
+ connectable_t connectable;
+ float last;
+};
+
+struct _atom_port_t {
+ connectable_t connectable;
+ port_buffer_type_t buffer_type; // none, sequence
+ bool patchable; // support patch:Message
+};
+
+struct _port_t {
+ mod_t *mod;
+
+ uint32_t index;
+ const char *symbol;
+
+ size_t size;
+ void *base;
+
+ port_type_t type; // audio, CV, control, atom
+ port_direction_t direction; // input, output
+ LV2_URID protocol; // floatProtocol, peakProtocol, atomTransfer, eventTransfer
+ const port_driver_t *driver;
+
+ int subscriptions; // subsriptions reference counter
+
+ // system_port iface
+ struct {
+ system_port_t type;
+ void *data;
+ } sys;
+
+ union {
+ control_port_t control;
+ audio_port_t audio;
+ cv_port_t cv;
+ atom_port_t atom;
+ };
+};
+
+struct _sp_app_t {
+ sp_app_driver_t *driver;
+ void *data;
+
+ atomic_bool dirty;
+
+ blocking_state_t block_state;
+ silencing_state_t silence_state;
+ bool load_bundle;
+
+ struct {
+ const char *home;
+ } dir;
+
+ int embedded;
+ LilvWorld *world;
+ const LilvPlugins *plugs;
+
+ reg_t regs;
+ LV2_Atom_Forge forge;
+
+ unsigned num_mods;
+ mod_t *mods [MAX_MODS];
+
+ sp_app_system_source_t system_sources [64]; //FIXME, how many?
+ sp_app_system_sink_t system_sinks [64]; //FIXME, how many?
+
+ LV2_State_Make_Path make_path;
+ LV2_State_Map_Path map_path;
+ LV2_Feature state_feature_list [2];
+ LV2_Feature *state_features [3];
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ LV2_URI_Map_Feature uri_to_id;
+#pragma GCC diagnostic pop
+
+ char *bundle_path;
+ char *bundle_filename;
+ LV2_URID bundle_urn; //FIXME use this instead of bundle_path
+
+ struct {
+ unsigned period_cnt;
+ unsigned bound;
+ unsigned counter;
+ } fps;
+
+ int ramp_samples;
+
+ Sratom *sratom;
+ app_prof_t prof;
+
+ int32_t ncols;
+ int32_t nrows;
+ float nleft;
+
+ dsp_master_t dsp_master;
+
+ LV2_OSC_URID osc_urid;
+
+ cross_clock_t clk_mono;
+ cross_clock_t clk_real;
+
+ struct {
+ float x;
+ float y;
+ } pos;
+
+ int32_t column_enabled;
+ int32_t row_enabled;
+};
+
+extern const port_driver_t control_port_driver;
+extern const port_driver_t audio_port_driver;
+extern const port_driver_t cv_port_driver;
+extern const port_driver_t atom_port_driver;
+extern const port_driver_t seq_port_driver;
+
+#define PORT_BASE_ALIGNED(PORT) ASSUME_ALIGNED((PORT)->base)
+#define PORT_SIZE(PORT) ((PORT)->size)
+
+/*
+ * Debug
+ */
+int __attribute__((format(printf, 2, 3)))
+sp_app_log_error(sp_app_t *app, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+sp_app_log_note(sp_app_t *app, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+sp_app_log_warning(sp_app_t *app, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+sp_app_log_trace(sp_app_t *app, const char *fmt, ...);
+
+/*
+ * UI
+ */
+static inline void *
+__sp_app_to_ui_request(sp_app_t *app, size_t minimum, size_t *maximum)
+{
+ if(app->driver->to_ui_request)
+ return app->driver->to_ui_request(minimum, maximum, app->data);
+
+ sp_app_log_trace(app, "%s: failed to request buffer\n", __func__);
+ return NULL;
+}
+#define _sp_app_to_ui_request(APP, MINIMUM) \
+ ASSUME_ALIGNED(__sp_app_to_ui_request((APP), (MINIMUM), NULL))
+#define _sp_app_to_ui_request_max(APP, MINIMUM, MAXIMUM) \
+ ASSUME_ALIGNED(__sp_app_to_ui_request((APP), (MINIMUM), (MAXIMUM)))
+
+static inline void
+_sp_app_to_ui_advance(sp_app_t *app, size_t written)
+{
+ if(app->driver->to_ui_advance)
+ app->driver->to_ui_advance(written, app->data);
+ else
+ sp_app_log_trace(app, "%s: failed to advance buffer\n", __func__);
+}
+
+static inline LV2_Atom *
+_sp_app_to_ui_request_atom(sp_app_t *app)
+{
+ size_t maximum;
+ LV2_Atom *atom = _sp_app_to_ui_request_max(app, 4096, &maximum); //FIXME what should minimum be?
+
+ if(atom)
+ lv2_atom_forge_set_buffer(&app->forge, (uint8_t *)atom, maximum);
+ else
+ sp_app_log_trace(app, "%s: failed to request atom\n", __func__);
+
+ return atom;
+}
+
+static inline void
+_sp_app_to_ui_advance_atom(sp_app_t *app, const LV2_Atom *atom)
+{
+ _sp_app_to_ui_advance(app, lv2_atom_total_size(atom));
+}
+
+static inline void
+_sp_app_to_ui_overflow(sp_app_t *app)
+{
+ sp_app_log_trace(app, "%s: buffer overflow\n", __func__);
+}
+
+static inline LV2_Atom *
+_sp_request_atom(sp_app_t *app, sp_to_request_t req, void *data)
+{
+ size_t maximum;
+ LV2_Atom *atom = req(4096, &maximum, data); //FIXME what should minimum be?
+
+ if(atom)
+ lv2_atom_forge_set_buffer(&app->forge, (uint8_t *)atom, maximum);
+ else
+ sp_app_log_trace(app, "%s: failed to request atom\n", __func__);
+
+ return atom;
+}
+
+static inline void
+_sp_advance_atom(sp_app_t *app, const LV2_Atom *atom, sp_to_advance_t adv, void *data)
+{
+ adv(lv2_atom_total_size(atom), data);
+}
+
+/*
+ * Worker
+ */
+
+static inline void *
+__sp_app_to_worker_request(sp_app_t *app, size_t minimum, size_t *maximum)
+{
+ if(app->driver->to_worker_request)
+ return app->driver->to_worker_request(minimum, maximum, app->data);
+
+ sp_app_log_trace(app, "%s: failed to request buffer\n", __func__);
+ return NULL;
+}
+#define _sp_app_to_worker_request(APP, MINIMUM) \
+ ASSUME_ALIGNED(__sp_app_to_worker_request((APP), (MINIMUM), NULL))
+#define _sp_app_to_worker_request_max(APP, MINIMUM, MAXIMUM) \
+ ASSUME_ALIGNED(__sp_app_to_worker_request((APP), (MINIMUM), (MAXIMUM)))
+
+static inline void
+_sp_app_to_worker_advance(sp_app_t *app, size_t written)
+{
+ if(app->driver->to_worker_advance)
+ app->driver->to_worker_advance(written, app->data);
+ else
+ sp_app_log_trace(app, "%s: failed to advance buffer\n", __func__);
+}
+
+void
+_sp_app_order(sp_app_t *app);
+
+void
+_sp_app_reset(sp_app_t *app);
+
+void
+_sp_app_populate(sp_app_t *app);
+
+/*
+ * State
+ */
+void
+_sp_app_state_preset_restore(sp_app_t *app, mod_t *mod, LilvState *const state,
+ bool async);
+
+int
+_sp_app_state_preset_load(sp_app_t *app, mod_t *mod, const char *uri, bool async);
+
+LilvState *
+_sp_app_state_preset_create(sp_app_t *app, mod_t *mod, const char *bndl);
+
+int
+_sp_app_state_preset_save(sp_app_t *app, mod_t *mod, const char *target);
+
+int
+_sp_app_state_bundle_save(sp_app_t *app, const char *bundle_path);
+
+int
+_sp_app_state_bundle_load(sp_app_t *app, const char *bundle_path);
+
+/*
+ * Mod
+ */
+mod_t *
+_sp_app_mod_add(sp_app_t *app, const char *uri, LV2_URID urn);
+
+void
+_sp_app_mod_eject(sp_app_t *app, mod_t *mod);
+
+int
+_sp_app_mod_del(sp_app_t *app, mod_t *mod);
+
+mod_t *
+_sp_app_mod_get_by_uid(sp_app_t *app, int32_t uid);
+
+void
+_sp_app_mod_reinitialize(mod_t *mod);
+
+LV2_Worker_Status
+_sp_app_mod_worker_work_sync(mod_t *mod, size_t size, const void *payload);
+
+void
+_sp_app_mod_queue_draw(mod_t *mod);
+
+void
+_sp_app_mod_reinstantiate(sp_app_t *app, mod_t *mod);
+
+/*
+ * Port
+ */
+void
+_dsp_master_reorder(sp_app_t *app);
+
+void
+_sp_app_port_disconnect(sp_app_t *app, port_t *src_port, port_t *snk_port);
+
+int
+_sp_app_port_disconnect_request(sp_app_t *app, port_t *src_port, port_t *snk_port,
+ ramp_state_t ramp_state);
+
+bool
+_sp_app_port_connected(port_t *src_port, port_t *snk_port, float gain);
+
+int
+_sp_app_port_connect(sp_app_t *app, port_t *src_port, port_t *snk_port, float gain);
+
+int
+_sp_app_port_silence_request(sp_app_t *app, port_t *src_port, port_t *snk_port,
+ ramp_state_t ramp_state);
+
+void
+_sp_app_port_control_stash(port_t *port);
+
+int
+_sp_app_port_desilence(sp_app_t *app, port_t *src_port, port_t *snk_port);
+
+connectable_t *
+_sp_app_port_connectable(port_t *src_port);
+
+static inline void
+_sp_app_port_spin_lock(control_port_t *control)
+{
+ while(atomic_flag_test_and_set_explicit(&control->lock, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_sp_app_port_try_lock(control_port_t *control)
+{
+ return atomic_flag_test_and_set_explicit(&control->lock, memory_order_acquire) == false;
+}
+
+static inline void
+_sp_app_port_unlock(control_port_t *control)
+{
+ atomic_flag_clear_explicit(&control->lock, memory_order_release);
+}
+
+/*
+ * Ui
+ */
+void
+_sp_app_ui_set_modlist(sp_app_t *app, LV2_URID subj, int32_t seqn);
+
+void
+_connection_list_add(sp_app_t *app, const LV2_Atom_Object *obj);
+
+void
+_node_list_add(sp_app_t *app, const LV2_Atom_Object *obj);
+
+void
+_automation_refresh_mul_add(auto_t *automation);
+
+void
+_automation_list_add(sp_app_t *app, const LV2_Atom_Object *obj);
+
+LV2_Atom_Forge_Ref
+_sp_app_forge_midi_automation(sp_app_t *app, LV2_Atom_Forge_Frame *frame,
+ mod_t *mod, port_t *port, const auto_t *automation);
+
+LV2_Atom_Forge_Ref
+_sp_app_forge_osc_automation(sp_app_t *app, LV2_Atom_Forge_Frame *frame,
+ mod_t *mod, port_t *port, const auto_t *automation);
+
+#endif
diff --git a/app/synthpod_app_state.c b/app/synthpod_app_state.c
new file mode 100644
index 00000000..224e7829
--- /dev/null
+++ b/app/synthpod_app_state.c
@@ -0,0 +1,1822 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+
+#include <synthpod_app_private.h>
+
+typedef struct _atom_ser_t atom_ser_t;
+
+struct _atom_ser_t {
+ uint32_t size;
+ uint8_t *buf;
+ uint32_t offset;
+};
+
+__non_realtime static char *
+_abstract_path(LV2_State_Map_Path_Handle instance, const char *absolute_path)
+{
+ const char *prefix_path = instance;
+
+ const char *offset = absolute_path + strlen(prefix_path) + 1; // + 'file://' '/'
+
+ return strdup(offset);
+}
+
+__non_realtime static char *
+_absolute_path(LV2_State_Map_Path_Handle instance, const char *abstract_path)
+{
+ const char *prefix_path = instance;
+
+ char *absolute_path = NULL;
+ asprintf(&absolute_path, "%s/%s", prefix_path, abstract_path);
+
+ return absolute_path;
+}
+
+__non_realtime static char *
+_make_path(LV2_State_Make_Path_Handle instance, const char *abstract_path)
+{
+ char *absolute_path = _absolute_path(instance, abstract_path);
+
+ // create leading directory tree, e.g. up to last '/'
+ if(absolute_path)
+ {
+ const char *end = strrchr(absolute_path, '/');
+ if(end)
+ {
+ char *path = strndup(absolute_path, end - absolute_path);
+ if(path)
+ {
+ mkpath(path);
+
+ free(path);
+ }
+ }
+ }
+
+ return absolute_path;
+}
+
+static LV2_Worker_Status
+_preset_schedule_work_async(LV2_Worker_Schedule_Handle instance, uint32_t size, const void *data)
+{
+ mod_t *mod = instance;
+ mod_worker_t *mod_worker = &mod->mod_worker;
+
+ void *target;
+ if((target = varchunk_write_request(mod_worker->state_to_worker, size)))
+ {
+ memcpy(target, data, size);
+ varchunk_write_advance(mod_worker->state_to_worker, size);
+ sem_post(&mod_worker->sem);
+
+ return LV2_WORKER_SUCCESS;
+ }
+ else
+ {
+ sp_app_log_error(mod->app, "%s: failed to request buffer\n", __func__);
+ }
+
+ return LV2_WORKER_ERR_NO_SPACE;
+}
+
+static LV2_Worker_Status
+_preset_schedule_work_sync(LV2_Worker_Schedule_Handle instance, uint32_t size, const void *data)
+{
+ mod_t *mod = instance;
+
+ // call work:work synchronously
+ return _sp_app_mod_worker_work_sync(mod, size, data);
+}
+
+static const LV2_Feature *const *
+_preset_features(mod_t *mod, bool async)
+{
+ mod->state_worker.handle = mod;
+ mod->state_worker.schedule_work = async
+ ? _preset_schedule_work_async
+ : _preset_schedule_work_sync; // for state:loadDefaultState
+
+ mod->state_feature_list[0].URI = LV2_WORKER__schedule;
+ mod->state_feature_list[0].data = &mod->state_worker;
+
+ mod->state_features[0] = &mod->state_feature_list[0];
+ mod->state_features[1] = NULL;
+
+ return (const LV2_Feature *const *)mod->state_features;
+}
+
+const LV2_Feature *const *
+sp_app_state_features(sp_app_t *app, void *prefix_path)
+{
+ // construct LV2 state features
+ app->make_path.handle = prefix_path;
+ app->make_path.path = _make_path;
+
+ app->map_path.handle = prefix_path;
+ app->map_path.abstract_path = _abstract_path;
+ app->map_path.absolute_path = _absolute_path;
+
+ app->state_feature_list[0].URI = LV2_STATE__makePath;
+ app->state_feature_list[0].data = &app->make_path;
+
+ app->state_feature_list[1].URI = LV2_STATE__mapPath;
+ app->state_feature_list[1].data = &app->map_path;
+
+ app->state_features[0] = &app->state_feature_list[0];
+ app->state_features[1] = &app->state_feature_list[1];
+ app->state_features[2] = NULL;
+
+ return (const LV2_Feature *const *)app->state_features;
+}
+
+__non_realtime static void
+_state_set_value(const char *symbol, void *data,
+ const void *value, uint32_t size, uint32_t type)
+{
+ mod_t *mod = data;
+ sp_app_t *app = mod->app;
+
+ LilvNode *symbol_uri = lilv_new_string(app->world, symbol);
+ if(!symbol_uri)
+ {
+ sp_app_log_error(app, "%s: invalid symbol\n", __func__);
+ return;
+ }
+
+ const LilvPort *port = lilv_plugin_get_port_by_symbol(mod->plug, symbol_uri);
+ lilv_node_free(symbol_uri);
+ if(!port)
+ {
+ sp_app_log_error(app, "%s: failed to get port by symbol\n", __func__);
+ return;
+ }
+
+ uint32_t index = lilv_port_get_index(mod->plug, port);
+ port_t *tar = &mod->ports[index];
+
+ float val = 0.f;
+
+ if( (type == app->forge.Int) && (size == sizeof(int32_t)) )
+ val = *(const int32_t *)value;
+ else if( (type == app->forge.Long) && (size == sizeof(int64_t)) )
+ val = *(const int64_t *)value;
+ else if( (type == app->forge.Float) && (size == sizeof(float)) )
+ val = *(const float *)value;
+ else if( (type == app->forge.Double) && (size == sizeof(double)) )
+ val = *(const double *)value;
+ else if( (type == app->forge.Bool) && (size == sizeof(int32_t)) )
+ val = *(const int32_t *)value;
+ else
+ {
+ sp_app_log_error(app, "%s: value of unknown type\n", __func__);
+ return;
+ }
+
+ if(tar->type == PORT_TYPE_CONTROL)
+ {
+ control_port_t *control = &tar->control;
+
+ // FIXME not rt-safe
+ float *buf_ptr = PORT_BASE_ALIGNED(tar);
+ *buf_ptr = val;
+ control->last = tar->subscriptions
+ ? val - 0.1 // trigger notification
+ : val; // don't trigger any notifications
+ control->auto_dirty = true; // trigger output automation
+ // FIXME not rt-safe
+
+ _sp_app_port_spin_lock(control);
+ control->stash = val;
+ _sp_app_port_unlock(control);
+ }
+ else if(tar->type == PORT_TYPE_CV)
+ {
+ cv_port_t *cv = &tar->cv;
+
+ // FIXME not rt-safe
+ float *buf_ptr = PORT_BASE_ALIGNED(tar);
+ for(unsigned i=0; i<app->driver->max_block_size; i++)
+ {
+ buf_ptr[i] = val;
+ }
+ // FIXME not rt-safe
+ }
+}
+
+__non_realtime static const void *
+_state_get_value(const char *symbol, void *data, uint32_t *size, uint32_t *type)
+{
+ mod_t *mod = data;
+ sp_app_t *app = mod->app;
+
+ LilvNode *symbol_uri = lilv_new_string(app->world, symbol);
+ if(!symbol_uri)
+ {
+ sp_app_log_error(app, "%s: failed to create symbol URI\n", __func__);
+ goto fail;
+ }
+
+ const LilvPort *port = lilv_plugin_get_port_by_symbol(mod->plug, symbol_uri);
+ lilv_node_free(symbol_uri);
+ if(!port)
+ {
+ sp_app_log_error(app, "%s: failed to get port by symbol\n", __func__);
+ goto fail;
+ }
+
+ const uint32_t index = lilv_port_get_index(mod->plug, port);
+ port_t *tar = &mod->ports[index];
+
+ if( (tar->direction == PORT_DIRECTION_INPUT)
+ && (tar->type == PORT_TYPE_CONTROL) )
+ {
+ control_port_t *control = &tar->control;
+
+ _sp_app_port_spin_lock(control); // concurrent acess from worker and rt thread
+ const float stash = control->stash;
+ _sp_app_port_unlock(control);
+
+ const void *ptr = NULL;
+ if(control->is_toggled)
+ {
+ *size = sizeof(int32_t);
+ *type = app->forge.Bool;
+ control->i32 = floor(stash);
+ ptr = &control->i32;
+ }
+ else if(control->is_integer)
+ {
+ *size = sizeof(int32_t);
+ *type = app->forge.Int;
+ control->i32 = floor(stash);
+ ptr = &control->i32;
+ }
+ else // float
+ {
+ *size = sizeof(float);
+ *type = app->forge.Float;
+ control->f32 = stash;
+ ptr = &control->f32;
+ }
+
+ return ptr;
+ }
+
+fail:
+ *size = 0;
+ *type = 0;
+ return NULL;
+}
+
+void
+_sp_app_state_preset_restore(sp_app_t *app, mod_t *mod, LilvState *const state,
+ bool async)
+{
+ lilv_state_restore(state, mod->inst, _state_set_value, mod,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, _preset_features(mod, async));
+}
+
+int
+_sp_app_state_preset_load(sp_app_t *app, mod_t *mod, const char *uri, bool async)
+{
+ LilvNode *preset = lilv_new_uri(app->world, uri);
+
+ if(!preset) // preset not existing
+ {
+ sp_app_log_error(app, "%s: failed to create preset URI\n", __func__);
+ return -1;
+ }
+
+ // load preset resource
+ lilv_world_load_resource(app->world, preset);
+
+ // load preset
+ LilvState *state = lilv_state_new_from_world(app->world, app->driver->map,
+ preset);
+
+ // unload preset resource
+ lilv_world_unload_resource(app->world, preset);
+
+ // free preset node
+ lilv_node_free(preset);
+
+ if(!state)
+ {
+ sp_app_log_error(app, "%s: failed to get state from world\n", __func__);
+ return -1;
+ }
+
+ _sp_app_state_preset_restore(app, mod, state, async);
+
+ lilv_state_free(state);
+
+ return 0; // success
+}
+
+LilvState *
+_sp_app_state_preset_create(sp_app_t *app, mod_t *mod, const char *bndl)
+{
+ LilvState *const state = lilv_state_new_from_instance(mod->plug, mod->inst,
+ app->driver->map, bndl, bndl, bndl, bndl,
+ _state_get_value, mod, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
+ NULL);
+
+ return state;
+}
+
+int
+_sp_app_state_preset_save(sp_app_t *app, mod_t *mod, const char *uri)
+{
+ const LilvNode *name_node = lilv_plugin_get_name(mod->plug);
+ if(!name_node)
+ {
+ sp_app_log_error(app, "%s: failed to create preset URI\n", __func__);
+ return -1;
+ }
+
+ const char *home = getenv("HOME");
+ if(!home)
+ {
+ sp_app_log_error(app, "%s: failed to get HOME from environment\n", __func__);
+ return -1;
+ }
+
+ const char *mod_label = lilv_node_as_string(name_node);
+ char *prefix_path;
+ if(asprintf(&prefix_path, "file://%s/.lv2/%s_", home, mod_label) == -1)
+ prefix_path = NULL;
+
+ if(prefix_path)
+ {
+ // replace white space with underline
+ const char *whitespace = " \t\r\n";
+ for(char *c = strpbrk(prefix_path, whitespace); c; c = strpbrk(c, whitespace))
+ *c = '_';
+ }
+
+ const char *bndl = !strncmp(uri, "file://", 7)
+ ? uri + 7
+ : uri;
+
+ const char *target = prefix_path && !strncmp(uri, prefix_path, strlen(prefix_path))
+ ? uri + strlen(prefix_path)
+ : uri;
+
+ char *dest = strdup(target);
+ if(dest)
+ {
+ char *term = strstr(dest, ".preset.lv2");
+ if(term)
+ *term = '\0';
+
+ const char underline = '_';
+ for(char *c = strchr(dest, underline); c; c = strchr(c, underline))
+ *c = ' ';
+ }
+
+ mkpath((char *)uri);
+
+ sp_app_log_note(app, "%s: preset save: <%s> as %s\n",
+ __func__, uri, dest ? dest : target);
+
+ LilvState *const state = _sp_app_state_preset_create(app, mod, bndl);
+
+ if(state)
+ {
+ // set preset label
+ lilv_state_set_label(state, dest ? dest : target);
+
+ /*FIXME for lilv 0.24
+ const char *comment = "this is a comment";
+ lilv_state_set_metadata(state, app->regs.rdfs.comment.urid,
+ comment, strlen(comment)+1, app->forge.String,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const char *license = "http://opensource.org/licenses/Artistic-2.0";
+ lilv_state_set_metadata(state, app->regs.doap.license.urid,
+ license, strlen(license)+1, app->forge.URI,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ const LV2_URID bank = app->driver->map->map(app->driver->map->handle,
+ "http://open-music-kontrollers.ch/banks#Bank1");
+ lilv_state_set_metadata(state, app->regs.pset.preset_bank.urid,
+ &bank, sizeof(LV2_URID), app->forge.URID,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ */
+
+ // actually save the state to disk
+ lilv_state_save(app->world, app->driver->map, app->driver->unmap,
+ state, NULL, bndl, "state.ttl");
+ lilv_state_free(state);
+
+ // reload presets for this module
+ mod->presets = _preset_reload(app->world, &app->regs, mod->plug,
+ mod->presets, bndl);
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: failed to create state from instance\n", __func__);
+ }
+
+ // cleanup
+ if(prefix_path)
+ free(prefix_path);
+ if(dest)
+ free(dest);
+
+ return 0; // success
+}
+
+#define CUINT8(str) ((const uint8_t *)(str))
+
+static char *
+synthpod_to_turtle(Sratom* sratom, LV2_URID_Unmap* unmap,
+ uint32_t type, uint32_t size, const void *body)
+{
+ const char* base_uri = "file:///tmp/base/";
+ SerdURI buri = SERD_URI_NULL;
+ SerdNode base = serd_node_new_uri_from_string(CUINT8(base_uri), NULL, &buri);
+ SerdEnv *env = serd_env_new(&base);
+ if(env)
+ {
+ SerdChunk str = { .buf = NULL, .len = 0 };
+
+ serd_env_set_prefix_from_strings(env, CUINT8("midi"), CUINT8(LV2_MIDI_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("atom"), CUINT8(LV2_ATOM_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("rdf"), CUINT8(RDF_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("xsd"), CUINT8(XSD_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("rdfs"), CUINT8(RDFS_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("lv2"), CUINT8(LV2_CORE_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("pset"), CUINT8(LV2_PRESETS_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("param"), CUINT8(LV2_PARAMETERS_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("state"), CUINT8(LV2_STATE_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("ui"), CUINT8(LV2_UI_PREFIX));
+ serd_env_set_prefix_from_strings(env, CUINT8("spod"), CUINT8(SPOD_PREFIX));
+
+ SerdWriter *writer = serd_writer_new(SERD_TURTLE,
+ SERD_STYLE_ABBREVIATED | SERD_STYLE_RESOLVED | SERD_STYLE_CURIED,
+ env, &buri, serd_chunk_sink, &str);
+
+ if(writer)
+ {
+ // Write @prefix directives
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+
+ sratom_set_sink(sratom, NULL,
+ (SerdStatementSink)serd_writer_write_statement,
+ (SerdEndSink)serd_writer_end_anon,
+ writer);
+ sratom_write(sratom, unmap, SERD_EMPTY_S, NULL, NULL, 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);
+ }
+ serd_env_free(env);
+ }
+ serd_node_free(&base);
+
+ return NULL;
+}
+
+static inline void
+_serialize_to_turtle(Sratom *sratom, LV2_URID_Unmap *unmap, const LV2_Atom *atom, const char *path)
+{
+ FILE *f = fopen(path, "wb");
+ if(f)
+ {
+ char *ttl = synthpod_to_turtle(sratom, unmap,
+ atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+
+ if(ttl)
+ {
+ fprintf(f, "%s", ttl);
+ free(ttl);
+ }
+
+ fclose(f);
+ }
+}
+
+static inline LV2_Atom_Object *
+_deserialize_from_turtle(Sratom *sratom, LV2_URID_Unmap *unmap, const char *path)
+{
+ LV2_Atom_Object *obj = NULL;
+
+ FILE *f = fopen(path, "rb");
+ if(f)
+ {
+ fseek(f, 0, SEEK_END);
+ long fsize = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ char *ttl = malloc(fsize + 1);
+ if(ttl)
+ {
+ if(fread(ttl, fsize, 1, f) == 1)
+ {
+ ttl[fsize] = 0;
+
+ const char* base_uri = "file:///tmp/base/";
+
+ SerdNode s = serd_node_from_string(SERD_URI, CUINT8(""));
+ SerdNode p = serd_node_from_string(SERD_URI, CUINT8(LV2_STATE__state));
+ obj = (LV2_Atom_Object *)sratom_from_turtle(sratom, base_uri, &s, &p, ttl);
+ }
+
+ free(ttl);
+ }
+
+ fclose(f);
+ }
+
+ return obj;
+}
+
+#undef CUINT8
+
+// non-rt / rt
+__non_realtime static LV2_State_Status
+_state_store(LV2_State_Handle state, uint32_t key, const void *value,
+ size_t size, uint32_t type, uint32_t flags)
+{
+ LV2_Atom_Forge *forge = state;
+
+ if(!(flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)))
+ return LV2_STATE_ERR_BAD_FLAGS;
+
+ if( lv2_atom_forge_key(forge, key)
+ && lv2_atom_forge_atom(forge, size, type)
+ && lv2_atom_forge_raw(forge, value, size) )
+ {
+ lv2_atom_forge_pad(forge, size);
+ return LV2_STATE_SUCCESS;
+ }
+
+ return LV2_STATE_ERR_UNKNOWN;
+}
+
+__non_realtime const void *
+sp_app_state_retrieve(LV2_State_Handle state, uint32_t key, size_t *size,
+ uint32_t *type, uint32_t *flags)
+{
+ const LV2_Atom_Object *obj = state;
+
+ const LV2_Atom *atom = NULL;
+ lv2_atom_object_get(obj,
+ key, &atom,
+ 0);
+
+ if(atom)
+ {
+ *size = atom->size;
+ *type = atom->type;
+ *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE;
+ return LV2_ATOM_BODY_CONST(atom);
+ }
+
+ *size = 0;
+ *type = 0;
+ *flags = 0;
+ return NULL;
+}
+
+static void
+_toggle_dirty(sp_app_t *app)
+{
+ while(true)
+ {
+ bool expected = false;
+ const bool desired = true;
+ if(atomic_compare_exchange_weak(&app->dirty, &expected, desired))
+ break;
+ }
+}
+
+int
+_sp_app_state_bundle_load(sp_app_t *app, const char *bundle_path)
+{
+ //printf("_bundle_load: %s\n", bundle_path);
+
+ if(!app->sratom)
+ {
+ sp_app_log_error(app, "%s: invalid sratom\n", __func__);
+ return -1;
+ }
+
+ if(app->bundle_path)
+ free(app->bundle_path);
+
+ app->bundle_path = strdup(bundle_path);
+ if(!app->bundle_path)
+ {
+ sp_app_log_error(app, "%s: path duplication failed\n", __func__);
+ return -1;
+ }
+
+ char *state_dst = _make_path(app->bundle_path, "state.ttl");
+ if(!state_dst)
+ {
+ sp_app_log_error(app, "%s: _make_path failed\n", __func__);
+ return -1;
+ }
+
+ LV2_Atom_Object *obj = _deserialize_from_turtle(app->sratom, app->driver->unmap, state_dst);
+ if(obj) // existing project
+ {
+ // restore state
+ sp_app_restore(app, sp_app_state_retrieve, obj,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
+ sp_app_state_features(app, app->bundle_path));
+
+ free(obj); // allocated by _deserialize_from_turtle
+ }
+ else if(!strcmp(bundle_path, SYNTHPOD_PREFIX"stereo")) // new project from UI
+ {
+ _sp_app_reset(app);
+ _sp_app_populate(app);
+ _toggle_dirty(app);
+ }
+ else // new project from CLI
+ {
+ _toggle_dirty(app);
+ }
+
+ free(state_dst);
+
+ return 0; // success
+}
+
+__non_realtime static inline LV2_Atom_Forge_Ref
+_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size)
+{
+ atom_ser_t *ser = handle;
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+
+ const uint32_t new_offset = ser->offset + size;
+ if(new_offset > ser->size)
+ {
+ uint32_t new_size = ser->size << 1;
+ while(new_offset > new_size)
+ new_size <<= 1;
+
+ if(!(ser->buf = realloc(ser->buf, new_size)))
+ return 0; // realloc failed
+
+ ser->size = new_size;
+ }
+
+ memcpy(ser->buf + ser->offset, buf, size);
+ ser->offset = new_offset;
+
+ return ref;
+}
+
+__non_realtime static inline LV2_Atom *
+_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ atom_ser_t *ser = handle;
+
+ const uint32_t offset = ref - 1;
+
+ return (LV2_Atom *)(ser->buf + offset);
+}
+
+int
+_sp_app_state_bundle_save(sp_app_t *app, const char *bundle_path)
+{
+ //printf("_bundle_save: %s\n", bundle_path);
+
+ if(app->bundle_path)
+ free(app->bundle_path);
+
+ app->bundle_path = strdup(bundle_path);
+ if(!app->bundle_path)
+ {
+ sp_app_log_error(app, "%s: path duplication failed\n", __func__);
+ return -1;
+ }
+
+ char *manifest_dst = _make_path(app->bundle_path, "manifest.ttl");
+ char *state_dst = _make_path(app->bundle_path, "state.ttl");
+ if(manifest_dst && state_dst)
+ {
+ // create temporary forge
+ LV2_Atom_Forge _forge;
+ LV2_Atom_Forge *forge = &_forge;
+ memcpy(forge, &app->forge, sizeof(LV2_Atom_Forge));
+
+ LV2_Atom_Forge_Frame pset_frame;
+ LV2_Atom_Forge_Frame state_frame;
+
+ if(app->sratom)
+ {
+ atom_ser_t ser = { .size = 1024, .offset = 0 };
+ ser.buf = malloc(ser.size);
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ if( ser.buf
+ && lv2_atom_forge_object(forge, &pset_frame, app->regs.synthpod.state.urid, app->regs.pset.preset.urid)
+ && lv2_atom_forge_key(forge, app->regs.core.applies_to.urid)
+ && lv2_atom_forge_urid(forge, app->regs.synthpod.stereo.urid)
+ && lv2_atom_forge_key(forge, app->regs.core.applies_to.urid)
+ && lv2_atom_forge_urid(forge, app->regs.synthpod.monoatom.urid)
+ && lv2_atom_forge_key(forge, app->regs.rdfs.see_also.urid)
+ && lv2_atom_forge_urid(forge, app->regs.synthpod.state.urid) )
+ {
+ lv2_atom_forge_pop(forge, &pset_frame);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ _serialize_to_turtle(app->sratom, app->driver->unmap, atom, manifest_dst);
+ free(ser.buf);
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: forge failed\n", __func__);
+ }
+
+ ser.size = 4096;
+ ser.offset = 0;
+ ser.buf = malloc(ser.size);
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ // try to extract label from bundle path
+ char *rdfs_label = NULL;
+ const char *from = strstr(app->bundle_path, "Synthpod_Stereo");
+ const char *to = strstr(app->bundle_path, ".preset.lv2");
+ if(from && to)
+ {
+ from += 15 + 1;
+ const size_t sz = to - from;
+ rdfs_label = malloc(sz + 1);
+ if(rdfs_label)
+ {
+ strncpy(rdfs_label, from, sz);
+ rdfs_label[sz] = '\0';
+ }
+ }
+
+ if( ser.buf
+ && lv2_atom_forge_object(forge, &pset_frame, app->regs.synthpod.null.urid, app->regs.pset.preset.urid)
+ && lv2_atom_forge_key(forge, app->regs.core.applies_to.urid)
+ && lv2_atom_forge_urid(forge, app->regs.synthpod.stereo.urid)
+ && lv2_atom_forge_key(forge, app->regs.core.applies_to.urid)
+ && lv2_atom_forge_urid(forge, app->regs.synthpod.monoatom.urid)
+
+ && lv2_atom_forge_key(forge, app->regs.rdfs.label.urid)
+ && lv2_atom_forge_string(forge, rdfs_label ? rdfs_label : app->bundle_path,
+ rdfs_label ? strlen(rdfs_label) : strlen(app->bundle_path) )
+
+ && lv2_atom_forge_key(forge, app->regs.state.state.urid)
+ && lv2_atom_forge_object(forge, &state_frame, 0, 0) )
+ {
+ // store state
+ sp_app_save(app, _state_store, forge,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
+ sp_app_state_features(app, app->bundle_path));
+
+ lv2_atom_forge_pop(forge, &state_frame);
+ lv2_atom_forge_pop(forge, &pset_frame);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ _serialize_to_turtle(app->sratom, app->driver->unmap, atom, state_dst);
+ free(ser.buf);
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: forge failed\n", __func__);
+ }
+
+ if(rdfs_label)
+ free(rdfs_label);
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: invalid sratom\n", __func__);
+ }
+
+ free(manifest_dst);
+ free(state_dst);
+
+ return LV2_STATE_SUCCESS;
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: _make_path failed\n", __func__);
+ }
+
+ if(manifest_dst)
+ free(manifest_dst);
+ if(state_dst)
+ free(state_dst);
+
+ return LV2_STATE_ERR_UNKNOWN;
+}
+
+LV2_State_Status
+sp_app_save(sp_app_t *app, LV2_State_Store_Function store,
+ LV2_State_Handle hndl, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Make_Path *make_path = NULL;
+ const LV2_State_Map_Path *map_path = NULL;
+
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__makePath))
+ make_path = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ map_path = features[i]->data;
+ }
+
+ if(!make_path)
+ {
+ sp_app_log_error(app, "%s: LV2_STATE__makePath not supported\n", __func__);
+ return LV2_STATE_ERR_UNKNOWN;
+ }
+ if(!map_path)
+ {
+ sp_app_log_error(app, "%s: LV2_STATE__mapPath not supported\n", __func__);
+ return LV2_STATE_ERR_UNKNOWN;
+ }
+
+ /*FIXME
+ // cleanup state module trees
+ for(int uid=0; uid<app->uid; uid++)
+ {
+ int exists = 0;
+
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->uid == uid)
+ {
+ exists = 1;
+ break;
+ }
+ }
+
+ if(!exists)
+ {
+ char uid_str [64];
+ sprintf(uid_str, "%u", uid);
+
+ char *root_path = map_path->absolute_path(map_path->handle, uid_str);
+ if(root_path)
+ {
+ // remove whole bundle tree
+ rmrf_const(root_path);
+
+ free(root_path);
+ }
+ }
+ else
+ {
+ char uid_str [64];
+ sprintf(uid_str, "%u/manifest.ttl", uid);
+
+ char *manifest_path = map_path->absolute_path(map_path->handle, uid_str);
+ if(manifest_path)
+ {
+ remove(manifest_path);
+
+ free(manifest_path);
+ }
+ }
+ }
+*/
+
+ // store minor version
+ const int32_t minor_version = SYNTHPOD_MINOR_VERSION;
+ store(hndl, app->regs.core.minor_version.urid,
+ &minor_version, sizeof(int32_t), app->forge.Int,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ // store micro version
+ const int32_t micro_version = SYNTHPOD_MICRO_VERSION;
+ store(hndl, app->regs.core.micro_version.urid,
+ &micro_version, sizeof(int32_t), app->forge.Int,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ // create temporary forge
+ LV2_Atom_Forge _forge;
+ LV2_Atom_Forge *forge = &_forge;
+ memcpy(forge, &app->forge, sizeof(LV2_Atom_Forge));
+
+ atom_ser_t ser = { .size = 4096, .offset = 0 };
+ ser.buf = malloc(ser.size);
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ if(ser.buf)
+ {
+ // spod:moduleList
+ {
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Forge_Frame mod_list_frame;
+
+ if( (ref = lv2_atom_forge_object(forge, &mod_list_frame, 0, 0)) )
+ {
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ char dir [128];
+ snprintf(dir, sizeof(dir), "%s/", mod->urn_uri); // only save in new format
+
+ char *path = make_path->path(make_path->handle, dir);
+ if(path)
+ {
+ LilvState *const state = lilv_state_new_from_instance(mod->plug, mod->inst,
+ app->driver->map, path, path, path, path,
+ _state_get_value, mod, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, NULL);
+
+ if(state)
+ {
+ lilv_state_set_label(state, "state"); //TODO use path prefix?
+ lilv_state_save(app->world, app->driver->map, app->driver->unmap,
+ state, NULL, path, "state.ttl");
+ lilv_state_free(state);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid state\n", __func__);
+
+ free(path);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid path\n", __func__);
+
+ const LV2_URID uri_urid = app->driver->map->map(app->driver->map->handle, mod->uri_str);
+
+ LV2_Atom_Forge_Frame mod_frame;
+ if( ref
+ && lv2_atom_forge_key(forge, mod->urn)
+ && lv2_atom_forge_object(forge, &mod_frame, 0, uri_urid) )
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.module_position_x.urid)
+ && lv2_atom_forge_float(forge, mod->pos.x);
+
+ if(ref)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.module_position_y.urid)
+ && lv2_atom_forge_float(forge, mod->pos.y);
+ }
+
+ if(ref && strlen(mod->alias))
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.module_alias.urid)
+ && lv2_atom_forge_string(forge, mod->alias, strlen(mod->alias));
+ }
+
+ if(ref && mod->ui)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.ui.ui.urid)
+ && lv2_atom_forge_urid(forge, mod->ui);
+ }
+
+ if(ref && mod->visible)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.module_visible.urid)
+ && lv2_atom_forge_urid(forge, mod->visible);
+ }
+
+ if(ref && mod->disabled)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.module_disabled.urid)
+ && lv2_atom_forge_bool(forge, mod->disabled);
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &mod_frame);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid mod\n", __func__);
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &mod_list_frame);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid spod:moduleList\n", __func__);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ if(ref && atom)
+ {
+ store(hndl, app->regs.synthpod.module_list.urid,
+ LV2_ATOM_BODY_CONST(atom), atom->size, atom->type,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid ref or atom\n", __func__);
+ }
+
+ // reset ser
+ ser.offset = 0;
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ // spod:connectionList
+ {
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Forge_Frame conn_list_frame;
+
+ if( (ref = lv2_atom_forge_tuple(forge, &conn_list_frame)) )
+ {
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ for(unsigned p=0; p<mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ // serialize port connections
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ for(int j=0; j<conn->num_sources; j++)
+ {
+ source_t *source = &conn->sources[j];
+ port_t *source_port = source->port;
+
+ LV2_Atom_Forge_Frame source_frame;
+ if( ref
+ && lv2_atom_forge_object(forge, &source_frame, 0, 0) )
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.source_module.urid)
+ && lv2_atom_forge_urid(forge, source_port->mod->urn)
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_symbol.urid)
+ && lv2_atom_forge_string(forge, source_port->symbol, strlen(source_port->symbol))
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_module.urid)
+ && lv2_atom_forge_urid(forge, port->mod->urn)
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_symbol.urid)
+ && lv2_atom_forge_string(forge, port->symbol, strlen(port->symbol))
+ && lv2_atom_forge_key(forge, app->regs.param.gain.urid)
+ && lv2_atom_forge_float(forge, source->gain);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &source_frame);
+ }
+ }
+ }
+
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &conn_list_frame);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid spod:connectionList\n", __func__);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ if(ref && atom)
+ {
+ store(hndl, app->regs.synthpod.connection_list.urid,
+ LV2_ATOM_BODY_CONST(atom), atom->size, atom->type,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid ref or atom\n", __func__);
+ }
+
+ // reset ser
+ ser.offset = 0;
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ // spod:nodeList
+ {
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Forge_Frame node_list_frame;
+
+ if( (ref = lv2_atom_forge_tuple(forge, &node_list_frame)) )
+ {
+ for(unsigned m1=0; m1<app->num_mods; m1++)
+ {
+ mod_t *snk_mod = app->mods[m1];
+
+ for(unsigned m2=0; m2<app->num_mods; m2++)
+ {
+ mod_t *src_mod = app->mods[m2];
+ bool mods_are_connected = false;
+ float x = 0.f;
+ float y = 0.f;
+
+ for(unsigned p=0; p<snk_mod->num_ports; p++)
+ {
+ port_t *port = &snk_mod->ports[p];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ for(int j=0; j<conn->num_sources; j++)
+ {
+ source_t *source = &conn->sources[j];
+ port_t *src_port = source->port;
+
+ if(src_port->mod == src_mod)
+ {
+ mods_are_connected = true;
+ x = source->pos.x;
+ y = source->pos.y;
+ break;
+ }
+ }
+ }
+
+ if(mods_are_connected)
+ break;
+ }
+
+ if(mods_are_connected)
+ {
+ LV2_Atom_Forge_Frame source_frame;
+ if( ref
+ && lv2_atom_forge_object(forge, &source_frame, 0, 0) )
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.source_module.urid)
+ && lv2_atom_forge_urid(forge, src_mod->urn)
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_module.urid)
+ && lv2_atom_forge_urid(forge, snk_mod->urn)
+ && lv2_atom_forge_key(forge, app->regs.synthpod.node_position_x.urid)
+ && lv2_atom_forge_float(forge, x)
+ && lv2_atom_forge_key(forge, app->regs.synthpod.node_position_y.urid)
+ && lv2_atom_forge_float(forge, y);
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &source_frame);
+ }
+ }
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &node_list_frame);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid spod:nodeList\n", __func__);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ if(ref && atom)
+ {
+ store(hndl, app->regs.synthpod.node_list.urid,
+ LV2_ATOM_BODY_CONST(atom), atom->size, atom->type,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid ref or atom\n", __func__);
+ }
+
+ // reset ser
+ ser.offset = 0;
+ lv2_atom_forge_set_sink(forge, _sink, _deref, &ser);
+
+ // spod:automationList
+ {
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Forge_Frame conn_list_frame;
+
+ if( (ref = lv2_atom_forge_tuple(forge, &conn_list_frame)) )
+ {
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+ port_t *port = &mod->ports[automation->index];
+
+ if(automation->type == AUTO_TYPE_MIDI)
+ {
+ midi_auto_t *mauto = &automation->midi;
+
+ LV2_Atom_Forge_Frame auto_frame;
+ if( ref
+ && lv2_atom_forge_object(forge, &auto_frame, 0, app->regs.midi.Controller.urid) )
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.sink_module.urid)
+ && lv2_atom_forge_urid(forge, mod->urn)
+
+ && lv2_atom_forge_key(forge, app->regs.midi.channel.urid)
+ && lv2_atom_forge_int(forge, mauto->channel)
+
+ && lv2_atom_forge_key(forge, app->regs.midi.controller_number.urid)
+ && lv2_atom_forge_int(forge, mauto->controller)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_min.urid)
+ && lv2_atom_forge_double(forge, automation->a)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_max.urid)
+ && lv2_atom_forge_double(forge, automation->b)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_min.urid)
+ && lv2_atom_forge_double(forge, automation->c)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_max.urid)
+ && lv2_atom_forge_double(forge, automation->d)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_enabled.urid)
+ && lv2_atom_forge_bool(forge, automation->src_enabled)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_enabled.urid)
+ && lv2_atom_forge_bool(forge, automation->snk_enabled);
+
+ if(ref)
+ {
+ if(automation->property)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.patch.property.urid)
+ && lv2_atom_forge_urid(forge, automation->property)
+ && lv2_atom_forge_key(forge, app->regs.rdfs.range.urid)
+ && lv2_atom_forge_urid(forge, automation->range);
+ }
+ else
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.sink_symbol.urid)
+ && lv2_atom_forge_string(forge, port->symbol, strlen(port->symbol));
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &auto_frame);
+ }
+ }
+ else if(automation->type == AUTO_TYPE_OSC)
+ {
+ osc_auto_t *oauto = &automation->osc;
+
+ LV2_Atom_Forge_Frame auto_frame;
+ if( ref
+ && lv2_atom_forge_object(forge, &auto_frame, 0, app->regs.osc.message.urid) )
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.sink_module.urid)
+ && lv2_atom_forge_urid(forge, mod->urn)
+
+ && lv2_atom_forge_key(forge, app->regs.osc.path.urid)
+ && lv2_atom_forge_string(forge, oauto->path, strlen(oauto->path))
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_min.urid)
+ && lv2_atom_forge_double(forge, automation->a)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_max.urid)
+ && lv2_atom_forge_double(forge, automation->b)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_min.urid)
+ && lv2_atom_forge_double(forge, automation->c)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_max.urid)
+ && lv2_atom_forge_double(forge, automation->d)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.source_enabled.urid)
+ && lv2_atom_forge_bool(forge, automation->src_enabled)
+
+ && lv2_atom_forge_key(forge, app->regs.synthpod.sink_enabled.urid)
+ && lv2_atom_forge_bool(forge, automation->snk_enabled);
+
+ if(ref)
+ {
+ if(automation->property)
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.patch.property.urid)
+ && lv2_atom_forge_urid(forge, automation->property)
+ && lv2_atom_forge_key(forge, app->regs.rdfs.range.urid)
+ && lv2_atom_forge_urid(forge, automation->range);
+ }
+ else
+ {
+ ref = lv2_atom_forge_key(forge, app->regs.synthpod.sink_symbol.urid)
+ && lv2_atom_forge_string(forge, port->symbol, strlen(port->symbol));
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &auto_frame);
+ }
+ }
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &conn_list_frame);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid spod:automationList\n", __func__);
+
+ const LV2_Atom *atom = (const LV2_Atom *)ser.buf;
+ if(ref && atom)
+ {
+ store(hndl, app->regs.synthpod.automation_list.urid,
+ LV2_ATOM_BODY_CONST(atom), atom->size, atom->type,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+ }
+ else
+ sp_app_log_error(app, "%s: invalid ref or atom\n", __func__);
+ }
+
+ free(ser.buf);
+
+ // spod:graphPositionX
+ store(hndl, app->regs.synthpod.graph_position_x.urid,
+ &app->pos.x, sizeof(float), app->forge.Float,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ // spod:graphPositionY
+ store(hndl, app->regs.synthpod.graph_position_y.urid,
+ &app->pos.y, sizeof(float), app->forge.Float,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ // spod:columnEnabled
+ store(hndl, app->regs.synthpod.column_enabled.urid,
+ &app->column_enabled, sizeof(int32_t), app->forge.Bool,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ // spod:rowEnabled
+ store(hndl, app->regs.synthpod.row_enabled.urid,
+ &app->row_enabled, sizeof(int32_t), app->forge.Bool,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ return LV2_STATE_SUCCESS;
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: invalid buffer\n", __func__);
+ }
+
+ return LV2_STATE_ERR_UNKNOWN;
+}
+
+static mod_t *
+_mod_inject(sp_app_t *app, int32_t mod_uid, LV2_URID mod_urn, const LV2_Atom_Object *mod_obj,
+ const LV2_State_Map_Path *map_path)
+{
+ if( !lv2_atom_forge_is_object_type(&app->forge, mod_obj->atom.type)
+ || !mod_obj->body.otype)
+ return NULL;
+
+ const LV2_Atom_Float *mod_pos_x = NULL;
+ const LV2_Atom_Float *mod_pos_y = NULL;
+ const LV2_Atom_String *mod_alias = NULL;
+ const LV2_Atom_URID *mod_ui = NULL;
+ const LV2_Atom_Bool *mod_visible = NULL;
+ const LV2_Atom_Bool *mod_disabled = NULL;
+ lv2_atom_object_get(mod_obj,
+ app->regs.synthpod.module_position_x.urid, &mod_pos_x,
+ app->regs.synthpod.module_position_y.urid, &mod_pos_y,
+ app->regs.synthpod.module_alias.urid, &mod_alias,
+ app->regs.ui.ui.urid, &mod_ui,
+ app->regs.synthpod.module_visible.urid, &mod_visible,
+ app->regs.synthpod.module_disabled.urid, &mod_disabled,
+ 0);
+
+ const char *mod_uri_str = app->driver->unmap->unmap(app->driver->unmap->handle, mod_obj->body.otype);
+ mod_t *mod = _sp_app_mod_add(app, mod_uri_str, mod_urn);
+ if(!mod)
+ {
+ sp_app_log_error(app, "%s: _sp_app_mod_add fialed\n", __func__);
+ return NULL;
+ }
+
+ // inject module into module graph
+ app->mods[app->num_mods] = mod;
+ app->num_mods += 1;
+
+ mod->pos.x = mod_pos_x && (mod_pos_x->atom.type == app->forge.Float)
+ ? mod_pos_x->body : 0.f;
+ mod->pos.y = mod_pos_y && (mod_pos_y->atom.type == app->forge.Float)
+ ? mod_pos_y->body : 0.f;
+ mod->visible = mod_visible && (mod_visible->atom.type == app->forge.URID)
+ ? mod_visible->body : 0;
+ mod->disabled = mod_disabled && (mod_disabled->atom.type == app->forge.Bool)
+ ? mod_disabled->body : false;
+ if(mod_alias)
+ strncpy(mod->alias, LV2_ATOM_BODY_CONST(&mod_alias->atom), ALIAS_MAX - 1);
+ mod->ui = mod_ui && (mod_ui->atom.type == app->forge.URID)
+ ? mod_ui->body : 0;
+
+ mod->uid = mod_uid;
+
+ char dir [128];
+ if(mod->uid) // support for old foramt
+ snprintf(dir, sizeof(dir), "%"PRIi32"/state.ttl", mod->uid);
+ else
+ snprintf(dir, sizeof(dir), "%s/state.ttl", mod->urn_uri);
+
+ char *path = map_path->absolute_path(map_path->handle, dir);
+ if(!path)
+ {
+ sp_app_log_error(app, "%s: invaild path\n", __func__);
+ //TODO free mod
+ return NULL;
+ }
+
+ // strip 'file://'
+ const char *tmp = !strncmp(path, "file://", 7)
+ ? path + 7
+ : path;
+
+ LilvState *state = lilv_state_new_from_file(app->world,
+ app->driver->map, NULL, tmp);
+
+ if(state)
+ {
+ _sp_app_state_preset_restore(app, mod, state, false);
+ lilv_state_free(state);
+ }
+ else
+ sp_app_log_error(app, "%s: failed to load state from file\n", __func__);
+
+ free(path);
+
+ return mod;
+}
+
+LV2_Atom_Object *
+sp_app_stash(sp_app_t *app, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle hndl, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_URID keys [11] = {
+ app->regs.core.minor_version.urid,
+ app->regs.core.micro_version.urid,
+ app->regs.synthpod.module_list.urid,
+ app->regs.synthpod.connection_list.urid,
+ app->regs.synthpod.node_list.urid,
+ app->regs.synthpod.automation_list.urid,
+ app->regs.synthpod.graph.urid,
+ app->regs.synthpod.graph_position_x.urid,
+ app->regs.synthpod.graph_position_y.urid,
+ app->regs.synthpod.column_enabled.urid,
+ app->regs.synthpod.row_enabled.urid
+ };
+ const unsigned num_keys = sizeof(keys) / sizeof(LV2_URID);
+
+ uint8_t *buf = malloc(0x100000); //FIXME
+ LV2_Atom_Forge forge = app->forge;
+ lv2_atom_forge_set_buffer(&forge, buf, 0x100000);
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ ref = lv2_atom_forge_object(&forge, &frame, 0, 0);
+
+ for(unsigned i = 0; i < num_keys; i++)
+ {
+ size_t size;
+ uint32_t type;
+ uint32_t _flags;
+
+ const void *data = retrieve(hndl, keys[i], &size, &type, &_flags);
+
+ if(!data)
+ continue;
+
+ if(ref)
+ ref = lv2_atom_forge_key(&forge, keys[i]);
+ if(ref)
+ ref = lv2_atom_forge_atom(&forge, size, type);
+ if(ref)
+ ref = lv2_atom_forge_write(&forge, data, size);
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(&forge, &frame);
+
+
+ return (LV2_Atom_Object *)buf;
+}
+
+LV2_State_Status
+sp_app_restore(sp_app_t *app, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle hndl, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ map_path = features[i]->data;
+ }
+
+ if(!map_path)
+ {
+ sp_app_log_error(app, "%s: LV2_STATE__mapPath not supported\n", __func__);
+ return LV2_STATE_ERR_UNKNOWN;
+ }
+
+ size_t size;
+ uint32_t _flags;
+ uint32_t type;
+
+ // retrieve minor version
+ const int32_t *minor_version = retrieve(hndl, app->regs.core.minor_version.urid,
+ &size, &type, &_flags);
+ if(minor_version && (type == app->forge.Int) && (size == sizeof(int32_t)) )
+ {
+ //TODO check with running version
+ }
+
+ // retrieve micro version
+ const int32_t *micro_version = retrieve(hndl, app->regs.core.micro_version.urid,
+ &size, &type, &_flags);
+ if(micro_version && (type == app->forge.Int) && (size == sizeof(int32_t)) )
+ {
+ //TODO check with running version
+ }
+
+ // retrieve spod:moduleList
+ const LV2_Atom_Object_Body *mod_list_body = retrieve(hndl, app->regs.synthpod.module_list.urid,
+ &size, &type, &_flags);
+ if( mod_list_body
+ && (type == app->forge.Object)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ _sp_app_reset(app);
+
+ LV2_ATOM_OBJECT_BODY_FOREACH(mod_list_body, size, prop)
+ {
+ const int32_t mod_index = 0;
+ const LV2_URID mod_urn = prop->key;
+ LV2_Atom_Object *mod_obj = (LV2_Atom_Object *)&prop->value;
+
+ mod_t *mod = _mod_inject(app, mod_index, mod_urn, mod_obj, map_path);
+ if(!mod)
+ {
+ mod_obj->body.otype = app->regs.synthpod.placeholder.urid;
+ mod = _mod_inject(app, mod_index, mod_urn, mod_obj, map_path);
+ if(mod)
+ {
+ snprintf(mod->alias, sizeof(mod->alias), "%s", "!!! Failed to load !!!");
+ }
+ }
+ }
+
+ _sp_app_order(app);
+ }
+ else
+ sp_app_log_error(app, "%s: invaild moduleList\n", __func__);
+
+ // retrieve spod:connectionList
+ const LV2_Atom_Object_Body *conn_list_body = retrieve(hndl, app->regs.synthpod.connection_list.urid,
+ &size, &type, &_flags);
+ if( conn_list_body
+ && (type == app->forge.Tuple)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ LV2_ATOM_TUPLE_BODY_FOREACH(conn_list_body, size, item)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)item;
+
+ if(!lv2_atom_forge_is_object_type(&app->forge, obj->atom.type))
+ continue;
+
+ _connection_list_add(app, obj);
+ }
+ }
+ else
+ sp_app_log_error(app, "%s: invaild connectionList\n", __func__);
+
+ // retrieve spod:nodeList
+ const LV2_Atom_Object_Body *node_list_body = retrieve(hndl, app->regs.synthpod.node_list.urid,
+ &size, &type, &_flags);
+ if( node_list_body
+ && (type == app->forge.Tuple)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ LV2_ATOM_TUPLE_BODY_FOREACH(node_list_body, size, item)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)item;
+
+ if(!lv2_atom_forge_is_object_type(&app->forge, obj->atom.type))
+ continue;
+
+ _node_list_add(app, obj);
+ }
+ }
+ else
+ sp_app_log_error(app, "%s: invaild nodeList \n", __func__);
+
+ // retrieve spod:automationList
+ const LV2_Atom_Object_Body *auto_list_body = retrieve(hndl, app->regs.synthpod.automation_list.urid,
+ &size, &type, &_flags);
+ if( auto_list_body
+ && (type == app->forge.Tuple)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ LV2_ATOM_TUPLE_BODY_FOREACH(auto_list_body, size, item)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)item;
+
+ if(!lv2_atom_forge_is_object_type(&app->forge, obj->atom.type))
+ continue;
+
+ _automation_list_add(app, obj);
+ }
+ }
+ else
+ sp_app_log_error(app, "%s: invaild automationList\n", __func__);
+
+ // retrieve spod:graphPositionX
+ const float *graph_position_x_body = retrieve(hndl, app->regs.synthpod.graph_position_x.urid,
+ &size, &type, &_flags);
+ if( graph_position_x_body
+ && (type == app->forge.Float)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ app->pos.x = *graph_position_x_body;
+ }
+ else
+ sp_app_log_error(app, "%s: invalid graphPositionX\n", __func__);
+
+ // retrieve spod:graphPositionY
+ const float *graph_position_y_body = retrieve(hndl, app->regs.synthpod.graph_position_y.urid,
+ &size, &type, &_flags);
+ if( graph_position_y_body
+ && (type == app->forge.Float)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ app->pos.y = *graph_position_y_body;
+ }
+ else
+ sp_app_log_error(app, "%s: invalid graphPositionY\n", __func__);
+
+ // retrieve spod:columnEnabled
+ const int32_t *column_enabled = retrieve(hndl, app->regs.synthpod.column_enabled.urid,
+ &size, &type, &_flags);
+ if( column_enabled
+ && (type == app->forge.Bool)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ app->column_enabled = *column_enabled;
+ }
+ else
+ sp_app_log_error(app, "%s: invalid columnEnabled\n", __func__);
+
+ // retrieve spod:rowEnabled
+ const int32_t *row_enabled = retrieve(hndl, app->regs.synthpod.row_enabled.urid,
+ &size, &type, &_flags);
+ if( row_enabled
+ && (type == app->forge.Bool)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ app->row_enabled = *row_enabled;
+ }
+ else
+ sp_app_log_error(app, "%s: invalid rowEnabled\n", __func__);
+
+ // retrieve spod:graph // XXX old save format
+ const LV2_Atom_Object_Body *graph_body = retrieve(hndl, app->regs.synthpod.graph.urid,
+ &size, &type, &_flags);
+ if( graph_body
+ && (type == app->forge.Tuple)
+ && (_flags & (LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE)) )
+ {
+ _sp_app_reset(app);
+
+ LV2_ATOM_TUPLE_BODY_FOREACH(graph_body, size, mod_item)
+ {
+ LV2_Atom_Object *mod_obj = (LV2_Atom_Object *)mod_item;
+
+ if( !lv2_atom_forge_is_object_type(&app->forge, mod_obj->atom.type)
+ || !mod_obj->body.otype)
+ continue;
+
+ const LV2_Atom_Int *mod_index = NULL;
+ lv2_atom_object_get(mod_obj,
+ app->regs.core.index.urid, &mod_index,
+ 0);
+
+ if(!mod_index || (mod_index->atom.type != app->forge.Int) )
+ continue;
+
+ const LV2_URID mod_urn = 0;
+ mod_t *mod = _mod_inject(app, mod_index->body, mod_urn, mod_obj, map_path);
+ if(!mod)
+ {
+ mod_obj->body.otype = app->regs.synthpod.placeholder.urid;
+ mod = _mod_inject(app, mod_index->body, mod_urn, mod_obj, map_path);
+ if(mod)
+ {
+ snprintf(mod->alias, sizeof(mod->alias), "%s", "!!! Failed to load !!!");
+ }
+ }
+ }
+
+ _sp_app_order(app);
+
+ LV2_ATOM_TUPLE_BODY_FOREACH(graph_body, size, mod_item)
+ {
+ const LV2_Atom_Object *mod_obj = (const LV2_Atom_Object *)mod_item;
+
+ if( !lv2_atom_forge_is_object_type(&app->forge, mod_obj->atom.type)
+ || !mod_obj->body.otype)
+ continue;
+
+ const LV2_Atom_Int *mod_index = NULL;
+ lv2_atom_object_get(mod_obj,
+ app->regs.core.index.urid, &mod_index,
+ 0);
+
+ if(!mod_index || (mod_index->atom.type != app->forge.Int) )
+ continue;
+
+ mod_t *mod = _sp_app_mod_get_by_uid(app, mod_index->body);
+ if(!mod)
+ continue;
+
+ LV2_ATOM_OBJECT_FOREACH(mod_obj, item)
+ {
+ const LV2_Atom_Object *port_obj = (const LV2_Atom_Object *)&item->value;
+
+ if( (item->key != app->regs.core.port.urid)
+ || !lv2_atom_forge_is_object_type(&app->forge, port_obj->atom.type)
+ || (port_obj->body.otype != app->regs.core.Port.urid) )
+ continue;
+
+ const LV2_Atom_String *port_symbol = NULL;
+ lv2_atom_object_get(port_obj,
+ app->regs.core.symbol.urid, &port_symbol,
+ 0);
+
+ if(!port_symbol || (port_symbol->atom.type != app->forge.String) )
+ continue;
+
+ const char *port_symbol_str = LV2_ATOM_BODY_CONST(port_symbol);
+
+ for(unsigned i=0; i<mod->num_ports; i++)
+ {
+ port_t *port = &mod->ports[i];
+
+ // search for matching port symbol
+ if(strcmp(port_symbol_str, port->symbol))
+ continue;
+
+ LV2_ATOM_OBJECT_FOREACH(port_obj, sub)
+ {
+ const LV2_Atom_Object *source_obj = (const LV2_Atom_Object *)&sub->value;
+
+ if( (sub->key != app->regs.core.port.urid)
+ || !lv2_atom_forge_is_object_type(&app->forge, source_obj->atom.type)
+ || (source_obj->body.otype != app->regs.core.Port.urid) )
+ continue;
+
+ const LV2_Atom_String *source_symbol = NULL;
+ const LV2_Atom_Int *source_index = NULL;
+ lv2_atom_object_get(source_obj,
+ app->regs.core.symbol.urid, &source_symbol,
+ app->regs.core.index.urid, &source_index,
+ 0);
+
+ if( !source_symbol || (source_symbol->atom.type != app->forge.String)
+ || !source_index || (source_index->atom.type != app->forge.Int) )
+ continue;
+
+ const char *source_symbol_str = LV2_ATOM_BODY_CONST(source_symbol);
+
+ mod_t *source = _sp_app_mod_get_by_uid(app, source_index->body);
+ if(!source)
+ continue;
+
+ for(unsigned j=0; j<source->num_ports; j++)
+ {
+ port_t *tar = &source->ports[j];
+
+ if(strcmp(source_symbol_str, tar->symbol))
+ continue;
+
+ _sp_app_port_connect(app, tar, port, 1.f);
+
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ /*
+ else
+ sp_app_log_error(app, "%s: invaild graph\n", __func__);
+ */
+
+ _toggle_dirty(app);
+
+ return LV2_STATE_SUCCESS;
+}
diff --git a/app/synthpod_app_ui.c b/app/synthpod_app_ui.c
new file mode 100644
index 00000000..26461279
--- /dev/null
+++ b/app/synthpod_app_ui.c
@@ -0,0 +1,1875 @@
+/*
+ * 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.
+ */
+
+#include <synthpod_app_private.h>
+#include <synthpod_patcher.h>
+
+static inline bool
+_mod_needs_ramping(mod_t *mod, ramp_state_t state, bool silencing)
+{
+ sp_app_t *app = mod->app;
+
+ // ramping
+ int needs_ramping = 0;
+ for(unsigned p1=0; p1<mod->num_ports; p1++)
+ {
+ port_t *port = &mod->ports[p1];
+
+ // silence sources
+ /* TODO is this needed?
+ for(int s=0; s<port->num_sources; s++)
+ {
+ _sp_app_port_silence_request(app,
+ port->sources[s].port, port, state);
+ }
+ */
+
+ // silence sinks
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ for(unsigned p2=0; p2<app->mods[m]->num_ports; p2++)
+ {
+ if(silencing)
+ {
+ needs_ramping += _sp_app_port_silence_request(app,
+ port, &app->mods[m]->ports[p2], state);
+ }
+ else
+ {
+ needs_ramping += _sp_app_port_desilence(app,
+ port, &app->mods[m]->ports[p2]);
+ }
+ }
+ }
+ }
+
+ return needs_ramping > 0;
+}
+
+//FIXME move into another file
+__realtime static mod_t *
+_mod_find_by_urn(sp_app_t *app, LV2_URID urn)
+{
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->urn == urn)
+ return mod;
+ }
+
+ return NULL;
+}
+
+//FIXME move into another file
+__realtime static port_t *
+_port_find_by_symbol(sp_app_t *app, LV2_URID urn, const char *symbol)
+{
+ mod_t *mod = _mod_find_by_urn(app, urn);
+ if(mod)
+ {
+ for(unsigned p = 0; p < mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(!strcmp(port->symbol, symbol))
+ return port;
+ }
+ }
+
+ return NULL;
+}
+
+__realtime void
+_sp_app_ui_set_modlist(sp_app_t *app, LV2_URID subj, int32_t seqn)
+{
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], subj, seqn, app->regs.synthpod.module_list.urid);
+ if(ref)
+ ref = lv2_atom_forge_tuple(&app->forge, &frame[1]);
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, mod->urn);
+ }
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+}
+
+__realtime LV2_Atom_Forge_Ref
+_sp_app_forge_midi_automation(sp_app_t *app, LV2_Atom_Forge_Frame *frame,
+ mod_t *mod, port_t *port, const auto_t *automation)
+{
+ const midi_auto_t *mauto = &automation->midi;
+ LV2_Atom_Forge_Ref ref;
+
+ ref = lv2_atom_forge_object(&app->forge, frame, 0, app->regs.midi.Controller.urid);
+ if(ref)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, mod->urn);
+
+ if(automation->property)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.patch.property.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, automation->property);
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.rdfs.range.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, automation->range);
+ }
+ else
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_symbol.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, port->symbol, strlen(port->symbol));
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.midi.channel.urid);
+ if(ref)
+ ref = lv2_atom_forge_int(&app->forge, mauto->channel);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.midi.controller_number.urid);
+ if(ref)
+ ref = lv2_atom_forge_int(&app->forge, mauto->controller);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_min.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->a);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_max.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->b);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_min.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->c);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_max.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->d);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_enabled.urid);
+ if(ref)
+ ref = lv2_atom_forge_bool(&app->forge, automation->src_enabled);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_enabled.urid);
+ if(ref)
+ ref = lv2_atom_forge_bool(&app->forge, automation->snk_enabled);
+ }
+ if(ref)
+ lv2_atom_forge_pop(&app->forge, frame);
+
+ return ref;
+}
+
+__realtime LV2_Atom_Forge_Ref
+_sp_app_forge_osc_automation(sp_app_t *app, LV2_Atom_Forge_Frame *frame,
+ mod_t *mod, port_t *port, const auto_t *automation)
+{
+ const osc_auto_t *oauto = &automation->osc;
+ LV2_Atom_Forge_Ref ref;
+
+ ref = lv2_atom_forge_object(&app->forge, frame, 0, app->regs.osc.message.urid);
+ if(ref)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, mod->urn);
+
+ if(automation->property)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.patch.property.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, automation->property);
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.rdfs.range.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, automation->range);
+ }
+ else
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_symbol.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, port->symbol, strlen(port->symbol));
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.osc.path.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, oauto->path, strlen(oauto->path));
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_min.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->a);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_max.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->b);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_min.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->c);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_max.urid);
+ if(ref)
+ ref = lv2_atom_forge_double(&app->forge, automation->d);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_enabled.urid);
+ if(ref)
+ ref = lv2_atom_forge_bool(&app->forge, automation->src_enabled);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_enabled.urid);
+ if(ref)
+ ref = lv2_atom_forge_bool(&app->forge, automation->snk_enabled);
+ }
+ if(ref)
+ lv2_atom_forge_pop(&app->forge, frame);
+
+ return ref;
+}
+
+__realtime static bool
+_sp_app_from_ui_patch_get(sp_app_t *app, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = ASSUME_ALIGNED(atom);
+
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *seqn = NULL;
+ const LV2_Atom_URID *property = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.patch.subject.urid, &subject,
+ app->regs.patch.sequence_number.urid, &seqn,
+ app->regs.patch.property.urid, &property,
+ 0);
+
+ const LV2_URID subj = subject && (subject->atom.type == app->forge.URID)
+ ? subject->body : 0;
+ const int32_t sn = seqn && (seqn->atom.type == app->forge.Int)
+ ? seqn->body : 0;
+ const LV2_URID prop = property && (property->atom.type == app->forge.URID)
+ ? property->body : 0;
+
+ //printf("got patch:Get for <%s>\n", app->driver->unmap->unmap(app->driver->unmap->handle, subj));
+
+ if(!subj && prop) //FIXME
+ {
+ //printf("\tpatch:property <%s>\n", app->driver->unmap->unmap(app->driver->unmap->handle, prop));
+
+ if(prop == app->regs.synthpod.module_list.urid)
+ {
+ _sp_app_ui_set_modlist(app, subj, sn);
+ }
+ else if(prop == app->regs.synthpod.connection_list.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], subj, sn, prop);
+ if(ref)
+ ref = lv2_atom_forge_tuple(&app->forge, &frame[1]);
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ for(unsigned p = 0; p < mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ for(int s = 0; s < conn->num_sources; s++)
+ {
+ source_t *source = &conn->sources[s];
+
+ if(ref)
+ ref = lv2_atom_forge_object(&app->forge, &frame[2], 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, source->port->mod->urn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_symbol.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, source->port->symbol, strlen(source->port->symbol));
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, port->mod->urn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_symbol.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, port->symbol, strlen(port->symbol));
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.param.gain.urid);
+ if(ref)
+ ref = lv2_atom_forge_float(&app->forge, source->gain);
+ }
+ if(ref)
+ lv2_atom_forge_pop(&app->forge, &frame[2]);
+ }
+ }
+ }
+ }
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.node_list.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], subj, sn, prop);
+ if(ref)
+ ref = lv2_atom_forge_tuple(&app->forge, &frame[1]);
+ for(unsigned m1 = 0; m1 < app->num_mods; m1++)
+ {
+ mod_t *snk_mod = app->mods[m1];
+
+ for(unsigned m2=0; m2<app->num_mods; m2++)
+ {
+ mod_t *src_mod = app->mods[m2];
+ bool mods_are_connected = false;
+ float x = 0.f;
+ float y = 0.f;
+
+ for(unsigned p=0; p<snk_mod->num_ports; p++)
+ {
+ port_t *port = &snk_mod->ports[p];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ for(int j=0; j<conn->num_sources; j++)
+ {
+ source_t *source = &conn->sources[j];
+ port_t *src_port = source->port;
+
+ if(src_port->mod == src_mod)
+ {
+ mods_are_connected = true;
+ x = source->pos.x;
+ y = source->pos.y;
+ break;
+ }
+ }
+ }
+
+ if(mods_are_connected)
+ break;
+ }
+
+ if(mods_are_connected)
+ {
+ if(ref)
+ ref = lv2_atom_forge_object(&app->forge, &frame[2], 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.source_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, src_mod->urn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.sink_module.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, snk_mod->urn);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.node_position_x.urid);
+ if(ref)
+ ref = lv2_atom_forge_float(&app->forge, x);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.node_position_y.urid);
+ if(ref)
+ ref = lv2_atom_forge_float(&app->forge, y);
+ }
+ if(ref)
+ lv2_atom_forge_pop(&app->forge, &frame[2]);
+ }
+ }
+ }
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.pset.preset.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const LV2_URID bundle_urid = app->driver->map->map(app->driver->map->handle, app->bundle_path); //FIXME store bundle path as URID
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(uint32_t), app->forge.URID, &bundle_urid);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.automation_list.urid)
+ {
+ //printf("patch:Get for spod:automationList\n");
+
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set_object(
+ &app->regs, &app->forge, &frame[0], subj, sn, prop);
+ if(ref)
+ ref = lv2_atom_forge_tuple(&app->forge, &frame[1]);
+
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+ port_t *port = &mod->ports[automation->index];
+
+ if(automation->type == AUTO_TYPE_MIDI)
+ {
+ if(ref)
+ ref = _sp_app_forge_midi_automation(app, &frame[2], mod, port, automation);
+ }
+ else if(automation->type == AUTO_TYPE_OSC)
+ {
+ if(ref)
+ ref = _sp_app_forge_osc_automation(app, &frame[2], mod, port, automation);
+ }
+ }
+ }
+
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.graph_position_x.urid)
+ {
+ //printf("patch:Get for spod:graphPositionX\n");
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(float), app->forge.Float, &app->pos.x);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.graph_position_y.urid)
+ {
+ //printf("patch:Get for spod:graphPositionY\n");
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(float), app->forge.Float, &app->pos.y);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.column_enabled.urid)
+ {
+ //printf("patch:Get for spod:columnEnabled\n");
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Bool, &app->column_enabled);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.row_enabled.urid)
+ {
+ //printf("patch:Get for spod:rowEnabled\n");
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Bool, &app->row_enabled);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.cpus_available.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t cpus_available = app->dsp_master.num_slaves + 1;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Int, &cpus_available);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.cpus_used.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t cpus_used = (app->dsp_master.concurrent > app->dsp_master.num_slaves + 1)
+ ? app->dsp_master.num_slaves + 1
+ : app->dsp_master.concurrent;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Int, &cpus_used);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.period_size.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t period_size = app->driver->max_block_size;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Int, &period_size);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else if(prop == app->regs.synthpod.num_periods.urid)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ const int32_t num_periods = app->driver->num_periods;
+
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_set(
+ &app->regs, &app->forge, subj, sn, prop,
+ sizeof(int32_t), app->forge.Int, &num_periods);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ //TODO handle more properties
+ }
+ else if(subj)
+ {
+ for(unsigned m = 0; m < app->num_mods; m++)
+ {
+ mod_t *mod = app->mods[m];
+
+ if(mod->urn == subj)
+ {
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_put_object(
+ &app->regs, &app->forge, &frame[0], subj, sn);
+ if(ref)
+ ref = lv2_atom_forge_object(&app->forge, &frame[1], 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.core.plugin.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, mod->plug_urid);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.module_position_x.urid);
+ if(ref)
+ ref = lv2_atom_forge_float(&app->forge, mod->pos.x);
+
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.module_position_y.urid);
+ if(ref)
+ ref = lv2_atom_forge_float(&app->forge, mod->pos.y);
+
+ if(strlen(mod->alias))
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.synthpod.module_alias.urid);
+ if(ref)
+ ref = lv2_atom_forge_string(&app->forge, mod->alias, strlen(mod->alias));
+ }
+
+ if(mod->ui)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(&app->forge, app->regs.ui.ui.urid);
+ if(ref)
+ ref = lv2_atom_forge_urid(&app->forge, mod->ui);
+ }
+ }
+ if(ref)
+ {
+ synthpod_patcher_pop(&app->forge, frame, 2);
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ break; // match
+ }
+ }
+ }
+
+ return advance_ui[app->block_state];
+}
+
+__realtime static bool
+_sp_app_from_ui_patch_set(sp_app_t *app, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = ASSUME_ALIGNED(atom);
+
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *seqn = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom *value = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.patch.subject.urid, &subject,
+ app->regs.patch.sequence_number.urid, &seqn,
+ app->regs.patch.property.urid, &property,
+ app->regs.patch.value.urid, &value,
+ 0);
+
+ const LV2_URID subj = subject && (subject->atom.type == app->forge.URID)
+ ? subject->body : 0;
+ const int32_t sn = seqn && (seqn->atom.type == app->forge.Int)
+ ? seqn->body : 0;
+ const LV2_URID prop = property && (property->atom.type == app->forge.URID)
+ ? property->body : 0;
+
+ if(subj && prop && value) // is for a module
+ {
+ //printf("got patch:Set: %s\n", app->driver->unmap->unmap(app->driver->unmap->handle, prop));
+
+ mod_t *mod = _mod_find_by_urn(app, subj);
+ if(mod)
+ {
+ if( (prop == app->regs.synthpod.module_position_x.urid)
+ && (value->type == app->forge.Float) )
+ {
+ mod->pos.x = ((const LV2_Atom_Float *)value)->body;
+ _sp_app_order(app);
+ }
+ else if( (prop == app->regs.synthpod.module_position_y.urid)
+ && (value->type == app->forge.Float) )
+ {
+ mod->pos.y = ((const LV2_Atom_Float *)value)->body;
+ _sp_app_order(app);
+ }
+ else if( (prop == app->regs.synthpod.module_alias.urid)
+ && (value->type == app->forge.String) )
+ {
+ strncpy(mod->alias, LV2_ATOM_BODY_CONST(value), ALIAS_MAX - 1);
+ }
+ else if( (prop == app->regs.ui.ui.urid)
+ && (value->type == app->forge.URID) )
+ {
+ mod->ui = ((const LV2_Atom_URID *)value)->body;
+ }
+ else if( (prop == app->regs.pset.preset.urid)
+ && (value->type == app->forge.URID) )
+ {
+ if(app->block_state == BLOCKING_STATE_RUN)
+ {
+ const bool needs_ramping = _mod_needs_ramping(mod, RAMP_STATE_DOWN_DRAIN, true);
+ app->silence_state = !needs_ramping
+ ? SILENCING_STATE_RUN
+ : SILENCING_STATE_BLOCK;
+
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_DRAIN; // wait for drain
+
+ job->request = JOB_TYPE_REQUEST_DRAIN;
+ job->status = 0;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ else if(app->block_state == BLOCKING_STATE_BLOCK)
+ {
+ if(app->silence_state == SILENCING_STATE_BLOCK)
+ return false; // not fully silenced yet, wait
+
+ // send request to worker thread
+ const LV2_URID pset_urn = ((const LV2_Atom_URID *)value)->body;
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+ mod->bypassed = mod->needs_bypassing;
+
+ job->request = JOB_TYPE_REQUEST_PRESET_LOAD;
+ job->mod = mod;
+ job->urn = pset_urn;
+ _sp_app_to_worker_advance(app, size);
+
+ return true; // advance
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ }
+ else if( (prop == app->regs.idisp.surface.urid)
+ && (value->type == app->forge.Bool) )
+ {
+ mod->idisp.subscribed = ((const LV2_Atom_Bool *)value)->body;
+
+ if(mod->idisp.iface && mod->idisp.subscribed)
+ {
+ _sp_app_mod_queue_draw(mod); // trigger update
+ }
+ }
+ else if( (prop == app->regs.synthpod.module_reinstantiate.urid)
+ && (value->type == app->forge.Bool) )
+ {
+ if(app->block_state == BLOCKING_STATE_RUN)
+ {
+ const bool needs_ramping = _mod_needs_ramping(mod, RAMP_STATE_DOWN_DRAIN, true);
+ app->silence_state = !needs_ramping
+ ? SILENCING_STATE_RUN
+ : SILENCING_STATE_BLOCK;
+
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_DRAIN; // wait for drain
+
+ job->request = JOB_TYPE_REQUEST_DRAIN;
+ job->status = 0;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ else if(app->block_state == BLOCKING_STATE_BLOCK)
+ {
+ if(app->silence_state == SILENCING_STATE_BLOCK)
+ return false; // not fully silenced yet, wait
+
+ // send request to worker thread
+ const LV2_URID pset_urn = ((const LV2_Atom_URID *)value)->body;
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+ mod->bypassed = mod->needs_bypassing;
+
+ job->request = JOB_TYPE_REQUEST_MODULE_REINSTANTIATE;
+ job->mod = mod;
+ _sp_app_to_worker_advance(app, size);
+
+ return true; // advance
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ }
+ }
+
+ //TODO handle more properties
+ }
+ else if(prop && value)// is for host
+ {
+ if( (prop == app->regs.synthpod.graph_position_x.urid)
+ && (value->type == app->forge.Float) )
+ {
+ app->pos.x = ((const LV2_Atom_Float *)value)->body;
+ }
+ else if( (prop == app->regs.synthpod.graph_position_y.urid)
+ && (value->type == app->forge.Float) )
+ {
+ app->pos.y = ((const LV2_Atom_Float *)value)->body;
+ }
+ else if( (prop == app->regs.synthpod.column_enabled.urid)
+ && (value->type == app->forge.Bool) )
+ {
+ app->column_enabled = ((const LV2_Atom_Bool *)value)->body;
+ }
+ else if( (prop == app->regs.synthpod.row_enabled.urid)
+ && (value->type == app->forge.Bool) )
+ {
+ app->row_enabled = ((const LV2_Atom_Bool *)value)->body;
+ }
+ }
+
+ return advance_ui[app->block_state];
+}
+
+__realtime static bool
+_sp_app_from_ui_patch_copy(sp_app_t *app, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = ASSUME_ALIGNED(atom);
+
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *seqn = NULL;
+ const LV2_Atom_URID *destination = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.patch.subject.urid, &subject,
+ app->regs.patch.sequence_number.urid, &seqn,
+ app->regs.patch.destination.urid, &destination,
+ 0);
+
+ const LV2_URID subj = subject && (subject->atom.type == app->forge.URID)
+ ? subject->body : 0;
+ const int32_t sn = seqn && (seqn->atom.type == app->forge.Int)
+ ? seqn->body : 0;
+ const LV2_URID dest = destination && (destination->atom.type == app->forge.URID)
+ ? destination->body : 0;
+
+ if(!subj && dest) // save bundle to dest
+ {
+ if(app->block_state == BLOCKING_STATE_RUN)
+ {
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_DRAIN; // wait for drain
+
+ job->request = JOB_TYPE_REQUEST_DRAIN;
+ job->status = 0;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ else if(app->block_state == BLOCKING_STATE_BLOCK)
+ {
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+
+ job->request = JOB_TYPE_REQUEST_BUNDLE_SAVE;
+ job->status = -1; // TODO for what for?
+ job->urn = dest;
+ _sp_app_to_worker_advance(app, size);
+
+ return true; // advance
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ }
+ else if(subj && !dest) // copy bundle from subj
+ {
+ if(app->block_state == BLOCKING_STATE_RUN)
+ {
+ //FIXME ramp down system outputs
+
+ // send request to worker thread
+ size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_DRAIN; // wait for drain
+
+ job->request = JOB_TYPE_REQUEST_DRAIN;
+ job->status = 0;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ else if(app->block_state == BLOCKING_STATE_BLOCK)
+ {
+ //FIXME ramp up system outputs
+
+ // send request to worker thread
+ job_t *job = _sp_app_to_worker_request(app, sizeof(job_t));
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+ app->load_bundle = true; // for sp_app_bypassed
+
+ job->request = JOB_TYPE_REQUEST_BUNDLE_LOAD;
+ job->status = -1; // TODO for what for?
+ job->urn = subj;
+ _sp_app_to_worker_advance(app, sizeof(job_t));
+
+ return true; // advance
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ }
+ else if(subj && dest) // copy preset to dest
+ {
+ mod_t *mod = _mod_find_by_urn(app, subj);
+
+ if(app->block_state == BLOCKING_STATE_RUN)
+ {
+ // send request to worker thread
+ job_t *job = _sp_app_to_worker_request(app, sizeof(job_t));
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_DRAIN; // wait for drain
+
+ job->request = JOB_TYPE_REQUEST_DRAIN;
+ job->status = 0;
+ _sp_app_to_worker_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ else if(app->block_state == BLOCKING_STATE_BLOCK)
+ {
+ // send request to worker thread
+ job_t *job = _sp_app_to_worker_request(app, sizeof(job_t));
+ if(job)
+ {
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+
+ job->request = JOB_TYPE_REQUEST_PRESET_SAVE;
+ job->mod = mod;
+ job->urn = dest;
+ _sp_app_to_worker_advance(app, sizeof(job_t));
+
+ return true; // advance
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+ }
+ }
+
+ return advance_ui[app->block_state];
+}
+
+__realtime void
+_connection_list_add(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:add for connectionList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+ const LV2_Atom_URID *snk_module = NULL;
+ const LV2_Atom *snk_symbol = NULL;
+ const LV2_Atom_Float *link_gain = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.source_module.urid, &src_module,
+ app->regs.synthpod.source_symbol.urid, &src_symbol,
+ app->regs.synthpod.sink_module.urid, &snk_module,
+ app->regs.synthpod.sink_symbol.urid, &snk_symbol,
+ app->regs.param.gain.urid, &link_gain,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+ const LV2_URID snk_urn = snk_module
+ ? snk_module->body : 0;
+ const char *snk_sym = snk_symbol
+ ? LV2_ATOM_BODY_CONST(snk_symbol) : NULL;
+ const float gain = link_gain
+ ? link_gain->body : 1.f;
+
+ if(src_urn && src_sym && snk_urn && snk_sym)
+ {
+ port_t *src_port = _port_find_by_symbol(app, src_urn, src_sym);
+ port_t *snk_port = _port_find_by_symbol(app, snk_urn, snk_sym);
+
+ if(src_port && snk_port)
+ {
+ const int32_t state = _sp_app_port_connect(app, src_port, snk_port, gain);
+ (void)state;
+
+ // signal to UI
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_add_atom(&app->regs, &app->forge,
+ 0, 0, app->regs.synthpod.connection_list.urid, &obj->atom); //TODO subject
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ }
+}
+
+__realtime static void
+_connection_list_rem(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:remove for connectionList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+ const LV2_Atom_URID *snk_module = NULL;
+ const LV2_Atom *snk_symbol = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.source_module.urid, &src_module,
+ app->regs.synthpod.source_symbol.urid, &src_symbol,
+ app->regs.synthpod.sink_module.urid, &snk_module,
+ app->regs.synthpod.sink_symbol.urid, &snk_symbol,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+ const LV2_URID snk_urn = snk_module
+ ? snk_module->body : 0;
+ const char *snk_sym = snk_symbol
+ ? LV2_ATOM_BODY_CONST(snk_symbol) : NULL;
+
+ if(src_urn && src_sym && snk_urn && snk_sym)
+ {
+ port_t *src_port = _port_find_by_symbol(app, src_urn, src_sym);
+ port_t *snk_port = _port_find_by_symbol(app, snk_urn, snk_sym);
+
+ if(src_port && snk_port)
+ {
+ const int32_t state = _sp_app_port_disconnect_request(app, src_port, snk_port, RAMP_STATE_DOWN);
+ (void)state;
+
+ // signal to UI
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_remove_atom(&app->regs, &app->forge,
+ 0, 0, app->regs.synthpod.connection_list.urid, &obj->atom); //TODO subject
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ }
+}
+
+__realtime void
+_node_list_add(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:add for nodeList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom_URID *snk_module = NULL;
+ const LV2_Atom_Float *pos_x = NULL;
+ const LV2_Atom_Float *pos_y = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.source_module.urid, &src_module,
+ app->regs.synthpod.sink_module.urid, &snk_module,
+ app->regs.synthpod.node_position_x.urid, &pos_x,
+ app->regs.synthpod.node_position_y.urid, &pos_y,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const LV2_URID snk_urn = snk_module
+ ? snk_module->body : 0;
+ const float x = pos_x
+ ? pos_x->body : 0.f;
+ const float y = pos_y
+ ? pos_y->body : 0.f;
+
+ if(src_urn && snk_urn)
+ {
+ mod_t *src_mod = _mod_find_by_urn(app, src_urn);
+ mod_t *snk_mod = _mod_find_by_urn(app, snk_urn);
+
+ if(src_mod && snk_mod)
+ {
+ for(unsigned p=0; p<snk_mod->num_ports; p++)
+ {
+ port_t *port = &snk_mod->ports[p];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ for(int j=0; j<conn->num_sources; j++)
+ {
+ source_t *source = &conn->sources[j];
+ port_t *source_port = source->port;
+
+ if(source_port->mod == src_mod)
+ {
+ source->pos.x = x;
+ source->pos.y = y;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+//FIXME _subscription_list_clear, e.g. with patch:wildcard
+
+__realtime static void
+_subscription_list_add(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:add for subscriptionList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.sink_module.urid, &src_module,
+ app->regs.synthpod.sink_symbol.urid, &src_symbol,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+
+ if(src_urn && src_sym)
+ {
+ port_t *src_port = _port_find_by_symbol(app, src_urn, src_sym);
+
+ if(src_port)
+ {
+ src_port->subscriptions += 1;
+
+ if(src_port->type == PORT_TYPE_CONTROL)
+ {
+ const float *buf_ptr = PORT_BASE_ALIGNED(src_port);
+ src_port->control.last = *buf_ptr - 0.1; // will force notification
+ }
+ }
+ }
+}
+
+__realtime static void
+_subscription_list_rem(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:remove for subscriptionList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.sink_module.urid, &src_module,
+ app->regs.synthpod.sink_symbol.urid, &src_symbol,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+
+ if(src_urn && src_sym)
+ {
+ port_t *src_port = _port_find_by_symbol(app, src_urn, src_sym);
+
+ if(src_port)
+ {
+ if(src_port->subscriptions > 0)
+ src_port->subscriptions -= 1;
+ }
+ }
+}
+
+__realtime static void
+_notification_list_add(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:add for notificationList:\n");
+
+ const LV2_URID src_proto = obj->body.otype;
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+ const LV2_Atom *src_value = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.sink_module.urid, &src_module,
+ app->regs.synthpod.sink_symbol.urid, &src_symbol,
+ app->regs.rdf.value.urid, &src_value,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+
+ if(src_urn && src_sym && src_value)
+ {
+ port_t *src_port = _port_find_by_symbol(app, src_urn, src_sym);
+
+ if(src_port)
+ {
+ if( (src_proto == app->regs.port.float_protocol.urid)
+ && (src_value->type == app->forge.Float) )
+ {
+ const float val = ((const LV2_Atom_Float *)src_value)->body;
+ float *buf_ptr = PORT_BASE_ALIGNED(src_port);
+
+ if(src_port->type == PORT_TYPE_CONTROL)
+ {
+ *buf_ptr = val;
+ src_port->control.last = *buf_ptr; // we don't want any notification
+ src_port->control.auto_dirty = true;
+ _sp_app_port_control_stash(src_port);
+ }
+ else if(src_port->type == PORT_TYPE_CV)
+ {
+ for(unsigned i = 0; i < app->driver->max_block_size; i++)
+ {
+ buf_ptr[i] = val;
+ //FIXME omit notification ?
+ }
+ }
+ }
+ else if( (src_proto == app->regs.port.event_transfer.urid)
+ && (src_port->type == PORT_TYPE_ATOM) )
+ {
+ //printf("got atom:eventTransfer\n");
+
+ // messages from UI are ALWAYS appended to default port buffer, no matter
+ // how many sources the port may have
+ const uint32_t capacity = PORT_SIZE(src_port);
+ LV2_Atom_Sequence *seq = PORT_BASE_ALIGNED(src_port);
+
+ const LV2_Atom_Event *dummy = (const void *)src_value - offsetof(LV2_Atom_Event, body);
+ LV2_Atom_Event *ev = lv2_atom_sequence_append_event(seq, capacity, dummy);
+ if(ev)
+ ev->time.frames = 0;
+ else
+ sp_app_log_trace(app, "%s: failed to append\n", __func__);
+
+ //FIXME handle output automation
+ }
+ else if( (src_proto == app->regs.port.atom_transfer.urid)
+ && (src_port->type == PORT_TYPE_ATOM) )
+ {
+ //printf("got atom:atomTransfer\n");
+ LV2_Atom *atom = PORT_BASE_ALIGNED(src_port);
+ //FIXME memcpy(atom, src_value, lv2_atom_total-size(src_value));
+ }
+ }
+ }
+}
+
+__realtime static void
+_automation_list_rem_internal(port_t *port, LV2_URID prop)
+{
+ mod_t *mod = port->mod;
+
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if(automation->type == AUTO_TYPE_NONE)
+ continue; // ignore
+
+ if(!prop && (automation->index == port->index))
+ automation->type = AUTO_TYPE_NONE; // invalidate
+ else if(prop && (automation->property == prop) )
+ automation->type = AUTO_TYPE_NONE; // invalidate
+ }
+}
+
+__realtime static port_t *
+_automation_port_find(mod_t *mod, const char *src_sym, LV2_URID src_prop)
+{
+ for(unsigned p = 0; p < mod->num_ports; p++)
+ {
+ port_t *port = &mod->ports[p];
+
+ if(src_sym)
+ {
+ if( (port->type == PORT_TYPE_CONTROL) && !strcmp(port->symbol, src_sym) )
+ return port;
+ }
+ else if(src_prop)
+ {
+ if( (port->type == PORT_TYPE_ATOM) && port->atom.patchable)
+ return port;
+ }
+ }
+
+ return NULL;
+}
+
+__realtime static void
+_automation_list_rem(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:remove for automationList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+ const LV2_Atom_URID *src_property = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.sink_module.urid, &src_module,
+ app->regs.synthpod.sink_symbol.urid, &src_symbol,
+ app->regs.patch.property.urid, &src_property,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+ const LV2_URID src_prop = src_property
+ ? src_property->body : 0;
+
+ mod_t *mod = _mod_find_by_urn(app, src_urn);
+ if(mod)
+ {
+ port_t *port = _automation_port_find(mod, src_sym, src_prop);
+ if(port)
+ {
+ _automation_list_rem_internal(port, src_prop);
+ }
+ }
+}
+
+__realtime void
+_automation_refresh_mul_add(auto_t *automation)
+{
+ const double div = automation->b - automation->a;
+ automation->mul = div
+ ? (automation->d - automation->c) / div
+ : 0.0;
+ automation->add = div
+ ? (automation->c*automation->b - automation->a*automation->d) / div
+ : 0.0;
+}
+
+__realtime void
+_automation_list_add(sp_app_t *app, const LV2_Atom_Object *obj)
+{
+ //printf("got patch:add for automationList:\n");
+
+ const LV2_Atom_URID *src_module = NULL;
+ const LV2_Atom *src_symbol = NULL;
+ const LV2_Atom_URID *src_property = NULL;
+ const LV2_Atom_URID *src_range = NULL;
+ const LV2_Atom_Int *src_channel = NULL;
+ const LV2_Atom_Int *src_controller = NULL;
+ const LV2_Atom_String *src_path = NULL;
+ const LV2_Atom_Double *src_min = NULL;
+ const LV2_Atom_Double *src_max = NULL;
+ const LV2_Atom_Double *snk_min = NULL;
+ const LV2_Atom_Double *snk_max = NULL;
+ const LV2_Atom_Bool *src_enabled = NULL;
+ const LV2_Atom_Bool *snk_enabled = NULL;
+ const LV2_Atom_Bool *is_learning = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.synthpod.sink_module.urid, &src_module,
+ app->regs.synthpod.sink_symbol.urid, &src_symbol,
+ app->regs.patch.property.urid, &src_property,
+ app->regs.rdfs.range.urid, &src_range,
+ app->regs.midi.channel.urid, &src_channel,
+ app->regs.midi.controller_number.urid, &src_controller,
+ app->regs.osc.path.urid, &src_path,
+ app->regs.synthpod.source_min.urid, &src_min,
+ app->regs.synthpod.source_max.urid, &src_max,
+ app->regs.synthpod.sink_min.urid, &snk_min,
+ app->regs.synthpod.sink_max.urid, &snk_max,
+ app->regs.synthpod.source_enabled.urid, &src_enabled,
+ app->regs.synthpod.sink_enabled.urid, &snk_enabled,
+ app->regs.synthpod.learning.urid, &is_learning,
+ 0);
+
+ const LV2_URID src_urn = src_module
+ ? src_module->body : 0;
+ const char *src_sym = src_symbol
+ ? LV2_ATOM_BODY_CONST(src_symbol) : NULL;
+ const LV2_URID src_prop = src_property
+ ? src_property->body : 0;
+ const LV2_URID src_ran = src_range
+ ? src_range->body : 0;
+
+ mod_t *mod = _mod_find_by_urn(app, src_urn);
+ if(mod)
+ {
+ port_t *port = _automation_port_find(mod, src_sym, src_prop);
+ if(port)
+ {
+ _automation_list_rem_internal(port, src_prop); // remove any previously registered automation
+
+ for(unsigned i = 0; i < MAX_AUTOMATIONS; i++)
+ {
+ auto_t *automation = &mod->automations[i];
+
+ if(automation->type != AUTO_TYPE_NONE)
+ continue; // search empty slot
+
+ // fill slot
+ automation->index = port->index;
+ automation->property = src_prop;
+ automation->range = src_ran;
+
+ automation->a = src_min ? src_min->body : 0.0;
+ automation->b = src_max ? src_max->body : 0.0;
+ automation->c = snk_min ? snk_min->body : 0.0;
+ automation->d = snk_max ? snk_max->body : 0.0;
+ automation->src_enabled = src_enabled ? src_enabled->body : false;
+ automation->snk_enabled = snk_enabled ? snk_enabled->body : false;
+ automation->learning = is_learning ? is_learning->body : false;
+
+ _automation_refresh_mul_add(automation);
+
+ if(obj->body.otype == app->regs.midi.Controller.urid)
+ {
+ automation->type = AUTO_TYPE_MIDI;
+ automation->midi.channel = src_channel ? src_channel->body : -1;
+ automation->midi.controller = src_controller ? src_controller->body : -1;
+ }
+ else if(obj->body.otype == app->regs.osc.message.urid)
+ {
+ automation->type = AUTO_TYPE_OSC;
+ if(src_path)
+ strncpy(automation->osc.path, LV2_ATOM_BODY_CONST(src_path), 256);
+ else
+ automation->osc.path[0] = '\0';
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+__realtime static void
+_mod_list_add(sp_app_t *app, const LV2_Atom_URID *urid)
+{
+ //printf("got patch:add for moduleList: %s\n", uri);
+
+ // send request to worker thread
+ const size_t size = sizeof(job_t);
+ job_t *job = _sp_app_to_worker_request(app, size);
+ if(job)
+ {
+ job->request = JOB_TYPE_REQUEST_MODULE_ADD;
+ job->status = 0;
+ job->urn = urid->body;
+ _sp_app_to_worker_advance(app, size);
+ }
+ else
+ {
+ sp_app_log_trace(app, "%s: buffer request failed\n", __func__);
+ }
+}
+
+__realtime static void
+_mod_list_rem(sp_app_t *app, const LV2_Atom_URID *urn)
+{
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, urn->body);
+ //printf("got patch:remove for moduleList: %s\n", uri);
+
+ // search mod according to its URN
+ mod_t *mod = _mod_find_by_urn(app, urn->body);
+ if(!mod) // mod not found
+ return;
+
+ int needs_ramping = 0;
+ for(unsigned p1=0; p1<mod->num_ports; p1++)
+ {
+ port_t *port = &mod->ports[p1];
+
+ connectable_t *conn = _sp_app_port_connectable(port);
+ if(conn)
+ {
+ // disconnect sources
+ for(int s=0; s<conn->num_sources; s++)
+ {
+ _sp_app_port_disconnect_request(app,
+ conn->sources[s].port, port, RAMP_STATE_DOWN);
+ }
+ }
+
+ // disconnect sinks
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ for(unsigned p2=0; p2<app->mods[m]->num_ports; p2++)
+ {
+ needs_ramping += _sp_app_port_disconnect_request(app,
+ port, &app->mods[m]->ports[p2], RAMP_STATE_DOWN_DEL);
+ }
+ }
+ }
+ if(needs_ramping == 0)
+ _sp_app_mod_eject(app, mod);
+}
+
+__realtime static bool
+_sp_app_from_ui_patch_patch(sp_app_t *app, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = ASSUME_ALIGNED(atom);
+
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *seqn = NULL;
+ const LV2_Atom_Object *add = NULL;
+ const LV2_Atom_Object *rem = NULL;
+
+ lv2_atom_object_get(obj,
+ app->regs.patch.subject.urid, &subject,
+ app->regs.patch.sequence_number.urid, &seqn,
+ app->regs.patch.add.urid, &add,
+ app->regs.patch.remove.urid, &rem,
+ 0);
+
+ const LV2_URID subj = subject && (subject->atom.type == app->forge.URID)
+ ? subject->body : 0; //FIXME check for
+ const int32_t sn = seqn && (seqn->atom.type == app->forge.Int)
+ ? seqn->body : 0;
+
+ //printf("got patch:Patch: %s\n", app->driver->unmap->unmap(app->driver->unmap->handle, subj));
+
+ if( add && (add->atom.type == app->forge.Object)
+ && rem && (rem->atom.type == app->forge.Object) )
+ {
+ LV2_ATOM_OBJECT_FOREACH(rem, prop)
+ {
+ //printf("got patch:remove: %s\n", app->driver->unmap->unmap(app->driver->unmap->handle, prop->key));
+
+ if( (prop->key == app->regs.synthpod.connection_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _connection_list_rem(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.node_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ //FIXME never reached
+ }
+ else if( (prop->key == app->regs.synthpod.subscription_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _subscription_list_rem(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.notification_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ //FIXME never reached
+ }
+ else if( (prop->key == app->regs.synthpod.module_list.urid)
+ && (prop->value.type == app->forge.URID) )
+ {
+ _mod_list_rem(app, (const LV2_Atom_URID *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.automation_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _automation_list_rem(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ }
+
+ LV2_ATOM_OBJECT_FOREACH(add, prop)
+ {
+ //printf("got patch:add: %s\n", app->driver->unmap->unmap(app->driver->unmap->handle, prop->key));
+
+ if( (prop->key == app->regs.synthpod.connection_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _connection_list_add(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.node_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _node_list_add(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.subscription_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _subscription_list_add(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.notification_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _notification_list_add(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.module_list.urid)
+ && (prop->value.type == app->forge.URID) )
+ {
+ _mod_list_add(app, (const LV2_Atom_URID *)&prop->value);
+ }
+ else if( (prop->key == app->regs.synthpod.automation_list.urid)
+ && (prop->value.type == app->forge.Object) )
+ {
+ _automation_list_add(app, (const LV2_Atom_Object *)&prop->value);
+ }
+ }
+ }
+
+ return advance_ui[app->block_state];
+}
+
+bool
+sp_app_from_ui(sp_app_t *app, const LV2_Atom *atom)
+{
+ if(!advance_ui[app->block_state])
+ return false; // we are draining or waiting
+
+ const LV2_Atom_Object *obj = ASSUME_ALIGNED(atom);
+ //printf("%s\n", app->driver->unmap->unmap(app->driver->unmap->handle, obj->body.otype));
+
+ if(lv2_atom_forge_is_object_type(&app->forge, obj->atom.type))
+ {
+ if(obj->body.otype == app->regs.patch.get.urid)
+ return _sp_app_from_ui_patch_get(app, &obj->atom);
+ else if(obj->body.otype == app->regs.patch.set.urid)
+ return _sp_app_from_ui_patch_set(app, &obj->atom);
+ else if(obj->body.otype == app->regs.patch.copy.urid)
+ return _sp_app_from_ui_patch_copy(app, &obj->atom);
+ else if(obj->body.otype == app->regs.patch.patch.urid)
+ return _sp_app_from_ui_patch_patch(app, &obj->atom);
+ else
+ sp_app_log_trace(app, "%s: unknown object type\n", __func__);
+ }
+ else
+ sp_app_log_trace(app, "%s: not an atom object\n", __func__);
+
+ return advance_ui[app->block_state];
+}
diff --git a/app/synthpod_app_worker.c b/app/synthpod_app_worker.c
new file mode 100644
index 00000000..e53952ce
--- /dev/null
+++ b/app/synthpod_app_worker.c
@@ -0,0 +1,550 @@
+/*
+ * 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.
+ */
+
+#include <synthpod_app_private.h>
+#include <synthpod_patcher.h>
+
+static inline void *
+__sp_worker_to_app_request(sp_app_t *app, size_t minimum, size_t *maximum)
+{
+ if(app->driver->to_app_request)
+ return app->driver->to_app_request(minimum, maximum, app->data);
+
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ return NULL;
+}
+#define _sp_worker_to_app_request(APP, MINIMUM) \
+ ASSUME_ALIGNED(__sp_worker_to_app_request((APP), (MINIMUM), NULL))
+#define _sp_worker_to_app_request_max(APP, MINIMUM, MAXIMUM) \
+ ASSUME_ALIGNED(__sp_worker_to_app_request((APP), (MINIMUM), (MAXIMUM)))
+
+static inline void
+_sp_worker_to_app_advance(sp_app_t *app, size_t size)
+{
+ if(app->driver->to_app_advance)
+ app->driver->to_app_advance(size, app->data);
+ else
+ sp_app_log_error(app, "%s: buffer advance failed\n", __func__);
+}
+
+bool
+sp_app_from_worker(sp_app_t *app, uint32_t len, const void *data)
+{
+ if(!advance_work[app->block_state])
+ return false; // we are blocking
+
+ const job_t *job = ASSUME_ALIGNED(data);
+
+ switch(job->reply)
+ {
+ case JOB_TYPE_REPLY_MODULE_SUPPORTED:
+ {
+#if 0
+ //signal to UI
+ size_t size = sizeof(transmit_module_supported_t)
+ + lv2_atom_pad_size(strlen(job->uri) + 1);
+ transmit_module_supported_t *trans = _sp_app_to_ui_request(app, size);
+ if(trans)
+ {
+ _sp_transmit_module_supported_fill(&app->regs, &app->forge, trans, size,
+ job->status, job->uri);
+ _sp_app_to_ui_advance(app, size);
+ }
+#endif
+
+ break;
+ }
+ case JOB_TYPE_REPLY_MODULE_ADD:
+ {
+ mod_t *mod = job->mod;
+
+ if(app->num_mods >= MAX_MODS)
+ break; //TODO delete mod
+
+ // inject module into module graph
+ app->mods[app->num_mods] = app->mods[app->num_mods-1]; // system sink
+ app->mods[app->num_mods-1] = mod;
+ app->num_mods += 1;
+
+ _sp_app_order(app);
+
+ //signal to NK
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_add(&app->regs, &app->forge,
+ 0, 0, app->regs.synthpod.module_list.urid, //TODO subject
+ sizeof(uint32_t), app->forge.URID, &mod->urn);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REPLY_MODULE_DEL:
+ {
+ const LV2_URID urn = job->urn;
+
+ // signal to NK
+ size_t maximum;
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_remove(&app->regs, &app->forge,
+ 0, 0, app->regs.synthpod.module_list.urid, //TODO subject
+ sizeof(uint32_t), app->forge.URID, &urn);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REPLY_MODULE_REINSTANTIATE:
+ {
+ mod_t *mod = job->mod;
+
+ assert(app->block_state == BLOCKING_STATE_WAIT);
+ app->block_state = BLOCKING_STATE_RUN; // release block
+ mod->bypassed = false;
+
+ if(app->silence_state == SILENCING_STATE_WAIT)
+ {
+ app->silence_state = SILENCING_STATE_RUN;
+
+ // ramping
+ for(unsigned p1=0; p1<mod->num_ports; p1++)
+ {
+ port_t *port = &mod->ports[p1];
+
+ // desilence sinks
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ for(unsigned p2=0; p2<app->mods[m]->num_ports; p2++)
+ {
+ _sp_app_port_desilence(app, port, &app->mods[m]->ports[p2]);
+ }
+ }
+ }
+ }
+
+ //FIXME signal to ui
+
+ break;
+ }
+ case JOB_TYPE_REPLY_PRESET_LOAD:
+ {
+ //printf("app: preset loaded\n");
+ mod_t *mod = job->mod;
+
+ assert(app->block_state == BLOCKING_STATE_WAIT);
+ app->block_state = BLOCKING_STATE_RUN; // release block
+ mod->bypassed = false;
+
+ if(app->silence_state == SILENCING_STATE_WAIT)
+ {
+ app->silence_state = SILENCING_STATE_RUN;
+
+ // ramping
+ for(unsigned p1=0; p1<mod->num_ports; p1++)
+ {
+ port_t *port = &mod->ports[p1];
+
+ // desilence sinks
+ for(unsigned m=0; m<app->num_mods; m++)
+ {
+ for(unsigned p2=0; p2<app->mods[m]->num_ports; p2++)
+ {
+ _sp_app_port_desilence(app, port, &app->mods[m]->ports[p2]);
+ }
+ }
+ }
+ }
+
+#if 0
+ //signal to UI
+ size_t size = sizeof(transmit_module_preset_load_t);
+ transmit_module_preset_load_t *trans = _sp_app_to_ui_request(app, size);
+ if(trans)
+ {
+ _sp_transmit_module_preset_load_fill(&app->regs, &app->forge, trans, size,
+ mod->uid, NULL);
+ _sp_app_to_ui_advance(app, size);
+ }
+#endif
+
+ break;
+ }
+ case JOB_TYPE_REPLY_PRESET_SAVE:
+ {
+ //printf("app: preset saved\n");
+
+ assert(app->block_state == BLOCKING_STATE_WAIT);
+ app->block_state = BLOCKING_STATE_RUN; // release block
+
+ // signal to NK
+ size_t maximum;
+ LV2_Atom *answer = _sp_app_to_ui_request_atom(app);
+ if(answer)
+ {
+ LV2_Atom_Forge_Ref ref = synthpod_patcher_copy(&app->regs, &app->forge,
+ job->mod->urn, 0, job->urn);
+ if(ref)
+ {
+ _sp_app_to_ui_advance_atom(app, answer);
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+ }
+ else
+ {
+ _sp_app_to_ui_overflow(app);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REPLY_BUNDLE_LOAD:
+ {
+ //printf("app: bundle loaded\n");
+
+ assert(app->block_state == BLOCKING_STATE_WAIT);
+ app->block_state = BLOCKING_STATE_RUN; // releae block
+ assert(app->load_bundle == true);
+ app->load_bundle = false; // for sp_app_bypassed
+
+#if 0
+ // signal to UI
+ size_t size = sizeof(transmit_bundle_load_t)
+ + lv2_atom_pad_size(strlen(job->uri) + 1);
+ transmit_bundle_load_t *trans = _sp_app_to_ui_request(app, size);
+ if(trans)
+ {
+ _sp_transmit_bundle_load_fill(&app->regs, &app->forge, trans, size,
+ job->status, job->uri);
+ _sp_app_to_ui_advance(app, size);
+ }
+#endif
+
+ break;
+ }
+ case JOB_TYPE_REPLY_BUNDLE_SAVE:
+ {
+ //printf("app: bundle saved\n");
+
+ assert(app->block_state == BLOCKING_STATE_WAIT);
+ app->block_state = BLOCKING_STATE_RUN; // release block
+ assert(app->load_bundle == false);
+
+#if 0
+ // signal to UI
+ size_t size = sizeof(transmit_bundle_save_t)
+ + lv2_atom_pad_size(strlen(job->uri) + 1);
+ transmit_bundle_save_t *trans = _sp_app_to_ui_request(app, size);
+ if(trans)
+ {
+ _sp_transmit_bundle_save_fill(&app->regs, &app->forge, trans, size,
+ job->status, job->uri);
+ _sp_app_to_ui_advance(app, size);
+ }
+#endif
+
+ break;
+ }
+ case JOB_TYPE_REPLY_DRAIN:
+ {
+ assert(app->block_state == BLOCKING_STATE_DRAIN);
+ app->block_state = BLOCKING_STATE_BLOCK;
+
+ break;
+ }
+ }
+
+ return advance_work[app->block_state];
+}
+
+void
+sp_worker_from_app(sp_app_t *app, uint32_t len, const void *data)
+{
+ const job_t *job = ASSUME_ALIGNED(data);
+
+ switch(job->request)
+ {
+ case JOB_TYPE_REQUEST_MODULE_SUPPORTED:
+ {
+#if 0
+ const int32_t status= _sp_app_mod_is_supported(app, job->uri) ? 1 : 0;
+
+ // signal to app
+ size_t job_size = sizeof(job_t) + strlen(job->uri) + 1;
+ job_t *job1 = _sp_worker_to_app_request(app, job_size);
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_MODULE_SUPPORTED;
+ job1->status = status;
+ memcpy(job1->uri, job->uri, strlen(job->uri) + 1);
+ _sp_worker_to_app_advance(app, job_size);
+ }
+#endif
+ break;
+ }
+ case JOB_TYPE_REQUEST_MODULE_ADD:
+ {
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, job->urn);
+ mod_t *mod = uri ? _sp_app_mod_add(app, uri, 0) : NULL;
+ if(!mod)
+ break; //TODO report
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_MODULE_ADD;
+ job1->mod = mod;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_MODULE_DEL:
+ {
+ const LV2_URID urn = job->mod->urn;
+ int status = _sp_app_mod_del(app, job->mod);
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_MODULE_DEL;
+ job1->urn = urn;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_MODULE_REINSTANTIATE:
+ {
+ mod_t *mod = job->mod;
+ if(!mod)
+ break; //TODO report
+
+ _sp_app_mod_reinstantiate(app, mod);
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_MODULE_REINSTANTIATE;
+ job1->mod = job->mod;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_PRESET_LOAD:
+ {
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, job->urn);
+ int status = _sp_app_state_preset_load(app, job->mod, uri, true); (void)status; //FIXME check this
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_PRESET_LOAD;
+ job1->mod = job->mod;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_PRESET_SAVE:
+ {
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, job->urn);
+ int status = _sp_app_state_preset_save(app, job->mod, uri);
+ (void)status; //FIXME check this
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_PRESET_SAVE;
+ job1->mod = job->mod;
+ job1->urn = job->urn;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_BUNDLE_LOAD:
+ {
+ sp_app_bundle_load(app, job->urn, true);
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_BUNDLE_SAVE:
+ {
+ sp_app_bundle_save(app, job->urn, true);
+
+ break;
+ }
+ case JOB_TYPE_REQUEST_DRAIN:
+ {
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_DRAIN;
+ job1->status = 0;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+
+ break;
+ }
+ }
+}
+
+void
+sp_app_bundle_load(sp_app_t *app, LV2_URID urn, bool via_app)
+{
+ if(!via_app) //FIXME not rt-safe
+ {
+ // manually switch to blocking state and wait for initial bundle to be loaded
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+ app->load_bundle = true; // for sp_app_bypassed
+ // TODO keep in sync with synthpod_app_ui
+ }
+
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, urn);
+ int status = _sp_app_state_bundle_load(app, uri);
+ sp_app_log_note(app, "%s: <%s>\n", __func__, uri);
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_BUNDLE_LOAD;
+ job1->status = status;
+ job1->urn = urn;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+}
+
+void
+sp_app_bundle_save(sp_app_t *app, LV2_URID urn, bool via_app)
+{
+ if(!via_app) // FIXME not rt-safe
+ {
+ // manually switch to blocking state and wait for bundle to be saved
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+ }
+
+ const char *uri = app->driver->unmap->unmap(app->driver->unmap->handle, urn);
+ const int status = _sp_app_state_bundle_save(app, uri);
+ sp_app_log_note(app, "%s: <%s>\n", __func__, uri);
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_BUNDLE_SAVE;
+ job1->status = status;
+ job1->urn = urn;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+}
+
+void
+sp_app_apply(sp_app_t *app, LV2_Atom_Object *obj, char *bundle_path)
+{
+ //FIXME not rt-safe
+ // manually switch to blocking state and wait for bundle to be saved
+ app->block_state = BLOCKING_STATE_WAIT; // wait for job
+
+ sp_app_restore(app, sp_app_state_retrieve, obj,
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE,
+ sp_app_state_features(app, bundle_path));
+ sp_app_log_note(app, "%s: <%s>\n", __func__, bundle_path);
+
+ const int status = 0; //FIXME
+ const LV2_URID urn = 0; //FIXME
+
+ // signal to app
+ job_t *job1 = _sp_worker_to_app_request(app, sizeof(job_t));
+ if(job1)
+ {
+ job1->reply = JOB_TYPE_REPLY_BUNDLE_SAVE;
+ job1->status = status;
+ job1->urn = urn;
+ _sp_worker_to_app_advance(app, sizeof(job_t));
+ }
+ else
+ {
+ sp_app_log_error(app, "%s: buffer request failed\n", __func__);
+ }
+}
diff --git a/ardour.lv2/lv2_extensions.h b/ardour.lv2/lv2_extensions.h
new file mode 100644
index 00000000..64fc3bc6
--- /dev/null
+++ b/ardour.lv2/lv2_extensions.h
@@ -0,0 +1,174 @@
+/*
+ Copyright 2016 Robin Gareus <robin@gareus.org>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#ifndef _ardour_lv2_extensions_h_
+#define _ardour_lv2_extensions_h_
+
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+/**
+ @defgroup inlinedisplay Inline-Display
+
+ Support for displaying a miniaturized generic view
+ directly in the host's Mixer Window.
+
+ @{
+*/
+
+#define LV2_INLINEDISPLAY_URI "http://harrisonconsoles.com/lv2/inlinedisplay"
+#define LV2_INLINEDISPLAY_PREFIX LV2_INLINEDISPLAY_URI "#"
+#define LV2_INLINEDISPLAY__interface LV2_INLINEDISPLAY_PREFIX "interface"
+#define LV2_INLINEDISPLAY__queue_draw LV2_INLINEDISPLAY_PREFIX "queue_draw"
+
+/** Opaque handle for LV2_Inline_Display::queue_draw() */
+typedef void* LV2_Inline_Display_Handle;
+
+/** raw image pixmap format is ARGB32,
+ * the data pointer is owned by the plugin and must be valid
+ * from the first call to render until cleanup.
+ */
+typedef struct {
+ unsigned char *data;
+ int width;
+ int height;
+ int stride;
+} LV2_Inline_Display_Image_Surface;
+
+/** a LV2 Feature provided by the Host to the plugin */
+typedef struct {
+ /** Opaque host data */
+ LV2_Inline_Display_Handle handle;
+ /** Request from run() that the host should call render() at a later time
+ * to update the inline display */
+ void (*queue_draw)(LV2_Inline_Display_Handle handle);
+} LV2_Inline_Display;
+
+/**
+ * Plugin Inline-Display Interface.
+ */
+typedef struct {
+ /**
+ * The render method. This is called by the host in a non-realtime context,
+ * usually the main GUI thread.
+ * The data pointer is owned by the plugin and must be valid
+ * from the first call to render until cleanup.
+ *
+ * @param instance The LV2 instance
+ * @param w the max available width
+ * @param h the max available height
+ * @return pointer to a LV2_Inline_Display_Image_Surface or NULL
+ */
+ LV2_Inline_Display_Image_Surface* (*render)(LV2_Handle instance, uint32_t w, uint32_t h);
+} LV2_Inline_Display_Interface;
+
+/**
+ @}
+*/
+
+/**
+ @defgroup automate Self-Automation
+
+ Support for plugins to write automation data via Atom Events
+
+ @{
+*/
+
+#define LV2_AUTOMATE_URI "http://ardour.org/lv2/automate"
+#define LV2_AUTOMATE_URI_PREFIX LV2_AUTOMATE_URI "#"
+/** an lv2:optionalFeature */
+#define LV2_AUTOMATE_URI__can_write LV2_AUTOMATE_URI_PREFIX "canWriteAutomatation"
+/** atom:supports */
+#define LV2_AUTOMATE_URI__control LV2_AUTOMATE_URI_PREFIX "automationControl"
+/** lv2:portProperty */
+#define LV2_AUTOMATE_URI__controlled LV2_AUTOMATE_URI_PREFIX "automationControlled"
+#define LV2_AUTOMATE_URI__controller LV2_AUTOMATE_URI_PREFIX "automationController"
+
+/** atom messages */
+#define LV2_AUTOMATE_URI__event LV2_AUTOMATE_URI_PREFIX "event"
+#define LV2_AUTOMATE_URI__setup LV2_AUTOMATE_URI_PREFIX "setup"
+#define LV2_AUTOMATE_URI__finalize LV2_AUTOMATE_URI_PREFIX "finalize"
+#define LV2_AUTOMATE_URI__start LV2_AUTOMATE_URI_PREFIX "start"
+#define LV2_AUTOMATE_URI__end LV2_AUTOMATE_URI_PREFIX "end"
+#define LV2_AUTOMATE_URI__parameter LV2_AUTOMATE_URI_PREFIX "parameter"
+#define LV2_AUTOMATE_URI__value LV2_AUTOMATE_URI_PREFIX "value"
+
+/**
+ @}
+*/
+
+/**
+ @defgroup license License-Report
+
+ Allow for commercial LV2 to report their
+ licensing status.
+
+ @{
+*/
+
+#define LV2_PLUGINLICENSE_URI "http://harrisonconsoles.com/lv2/license"
+#define LV2_PLUGINLICENSE_PREFIX LV2_PLUGINLICENSE_URI "#"
+#define LV2_PLUGINLICENSE__interface LV2_PLUGINLICENSE_PREFIX "interface"
+
+typedef struct _LV2_License_Interface {
+ /* @return -1 if no license is needed; 0 if unlicensed, 1 if licensed */
+ int (*is_licensed)(LV2_Handle instance);
+ /* @return a string copy of the licensee name if licensed, or NULL, the caller needs to free this */
+ char* (*licensee)(LV2_Handle instance);
+ /* @return a URI identifying the plugin-bundle or plugin for which a given license is valid */
+ const char* (*product_uri)(LV2_Handle instance);
+ /* @return human readable product name for the URI */
+ const char* (*product_name)(LV2_Handle instance);
+ /* @return link to website or webstore */
+ const char* (*store_url)(LV2_Handle instance);
+} LV2_License_Interface;
+
+/**
+ @}
+*/
+
+/**
+ @defgroup plugin provided bypass
+
+ A port with the designation "processing#enable" must
+ control a plugin's internal bypass mode.
+
+ If the port value is larger than zero the plugin processes
+ normally.
+
+ If the port value is zero, the plugin is expected to bypass
+ all signals unmodified.
+
+ The plugin is responsible for providing a click-free transition
+ between the states.
+
+ (values less than zero are reserved for future use:
+ e.g click-free insert/removal of latent plugins.
+ Generally values <= 0 are to be treated as bypassed.)
+
+ lv2:designation <http://ardour.org/lv2/processing#enable> ;
+
+ @{
+*/
+
+#define LV2_PROCESSING_URI "http://ardour.org/lv2/processing"
+#define LV2_PROCESSING_URI_PREFIX LV2_PROCESSING_URI "#"
+#define LV2_PROCESSING_URI__enable LV2_PROCESSING_URI_PREFIX "enable"
+
+/**
+ @}
+*/
+
+#endif
diff --git a/bin/GPLv3 b/bin/GPLv3
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/bin/GPLv3
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/bin/meson.build b/bin/meson.build
new file mode 100644
index 00000000..e69a252e
--- /dev/null
+++ b/bin/meson.build
@@ -0,0 +1,139 @@
+bin_srcs = ['synthpod_bin.c']
+
+bin = static_library('synthpod_bin', bin_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : bin_deps)
+
+if use_dummy
+ dummy_srcs = ['synthpod_dummy.c']
+
+ dummy = executable('synthpod_dummy', dummy_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : bin_deps,
+ link_with : [bin, app, sbox_master],
+ install : true)
+
+ install_man('synthpod_dummy.1')
+endif
+
+if use_alsa and alsa_dep.found() and zita_dep.found()
+ alsa_srcs = ['synthpod_alsa.c', 'pcmi.cpp']
+
+ alsa = executable('synthpod_alsa', alsa_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [bin_deps, alsa_dep, zita_dep],
+ link_with : [bin, app, sbox_master],
+ install : true)
+
+ install_man('synthpod_alsa.1')
+endif
+
+if use_jack and jack_dep.found()
+
+ jack_srcs = ['synthpod_jack.c']
+ jack_c_args = []
+
+ if cc.has_header('jack/metadata.h')
+ jack_c_args += '-DJACK_HAS_METADATA_API'
+ endif
+
+ jack = executable('synthpod_jack', jack_srcs,
+ include_directories : bin_incs,
+ c_args : c_args + jack_c_args,
+ dependencies : [bin_deps, jack_dep],
+ link_with : [bin, app, sbox_master],
+ install : true)
+
+ install_man('synthpod_jack.1')
+endif
+
+if use_x11 and xcb_dep.found() and xcbicccm_dep.found()
+ x11_srcs = ['synthpod_sandbox_x11.c']
+
+ x11 = executable('synthpod_sandbox_x11', x11_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep, xcb_dep, xcbicccm_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if use_gtk2 and gtk2_dep.found()
+ gtk2_srcs = ['synthpod_sandbox_gtk.c']
+
+ gtk2 = executable('synthpod_sandbox_gtk2', gtk2_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep, gtk2_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if use_gtk3 and gtk3_dep.found()
+ gtk3_srcs = ['synthpod_sandbox_gtk.c']
+
+ gtk3 = executable('synthpod_sandbox_gtk3', gtk3_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep, gtk3_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if use_qt4 and qt4_dep.found()
+ qt4_mod = import('qt4')
+ qt4_srcs = ['synthpod_sandbox_qt.cpp']
+ qt4_mocs = qt4_mod.preprocess(moc_sources : qt4_srcs)
+
+ qt4 = executable('synthpod_sandbox_qt4', [qt4_srcs, qt4_mocs],
+ include_directories : bin_incs,
+ cpp_args : '-DSYNTHPOD_SANDBOX_QT=4',
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep, qt4_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if use_qt5 and qt5_dep.found()
+ qt5_mod = import('qt5')
+ qt5_srcs = ['synthpod_sandbox_qt.cpp']
+ qt5_mocs = qt5_mod.preprocess(moc_sources : qt5_srcs)
+
+ qt5 = executable('synthpod_sandbox_qt5', [qt5_srcs, qt5_mocs],
+ include_directories : bin_incs,
+ cpp_args : '-DSYNTHPOD_SANDBOX_QT=5',
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep, qt5_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if true
+ show_srcs = ['synthpod_sandbox_show.c']
+
+ show = executable('synthpod_sandbox_show', show_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+if true
+ kx_srcs = ['synthpod_sandbox_kx.c']
+
+ kx = executable('synthpod_sandbox_kx', kx_srcs,
+ include_directories : bin_incs,
+ c_args : c_args,
+ dependencies : [lv2_dep, rt_dep, thread_dep],
+ link_with : [sbox_slave],
+ install : true)
+endif
+
+install_data('synthpod_ui',
+ install_dir : get_option('bindir'),
+ install_mode : 'rwxr-xr-x')
+install_man('synthpod_sandbox.1')
diff --git a/bin/pcmi.cpp b/bin/pcmi.cpp
new file mode 100644
index 00000000..187b9687
--- /dev/null
+++ b/bin/pcmi.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+#include <pcmi.h>
+#include <zita-alsa-pcmi.h>
+
+pcmi_t *
+pcmi_new(const char *play_name, const char *capt_name, uint32_t srate,
+ uint32_t frsize, uint32_t nfrags, bool twochan, bool debug)
+{
+ unsigned int opts = 0;
+ if(debug)
+ opts |= Alsa_pcmi::DEBUG_ALL;
+ if(twochan) // force 2 channels
+ opts |= Alsa_pcmi::FORCE_2CH;
+
+ Alsa_pcmi *_pcmi = new Alsa_pcmi(play_name, capt_name, NULL,
+ srate, frsize, nfrags, opts);
+ if(_pcmi->state())
+ {
+ delete _pcmi;
+ return NULL;
+ }
+
+ return (pcmi_t *)_pcmi;
+}
+
+void
+pcmi_free(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ delete _pcmi;
+}
+
+void
+pcmi_printinfo(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->printinfo();
+}
+
+int
+pcmi_ncapt(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ return _pcmi->ncapt();
+}
+
+int
+pcmi_nplay(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ return _pcmi->nplay();
+}
+
+void
+pcmi_pcm_start(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->pcm_start();
+}
+
+int
+pcmi_pcm_wait(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ return _pcmi->pcm_wait();
+}
+
+int
+pcmi_pcm_idle(pcmi_t *pcmi, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ return _pcmi->pcm_idle(frsize);
+}
+
+void
+pcmi_pcm_stop(pcmi_t *pcmi)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->pcm_stop();
+}
+
+void
+pcmi_capt_init(pcmi_t *pcmi, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->capt_init(frsize);
+}
+
+void
+pcmi_capt_chan(pcmi_t *pcmi, uint32_t channel, float *dst, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->capt_chan(channel, dst, frsize);
+}
+
+void
+pcmi_capt_done(pcmi_t *pcmi, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->capt_done(frsize);
+}
+
+void
+pcmi_play_init(pcmi_t *pcmi, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->play_init(frsize);
+}
+
+void
+pcmi_clear_chan(pcmi_t *pcmi, uint32_t channel, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->clear_chan(channel, frsize);
+}
+
+void
+pcmi_play_chan(pcmi_t *pcmi, uint32_t channel, const float *src, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->play_chan(channel, src, frsize);
+}
+
+void
+pcmi_play_done(pcmi_t *pcmi, uint32_t frsize)
+{
+ Alsa_pcmi *_pcmi = (Alsa_pcmi *)pcmi;
+
+ _pcmi->play_done(frsize);
+}
diff --git a/bin/pcmi.h b/bin/pcmi.h
new file mode 100644
index 00000000..e6bb1801
--- /dev/null
+++ b/bin/pcmi.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef void pcmi_t;
+
+pcmi_t *
+pcmi_new(const char *play_name, const char *capt_name, uint32_t srate,
+ uint32_t frsize, uint32_t nfrags, bool twochan, bool debug);
+
+void
+pcmi_free(pcmi_t *pcmi);
+
+void
+pcmi_printinfo(pcmi_t *pcmi);
+
+int
+pcmi_ncapt(pcmi_t *pcmi);
+
+int
+pcmi_nplay(pcmi_t *pcmi);
+
+void
+pcmi_pcm_start(pcmi_t *pcmi);
+
+int
+pcmi_pcm_wait(pcmi_t *pcmi);
+
+int
+pcmi_pcm_idle(pcmi_t *pcmi, uint32_t frsize);
+
+void
+pcmi_pcm_stop(pcmi_t *pcmi);
+
+void
+pcmi_capt_init(pcmi_t *pcmi, uint32_t frsize);
+
+void
+pcmi_capt_chan(pcmi_t *pcmi, uint32_t channel, float *dst, uint32_t frsize);
+
+void
+pcmi_capt_done(pcmi_t *pcmi, uint32_t frsize);
+
+void
+pcmi_play_init(pcmi_t *pcmi, uint32_t frsize);
+
+void
+pcmi_clear_chan(pcmi_t *pcmi, uint32_t channel, uint32_t frsize);
+
+void
+pcmi_play_chan(pcmi_t *pcmi, uint32_t channel, const float *src, uint32_t frsize);
+
+void
+pcmi_play_done(pcmi_t *pcmi, uint32_t frsize);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/bin/synthpod_alsa.1 b/bin/synthpod_alsa.1
new file mode 100644
index 00000000..a30aa738
--- /dev/null
+++ b/bin/synthpod_alsa.1
@@ -0,0 +1,183 @@
+.TH SYNTHPOD "1" "Feb 24, 2017"
+
+.SH NAME
+synthpod \- a lightweight nonlinear LV2 plugin container
+
+.SH SYNOPSIS
+.B synthpod_alsa
+[\fIoptions\fR] [\fIbundle-path\fR]
+
+.SH DESCRIPTION
+\fBsynthpod\fP is a lightweight nonlinear LV2 plugin container, aka host.
+.PP
+It is a pure stand-alone ALSA client with NSM session support and ALSA AUDIO/MIDI.
+.PP
+It is also a first-class LV2 plugin (http://open-music-kontrollers.ch/lv2/synthpod#stereo).
+
+.SH OPTIONS
+.HP
+\fB\-v\fR
+.IP
+Print version and license information
+
+.HP
+\fB\-h\fR
+.IP
+Print usage information
+
+.HP
+\fB\-g\fR
+.IP
+Load GUI
+
+.HP
+\fB\-G\fR
+.IP
+Do NOT load GUI (default)
+
+.HP
+\fB\-k\fR
+.IP
+Kill DSP with GUI
+
+.HP
+\fB\-K\fR
+.IP
+Do NOT kill DSP with GUI (default)
+
+.HP
+\fB\-b\fR
+.IP
+Enable bad plugins
+
+.HP
+\fB\-B\fR
+.IP
+Disable bad plugins (default)
+
+.HP
+\fB\-a\fR
+.IP
+Enable CPU affinity
+
+.HP
+\fB\-A\fR
+.IP
+Disable CPU affinity (default)
+
+.HP
+\fB\-O\fR
+.IP
+Disable playback
+
+.HP
+\fB\-I\fR
+.IP
+Disable capture
+
+.HP
+\fB\-t\fR
+.IP
+Force 2 channel stereo mode
+
+.HP
+\fB\-T\fR
+.IP
+Do NOT force 2 channel stereo mode
+
+.HP
+\fB\-x\fR
+.IP
+Notify about XRuns
+
+.HP
+\fB\-X\fR
+.IP
+Do NOT notify about XRuns
+
+.HP
+\fB\-y\fR audio-priority
+.IP
+Audio thread realtime priority (70)
+
+.HP
+\fB\-Y\fR
+.IP
+Disable audio thread realtime priority
+
+.HP
+\fB\-w\fR worker-priority
+.IP
+Worker thread realtime priority (60)
+
+.HP
+\fB\-W\fR
+.IP
+Disable worker thread realtime priority
+
+.HP
+\fB\-l\fR
+.IP
+Socket link path (shm:///synthpod), e.g. tcp://*:9090
+
+.HP
+\fB\-d\fR device
+.IP
+Use same device for Playback and Capture ("hw:0")
+
+.HP
+\fB\-o\fR playback-device
+.IP
+Use separate Playback device ("hw:0")
+
+.HP
+\fB\-i\fR capture-device
+.IP
+Use separate Capture device ("hw:0")
+
+.HP
+\fB\-r\fR sample-rate
+.IP
+Sample Rate (48000)
+
+.HP
+\fB\-p\fR sample-period
+.IP
+Frames per period (1024)
+
+.HP
+\fB\-n\fR period-number
+.IP
+Number of periods of playback latency (3)
+
+.HP
+\fB\-s\fR sequence-size
+.IP
+Minimal byte size of event sequence buffers (8192)
+
+.HP
+\fB\-c\fR slave-cores
+.IP
+Number of slave cores for parallel audio processing (auto)
+
+.HP
+\fB\-f\fR update-rate
+.IP
+Update rate in frames per second of GUI
+
+.SH FILES
+.TP
+.I $HOME/.lv2/Synthpod_default.preset.lv2
+Default bundle state directory
+.TP
+.I $HOME/.lv2
+Default LV2 preset directory
+
+.SH LICENSE
+GNU General Public License 3
+
+.SH AUTHOR
+Hanspeter Portner (dev@open-music-kontrollers.ch).
+
+.SH SEE ALSO
+synthpod_jack(1), synthpod_dummy(1), synthpod_sandbox(1)
diff --git a/bin/synthpod_alsa.c b/bin/synthpod_alsa.c
new file mode 100644
index 00000000..074d4679
--- /dev/null
+++ b/bin/synthpod_alsa.c
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <math.h>
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+# include <pthread_np.h>
+typedef cpuset_t cpu_set_t;
+#endif
+
+#include <asoundlib.h>
+#include <pcmi.h>
+
+#include <synthpod_bin.h>
+
+#define MIDI_SEQ_SIZE 2048
+#define NANO_SECONDS 1000000000
+
+typedef enum _chan_type_t chan_type_t;
+typedef struct _prog_t prog_t;
+typedef struct _chan_t chan_t;
+
+enum _chan_type_t {
+ CHAN_TYPE_PCMI,
+ CHAN_TYPE_MIDI
+};
+
+struct _chan_t {
+ chan_type_t type;
+
+ union {
+ struct {
+ //TODO
+ } audio;
+ struct {
+ int port;
+ snd_midi_event_t *trans;
+
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+ LV2_Atom_Sequence *seq_in;
+ int64_t last;
+ } midi;
+ };
+};
+
+struct _prog_t {
+ bin_t bin;
+
+ LV2_Atom_Forge forge;
+
+ LV2_URID midi_MidiEvent;
+
+ pcmi_t *pcmi;
+ snd_seq_t *seq;
+ int queue;
+ uint8_t m [MIDI_SEQ_SIZE];
+
+ save_state_t save_state;
+ atomic_int kill;
+ pthread_t thread;
+
+ uint32_t srate;
+ uint32_t frsize;
+ uint32_t nfrags;
+ bool twochan;
+ bool debug;
+ bool do_play;
+ bool do_capt;
+ uint32_t seq_size;
+
+ const char *io_name;
+ const char *play_name;
+ const char *capt_name;
+
+ LV2_OSC_Schedule osc_sched;
+ struct timespec cur_ntp;
+ struct timespec nxt_ntp;
+ struct {
+ uint64_t cur_frames;
+ uint64_t ref_frames;
+ double dT;
+ double dTm1;
+ } cycle;
+};
+
+static inline void
+_ntp_now(cross_clock_t *clk, struct timespec *ntp)
+{
+ cross_clock_gettime(clk, ntp);
+ ntp->tv_sec += JAN_1970; // convert NTP to OSC time
+}
+
+static inline void
+_ntp_clone(struct timespec *dst, struct timespec *src)
+{
+ dst->tv_sec = src->tv_sec;
+ dst->tv_nsec = src->tv_nsec;
+}
+
+static inline void
+_ntp_add_nanos(struct timespec *ntp, uint64_t nanos)
+{
+ ntp->tv_nsec += nanos;
+ while(ntp->tv_nsec >= NANO_SECONDS) // has overflowed
+ {
+ ntp->tv_sec += 1;
+ ntp->tv_nsec -= NANO_SECONDS;
+ }
+}
+
+static inline double
+_ntp_diff(struct timespec *from, struct timespec *to)
+{
+ double diff = to->tv_sec;
+ diff -= from->tv_sec;
+ diff += 1e-9 * to->tv_nsec;
+ diff -= 1e-9 * from->tv_nsec;
+
+ return diff;
+}
+
+__realtime static inline void
+_process(prog_t *handle)
+{
+ pcmi_t *pcmi = handle->pcmi;
+ bin_t *bin = &handle->bin;
+ sp_app_t *app = bin->app;
+
+ const uint32_t nsamples = handle->frsize;
+ int nplay = pcmi_nplay(pcmi);
+ int ncapt = pcmi_ncapt(pcmi);
+ int play_num;
+ int capt_num;
+
+ const uint64_t nanos_per_period = (uint64_t)nsamples * NANO_SECONDS / handle->srate;
+ handle->cycle.cur_frames = 0; // initialize frame counter
+ _ntp_now(&bin->clk_real, &handle->nxt_ntp);
+
+ snd_seq_queue_status_t *stat = NULL;
+ const snd_seq_real_time_t *real_time = NULL;
+ snd_seq_real_time_t ref_time;
+ snd_seq_queue_status_malloc(&stat);
+
+ pcmi_pcm_start(handle->pcmi);
+ while(!atomic_load_explicit(&handle->kill, memory_order_relaxed))
+ {
+ uint32_t na = pcmi_pcm_wait(pcmi);
+
+ // current time is next time from last cycle
+ _ntp_clone(&handle->cur_ntp, &handle->nxt_ntp);
+
+ // extrapolate new nxt_ntp
+ struct timespec nxt_ntp;
+ _ntp_now(&bin->clk_real, &nxt_ntp);
+ _ntp_clone(&handle->nxt_ntp, &nxt_ntp);
+
+ // reset ref_frames
+ handle->cycle.ref_frames = handle->cycle.cur_frames;
+
+ // calculate apparent period
+ _ntp_add_nanos(&nxt_ntp, nanos_per_period);
+ double diff = _ntp_diff(&handle->cur_ntp, &nxt_ntp);
+
+ // calculate apparent samples per period
+ handle->cycle.dT = nsamples / diff;
+ handle->cycle.dTm1 = 1.0 / handle->cycle.dT;
+
+ // get ALSA sequencer reference timestamp
+ snd_seq_get_queue_status(handle->seq, handle->queue, stat);
+ real_time = snd_seq_queue_status_get_real_time(stat);
+ ref_time.tv_sec = real_time->tv_sec;
+ ref_time.tv_nsec = real_time->tv_nsec;
+
+ uint32_t pos = 0;
+ for( ; na >= nsamples;
+ na -= nsamples,
+ handle->cycle.ref_frames += nsamples,
+ pos += nsamples,
+ _ntp_add_nanos(&handle->nxt_ntp, nanos_per_period) )
+ {
+ const sp_app_system_source_t *sources = sp_app_get_system_sources(app);
+
+ if(sp_app_bypassed(app))
+ {
+ //const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ //fprintf(stderr, "app is bypassed\n");
+
+ pcmi_pcm_idle(pcmi, nsamples);
+
+ bin_process_pre(bin, nsamples, true);
+ bin_process_post(bin);
+
+ continue;
+ }
+
+ // fill input buffers
+ if(ncapt)
+ pcmi_capt_init(pcmi, nsamples);
+ capt_num = 0;
+ for(const sp_app_system_source_t *source=sources;
+ source->type != SYSTEM_PORT_NONE;
+ source++)
+ {
+ chan_t *chan = source->sys_port;
+
+ switch(source->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_CV:
+ case SYSTEM_PORT_OSC:
+ break;
+
+ case SYSTEM_PORT_AUDIO:
+ {
+
+ if(capt_num < ncapt)
+ pcmi_capt_chan(pcmi, capt_num++, source->buf, nsamples);
+
+ break;
+ }
+
+ case SYSTEM_PORT_MIDI:
+ {
+ void *seq_in = source->buf;
+
+ // initialize LV2 event port
+ LV2_Atom_Forge *forge = &chan->midi.forge;
+ chan->midi.seq_in = seq_in; // needed for lv2_atom_sequence_clear
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ chan->midi.ref = lv2_atom_forge_sequence_head(forge, &chan->midi.frame, 0);
+ chan->midi.last = 0;
+
+ break;
+ }
+
+ case SYSTEM_PORT_COM:
+ {
+ void *seq_in = source->buf;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ const LV2_Atom_Object *obj;
+ size_t size;
+ while((obj = varchunk_read_request(bin->app_from_com, &size)))
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, obj, size);
+
+ varchunk_read_advance(bin->app_from_com);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(seq_in);
+
+ break;
+ }
+ }
+ }
+
+ // read incoming MIDI and dispatch to corresponding MIDI port
+ {
+ snd_seq_event_t *sev;
+ while(snd_seq_event_input_pending(handle->seq, 1) > 0)
+ {
+ chan_t *chan = NULL;
+
+ // get event
+ snd_seq_event_input(handle->seq, &sev);
+
+ // search for matching port
+ for(const sp_app_system_source_t *source=sources;
+ source->type != SYSTEM_PORT_NONE;
+ source++)
+ {
+ if(source->type == SYSTEM_PORT_MIDI)
+ {
+ chan_t *_chan = source->sys_port;
+
+ if(_chan->midi.port == sev->dest.port)
+ {
+ chan = _chan;
+ break; // right port found, break out of loop
+ }
+ }
+ }
+
+ if(chan)
+ {
+ long len;
+ if((len = snd_midi_event_decode(chan->midi.trans, handle->m,
+ MIDI_SEQ_SIZE, sev)) > 0)
+ {
+ LV2_Atom_Forge *forge = &chan->midi.forge;
+
+ const bool is_real = snd_seq_ev_is_real(sev);
+ const bool is_abs = snd_seq_ev_is_abstime(sev);
+ assert(is_real);
+
+ volatile double dd = sev->time.time.tv_sec;
+ if(is_abs) // calculate relative time difference
+ dd -= ref_time.tv_sec;
+ dd += sev->time.time.tv_nsec * 1e-9;
+ if(is_abs) // calculate relative time difference
+ dd -= ref_time.tv_nsec * 1e-9;
+
+ int64_t frames = dd * handle->srate - pos;
+ if(frames < 0)
+ frames = 0;
+ else if(frames >= nsamples)
+ frames = nsamples - 1; //TODO report this
+
+ if(frames < chan->midi.last)
+ frames = chan->midi.last; // frame time must be increasing
+ else
+ chan->midi.last = frames;
+
+ // fix up noteOn(vel=0) -> noteOff(vel=0)
+ if( (len == 3) && ( (handle->m[0] & 0xf0) == 0x90)
+ && (handle->m[2] == 0x0) )
+ {
+ handle->m[0] = 0x80 | (handle->m[0] & 0x0f);
+ handle->m[2] = 0x0;
+ }
+
+ if(chan->midi.ref)
+ chan->midi.ref = lv2_atom_forge_frame_time(forge, frames);
+ if(chan->midi.ref)
+ chan->midi.ref = lv2_atom_forge_atom(forge, len, handle->midi_MidiEvent);
+ if(chan->midi.ref)
+ chan->midi.ref = lv2_atom_forge_write(forge, handle->m, len);
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: MIDI event decode failed\n", __func__);
+ }
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: no matching port for MIDI event\n", __func__);
+ }
+
+ if(snd_seq_free_event(sev))
+ {
+ bin_log_trace(bin, "%s: MIDI event free failed\n", __func__);
+ }
+ }
+ }
+
+ for(const sp_app_system_source_t *source=sources;
+ source->type != SYSTEM_PORT_NONE;
+ source++)
+ {
+ chan_t *chan = source->sys_port;
+
+ if(source->type == SYSTEM_PORT_MIDI)
+ {
+ LV2_Atom_Forge *forge = &chan->midi.forge;
+
+ // finalize LV2 event port
+ if(chan->midi.ref)
+ lv2_atom_forge_pop(forge, &chan->midi.frame);
+ else
+ lv2_atom_sequence_clear(chan->midi.seq_in);
+ }
+ }
+ if(ncapt)
+ pcmi_capt_done(pcmi, nsamples);
+
+ bin_process_pre(bin, nsamples, false);
+
+ const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ // fill output buffers
+ if(nplay)
+ pcmi_play_init(pcmi, nsamples);
+ play_num = 0;
+ for(const sp_app_system_sink_t *sink=sinks;
+ sink->type != SYSTEM_PORT_NONE;
+ sink++)
+ {
+ chan_t *chan = sink->sys_port;
+
+ switch(sink->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_CV:
+ case SYSTEM_PORT_OSC:
+ break;
+
+ case SYSTEM_PORT_AUDIO:
+ {
+
+ if(play_num < nplay)
+ pcmi_play_chan(pcmi, play_num++, sink->buf, nsamples);
+
+ break;
+ }
+
+ case SYSTEM_PORT_MIDI:
+ {
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ if(atom->type != handle->midi_MidiEvent)
+ continue; // ignore non-MIDI events
+
+ snd_seq_event_t sev;
+ snd_seq_ev_clear(&sev);
+ long consumed;
+ const uint8_t *buf = LV2_ATOM_BODY_CONST(atom);
+ if( (consumed = snd_midi_event_encode(chan->midi.trans, buf, atom->size, &sev)) != atom->size)
+ {
+ bin_log_trace(bin, "%s: MIDI encode event failed: %li\n", __func__, consumed);
+ continue;
+ }
+
+ // absolute timestamp
+ volatile double dd = (double)(ev->time.frames + pos) / handle->srate;
+ double sec;
+ double nsec = modf(dd, &sec);
+ struct snd_seq_real_time rtime = {
+ .tv_sec = ref_time.tv_sec + sec,
+ .tv_nsec = ref_time.tv_nsec + nsec
+ };
+ while(rtime.tv_nsec >= NANO_SECONDS) // handle overflow
+ {
+ rtime.tv_sec += 1;
+ rtime.tv_nsec -= NANO_SECONDS;
+ }
+
+ // schedule midi
+ snd_seq_ev_set_source(&sev, chan->midi.port);
+ snd_seq_ev_set_subs(&sev); // set broadcasting to subscribers
+ snd_seq_ev_schedule_real(&sev, handle->queue, 0, &rtime); // absolute scheduling
+ snd_seq_event_output(handle->seq, &sev);
+ //snd_seq_drain_output(handle->seq);
+ }
+
+ break;
+ }
+
+ case SYSTEM_PORT_COM:
+ {
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ // try do process events directly
+ bin->advance_ui = sp_app_from_ui(bin->app, atom);
+ if(!bin->advance_ui) // queue event in ringbuffer instead
+ {
+ //fprintf(stderr, "plugin ui direct is blocked\n");
+
+ void *ptr;
+ size_t size = lv2_atom_total_size(atom);
+ if((ptr = varchunk_write_request(bin->app_from_app, size)))
+ {
+ memcpy(ptr, atom, size);
+ varchunk_write_advance(bin->app_from_app, size);
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: app_from_app ringbuffer full\n", __func__);
+ //FIXME
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ snd_seq_drain_output(handle->seq); //TODO is this rt-safe?
+
+ // clear unused output channels
+ while(play_num<nplay)
+ {
+ pcmi_clear_chan(pcmi, play_num++, nsamples);
+ }
+
+ if(nplay)
+ pcmi_play_done(pcmi, nsamples);
+
+ bin_process_post(bin);
+ }
+
+ // increase cur_frames
+ handle->cycle.cur_frames = handle->cycle.ref_frames;
+ //sched_yield();
+ }
+ pcmi_pcm_stop(handle->pcmi);
+
+ snd_seq_queue_status_free(stat);
+}
+
+__non_realtime static void *
+_rt_thread(void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ bin->dsp_thread = pthread_self();
+
+ if(handle->bin.audio_prio)
+ {
+ struct sched_param schedp;
+ memset(&schedp, 0, sizeof(struct sched_param));
+ schedp.sched_priority = handle->bin.audio_prio;
+
+ if(schedp.sched_priority)
+ {
+ if(pthread_setschedparam(bin->dsp_thread, SCHED_FIFO, &schedp))
+ bin_log_error(bin, "%s: pthread_setschedparam failed\n", __func__);
+ }
+ }
+
+ if(handle->bin.cpu_affinity)
+ {
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(0, &cpuset);
+ if(pthread_setaffinity_np(bin->dsp_thread, sizeof(cpu_set_t), &cpuset))
+ bin_log_error(bin, "%s: pthread_setaffinity_np failed\n", __func__);
+ }
+
+ _process(handle);
+
+ return NULL;
+}
+
+__non_realtime static void *
+_system_port_add(void *data, system_port_t type, const char *short_name,
+ const char *pretty_name, const char *designation, bool input, uint32_t order)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ chan_t *chan = NULL;
+
+ switch(type)
+ {
+ case SYSTEM_PORT_NONE:
+ {
+ // skip
+ break;
+ }
+
+ case SYSTEM_PORT_CONTROL:
+ {
+ // unsupported, skip
+ break;
+ }
+
+ case SYSTEM_PORT_AUDIO:
+ {
+ chan = calloc(1, sizeof(chan_t));
+ if(chan)
+ {
+ chan->type = CHAN_TYPE_PCMI;
+ //TODO
+ }
+
+ break;
+ }
+ case SYSTEM_PORT_CV:
+ {
+ // unsupported, skip
+ break;
+ }
+
+ case SYSTEM_PORT_MIDI:
+ {
+ chan = calloc(1, sizeof(chan_t));
+ if(chan)
+ {
+ chan->type = CHAN_TYPE_MIDI;
+ memcpy(&chan->midi.forge, &handle->forge, sizeof(LV2_Atom_Forge)); // initialize forge
+
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_malloc(&pinfo);
+
+ snd_seq_port_info_set_name(pinfo, short_name);
+ snd_seq_port_info_set_capability(pinfo, input
+ ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE
+ : SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
+ snd_seq_port_info_set_midi_voices(pinfo, 64);
+ snd_seq_port_info_set_synth_voices(pinfo, 0);
+ snd_seq_port_info_set_timestamping(pinfo, 1);
+ snd_seq_port_info_set_timestamp_real(pinfo, 1);
+ snd_seq_port_info_set_timestamp_queue(pinfo, handle->queue);
+
+ snd_seq_create_port(handle->seq, pinfo);
+ chan->midi.port = snd_seq_port_info_get_port(pinfo);
+ if(chan->midi.port < 0)
+ bin_log_error(bin, "%s: could not create MIDI port\n", __func__);
+
+ snd_seq_port_info_free(pinfo);
+
+ if(snd_midi_event_new(MIDI_SEQ_SIZE, &chan->midi.trans))
+ bin_log_error(bin, "%s: could not create MIDI event translator\n", __func__);
+ snd_midi_event_init(chan->midi.trans);
+ if(input)
+ snd_midi_event_reset_encode(chan->midi.trans);
+ else
+ snd_midi_event_reset_decode(chan->midi.trans);
+ snd_midi_event_no_status(chan->midi.trans, 1);
+ }
+
+ break;
+ }
+ case SYSTEM_PORT_OSC:
+ {
+ // unsupported, skip
+ break;
+ }
+ case SYSTEM_PORT_COM:
+ {
+ // unsupported, skip
+ break;
+ }
+ }
+
+ return chan;
+}
+
+__non_realtime static void
+_system_port_del(void *data, void *sys_port)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ chan_t *chan = sys_port;
+
+ if(!chan)
+ return;
+
+ switch(chan->type)
+ {
+ case CHAN_TYPE_PCMI:
+ {
+ //TODO
+
+ break;
+ }
+ case CHAN_TYPE_MIDI:
+ {
+ snd_midi_event_free(chan->midi.trans);
+ if(handle->seq)
+ snd_seq_delete_simple_port(handle->seq, chan->midi.port);
+
+ break;
+ }
+ }
+
+ free(chan);
+}
+
+__non_realtime static void
+_saved(bin_t *bin, int status)
+{
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ if(handle->save_state == SAVE_STATE_NSM)
+ {
+ nsmc_saved(bin->nsm, status);
+ }
+ handle->save_state = SAVE_STATE_INTERNAL;
+
+ if(atomic_load_explicit(&handle->kill, memory_order_relaxed))
+ {
+ bin_quit(bin);
+ }
+}
+
+static int
+_alsa_init(prog_t *handle, const char *id)
+{
+ bin_t *bin = &handle->bin;
+
+ // init alsa sequencer
+ if(snd_seq_open(&handle->seq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK))
+ bin_log_error(bin, "%s: could not open sequencer\n", __func__);
+ if(snd_seq_set_client_name(handle->seq, id))
+ bin_log_error(bin, "%s: could not set name\n", __func__);
+ handle->queue = snd_seq_alloc_queue(handle->seq);
+ if(handle->queue < 0)
+ bin_log_error(bin, "%s: could not allocate queue\n", __func__);
+ snd_seq_start_queue(handle->seq, handle->queue, NULL);
+
+ // init alsa pcm
+ handle->pcmi = pcmi_new(handle->play_name, handle->capt_name,
+ handle->srate, handle->frsize, handle->nfrags, handle->twochan, handle->debug);
+ if(!handle->pcmi)
+ return -1;
+ pcmi_printinfo(handle->pcmi);
+
+ return 0;
+}
+
+static void
+_alsa_deinit(prog_t *handle)
+{
+ bin_t *bin = &handle->bin;
+
+ if(handle->thread)
+ {
+ atomic_store_explicit(&handle->kill, 1, memory_order_relaxed);
+ pthread_join(handle->thread, NULL);
+ }
+
+ if(handle->pcmi)
+ {
+ pcmi_free(handle->pcmi);
+
+ handle->pcmi = NULL;
+ }
+
+ if(handle->seq)
+ {
+ if(snd_seq_drain_output(handle->seq))
+ bin_log_error(bin, "%s: draining output failed\n", __func__);
+ snd_seq_stop_queue(handle->seq, handle->queue, NULL);
+ if(snd_seq_free_queue(handle->seq, handle->queue))
+ bin_log_error(bin, "%s: freeing queue failed\n", __func__);
+ if(snd_seq_close(handle->seq))
+ bin_log_error(bin, "%s: close sequencer failed\n", __func__);
+
+ handle->seq = NULL;
+ handle->queue = 0;
+ }
+}
+
+__non_realtime static int
+_open(const char *path, const char *name, const char *id, void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+ (void)name;
+
+ if(bin->path)
+ free(bin->path);
+ bin->path = strdup(path);
+
+ // alsa init
+ if(_alsa_init(handle, id))
+ {
+ nsmc_opened(bin->nsm, -1);
+ return -1;
+ }
+
+ // synthpod init
+ bin->app_driver.sample_rate = handle->srate;
+ bin->app_driver.update_rate = handle->bin.update_rate;
+ bin->app_driver.max_block_size = handle->frsize;
+ bin->app_driver.min_block_size = 1;
+ bin->app_driver.seq_size = handle->seq_size;
+ bin->app_driver.num_periods = handle->nfrags;
+
+ // app init
+ bin->app = sp_app_new(NULL, &bin->app_driver, bin);
+
+ // alsa activate
+ atomic_init(&handle->kill, 0);
+ if(pthread_create(&handle->thread, NULL, _rt_thread, handle))
+ bin_log_error(bin, "%s: creation of realtime thread failed\n", __func__);
+
+ bin_bundle_load(bin, bin->path);
+ nsmc_opened(bin->nsm, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_save(void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ handle->save_state = SAVE_STATE_NSM;
+ bin_bundle_save(bin, bin->path);
+ _saved(bin, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_show(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_show(bin);
+}
+
+__non_realtime static int
+_hide(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_hide(bin);
+}
+
+static const nsmc_driver_t nsm_driver = {
+ .open = _open,
+ .save = _save,
+ .show = _show,
+ .hide = _hide
+};
+
+// rt
+__realtime static double
+_osc_schedule_osc2frames(LV2_OSC_Schedule_Handle instance, uint64_t timestamp)
+{
+ prog_t *handle = instance;
+
+ if(timestamp == 1ULL)
+ return 0; // inject at start of period
+
+ const uint64_t time_sec = timestamp >> 32;
+ const uint64_t time_frac = timestamp & 0xffffffff;
+
+ const double diff = (time_sec - handle->cur_ntp.tv_sec)
+ + time_frac * 0x1p-32
+ - handle->cur_ntp.tv_nsec * 1e-9;
+
+ const double frames = diff * handle->cycle.dT
+ - handle->cycle.ref_frames
+ + handle->cycle.cur_frames;
+
+ return frames;
+}
+
+// rt
+__realtime static uint64_t
+_osc_schedule_frames2osc(LV2_OSC_Schedule_Handle instance, double frames)
+{
+ prog_t *handle = instance;
+
+ double diff = (frames - handle->cycle.cur_frames + handle->cycle.ref_frames)
+ * handle->cycle.dTm1;
+ diff += handle->cur_ntp.tv_nsec * 1e-9;
+ diff += handle->cur_ntp.tv_sec;
+
+ double time_sec_d;
+ double time_frac_d = modf(diff, &time_sec_d);
+
+ uint64_t time_sec = time_sec_d;
+ uint64_t time_frac = time_frac_d * 0x1p32;
+ if(time_frac >= 0x100000000ULL) // illegal overflow
+ time_frac = 0xffffffffULL;
+
+ uint64_t timestamp = (time_sec << 32) | time_frac;
+
+ return timestamp;
+}
+
+int
+main(int argc, char **argv)
+{
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+
+ static prog_t handle;
+ bin_t *bin = &handle.bin;
+
+ handle.srate = 48000;
+ handle.frsize = 1024;
+ handle.nfrags = 3;
+ handle.twochan = false;
+ handle.debug = false;
+ handle.do_play = true;
+ handle.do_capt = true;
+ handle.seq_size = SEQ_SIZE;
+
+ const char *def = "hw:0";
+ handle.io_name = def;
+ handle.play_name = NULL;
+ handle.capt_name = NULL;
+
+ bin->audio_prio = 70;
+ bin->worker_prio = 60;
+ bin->num_slaves = sysconf(_SC_NPROCESSORS_ONLN) - 1;
+ bin->bad_plugins = false;
+ bin->has_gui = false;
+ bin->kill_gui = false;
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "shm:///synthpod-%i", getpid());
+ bin->update_rate = 25;
+ bin->cpu_affinity = false;
+
+ fprintf(stderr,
+ "Synthpod "SYNTHPOD_VERSION"\n"
+ "Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)\n"
+ "Released under GNU General Public License 3 by Open Music Kontrollers\n");
+
+ // read local configuration if present
+ /*FIXME
+ Efreet_Ini *ini = _read_config(&handle);
+ */
+
+ int c;
+ while((c = getopt(argc, argv, "vhgGbkKBaAIOtTxXy:Yw:Wl:d:i:o:r:p:n:s:c:f:")) != -1)
+ {
+ switch(c)
+ {
+ case 'v':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "This program is free software; you can redistribute it and/or modify\n"
+ "it under the terms of the GNU General Public License as published by\n"
+ "the Free Software Foundation; either version 3 of the License, or\n"
+ "(at your option) any later version.\n"
+ "\n"
+ "This program is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with this program. If not, see http://www.gnu.org/licenses.\n\n");
+ return 0;
+ case 'h':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "USAGE\n"
+ " %s [OPTIONS] [BUNDLE_PATH]\n"
+ "\n"
+ "OPTIONS\n"
+ " [-v] print version and full license information\n"
+ " [-h] print usage information\n"
+ " [-g] load GUI\n"
+ " [-G] do NOT load GUI (default)\n"
+ " [-k] kill DSP with GUI\n"
+ " [-K] do NOT kill DSP with GUI (default)\n"
+ " [-b] enable bad plugins\n"
+ " [-B] disable bad plugins (default)\n"
+ " [-a] enable CPU affinity\n"
+ " [-A] disable CPU affinity (default)\n"
+ " [-I] disable capture\n"
+ " [-O] disable playback\n"
+ " [-t] force 2 channel mode\n"
+ " [-T] do NOT force 2 channel mode (default)\n"
+ " [-x] notify about XRuns\n"
+ " [-X] do NOT notify about XRuns (default)\n"
+ " [-y] audio-priority audio thread realtime priority (70)\n"
+ " [-Y] do NOT use audio thread realtime priority\n"
+ " [-w] worker-priority worker thread realtime priority (60)\n"
+ " [-W] do NOT use worker thread realtime priority\n"
+ " [-l] link-path socket link path (shm:///synthpod)\n"
+ " [-d] device capture/playback device (\"hw:0\")\n"
+ " [-i] capture-device capture device (\"hw:0\")\n"
+ " [-o] playback-device playback device (\"hw:0\")\n"
+ " [-r] sample-rate sample rate (48000)\n"
+ " [-p] sample-period frames per period (1024)\n"
+ " [-n] period-number number of periods of playback latency (3)\n"
+ " [-s] sequence-size minimum sequence size (8192)\n"
+ " [-c] slave-cores number of slave cores (auto)\n"
+ " [-f] update-rate GUI update rate (25)\n\n"
+ , argv[0]);
+ return 0;
+ case 'g':
+ bin->has_gui = true;
+ break;
+ case 'G':
+ bin->has_gui = false;
+ break;
+ case 'k':
+ bin->kill_gui = true;
+ break;
+ case 'K':
+ bin->kill_gui = false;
+ break;
+ case 'b':
+ bin->bad_plugins = true;
+ break;
+ case 'B':
+ bin->bad_plugins = false;
+ break;
+ case 'a':
+ bin->cpu_affinity = true;
+ break;
+ case 'A':
+ bin->cpu_affinity = false;
+ break;
+ case 'I':
+ handle.do_capt = false;
+ break;
+ case 'O':
+ handle.do_play = false;
+ break;
+ case 't':
+ handle.twochan = true;
+ break;
+ case 'T':
+ handle.twochan = false;
+ break;
+ case 'x':
+ handle.debug = true;
+ break;
+ case 'X':
+ handle.debug = false;
+ break;
+ case 'y':
+ bin->audio_prio = atoi(optarg);
+ break;
+ case 'Y':
+ bin->audio_prio = 0;
+ break;
+ case 'w':
+ bin->worker_prio = atoi(optarg);
+ break;
+ case 'W':
+ bin->worker_prio = 0;
+ break;
+ case 'l':
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "%s", optarg);
+ break;
+ case 'd':
+ handle.do_capt = optarg != NULL;
+ handle.do_play = optarg != NULL;
+ handle.io_name = optarg;
+ break;
+ case 'i':
+ handle.do_capt = optarg != NULL;
+ handle.capt_name = optarg;
+ break;
+ case 'o':
+ handle.do_play = optarg != NULL;
+ handle.play_name = optarg;
+ break;
+ case 'r':
+ handle.srate = atoi(optarg);
+ break;
+ case 'p':
+ handle.frsize = atoi(optarg);
+ break;
+ case 'n':
+ handle.nfrags = atoi(optarg);
+ break;
+ case 's':
+ handle.seq_size = MAX(SEQ_SIZE, atoi(optarg));
+ break;
+ case 'c':
+ if(atoi(optarg) < bin->num_slaves)
+ bin->num_slaves = atoi(optarg);
+ break;
+ case 'f':
+ bin->update_rate = atoi(optarg);
+ break;
+ case '?':
+ if( (optopt == 'd') || (optopt == 'i') || (optopt == 'o') || (optopt == 'r')
+ || (optopt == 'p') || (optopt == 'n') || (optopt == 's') || (optopt == 'c')
+ || (optopt == 'l') || (optopt == 'f') )
+ fprintf(stderr, "Option `-%c' requires an argument.\n", optopt);
+ else if(isprint(optopt))
+ fprintf(stderr, "Unknown option `-%c'.\n", optopt);
+ else
+ fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ if(!handle.capt_name && handle.do_capt)
+ handle.capt_name = handle.io_name;
+ if(!handle.play_name && handle.do_play)
+ handle.play_name = handle.io_name;
+
+ bin_init(bin, handle.srate);
+
+ LV2_URID_Map *map = bin->map;
+
+ lv2_atom_forge_init(&handle.forge, map);
+
+ handle.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ bin->app_driver.system_port_add = _system_port_add;
+ bin->app_driver.system_port_del = _system_port_del;
+
+ handle.osc_sched.osc2frames = _osc_schedule_osc2frames;
+ handle.osc_sched.frames2osc = _osc_schedule_frames2osc;
+ handle.osc_sched.handle = &handle;
+ bin->app_driver.osc_sched = &handle.osc_sched;
+ bin->app_driver.features = SP_APP_FEATURE_FIXED_BLOCK_LENGTH; // always true for ALSA
+ if(handle.frsize && !(handle.frsize & (handle.frsize - 1))) // check for powerOf2
+ bin->app_driver.features |= SP_APP_FEATURE_POWER_OF_2_BLOCK_LENGTH;
+
+ // run
+ bin_run(bin, argv, &nsm_driver, NULL, NULL);
+
+ // stop
+ bin_stop(bin);
+
+ // deinit alsa
+ _alsa_deinit(&handle);
+
+ // deinit
+ bin_deinit(bin);
+
+ /*FIXME
+ if(ini)
+ efreet_ini_free(ini);
+ */
+
+ munlockall();
+
+ return 0;
+}
diff --git a/bin/synthpod_bin.c b/bin/synthpod_bin.c
new file mode 100644
index 00000000..55c5f0bf
--- /dev/null
+++ b/bin/synthpod_bin.c
@@ -0,0 +1,812 @@
+/*
+ * 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.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <unistd.h> // fork
+#include <sys/wait.h> // waitpid
+#include <errno.h> // waitpid
+#include <signal.h>
+
+#include <synthpod_bin.h>
+
+#define ANSI_COLOR_RED "\x1b[31m"
+#define ANSI_COLOR_GREEN "\x1b[32m"
+#define ANSI_COLOR_YELLOW "\x1b[33m"
+#define ANSI_COLOR_BLUE "\x1b[34m"
+#define ANSI_COLOR_MAGENTA "\x1b[35m"
+#define ANSI_COLOR_CYAN "\x1b[36m"
+#define ANSI_COLOR_RESET "\x1b[0m"
+
+#define SBOX_BUF_SIZE (size_t)0x1000000 // 16M
+#define CHUNK_SIZE 0x100000 // 1M
+#define MAX_MSGS 10 //FIXME limit to how many events?
+
+#define CONTROL_PORT_INDEX 14
+#define NOTIFY_PORT_INDEX 15
+
+static atomic_bool done = ATOMIC_VAR_INIT(false);
+
+static uint8_t ui_buf [CHUNK_SIZE]; //FIXME
+
+enum {
+ COLOR_TRACE = 0,
+ COLOR_LOG,
+ COLOR_ERROR,
+ COLOR_NOTE,
+ COLOR_WARNING,
+
+ COLOR_DSP
+};
+
+static const char *prefix [2][6] = {
+ [0] = {
+ [COLOR_TRACE] = "[Trace]",
+ [COLOR_LOG] = "[Log] ",
+ [COLOR_ERROR] = "[Error]",
+ [COLOR_NOTE] = "[Note] ",
+ [COLOR_WARNING] = "[Warn] ",
+
+ [COLOR_DSP] = "(DSP)"
+ },
+ [1] = {
+ [COLOR_TRACE] = "["ANSI_COLOR_BLUE "Trace"ANSI_COLOR_RESET"]",
+ [COLOR_LOG] = "["ANSI_COLOR_MAGENTA"Log"ANSI_COLOR_RESET"] ",
+ [COLOR_ERROR] = "["ANSI_COLOR_RED "Error"ANSI_COLOR_RESET"]",
+ [COLOR_NOTE] = "["ANSI_COLOR_GREEN "Note"ANSI_COLOR_RESET"] ",
+ [COLOR_WARNING] = "["ANSI_COLOR_YELLOW "Warn"ANSI_COLOR_RESET"] ",
+
+ [COLOR_DSP] = "("ANSI_COLOR_CYAN "DSP"ANSI_COLOR_RESET")"
+ }
+};
+
+__realtime static void
+_close_request(void *data)
+{
+ bin_t *bin = data;
+
+ bin_quit(bin);
+}
+
+__realtime static void *
+_app_to_ui_request(size_t minimum, size_t *maximum, void *data)
+{
+ bin_t *bin = data;
+
+ if(minimum <= CHUNK_SIZE)
+ {
+ *maximum = CHUNK_SIZE;
+ return ui_buf;
+ }
+
+ bin_log_trace(bin, "%s: buffer overflow\n", __func__);
+ return NULL;
+}
+__realtime static void
+_app_to_ui_advance(size_t written, void *data)
+{
+ bin_t *bin = data;
+
+ if(sandbox_master_send(bin->sb, NOTIFY_PORT_INDEX, written, bin->atom_eventTransfer, ui_buf) == -1)
+ bin_log_trace(bin, "%s: buffer overflow\n", __func__);
+
+ sandbox_master_signal_tx(bin->sb);
+}
+
+__realtime static void *
+_app_to_worker_request(size_t minimum, size_t *maximum, void *data)
+{
+ bin_t *bin = data;
+
+ return varchunk_write_request_max(bin->app_to_worker, minimum, maximum);
+}
+__realtime static void
+_app_to_worker_advance(size_t written, void *data)
+{
+ bin_t *bin = data;
+
+ varchunk_write_advance(bin->app_to_worker, written);
+ sem_post(&bin->sem);
+}
+
+__non_realtime static void *
+_worker_to_app_request(size_t minimum, size_t *maximum, void *data)
+{
+ bin_t *bin = data;
+
+ void *ptr;
+ do
+ {
+ ptr = varchunk_write_request_max(bin->app_from_worker, minimum, maximum);
+ }
+ while(!ptr); // wait until there is enough space
+
+ return ptr;
+}
+__non_realtime static void
+_worker_to_app_advance(size_t written, void *data)
+{
+ bin_t *bin = data;
+
+ varchunk_write_advance(bin->app_from_worker, written);
+}
+
+static inline void
+_atomic_spin_lock(atomic_flag *flag)
+{
+ while(atomic_flag_test_and_set_explicit(flag, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_atomic_try_lock(atomic_flag *flag)
+{
+ return !atomic_flag_test_and_set_explicit(flag, memory_order_acquire);
+}
+
+static inline void
+_atomic_unlock(atomic_flag *flag)
+{
+ atomic_flag_clear_explicit(flag, memory_order_release);
+}
+
+static inline bool
+_is_dsp_thread(bin_t *bin)
+{
+ const pthread_t this = pthread_self();
+
+ return pthread_equal(this, bin->dsp_thread);
+}
+
+__non_realtime static int
+_log_vprintf(void *data, LV2_URID type, const char *fmt, va_list args)
+{
+ bin_t *bin = data;
+
+ const bool is_worker_thread = !_is_dsp_thread(bin);
+
+ // check for trace mode AND DSP thread ID
+ if(!is_worker_thread)
+ {
+ size_t written = -1;
+ if(_atomic_try_lock(&bin->trace_lock)) //FIXME use per-dsp-thread ringbuffer
+ {
+ size_t sz = 0;
+ char *trace;
+ if((trace = varchunk_write_request_max(bin->app_to_log, 512, &sz)))
+ {
+ vsnprintf(trace, sz, fmt, args);
+
+ written = strlen(trace) + 1;
+ varchunk_write_advance(bin->app_to_log, written);
+ sem_post(&bin->sem);
+ }
+ }
+ _atomic_unlock(&bin->trace_lock);
+ return written;
+ }
+ else
+ {
+ // !log_trace OR not DSP thread ID
+ int idx = COLOR_LOG;
+ if(type == bin->log_trace)
+ idx = COLOR_TRACE;
+ else if(type == bin->log_error)
+ idx = COLOR_ERROR;
+ else if(type == bin->log_note)
+ idx = COLOR_NOTE;
+ else if(type == bin->log_warning)
+ idx = COLOR_WARNING;
+
+ //TODO send to UI?
+
+ const int istty = isatty(STDERR_FILENO);
+ fprintf(stderr, "%s %s ", prefix[istty][COLOR_DSP], prefix[istty][idx]);
+ return vfprintf(stderr, fmt, args);
+ }
+
+ return 0;
+}
+
+__non_realtime static int __attribute__((format(printf, 3, 4)))
+_log_printf(void *data, LV2_URID type, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(data, type, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__realtime static bool
+_sb_recv_cb(void *data, uint32_t index, uint32_t size, uint32_t format,
+ const void *buf)
+{
+ bin_t *bin = data;
+
+ if(index == CONTROL_PORT_INDEX) // control for synthpod:stereo
+ {
+ const LV2_Atom *atom = buf;
+
+ bin->advance_ui = sp_app_from_ui(bin->app, atom);
+ if(!bin->advance_ui)
+ {
+ //fprintf(stderr, "ui is blocked\n");
+ return false; // pause handling messages from UI (until fully drained)
+ }
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: unknown port index\n", __func__);
+ }
+
+ return true; // continue handling messages from UI
+}
+
+__realtime static void
+_sb_subscribe_cb(void *data, uint32_t index, uint32_t protocol, bool state)
+{
+ bin_t *bin = data;
+
+ // nothing
+}
+
+static bin_t *bin_ptr = NULL;
+__non_realtime static void
+_sig(int sig)
+{
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ if(bin_ptr)
+ sem_post(&bin_ptr->sem);
+}
+
+__realtime static char *
+_mapper_alloc_rt(void *data, size_t size)
+{
+ bin_t *bin = data;
+ const bool is_worker_thread = !_is_dsp_thread(bin);
+
+ bool more;
+ char *pool = lfrtm_alloc(bin->lfrtm, size, &more);
+
+ if(!pool)
+ {
+ if(is_worker_thread)
+ {
+ bin_log_error(bin, "%s: allocation failed\n", __func__);
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: allocation failed\n", __func__);
+ }
+ }
+ else if(more)
+ {
+ if(is_worker_thread)
+ {
+ if(lfrtm_inject(bin->lfrtm))
+ {
+ bin_log_error(bin, "%s: injection failed\n", __func__);
+ }
+ }
+ else
+ {
+ atomic_store(&bin->inject, true);
+ sem_post(&bin->sem);
+ }
+ }
+
+ return pool;
+}
+
+__realtime static void
+_mapper_free_rt(void *data, char *uri)
+{
+ (void)data;
+ (void)uri;
+
+ // nothing
+}
+
+__realtime static uint32_t
+_voice_map_new_uuid(void *data, uint32_t flags __attribute__((unused)))
+{
+ xpress_t *xpress = data;
+
+ return xpress_map(xpress);
+}
+
+__non_realtime void
+bin_init(bin_t *bin, uint32_t sample_rate)
+{
+ bin_ptr = bin;
+
+ bin->sample_rate = sample_rate;
+
+ // varchunk init
+ sem_init(&bin->sem, 0, 0);
+ bin->app_to_worker = varchunk_new(CHUNK_SIZE, true);
+ bin->app_from_worker = varchunk_new(CHUNK_SIZE, true);
+ bin->app_to_log = varchunk_new(CHUNK_SIZE, true);
+ bin->app_from_com = varchunk_new(CHUNK_SIZE, false);
+ bin->app_from_app = varchunk_new(CHUNK_SIZE, false);
+
+ bin->lfrtm = lfrtm_new(512, 0x100000); // 1M
+ bin->mapper = mapper_new(0x20000, 0, NULL, _mapper_alloc_rt, _mapper_free_rt, bin); // 128K
+
+ bin->map = mapper_get_map(bin->mapper);
+ bin->unmap = mapper_get_unmap(bin->mapper);
+
+ xpress_init(&bin->xpress, 0, bin->map, NULL,
+ XPRESS_EVENT_NONE, NULL, NULL, NULL);
+ bin->xmap.new_uuid = _voice_map_new_uuid;
+ bin->xmap.handle = &bin->xpress;
+
+ bin->log_error = bin->map->map(bin->map->handle, LV2_LOG__Error);
+ bin->log_note = bin->map->map(bin->map->handle, LV2_LOG__Note);
+ bin->log_trace = bin->map->map(bin->map->handle, LV2_LOG__Trace);
+ bin->log_warning = bin->map->map(bin->map->handle, LV2_LOG__Warning);
+ bin->atom_eventTransfer = bin->map->map(bin->map->handle, LV2_ATOM__eventTransfer);
+
+ bin->log.handle = bin;
+ bin->log.printf = _log_printf;
+ bin->log.vprintf = _log_vprintf;
+ bin->trace_lock = (atomic_flag)ATOMIC_FLAG_INIT;
+
+ bin->app_driver.map = bin->map;
+ bin->app_driver.unmap = bin->unmap;
+ bin->app_driver.xmap = &bin->xmap;
+ bin->app_driver.log = &bin->log;
+ bin->app_driver.to_ui_request = _app_to_ui_request;
+ bin->app_driver.to_ui_advance = _app_to_ui_advance;
+ bin->app_driver.to_worker_request = _app_to_worker_request;
+ bin->app_driver.to_worker_advance = _app_to_worker_advance;
+ bin->app_driver.to_app_request = _worker_to_app_request;
+ bin->app_driver.to_app_advance = _worker_to_app_advance;
+ bin->app_driver.num_slaves = bin->num_slaves;
+
+ bin->app_driver.audio_prio = bin->audio_prio;
+ bin->app_driver.bad_plugins = bin->bad_plugins;
+ bin->app_driver.cpu_affinity = bin->cpu_affinity;
+ bin->app_driver.close_request = _close_request;
+
+ bin->worker_thread = pthread_self(); // thread ID of UI thread
+ bin->first = true;
+
+ bin->sb_driver.socket_path = bin->socket_path;
+ bin->sb_driver.map = bin->map;
+ bin->sb_driver.unmap = bin->unmap;
+ bin->sb_driver.recv_cb = _sb_recv_cb;
+ bin->sb_driver.subscribe_cb = _sb_subscribe_cb;
+
+ bin->sb = sandbox_master_new(&bin->sb_driver, bin, SBOX_BUF_SIZE);
+
+ signal(SIGTERM, _sig);
+ signal(SIGQUIT, _sig);
+ signal(SIGINT, _sig);
+
+ if(bin->has_gui)
+ {
+ bin_show(bin);
+ }
+
+ cross_clock_init(&bin->clk_mono, CROSS_CLOCK_MONOTONIC);
+ cross_clock_init(&bin->clk_real, CROSS_CLOCK_REALTIME);
+}
+
+__realtime void
+bin_run(bin_t *bin, char **argv, const nsmc_driver_t *nsm_driver,
+ void (*idle)(void *data), void *data)
+{
+ char *fallback_path = NULL;
+
+ bin->argv = argv;
+ bin->optind = optind;
+
+ if(!argv[optind])
+ {
+ const char *home_dir = getenv("HOME");
+
+ if(asprintf(&fallback_path, "%s/.lv2/Synthpod_default.preset.lv2/", home_dir) == -1)
+ {
+ fallback_path = NULL;
+ }
+ }
+
+ // NSM init
+ const char *exe = strrchr(argv[0], '/');
+ exe = exe ? exe + 1 : argv[0]; // we only want the program name without path
+ bin->nsm = nsmc_new("Synthpod", exe, fallback_path ? fallback_path : argv[optind],
+ nsm_driver, bin); //TODO check
+
+ if(fallback_path)
+ {
+ free(fallback_path);
+ }
+
+ pthread_t self = pthread_self();
+
+ if(bin->worker_prio)
+ {
+ struct sched_param schedp;
+ memset(&schedp, 0, sizeof(struct sched_param));
+ schedp.sched_priority = bin->worker_prio;
+
+ if(schedp.sched_priority)
+ {
+ if(pthread_setschedparam(self, SCHED_RR, &schedp))
+ fprintf(stderr, "pthread_setschedparam error\n");
+ }
+ }
+
+ //FIXME no timeout needed, but with yet-to-come NSM support
+ const unsigned nsecs = 1000000000;
+ const unsigned nfreq = 120; // Hz
+ const unsigned nstep = nsecs / nfreq;
+ struct timespec to;
+ cross_clock_gettime(&bin->clk_real, &to);
+
+ while(!atomic_load_explicit(&done, memory_order_relaxed))
+ {
+ bool timedout = false;
+
+ if(sem_timedwait(&bin->sem, &to) == -1)
+ {
+ timedout = (errno == ETIMEDOUT);
+ }
+
+ if(timedout)
+ {
+ // check if GUI still running
+ if(bin->child > 0)
+ {
+ bool rolling = true;
+
+ int status;
+ const int res = waitpid(bin->child, &status, WUNTRACED | WNOHANG);
+ if(res < 0)
+ {
+ if(errno == ECHILD) // child not existing
+ rolling = false;
+ }
+ else if(res == bin->child)
+ {
+ if(!WIFSTOPPED(status) && !WIFCONTINUED(status)) // child exited/crashed
+ rolling = false;
+ }
+
+ if(!rolling)
+ {
+ bin->child = 0; // invalidate
+
+ if(nsmc_managed(bin->nsm))
+ nsmc_hidden(bin->nsm);
+
+ if(bin->kill_gui)
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ }
+ }
+
+ // schedule next timeout
+ uint64_t nanos = to.tv_nsec + nstep;
+ while(nanos >= nsecs)
+ {
+ nanos -= nsecs;
+ to.tv_sec += 1;
+ }
+ to.tv_nsec = nanos;
+ }
+
+ // handle lfrtm memory injection
+ if(atomic_exchange(&bin->inject, false))
+ {
+ if(lfrtm_inject(bin->lfrtm))
+ {
+ bin_log_error(bin, "%s: injection failed\n", __func__);
+ }
+ }
+
+ // read events from worker
+ {
+ size_t size;
+ const void *body;
+ while((body = varchunk_read_request(bin->app_to_worker, &size)))
+ {
+ sp_worker_from_app(bin->app, size, body);
+ varchunk_read_advance(bin->app_to_worker);
+ }
+ }
+
+ // read events from logger
+ {
+ size_t size;
+ const char *trace;
+ while((trace = varchunk_read_request(bin->app_to_log, &size)))
+ {
+ const int istty = isatty(STDERR_FILENO);
+ fprintf(stderr, "%s %s %s", prefix[istty][COLOR_DSP], prefix[istty][COLOR_TRACE], trace);
+
+ varchunk_read_advance(bin->app_to_log);
+ }
+ }
+
+ // run NSM
+ if(nsmc_managed(bin->nsm))
+ nsmc_run(bin->nsm);
+
+ // rund idle callback
+ if(idle)
+ {
+ idle(data);
+ }
+
+ //sched_yield();
+ }
+}
+
+__non_realtime void
+bin_stop(bin_t *bin)
+{
+ // NSM deinit
+ nsmc_free(bin->nsm);
+
+ bin_hide(bin);
+
+ if(bin->path)
+ free(bin->path);
+}
+
+__non_realtime void
+bin_deinit(bin_t *bin)
+{
+ if(bin->sb)
+ sandbox_master_free(bin->sb);
+
+ // synthpod deinit
+ sp_app_free(bin->app);
+
+ // mapper deinit
+ mapper_free(bin->mapper);
+
+ // lfrtm deinit
+ lfrtm_free(bin->lfrtm);
+
+ // varchunk deinit
+ sem_destroy(&bin->sem);
+ varchunk_free(bin->app_to_log);
+ varchunk_free(bin->app_to_worker);
+ varchunk_free(bin->app_from_worker);
+ varchunk_free(bin->app_from_com);
+ varchunk_free(bin->app_from_app);
+
+ bin_log_note(bin, "bye\n");
+
+ cross_clock_deinit(&bin->clk_mono);
+ cross_clock_deinit(&bin->clk_real);
+
+ xpress_deinit(&bin->xpress);
+}
+
+__realtime void
+bin_process_pre(bin_t *bin, uint32_t nsamples, bool bypassed)
+{
+ // read events from worker
+ {
+ size_t size;
+ const void *body;
+ unsigned n = 0;
+ while((body = varchunk_read_request(bin->app_from_worker, &size))
+ && (n++ < MAX_MSGS) )
+ {
+ bool advance = sp_app_from_worker(bin->app, size, body);
+ if(!advance)
+ {
+ //fprintf(stderr, "worker is blocked\n");
+ break;
+ }
+ varchunk_read_advance(bin->app_from_worker);
+ }
+ }
+
+ // run synthpod app pre
+ if(!bypassed)
+ sp_app_run_pre(bin->app, nsamples);
+
+ // read events from UI ringbuffer
+ if(sandbox_master_recv(bin->sb))
+ {
+ bin_quit(bin);
+ }
+
+ // read events from feedback ringbuffer
+ {
+ size_t size;
+ const LV2_Atom *atom;
+ unsigned n = 0;
+ while((atom = varchunk_read_request(bin->app_from_app, &size))
+ && (n++ < MAX_MSGS) )
+ {
+ bin->advance_ui = sp_app_from_ui(bin->app, atom);
+ if(!bin->advance_ui)
+ {
+ //fprintf(stderr, "ui is blocked\n");
+ break;
+ }
+ varchunk_read_advance(bin->app_from_app);
+ }
+ }
+
+ // run synthpod app post
+ if(!bypassed)
+ sp_app_run_post(bin->app, nsamples);
+}
+
+__realtime void
+bin_process_post(bin_t *bin)
+{
+ // nothing
+}
+
+__non_realtime void
+bin_bundle_new(bin_t *bin)
+{
+ // simply load the default state
+ bin_bundle_load(bin, SYNTHPOD_PREFIX"stereo");
+}
+
+__non_realtime void
+bin_bundle_load(bin_t *bin, const char *bundle_path)
+{
+ const LV2_URID urn = bin->map->map(bin->map->handle, bundle_path);
+ if(!urn)
+ {
+ bin_log_error(bin, "%s: invalid path: %s\n", __func__, bundle_path);
+ return;
+ }
+
+ sp_app_bundle_load(bin->app, urn, false);
+}
+
+__non_realtime void
+bin_bundle_save(bin_t *bin, const char *bundle_path)
+{
+ const LV2_URID urn = bin->map->map(bin->map->handle, bundle_path);
+ if(!urn)
+ {
+ bin_log_error(bin, "%s: invalid path: %s\n", __func__, bundle_path);
+ return;
+ }
+
+ sp_app_bundle_save(bin->app, urn, false);
+}
+
+__realtime void
+bin_quit(bin_t *bin)
+{
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+}
+
+__non_realtime int
+bin_log_error(bin_t *bin, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(bin, bin->log_error, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__non_realtime int
+bin_log_note(bin_t *bin, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(bin, bin->log_note, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__non_realtime int
+bin_log_warning(bin_t *bin, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(bin, bin->log_warning, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+__realtime int
+bin_log_trace(bin_t *bin, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(bin, bin->log_trace, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+int
+bin_show(bin_t *bin)
+{
+ char srate [32];
+ char urate [32];
+ char wname [384];
+ char minimum [32];
+ snprintf(srate, sizeof(srate), "%"PRIu32, bin->sample_rate);
+ snprintf(urate, sizeof(urate), "%"PRIu32, bin->update_rate);
+ snprintf(wname, sizeof(wname), "Synthpod - %s", bin->socket_path);
+ snprintf(minimum, sizeof(minimum), "%zu", SBOX_BUF_SIZE);
+
+ bin->child = fork();
+ if(bin->child == 0) // child
+ {
+ char *const args [] = {
+ "synthpod_sandbox_x11",
+ "-p", SYNTHPOD_STEREO_URI,
+ "-P", SYNTHPOD_PLUGIN_DIR,
+ "-u", SYNTHPOD_ROOT_NK_URI,
+ "-U", SYNTHPOD_PLUGIN_DIR,
+ "-s", (char *)bin->socket_path,
+ "-w", wname,
+ "-m", minimum,
+ "-r", srate,
+ "-f", urate,
+ NULL
+ };
+
+ execvp(args[0], args);
+ }
+ else if(bin->child == -1)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+bin_hide(bin_t *bin)
+{
+ if(bin->child > 0)
+ {
+ int status;
+
+ kill(bin->child, SIGINT);
+ waitpid(bin->child, &status, WUNTRACED); // blocking waitpid
+ bin->child = 0;
+ }
+
+ return 0;
+}
diff --git a/bin/synthpod_bin.h b/bin/synthpod_bin.h
new file mode 100644
index 00000000..ba85c12e
--- /dev/null
+++ b/bin/synthpod_bin.h
@@ -0,0 +1,177 @@
+/*
+ * 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 _SYNTHPOD_BIN_H
+#define _SYNTHPOD_BIN_H
+
+#include <semaphore.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include <limits.h>
+
+#include <synthpod_app.h>
+
+#define LFRTM_IMPLEMENTATION
+#include <lfrtm/lfrtm.h>
+
+#define MAPPER_IMPLEMENTATION
+#include <mapper.lv2/mapper.h>
+
+#define CROSS_CLOCK_IMPLEMENTATION
+#include <cross_clock/cross_clock.h>
+
+#include <varchunk.h>
+#include <sandbox_master.h>
+
+#include <synthpod_common.h>
+
+#define NSMC_IMPLEMENTATION
+#include <nsmc.h>
+
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+#include <lv2/lv2plug.in/ns/ext/time/time.h>
+
+#ifndef MAX
+# define MAX(A, B) ((A) > (B) ? (A) : (B))
+#endif
+
+#define SEQ_SIZE 0x2000
+#define JAN_1970 (uint64_t)0x83aa7e80
+
+typedef enum _save_state_t save_state_t;
+typedef struct _bin_t bin_t;
+
+enum _save_state_t {
+ SAVE_STATE_INTERNAL = 0,
+ SAVE_STATE_NSM,
+ SAVE_STATE_JACK
+};
+
+struct _bin_t {
+ atomic_bool inject;
+ lfrtm_t *lfrtm;
+ mapper_t *mapper;
+ LV2_URID_Map *map;
+ LV2_URID_Unmap *unmap;
+ xpress_t xpress;
+ xpress_map_t xmap;
+
+ sp_app_t *app;
+ sp_app_driver_t app_driver;
+
+ sem_t sem;
+ varchunk_t *app_to_worker;
+ varchunk_t *app_from_worker;
+ varchunk_t *app_to_log;
+
+ varchunk_t *app_from_com;
+
+ bool advance_ui;
+ varchunk_t *app_from_app;
+
+ char *path;
+ nsmc_t *nsm;
+
+ LV2_URID log_error;
+ LV2_URID log_note;
+ LV2_URID log_trace;
+ LV2_URID log_warning;
+ LV2_URID atom_eventTransfer;
+
+ LV2_Log_Log log;
+
+ pthread_t worker_thread;
+ pthread_t dsp_thread;
+ atomic_flag trace_lock;
+
+ bool has_gui;
+ bool kill_gui;
+ int audio_prio;
+ int worker_prio;
+ int num_slaves;
+ bool bad_plugins;
+ char socket_path [NAME_MAX];
+ int update_rate;
+ bool cpu_affinity;
+
+ sandbox_master_driver_t sb_driver;
+ sandbox_master_t *sb;
+
+ pid_t child;
+
+ bool first;
+
+ cross_clock_t clk_mono;
+ cross_clock_t clk_real;
+
+ char **argv;
+ int optind;
+
+ uint32_t sample_rate;
+};
+
+void
+bin_init(bin_t *bin, uint32_t sample_rate);
+
+void
+bin_run(bin_t *bin, char **argv, const nsmc_driver_t *nsm_driver,
+ void (*idle)(void *data), void *data);
+
+void
+bin_stop(bin_t *bin);
+
+void
+bin_deinit(bin_t *bin);
+
+void
+bin_process_pre(bin_t *bin, uint32_t nsamples, bool bypassed);
+
+void
+bin_process_post(bin_t *bin);
+
+void
+bin_bundle_new(bin_t *bin);
+
+void
+bin_bundle_load(bin_t *bin, const char *bundle_path);
+
+void
+bin_bundle_save(bin_t *bin, const char *bundle_path);
+
+void
+bin_quit(bin_t *bin);
+
+int __attribute__((format(printf, 2, 3)))
+bin_log_error(bin_t *bin, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+bin_log_note(bin_t *bin, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+bin_log_warning(bin_t *bin, const char *fmt, ...);
+
+int __attribute__((format(printf, 2, 3)))
+bin_log_trace(bin_t *bin, const char *fmt, ...);
+
+int
+bin_show(bin_t *bin);
+
+int
+bin_hide(bin_t *bin);
+
+#endif // _SYNTHPOD_BIN_H
diff --git a/bin/synthpod_dummy.1 b/bin/synthpod_dummy.1
new file mode 100644
index 00000000..4880b901
--- /dev/null
+++ b/bin/synthpod_dummy.1
@@ -0,0 +1,133 @@
+.TH SYNTHPOD "1" "Feb 24, 2017"
+
+.SH NAME
+synthpod \- a lightweight nonlinear LV2 plugin container
+
+.SH SYNOPSIS
+.B synthpod_dummy
+[\fIoptions\fR] [\fIbundle-path\fR]
+
+.SH DESCRIPTION
+\fBsynthpod\fP is a lightweight nonlinear LV2 plugin container, aka host.
+.PP
+It is a pure stand-alone Dummy client with NSM session support.
+.PP
+It is also a first-class LV2 plugin (http://open-music-kontrollers.ch/lv2/synthpod#stereo).
+
+.SH OPTIONS
+.HP
+\fB\-v\fR
+.IP
+Print version and license information
+
+.HP
+\fB\-h\fR
+.IP
+Print usage information
+
+.HP
+\fB\-g\fR
+.IP
+Load GUI
+
+.HP
+\fB\-G\fR
+.IP
+Do NOT load GUI (default)
+
+.HP
+\fB\-k\fR
+.IP
+Kill DSP with GUI
+
+.HP
+\fB\-K\fR
+.IP
+Do NOT kill DSP with GUI (default)
+
+.HP
+\fB\-b\fR
+.IP
+Enable bad plugins
+
+.HP
+\fB\-B\fR
+.IP
+Disable bad plugins (default)
+
+.HP
+\fB\-a\fR
+.IP
+Enable CPU affinity
+
+.HP
+\fB\-A\fR
+.IP
+Disable CPU affinity (default)
+
+.HP
+\fB\-y\fR audio-priority
+.IP
+Audio thread realtime priority (70)
+
+.HP
+\fB\-Y\fR
+.IP
+Disable audio thread realtime priority
+
+.HP
+\fB\-w\fR worker-priority
+.IP
+Worker thread realtime priority (60)
+
+.HP
+\fB\-W\fR
+.IP
+Disable worker thread realtime priority
+
+.HP
+\fB\-l\fR
+.IP
+Socket link path (shm:///synthpod), e.g. tcp://*:9090
+
+.HP
+\fB\-r\fR sample-rate
+.IP
+Sample Rate (48000)
+
+.HP
+\fB\-p\fR sample-period
+.IP
+Frames per period (1024)
+
+.HP
+\fB\-s\fR sequence-size
+.IP
+Minimal byte size of event sequence buffers (8192)
+
+.HP
+\fB\-c\fR slave-cores
+.IP
+Number of slave cores for parallel audio processing (auto)
+
+.HP
+\fB\-f\fR update-rate
+.IP
+Update rate in frames per second of GUI
+
+.SH FILES
+.TP
+.I $HOME/.lv2/Synthpod_default.preset.lv2
+Default bundle state directory
+.TP
+.I $HOME/.lv2
+Default LV2 preset directory
+
+.SH LICENSE
+Artistic License 2.0.
+
+.SH AUTHOR
+Hanspeter Portner (dev@open-music-kontrollers.ch).
+
+.SH SEE ALSO
+synthpod_alsa(1), synthpod_jack(1), synthpod_sandbox(1)
diff --git a/bin/synthpod_dummy.c b/bin/synthpod_dummy.c
new file mode 100644
index 00000000..451df052
--- /dev/null
+++ b/bin/synthpod_dummy.c
@@ -0,0 +1,650 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <math.h>
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+# include <pthread_np.h>
+typedef cpuset_t cpu_set_t;
+#endif
+
+#include <synthpod_bin.h>
+
+#define NANO_SECONDS 1000000000
+
+typedef struct _prog_t prog_t;
+
+struct _prog_t {
+ bin_t bin;
+
+ LV2_Atom_Forge forge;
+
+ save_state_t save_state;
+ atomic_int kill;
+ pthread_t thread;
+
+ uint32_t srate;
+ uint32_t frsize;
+ uint32_t nfrags;
+ uint32_t seq_size;
+
+ LV2_OSC_Schedule osc_sched;
+ struct timespec cur_ntp;
+ struct timespec nxt_ntp;
+ struct {
+ uint64_t cur_frames;
+ uint64_t ref_frames;
+ double dT;
+ double dTm1;
+ } cycle;
+};
+
+static inline void
+_ntp_now(cross_clock_t *clk, struct timespec *ntp)
+{
+ cross_clock_gettime(clk, ntp);
+ ntp->tv_sec += JAN_1970; // convert NTP to OSC time
+}
+
+static inline void
+_ntp_clone(struct timespec *dst, struct timespec *src)
+{
+ dst->tv_sec = src->tv_sec;
+ dst->tv_nsec = src->tv_nsec;
+}
+
+static inline void
+_ntp_add_nanos(struct timespec *ntp, uint64_t nanos)
+{
+ ntp->tv_nsec += nanos;
+ while(ntp->tv_nsec >= NANO_SECONDS) // has overflowed
+ {
+ ntp->tv_sec += 1;
+ ntp->tv_nsec -= NANO_SECONDS;
+ }
+}
+
+static inline double
+_ntp_diff(struct timespec *from, struct timespec *to)
+{
+ double diff = to->tv_sec;
+ diff -= from->tv_sec;
+ diff += 1e-9 * to->tv_nsec;
+ diff -= 1e-9 * from->tv_nsec;
+
+ return diff;
+}
+
+__realtime static inline void
+_process(prog_t *handle)
+{
+ bin_t *bin = &handle->bin;
+ sp_app_t *app = bin->app;
+
+ const uint32_t nsamples = handle->frsize;
+
+ const uint64_t nanos_per_period = (uint64_t)nsamples * NANO_SECONDS / handle->srate;
+ handle->cycle.cur_frames = 0; // initialize frame counter
+ _ntp_now(&bin->clk_real, &handle->nxt_ntp);
+
+ const unsigned n_period = handle->nfrags;
+
+ struct timespec sleep_to;
+ cross_clock_gettime(&bin->clk_mono, &sleep_to);
+
+ while(!atomic_load_explicit(&handle->kill, memory_order_relaxed))
+ {
+ cross_clock_nanosleep(&bin->clk_mono, true, &sleep_to);
+ _ntp_add_nanos(&sleep_to, nanos_per_period * n_period);
+
+ uint32_t na = nsamples * n_period;
+
+ // current time is next time from last cycle
+ _ntp_clone(&handle->cur_ntp, &handle->nxt_ntp);
+
+ // extrapolate new nxt_ntp
+ struct timespec nxt_ntp;
+ _ntp_now(&bin->clk_real, &nxt_ntp);
+ _ntp_clone(&handle->nxt_ntp, &nxt_ntp);
+
+ // reset ref_frames
+ handle->cycle.ref_frames = handle->cycle.cur_frames;
+
+ // calculate apparent period
+ _ntp_add_nanos(&nxt_ntp, nanos_per_period);
+ double diff = _ntp_diff(&handle->cur_ntp, &nxt_ntp);
+
+ // calculate apparent samples per period
+ handle->cycle.dT = nsamples / diff;
+ handle->cycle.dTm1 = 1.0 / handle->cycle.dT;
+
+ for( ; na >= nsamples;
+ na -= nsamples,
+ handle->cycle.ref_frames += nsamples,
+ _ntp_add_nanos(&handle->nxt_ntp, nanos_per_period) )
+ {
+ const sp_app_system_source_t *sources = sp_app_get_system_sources(app);
+
+ if(sp_app_bypassed(app))
+ {
+ //const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ //fprintf(stderr, "app is bypassed\n");
+
+ bin_process_pre(bin, nsamples, true);
+ bin_process_post(bin);
+
+ continue;
+ }
+
+ // fill input buffers
+ for(const sp_app_system_source_t *source=sources;
+ source->type != SYSTEM_PORT_NONE;
+ source++)
+ {
+ switch(source->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_CV:
+ case SYSTEM_PORT_OSC:
+ case SYSTEM_PORT_MIDI:
+ break;
+
+ case SYSTEM_PORT_COM:
+ {
+ void *seq_in = source->buf;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ const LV2_Atom_Object *obj;
+ size_t size;
+ while((obj = varchunk_read_request(bin->app_from_com, &size)))
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, obj, size);
+ if(ref)
+ lv2_atom_forge_pad(forge, size);
+
+ varchunk_read_advance(bin->app_from_com);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(seq_in);
+
+ break;
+ }
+ }
+ }
+
+ bin_process_pre(bin, nsamples, false);
+
+ const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ // fill output buffers
+ for(const sp_app_system_sink_t *sink=sinks;
+ sink->type != SYSTEM_PORT_NONE;
+ sink++)
+ {
+ switch(sink->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_CV:
+ case SYSTEM_PORT_OSC:
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_MIDI:
+ break;
+ case SYSTEM_PORT_COM:
+ {
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ // try do process events directly
+ bin->advance_ui = sp_app_from_ui(bin->app, atom);
+ if(!bin->advance_ui) // queue event in ringbuffer instead
+ {
+ //fprintf(stderr, "plugin ui direct is blocked\n");
+
+ void *ptr;
+ size_t size = lv2_atom_total_size(atom);
+ if((ptr = varchunk_write_request(bin->app_from_app, size)))
+ {
+ memcpy(ptr, atom, size);
+ varchunk_write_advance(bin->app_from_app, size);
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: app_from_app ringbuffer full\n", __func__);
+ //FIXME
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ bin_process_post(bin);
+ }
+
+ // increase cur_frames
+ handle->cycle.cur_frames = handle->cycle.ref_frames;
+ //sched_yield();
+ }
+}
+
+__non_realtime static void *
+_rt_thread(void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ bin->dsp_thread = pthread_self();
+
+ if(handle->bin.audio_prio)
+ {
+ struct sched_param schedp;
+ memset(&schedp, 0, sizeof(struct sched_param));
+ schedp.sched_priority = handle->bin.audio_prio;
+
+ if(schedp.sched_priority)
+ {
+ if(pthread_setschedparam(bin->dsp_thread, SCHED_FIFO, &schedp))
+ bin_log_error(bin, "%s: pthread_setschedparam error\n", __func__);
+ }
+ }
+
+ if(handle->bin.cpu_affinity)
+ {
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(0, &cpuset);
+ if(pthread_setaffinity_np(bin->dsp_thread, sizeof(cpu_set_t), &cpuset))
+ bin_log_error(bin, "%s: pthread_setaffinity_np error\n", __func__);
+ }
+
+ _process(handle);
+
+ return NULL;
+}
+
+__non_realtime static void *
+_system_port_add(void *data, system_port_t type, const char *short_name,
+ const char *pretty_name, const char *designation, bool input, uint32_t order)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+ (void)handle;
+
+ switch(type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_CV:
+ case SYSTEM_PORT_MIDI:
+ case SYSTEM_PORT_OSC:
+ case SYSTEM_PORT_COM:
+ // unsupported, skip
+ break;
+ }
+
+ return NULL;
+}
+
+__non_realtime static void
+_system_port_del(void *data, void *sys_port)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+ (void)handle;
+
+ // unsupported, skip
+}
+
+__non_realtime static void
+_saved(bin_t *bin, int status)
+{
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ if(handle->save_state == SAVE_STATE_NSM)
+ {
+ nsmc_saved(bin->nsm, status);
+ }
+ handle->save_state = SAVE_STATE_INTERNAL;
+
+ if(atomic_load_explicit(&handle->kill, memory_order_relaxed))
+ {
+ bin_quit(bin);
+ }
+}
+
+__non_realtime static int
+_open(const char *path, const char *name, const char *id, void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+ (void)name;
+
+ if(bin->path)
+ free(bin->path);
+ bin->path = strdup(path);
+
+ // synthpod init
+ bin->app_driver.sample_rate = handle->srate;
+ bin->app_driver.update_rate = handle->bin.update_rate;
+ bin->app_driver.max_block_size = handle->frsize;
+ bin->app_driver.min_block_size = 1;
+ bin->app_driver.seq_size = handle->seq_size;
+ bin->app_driver.num_periods = handle->nfrags;
+
+ // app init
+ bin->app = sp_app_new(NULL, &bin->app_driver, bin);
+
+ // alsa activate
+ atomic_init(&handle->kill, 0);
+ if(pthread_create(&handle->thread, NULL, _rt_thread, handle))
+ bin_log_error(bin, "%s: creation of realtime thread failed\n", __func__);
+
+ bin_bundle_load(bin, bin->path);
+ nsmc_opened(bin->nsm, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_save(void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ handle->save_state = SAVE_STATE_NSM;
+ bin_bundle_save(bin, bin->path);
+ _saved(bin, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_show(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_show(bin);
+}
+
+__non_realtime static int
+_hide(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_hide(bin);
+}
+
+static const nsmc_driver_t nsm_driver = {
+ .open = _open,
+ .save = _save,
+ .show = _show,
+ .hide = _hide
+};
+
+// rt
+__realtime static double
+_osc_schedule_osc2frames(LV2_OSC_Schedule_Handle instance, uint64_t timestamp)
+{
+ prog_t *handle = instance;
+
+ if(timestamp == 1ULL)
+ return 0; // inject at start of period
+
+ const uint64_t time_sec = timestamp >> 32;
+ const uint64_t time_frac = timestamp & 0xffffffff;
+
+ const double diff = (time_sec - handle->cur_ntp.tv_sec)
+ + time_frac * 0x1p-32
+ - handle->cur_ntp.tv_nsec * 1e-9;
+
+ const double frames = diff * handle->cycle.dT
+ - handle->cycle.ref_frames
+ + handle->cycle.cur_frames;
+
+ return frames;
+}
+
+// rt
+__realtime static uint64_t
+_osc_schedule_frames2osc(LV2_OSC_Schedule_Handle instance, double frames)
+{
+ prog_t *handle = instance;
+
+ double diff = (frames - handle->cycle.cur_frames + handle->cycle.ref_frames)
+ * handle->cycle.dTm1;
+ diff += handle->cur_ntp.tv_nsec * 1e-9;
+ diff += handle->cur_ntp.tv_sec;
+
+ double time_sec_d;
+ double time_frac_d = modf(diff, &time_sec_d);
+
+ uint64_t time_sec = time_sec_d;
+ uint64_t time_frac = time_frac_d * 0x1p32;
+ if(time_frac >= 0x100000000ULL) // illegal overflow
+ time_frac = 0xffffffffULL;
+
+ uint64_t timestamp = (time_sec << 32) | time_frac;
+
+ return timestamp;
+}
+
+int
+main(int argc, char **argv)
+{
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+
+ static prog_t handle;
+ bin_t *bin = &handle.bin;
+
+ handle.srate = 48000;
+ handle.frsize = 1024;
+ handle.nfrags = 3; //TODO make this configurable
+ handle.seq_size = SEQ_SIZE;
+
+ bin->audio_prio = 70;
+ bin->worker_prio = 60;
+ bin->num_slaves = sysconf(_SC_NPROCESSORS_ONLN) - 1;
+ bin->bad_plugins = false;
+ bin->has_gui = false;
+ bin->kill_gui = false;
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "shm:///synthpod-%i", getpid());
+ bin->update_rate = 25;
+ bin->cpu_affinity = false;
+
+ fprintf(stderr,
+ "Synthpod "SYNTHPOD_VERSION"\n"
+ "Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)\n"
+ "Released under Artistic License 2.0 by Open Music Kontrollers\n");
+
+ int c;
+ while((c = getopt(argc, argv, "vhgGkKbBaAy:Yw:Wl:r:p:s:c:f:")) != -1)
+ {
+ switch(c)
+ {
+ case 'v':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "This is free software: you can redistribute it and/or modify\n"
+ "it under the terms of the Artistic License 2.0 as published by\n"
+ "The Perl Foundation.\n"
+ "\n"
+ "This source is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "Artistic License 2.0 for more details.\n"
+ "\n"
+ "You should have received a copy of the Artistic License 2.0\n"
+ "along the source as a COPYING file. If not, obtain it from\n"
+ "http://www.perlfoundation.org/artistic_license_2_0.\n\n");
+ return 0;
+ case 'h':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "USAGE\n"
+ " %s [OPTIONS] [BUNDLE_PATH]\n"
+ "\n"
+ "OPTIONS\n"
+ " [-v] print version and full license information\n"
+ " [-h] print usage information\n"
+ " [-g] load GUI\n"
+ " [-G] do NOT load GUI (default)\n"
+ " [-k] kill DSP with GUI\n"
+ " [-K] do NOT kill DSP with GUI (default)\n"
+ " [-b] enable bad plugins\n"
+ " [-B] disable bad plugins (default)\n"
+ " [-a] enable CPU affinity\n"
+ " [-A] disable CPU affinity (default)\n"
+ " [-y] audio-priority audio thread realtime priority (70)\n"
+ " [-Y] do NOT use audio thread realtime priority\n"
+ " [-w] worker-priority worker thread realtime priority (60)\n"
+ " [-W] do NOT use worker thread realtime priority\n"
+ " [-l] link-path socket link path (shm:///synthpod)\n"
+ " [-r] sample-rate sample rate (48000)\n"
+ " [-p] sample-period frames per period (1024)\n"
+ " [-s] sequence-size minimum sequence size (8192)\n"
+ " [-c] slave-cores number of slave cores (auto)\n"
+ " [-f] update-rate GUI update rate (25)\n\n"
+ , argv[0]);
+ return 0;
+ case 'g':
+ bin->has_gui = true;
+ break;
+ case 'G':
+ bin->has_gui = false;
+ break;
+ case 'k':
+ bin->kill_gui = true;
+ break;
+ case 'K':
+ bin->kill_gui = false;
+ break;
+ case 'b':
+ bin->bad_plugins = true;
+ break;
+ case 'B':
+ bin->bad_plugins = false;
+ break;
+ case 'a':
+ bin->cpu_affinity = true;
+ break;
+ case 'A':
+ bin->cpu_affinity = false;
+ break;
+ case 'y':
+ bin->audio_prio = atoi(optarg);
+ break;
+ case 'Y':
+ bin->audio_prio = 0;
+ break;
+ case 'w':
+ bin->worker_prio = atoi(optarg);
+ break;
+ case 'W':
+ bin->worker_prio = 0;
+ break;
+ case 'l':
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "%s", optarg);
+ break;
+ case 'r':
+ handle.srate = atoi(optarg);
+ break;
+ case 'p':
+ handle.frsize = atoi(optarg);
+ break;
+ case 's':
+ handle.seq_size = MAX(SEQ_SIZE, atoi(optarg));
+ break;
+ case 'c':
+ if(atoi(optarg) < bin->num_slaves)
+ bin->num_slaves = atoi(optarg);
+ break;
+ case 'f':
+ bin->update_rate = atoi(optarg);
+ break;
+ case '?':
+ if( (optopt == 'r') || (optopt == 'p') || (optopt == 's') || (optopt == 'c')
+ || (optopt == 'l') || (optopt == 'f') )
+ fprintf(stderr, "Option `-%c' requires an argument.\n", optopt);
+ else if(isprint(optopt))
+ fprintf(stderr, "Unknown option `-%c'.\n", optopt);
+ else
+ fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ bin_init(bin, handle.srate);
+
+ LV2_URID_Map *map = bin->map;
+
+ lv2_atom_forge_init(&handle.forge, map);
+
+ bin->app_driver.system_port_add = _system_port_add;
+ bin->app_driver.system_port_del = _system_port_del;
+
+ handle.osc_sched.osc2frames = _osc_schedule_osc2frames;
+ handle.osc_sched.frames2osc = _osc_schedule_frames2osc;
+ handle.osc_sched.handle = &handle;
+ bin->app_driver.osc_sched = &handle.osc_sched;
+ bin->app_driver.features = SP_APP_FEATURE_FIXED_BLOCK_LENGTH; // always true for DUMMY
+ if(handle.frsize && !(handle.frsize & (handle.frsize - 1))) // check for powerOf2
+ bin->app_driver.features |= SP_APP_FEATURE_POWER_OF_2_BLOCK_LENGTH;
+
+ // run
+ bin_run(bin, argv, &nsm_driver, NULL, NULL);
+
+ // stop
+ bin_stop(bin);
+
+ // stop rt thread
+ if(handle.thread)
+ {
+ atomic_store_explicit(&handle.kill, 1, memory_order_relaxed);
+ pthread_join(handle.thread, NULL);
+ }
+
+ // deinit
+ bin_deinit(bin);
+
+ munlockall();
+
+ return 0;
+}
diff --git a/bin/synthpod_jack.1 b/bin/synthpod_jack.1
new file mode 100644
index 00000000..108c58e9
--- /dev/null
+++ b/bin/synthpod_jack.1
@@ -0,0 +1,123 @@
+.TH SYNTHPOD "1" "Feb 24, 2017"
+
+.SH NAME
+synthpod \- a lightweight nonlinear LV2 plugin container
+
+.SH SYNOPSIS
+.B synthpod_jack
+[\fIoptions\fR] [\fIbundle-path\fR]
+
+.SH DESCRIPTION
+\fBsynthpod\fP is a lightweight nonlinear LV2 plugin container, aka host.
+.PP
+It is a pure stand-alone JACK client with JACK/NSM session support and JACK AUDIO/MIDI.
+.PP
+It is also a first-class LV2 plugin (http://open-music-kontrollers.ch/lv2/synthpod#stereo).
+
+.SH OPTIONS
+.HP
+\fB\-v\fR
+.IP
+Print version and license information
+
+.HP
+\fB\-h\fR
+.IP
+Print usage information
+
+.HP
+\fB\-g\fR
+.IP
+Load GUI
+
+.HP
+\fB\-G\fR
+.IP
+Do NOT load GUI (default)
+
+.HP
+\fB\-k\fR
+.IP
+Kill DSP with GUI
+
+.HP
+\fB\-K\fR
+.IP
+Do NOT kill DSP with GUI (default)
+
+.HP
+\fB\-b\fR
+.IP
+Enable bad plugins
+
+.HP
+\fB\-B\fR
+.IP
+Disable bad plugins (default)
+
+.HP
+\fB\-a\fR
+.IP
+Enable CPU affinity
+
+.HP
+\fB\-A\fR
+.IP
+Disable CPU affinity (default)
+
+.HP
+\fB\-w\fR worker-priority
+.IP
+Worker thread realtime priority (60)
+
+.HP
+\fB\-W\fR
+.IP
+Disable worker thread realtime priority
+
+.HP
+\fB\-l\fR
+.IP
+Socket link path (shm:///synthpod), e.g. tcp://*:9090
+
+.HP
+\fB\-n\fR server-name
+.IP
+Connect to named JACK daemon
+
+.HP
+\fB\-u\fR client-uuid
+.IP
+Client UUID for JACK session management
+
+.HP
+\fB\-s\fR sequence-size
+.IP
+Minimal byte size of event sequence buffers (8192)
+
+.HP
+\fB\-c\fR slave-cores
+.IP
+Number of slave cores for parallel audio processing (auto)
+
+.HP
+\fB\-f\fR update-rate
+.IP
+Update rate in frames per second of GUI
+
+.SH FILES
+.TP
+.I $HOME/.lv2/Synthpod_default.preset.lv2
+Default bundle state directory
+.TP
+.I $HOME/.lv2
+Default LV2 preset directory
+
+.SH LICENSE
+Artistic License 2.0.
+
+.SH AUTHOR
+Hanspeter Portner (dev@open-music-kontrollers.ch).
+
+.SH SEE ALSO
+synthpod_alsa(1), synthpod_dummy(1), synthpod_sandbox(1), jackd(1)
diff --git a/bin/synthpod_jack.c b/bin/synthpod_jack.c
new file mode 100644
index 00000000..0df0e2ec
--- /dev/null
+++ b/bin/synthpod_jack.c
@@ -0,0 +1,1250 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+# include <pthread_np.h>
+typedef cpuset_t cpu_set_t;
+#endif
+
+#include <synthpod_bin.h>
+
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <jack/transport.h>
+#include <jack/session.h>
+#if defined(JACK_HAS_METADATA_API)
+# include <jack/metadata.h>
+# include <jack/uuid.h>
+# include <jackey.h>
+#endif
+
+#include <osc.lv2/forge.h>
+#include <osc.lv2/writer.h>
+
+#define OSC_SIZE 0x800
+
+typedef struct _prog_t prog_t;
+
+struct _prog_t {
+ bin_t bin;
+
+ LV2_Atom_Forge forge;
+
+ uint8_t osc_buf [OSC_SIZE]; //TODO how big?
+
+ LV2_OSC_URID osc_urid;
+
+ LV2_URID midi_MidiEvent;
+
+ LV2_URID time_position;
+ LV2_URID time_barBeat;
+ LV2_URID time_bar;
+ LV2_URID time_beatUnit;
+ LV2_URID time_beatsPerBar;
+ LV2_URID time_beatsPerMinute;
+ LV2_URID time_frame;
+ LV2_URID time_framesPerSecond;
+ LV2_URID time_speed;
+
+ atomic_int kill;
+ save_state_t save_state;
+ atomic_uintptr_t async;
+
+ char *server_name;
+ char *session_id;
+ jack_client_t *client;
+ jack_session_event_t *session_event;
+ uint32_t seq_size;
+
+ struct {
+ jack_transport_state_t rolling;
+ jack_nframes_t frame;
+ float beats_per_bar;
+ float beat_type;
+ double ticks_per_beat;
+ double beats_per_minute;
+ } trans;
+
+ LV2_OSC_Schedule osc_sched;
+ struct timespec cur_ntp;
+ struct {
+ jack_nframes_t cur_frames;
+ jack_nframes_t ref_frames;
+ jack_time_t cur_usecs;
+ jack_time_t nxt_usecs;
+ double dT;
+ double dTm1;
+ } cycle;
+};
+
+static LV2_Atom_Forge_Ref
+_trans_event(prog_t *prog, LV2_Atom_Forge *forge, int rolling, jack_position_t *pos)
+{
+ LV2_Atom_Forge_Frame frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &frame, 0, prog->time_position);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_frame);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, pos->frame);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_speed);
+ if(ref)
+ ref = lv2_atom_forge_float(forge, rolling ? 1.0 : 0.0);
+
+ if(pos->valid & JackPositionBBT)
+ {
+ float bar_beat = pos->beat - 1 + (pos->tick / pos->ticks_per_beat);
+ float bar = pos->bar - 1;
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_barBeat);
+ if(ref)
+ ref = lv2_atom_forge_float(forge, bar_beat);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_bar);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, bar);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_beatUnit);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, pos->beat_type);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_beatsPerBar);
+ if(ref)
+ ref = lv2_atom_forge_float(forge, pos->beats_per_bar);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, prog->time_beatsPerMinute);
+ if(ref)
+ ref = lv2_atom_forge_float(forge, pos->beats_per_minute);
+ }
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+
+ return ref;
+}
+
+__non_realtime static void
+_saved(bin_t *bin, int status)
+{
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ if(handle->save_state == SAVE_STATE_NSM)
+ {
+ nsmc_saved(bin->nsm, status);
+ }
+ else if(handle->save_state == SAVE_STATE_JACK)
+ {
+ jack_session_event_t *ev = handle->session_event;
+ if(ev)
+ {
+ if(status != 0)
+ ev->flags |= JackSessionSaveError;
+ jack_session_reply(handle->client, ev);
+ jack_session_event_free(ev);
+ handle->session_event = NULL;
+ }
+ }
+ handle->save_state = SAVE_STATE_INTERNAL;
+
+ if(atomic_load_explicit(&handle->kill, memory_order_relaxed))
+ {
+ bin_quit(bin);
+ }
+}
+
+// rt
+__realtime static int
+_process(jack_nframes_t nsamples, void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+ sp_app_t *app = bin->app;
+
+ if(bin->first)
+ {
+ bin->dsp_thread = pthread_self();
+
+ if(handle->bin.cpu_affinity)
+ {
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(0, &cpuset);
+ if(pthread_setaffinity_np(bin->dsp_thread, sizeof(cpu_set_t), &cpuset))
+ bin_log_trace(bin, "%s: pthread_setaffinity_np error\n", __func__);
+ }
+
+ bin->first = false;
+ }
+
+ cross_clock_gettime(&bin->clk_real, &handle->cur_ntp);
+ handle->cur_ntp.tv_sec += JAN_1970; // convert NTP to OSC time
+ //jack_nframes_t offset = jack_frames_since_cycle_start(handle->client);
+
+ float T;
+ jack_get_cycle_times(handle->client, &handle->cycle.cur_frames,
+ &handle->cycle.cur_usecs, &handle->cycle.nxt_usecs, &T);
+ (void)T;
+
+ handle->cycle.ref_frames = handle->cycle.cur_frames;
+
+ // calculate apparent period
+ double diff = 1e-6 * (handle->cycle.nxt_usecs - handle->cycle.cur_usecs);
+
+ // calculate apparent samples per period
+ handle->cycle.dT = nsamples / diff;
+ handle->cycle.dTm1 = 1.0 / handle->cycle.dT;
+
+ // get transport position
+ jack_position_t pos;
+ jack_transport_state_t rolling = jack_transport_query(handle->client, &pos) == JackTransportRolling;
+ int trans_changed = (rolling != handle->trans.rolling)
+ || (pos.frame != handle->trans.frame)
+ || (pos.beats_per_bar != handle->trans.beats_per_bar)
+ || (pos.beat_type != handle->trans.beat_type)
+ || (pos.ticks_per_beat != handle->trans.ticks_per_beat)
+ || (pos.beats_per_minute != handle->trans.beats_per_minute);
+
+ const size_t sample_buf_size = sizeof(float) * nsamples;
+ const sp_app_system_source_t *sources = sp_app_get_system_sources(app);
+
+ if(sp_app_bypassed(app)) // aka loading state
+ {
+ const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ //fprintf(stderr, "app is bypassed\n");
+
+ // clear output buffers
+ for(const sp_app_system_sink_t *sink=sinks;
+ sink->type != SYSTEM_PORT_NONE;
+ sink++)
+ {
+ switch(sink->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ case SYSTEM_PORT_COM:
+ break;
+
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_CV:
+ {
+ void *out_buf = jack_port_get_buffer(sink->sys_port, nsamples);
+ memset(out_buf, 0x0, sample_buf_size);
+ break;
+ }
+ case SYSTEM_PORT_MIDI:
+ case SYSTEM_PORT_OSC:
+ {
+ void *out_buf = jack_port_get_buffer(sink->sys_port, nsamples);
+ jack_midi_clear_buffer(out_buf);
+ break;
+ }
+ }
+ }
+
+ bin_process_pre(bin, nsamples, true);
+ bin_process_post(bin);
+
+ return 0;
+ }
+
+ //TODO use __builtin_assume_aligned
+
+ // fill input buffers
+ for(const sp_app_system_source_t *source=sources;
+ source->type != SYSTEM_PORT_NONE;
+ source++)
+ {
+ switch(source->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ break;
+
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_CV:
+ {
+ const void *in_buf = jack_port_get_buffer(source->sys_port, nsamples);
+ memcpy(source->buf, in_buf, sample_buf_size);
+ break;
+ }
+ case SYSTEM_PORT_MIDI:
+ {
+ void *in_buf = jack_port_get_buffer(source->sys_port, nsamples);
+ void *seq_in = source->buf;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ if(ref && trans_changed)
+ ref = _trans_event(handle, forge, rolling, &pos);
+
+ const int n = jack_midi_get_event_count(in_buf);
+ for(int i=0; i<n; i++)
+ {
+ jack_midi_event_t mev;
+ jack_midi_event_get(&mev, in_buf, i);
+
+ if( (mev.buffer[0] & 0x80) != 0x80)
+ continue; // no MIDI message
+
+ //add jack midi event to in_buf
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, mev.time);
+ if(ref)
+ ref = lv2_atom_forge_atom(forge, mev.size, handle->midi_MidiEvent);
+ // fix up noteOn(vel=0) -> noteOff(vel=0)
+ if( (mev.size == 3) && ( (mev.buffer[0] & 0xf0) == 0x90)
+ && (mev.buffer[2] == 0x00) )
+ {
+ const uint8_t note_off [3] = {
+ 0x80 | (mev.buffer[0] & 0xf),
+ mev.buffer[1],
+ 0x0
+ };
+ if(ref)
+ ref = lv2_atom_forge_write(forge, note_off, sizeof(note_off));
+ }
+ else
+ {
+ if(ref)
+ ref = lv2_atom_forge_write(forge, mev.buffer, mev.size);
+ }
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(seq_in);
+
+ break;
+ }
+
+ case SYSTEM_PORT_OSC:
+ {
+ void *in_buf = jack_port_get_buffer(source->sys_port, nsamples);
+ void *seq_in = source->buf;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ if(ref && trans_changed)
+ ref = _trans_event(handle, forge, rolling, &pos);
+
+ const int n = jack_midi_get_event_count(in_buf);
+ for(int i=0; i<n; i++)
+ {
+ jack_midi_event_t mev;
+ jack_midi_event_get(&mev, in_buf, i);
+
+ if( (mev.buffer[0] != '/') && (mev.buffer[0] != '#') )
+ continue; // no OSC message
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, mev.time);
+ if(ref)
+ ref = lv2_osc_forge_packet(forge, &handle->osc_urid, handle->bin.map,
+ mev.buffer, mev.size); // Note: an invalid packet will return 0
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(seq_in);
+
+ break;
+ }
+
+ case SYSTEM_PORT_COM:
+ {
+ void *seq_in = source->buf;
+
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ const LV2_Atom_Object *obj;
+ size_t size;
+ while((obj = varchunk_read_request(bin->app_from_com, &size)))
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, obj, size);
+
+ varchunk_read_advance(bin->app_from_com);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(seq_in);
+
+ break;
+ }
+ }
+ }
+
+ // update transport state
+ handle->trans.rolling = rolling;
+ handle->trans.frame = rolling
+ ? handle->trans.frame + nsamples
+ : pos.frame;
+ handle->trans.beats_per_bar = pos.beats_per_bar;
+ handle->trans.beat_type = pos.beat_type;
+ handle->trans.ticks_per_beat = pos.ticks_per_beat;
+ handle->trans.beats_per_minute = pos.beats_per_minute;
+
+ bin_process_pre(bin, nsamples, false);
+
+ const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
+
+ // fill output buffers
+ for(const sp_app_system_sink_t *sink=sinks;
+ sink->type != SYSTEM_PORT_NONE;
+ sink++)
+ {
+ switch(sink->type)
+ {
+ case SYSTEM_PORT_NONE:
+ case SYSTEM_PORT_CONTROL:
+ break;
+
+ case SYSTEM_PORT_AUDIO:
+ case SYSTEM_PORT_CV:
+ {
+ void *out_buf = jack_port_get_buffer(sink->sys_port, nsamples);
+ memcpy(out_buf, sink->buf, sample_buf_size);
+ break;
+ }
+ case SYSTEM_PORT_MIDI:
+ {
+ void *out_buf = jack_port_get_buffer(sink->sys_port, nsamples);
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ // fill midi output buffer
+ jack_midi_clear_buffer(out_buf);
+ if(seq_out)
+ {
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+
+ if(atom->type != handle->midi_MidiEvent)
+ continue; // ignore non-MIDI events
+
+ jack_midi_event_write(out_buf, ev->time.frames,
+ LV2_ATOM_BODY_CONST(atom), atom->size);
+ }
+ }
+
+ break;
+ }
+
+ case SYSTEM_PORT_OSC:
+ {
+ void *out_buf = jack_port_get_buffer(sink->sys_port, nsamples);
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ // fill midi output buffer
+ jack_midi_clear_buffer(out_buf);
+ if(seq_out)
+ {
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(!lv2_atom_forge_is_object_type(&handle->forge, obj->atom.type))
+ continue;
+
+ if(!lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype))
+ continue;
+
+ LV2_OSC_Writer writer;
+ lv2_osc_writer_initialize(&writer, handle->osc_buf, OSC_SIZE);
+ lv2_osc_writer_packet(&writer, &handle->osc_urid, handle->bin.unmap,
+ obj->atom.size, &obj->body);
+ size_t size;
+ uint8_t *osc_buf = lv2_osc_writer_finalize(&writer, &size);
+
+ if(size)
+ {
+ jack_midi_event_write(out_buf, ev->time.frames,
+ osc_buf, size);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case SYSTEM_PORT_COM:
+ {
+ const LV2_Atom_Sequence *seq_out = sink->buf;
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
+ {
+ const LV2_Atom *atom = (const LV2_Atom *)&ev->body;
+
+ // try do process events directly
+ bin->advance_ui = sp_app_from_ui(bin->app, atom);
+ if(!bin->advance_ui) // queue event in ringbuffer instead
+ {
+ //fprintf(stderr, "plugin ui direct is blocked\n");
+
+ void *ptr;
+ size_t size = lv2_atom_total_size(atom);
+ if((ptr = varchunk_write_request(bin->app_from_app, size)))
+ {
+ memcpy(ptr, atom, size);
+ varchunk_write_advance(bin->app_from_app, size);
+ }
+ else
+ {
+ bin_log_trace(bin, "%s: app_from_app ringbuffer full\n", __func__);
+ //FIXME
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ bin_process_post(bin);
+
+ return 0;
+}
+
+__non_realtime static void
+_session(jack_session_event_t *ev, void *data)
+{
+ prog_t *handle = data;
+
+ //printf("_session: %s %s %s\n",
+ // ev->session_dir, ev->client_uuid, ev->command_line);
+
+ jack_session_event_t *async = (void *)atomic_exchange(&handle->async, (uintptr_t)ev);
+ if(async)
+ jack_session_event_free(async);
+}
+
+// rt, but can do non-rt stuff, as process won't be called
+__non_realtime static int
+_buffer_size(jack_nframes_t block_size, void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ //printf("JACK: new buffer size: %p %u %u\n",
+ // handle->app, handle->app_driver.max_block_size, block_size);
+
+ if(bin->app)
+ return sp_app_nominal_block_length(bin->app, block_size);
+
+ return 0;
+}
+
+__non_realtime static int
+_sample_rate(jack_nframes_t sample_rate, void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ if(bin->app && (sample_rate != bin->app_driver.sample_rate) )
+ bin_log_error(bin, "%s: synthpod does not support dynamic sample rate changes\n", __func__);
+
+ return 0;
+}
+
+void
+_replace(char* str, const char* a, const char* b)
+{
+ const size_t len = strlen(str);
+ const size_t lena = strlen(a);
+ const size_t lenb = strlen(b);
+
+ for(char *p = str; (p = strstr(p, a)); ++p)
+ {
+ if(lena != lenb) // shift end as needed
+ {
+ memmove(p+lenb, p+lena, len - (p - str) + lenb);
+ }
+ memcpy(p, b, lenb);
+ }
+}
+
+__non_realtime static void *
+_system_port_add(void *data, system_port_t type, const char *short_name,
+ const char *pretty_name, const char *designation, bool input, uint32_t order)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ //printf("_system_port_add: %s\n", short_name);
+
+ const size_t len = jack_port_name_size();
+ char *name = alloca(len);
+ strncpy(name, short_name, len);
+
+ if(strstr(name, "sink"))
+ _replace(name, "sink", "source");
+ else if(strstr(name, "source"))
+ _replace(name, "source", "sink");
+
+ jack_port_t *jack_port = NULL;
+
+ unsigned long flags = input ? JackPortIsInput : JackPortIsOutput;
+
+ switch(type)
+ {
+ case SYSTEM_PORT_NONE:
+ {
+ // skip
+ break;
+ }
+
+ case SYSTEM_PORT_CONTROL:
+ {
+ // unsupported, skip
+ break;
+ }
+
+ case SYSTEM_PORT_AUDIO:
+ {
+ jack_port = jack_port_register(handle->client, name,
+ JACK_DEFAULT_AUDIO_TYPE, flags, 0);
+ break;
+ }
+ case SYSTEM_PORT_CV:
+ {
+ jack_port = jack_port_register(handle->client, name,
+ JACK_DEFAULT_AUDIO_TYPE, flags, 0);
+
+#if defined(JACK_HAS_METADATA_API)
+ if(jack_port)
+ {
+ jack_uuid_t uuid = jack_port_uuid(jack_port);
+ if(!jack_uuid_empty(uuid))
+ {
+ jack_set_property(handle->client, uuid,
+ JACKEY_SIGNAL_TYPE, "CV", "text/plain");
+ }
+ }
+#endif
+ break;
+ }
+
+ case SYSTEM_PORT_MIDI:
+ {
+ jack_port = jack_port_register(handle->client, name,
+ JACK_DEFAULT_MIDI_TYPE, flags, 0);
+
+#if defined(JACK_HAS_METADATA_API)
+ if(jack_port)
+ {
+ jack_uuid_t uuid = jack_port_uuid(jack_port);
+ if(!jack_uuid_empty(uuid))
+ {
+ jack_set_property(handle->client, uuid,
+ JACKEY_EVENT_TYPES, "MIDI", "text/plain");
+ }
+ }
+#endif
+ break;
+ }
+ case SYSTEM_PORT_OSC:
+ {
+ jack_port = jack_port_register(handle->client, name,
+ JACK_DEFAULT_MIDI_TYPE, flags, 0);
+
+#if defined(JACK_HAS_METADATA_API)
+ if(jack_port)
+ {
+ jack_uuid_t uuid = jack_port_uuid(jack_port);
+ if(!jack_uuid_empty(uuid))
+ {
+ jack_set_property(handle->client, uuid,
+ JACKEY_EVENT_TYPES, "OSC", "text/plain");
+ }
+ }
+#endif
+ break;
+ }
+
+ case SYSTEM_PORT_COM:
+ {
+ // unsupported, skip
+ break;
+ }
+ }
+
+#if defined(JACK_HAS_METADATA_API)
+ if(jack_port)
+ {
+ jack_uuid_t uuid = jack_port_uuid(jack_port);
+ if(!jack_uuid_empty(uuid))
+ {
+ if(pretty_name)
+ {
+ jack_set_property(handle->client, uuid,
+ JACK_METADATA_PRETTY_NAME, pretty_name, "text/plain");
+ }
+
+ if(designation)
+ {
+ jack_set_property(handle->client, uuid,
+ JACKEY_DESIGNATION, designation, "text/plain");
+ }
+
+ char order_str [32];
+ sprintf(order_str, "%"PRIu32, order);
+ jack_set_property(handle->client, uuid,
+ JACKEY_ORDER, order_str, "http://www.w3.org/2001/XMLSchema#integer");
+ }
+ }
+#endif
+
+ return jack_port;
+}
+
+__non_realtime static void
+_system_port_del(void *data, void *sys_port)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ jack_port_t *jack_port = sys_port;
+
+ if(!jack_port || !handle->client)
+ return;
+
+#if defined(JACK_HAS_METADATA_API)
+ jack_uuid_t uuid = jack_port_uuid(jack_port);
+ if(!jack_uuid_empty(uuid))
+ jack_remove_properties(handle->client, uuid);
+#endif
+
+ jack_port_unregister(handle->client, jack_port);
+}
+
+__non_realtime static void
+_shutdown(void *data)
+{
+ prog_t *handle = data;
+
+ //TODO do this asynchronously?
+ handle->client = NULL; // client has died, didn't it?
+ bin_quit(&handle->bin);
+}
+
+__non_realtime static int
+_xrun(void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ //TODO do this asynchronously?
+ bin_log_warning(bin, "JACK XRun\n");
+
+ return 0;
+}
+
+static int
+_jack_init(prog_t *handle, const char *id)
+{
+ jack_options_t opts = JackNullOption | JackNoStartServer;
+ if(handle->server_name)
+ opts |= JackServerName;
+ if(handle->session_id)
+ opts |= JackSessionID;
+
+ jack_status_t status;
+ if(!(handle->client = jack_client_open(id, opts, &status,
+ handle->server_name ? handle->server_name : handle->session_id,
+ handle->server_name ? handle->session_id : NULL)))
+ {
+ return -1;
+ }
+
+ //TODO check status
+
+ // set client pretty name
+#if defined(JACK_HAS_METADATA_API)
+ jack_uuid_t uuid;
+ const char *client_name = jack_get_client_name(handle->client);
+ const char *uuid_str = jack_get_uuid_for_client_name(handle->client, client_name);
+ if(uuid_str)
+ jack_uuid_parse(uuid_str, &uuid);
+ else
+ jack_uuid_clear(&uuid);
+
+ if(!jack_uuid_empty(uuid))
+ {
+ jack_set_property(handle->client, uuid,
+ JACK_METADATA_PRETTY_NAME, "Synthpod", "text/plain");
+ }
+#endif
+
+ // set client process callback
+ if(jack_set_process_callback(handle->client, _process, handle))
+ return -1;
+ if(jack_set_session_callback(handle->client, _session, handle))
+ return -1;
+ if(jack_set_sample_rate_callback(handle->client, _sample_rate, handle))
+ return -1;
+ if(jack_set_buffer_size_callback(handle->client, _buffer_size, handle))
+ return -1;
+ jack_on_shutdown(handle->client, _shutdown, handle);
+ jack_set_xrun_callback(handle->client, _xrun, handle);
+
+ return 0;
+}
+
+static void
+_jack_deinit(prog_t *handle)
+{
+ if(handle->client)
+ {
+ // remove client properties
+#if defined(JACK_HAS_METADATA_API)
+ jack_uuid_t uuid;
+ const char *client_name = jack_get_client_name(handle->client);
+ const char *uuid_str = jack_get_uuid_for_client_name(handle->client, client_name);
+ if(uuid_str)
+ jack_uuid_parse(uuid_str, &uuid);
+ else
+ jack_uuid_clear(&uuid);
+
+ if(!jack_uuid_empty(uuid))
+ jack_remove_properties(handle->client, uuid);
+#endif
+
+ jack_deactivate(handle->client);
+ jack_client_close(handle->client);
+ handle->client = NULL;
+ }
+}
+
+__non_realtime static int
+_open(const char *path, const char *name, const char *id, void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+ (void)name;
+
+ if(bin->path)
+ free(bin->path);
+ bin->path = strdup(path);
+
+ // jack init
+ if(_jack_init(handle, id))
+ {
+ nsmc_opened(bin->nsm, -1);
+ return -1;
+ }
+
+ // synthpod init
+ bin->app_driver.sample_rate = jack_get_sample_rate(handle->client);
+ bin->app_driver.update_rate = handle->bin.update_rate;
+ bin->app_driver.max_block_size = jack_get_buffer_size(handle->client);
+ bin->app_driver.min_block_size = 1;
+ bin->app_driver.seq_size = MAX(handle->seq_size,
+ jack_port_type_get_buffer_size(handle->client, JACK_DEFAULT_MIDI_TYPE));
+ bin->app_driver.num_periods = 1; //FIXME
+
+ // app init
+ bin->app = sp_app_new(NULL, &bin->app_driver, bin);
+
+ // jack activate
+ atomic_init(&handle->kill, 0);
+ jack_activate(handle->client); //TODO check
+
+ bin_bundle_load(bin, bin->path);
+ nsmc_opened(bin->nsm, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_save(void *data)
+{
+ bin_t *bin = data;
+ prog_t *handle = (void *)bin - offsetof(prog_t, bin);
+
+ handle->save_state = SAVE_STATE_NSM;
+ bin_bundle_save(bin, bin->path);
+ _saved(bin, 0);
+
+ return 0; // success
+}
+
+__non_realtime static int
+_show(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_show(bin);
+}
+
+__non_realtime static int
+_hide(void *data)
+{
+ bin_t *bin = data;
+
+ return bin_hide(bin);
+}
+
+static const nsmc_driver_t nsm_driver = {
+ .open = _open,
+ .save = _save,
+ .show = _show,
+ .hide = _hide
+};
+
+// rt
+__realtime static double
+_osc_schedule_osc2frames(LV2_OSC_Schedule_Handle instance, uint64_t timestamp)
+{
+ prog_t *handle = instance;
+
+ if(timestamp == 1ULL)
+ return 0; // inject at start of period
+
+ const uint64_t time_sec = timestamp >> 32;
+ const uint64_t time_frac = timestamp & 0xffffffff;
+
+ const double diff = (time_sec - handle->cur_ntp.tv_sec)
+ + time_frac * 0x1p-32
+ - handle->cur_ntp.tv_nsec * 1e-9;
+
+ const double frames = diff * handle->cycle.dT
+ - handle->cycle.ref_frames
+ + handle->cycle.cur_frames;
+
+ return frames;
+}
+
+// rt
+__realtime static uint64_t
+_osc_schedule_frames2osc(LV2_OSC_Schedule_Handle instance, double frames)
+{
+ prog_t *handle = instance;
+
+ double diff = (frames - handle->cycle.cur_frames + handle->cycle.ref_frames)
+ * handle->cycle.dTm1;
+ diff += handle->cur_ntp.tv_nsec * 1e-9;
+ diff += handle->cur_ntp.tv_sec;
+
+ double time_sec_d;
+ double time_frac_d = modf(diff, &time_sec_d);
+
+ uint64_t time_sec = time_sec_d;
+ uint64_t time_frac = time_frac_d * 0x1p32;
+ if(time_frac >= 0x100000000ULL) // illegal overflow
+ time_frac = 0xffffffffULL;
+
+ uint64_t timestamp = (time_sec << 32) | time_frac;
+
+ return timestamp;
+}
+
+static void
+_idle(void *data)
+{
+ prog_t *handle = data;
+ bin_t *bin = &handle->bin;
+
+ jack_session_event_t *async = (void *)atomic_exchange(&handle->async, 0);
+ if(!async)
+ return;
+
+ //printf("_session_async: %s %s %s\n",
+ // async->session_dir, async->client_uuid, async->command_line);
+
+ char path [PATH_MAX];
+ const char *resolvedpath = realpath(async->session_dir, path);
+ if(!resolvedpath)
+ resolvedpath = async->session_dir; // fall-back
+
+ // create command line
+ char *buf = NULL;
+ size_t sz = 0;
+ bool ignore = false;
+
+ for(int i=0; i<bin->optind; i++)
+ {
+ const char *arg = (i == 0)
+ ? "synthpod_jack"
+ : bin->argv[i];
+
+ if(ignore)
+ {
+ ignore = false;
+ continue;
+ }
+
+ if(!strcmp(arg, "-u"))
+ {
+ ignore = true;
+ continue;
+ }
+
+ const size_t len = strlen(arg);
+ buf = realloc(buf, sz + len);
+
+ sprintf(&buf[sz], "%s ", arg);
+ sz += len + 1;
+ }
+
+ {
+ const size_t len = 32;
+ buf = realloc(buf, sz + len);
+
+ sprintf(&buf[sz], "-u %s ${SESSION_DIR}", async->client_uuid);
+ }
+
+ async->command_line = buf;
+ handle->session_event = async;
+
+ switch(async->type)
+ {
+ case JackSessionSaveAndQuit:
+ atomic_store_explicit(&handle->kill, 1, memory_order_relaxed); // quit after saving
+ // fall-through
+ case JackSessionSave:
+ handle->save_state = SAVE_STATE_JACK;
+ bin_bundle_save(bin, resolvedpath);
+ _saved(bin, 0);
+ break;
+ case JackSessionSaveTemplate:
+ handle->save_state = SAVE_STATE_JACK;
+ bin_bundle_new(bin);
+ bin_bundle_save(bin, resolvedpath);
+ _saved(bin, 0);
+ break;
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+
+ static prog_t handle;
+ bin_t *bin = &handle.bin;
+
+ handle.server_name = NULL;
+ handle.session_id = NULL;
+ handle.seq_size = SEQ_SIZE;
+
+ bin->audio_prio = 70; // not used
+ bin->worker_prio = 60;
+ bin->num_slaves = sysconf(_SC_NPROCESSORS_ONLN) - 1;
+ bin->bad_plugins = false;
+ bin->has_gui = false;
+ bin->kill_gui = false;
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "shm:///synthpod-%i", getpid());
+ bin->update_rate = 25;
+ bin->cpu_affinity = false;
+
+ fprintf(stderr,
+ "Synthpod "SYNTHPOD_VERSION"\n"
+ "Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)\n"
+ "Released under Artistic License 2.0 by Open Music Kontrollers\n");
+
+ int c;
+ while((c = getopt(argc, argv, "vhgGkKbBaAw:Wl:n:u:s:c:f:")) != -1)
+ {
+ switch(c)
+ {
+ case 'v':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "This is free software: you can redistribute it and/or modify\n"
+ "it under the terms of the Artistic License 2.0 as published by\n"
+ "The Perl Foundation.\n"
+ "\n"
+ "This source is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "Artistic License 2.0 for more details.\n"
+ "\n"
+ "You should have received a copy of the Artistic License 2.0\n"
+ "along the source as a COPYING file. If not, obtain it from\n"
+ "http://www.perlfoundation.org/artistic_license_2_0.\n\n");
+ return 0;
+ case 'h':
+ fprintf(stderr,
+ "--------------------------------------------------------------------\n"
+ "USAGE\n"
+ " %s [OPTIONS] [BUNDLE_PATH]\n"
+ "\n"
+ "OPTIONS\n"
+ " [-v] print version and full license information\n"
+ " [-h] print usage information\n"
+ " [-g] load GUI\n"
+ " [-G] do NOT load GUI (default)\n"
+ " [-k] kill DSP with GUI\n"
+ " [-K] do NOT kill DSP with GUI (default)\n"
+ " [-b] enable bad plugins\n"
+ " [-B] disable bad plugins (default)\n"
+ " [-a] enable CPU affinity\n"
+ " [-A] disable CPU affinity (default)\n"
+ " [-w] worker-priority worker thread realtime priority (60)\n"
+ " [-W] do NOT use worker thread realtime priority\n"
+ " [-l] link-path socket link path (shm:///synthpod)\n"
+ " [-n] server-name connect to named JACK daemon\n"
+ " [-u] client-uuid client UUID for JACK session management\n"
+ " [-s] sequence-size minimum sequence size (8192)\n"
+ " [-c] slave-cores number of slave cores (auto)\n"
+ " [-f] update-rate GUI update rate (25)\n\n"
+ , argv[0]);
+ return 0;
+ case 'g':
+ bin->has_gui = true;
+ break;
+ case 'G':
+ bin->has_gui = false;
+ break;
+ case 'k':
+ bin->kill_gui = true;
+ break;
+ case 'K':
+ bin->kill_gui = false;
+ break;
+ case 'b':
+ bin->bad_plugins = true;
+ break;
+ case 'B':
+ bin->bad_plugins = false;
+ break;
+ case 'a':
+ bin->cpu_affinity = true;
+ break;
+ case 'A':
+ bin->cpu_affinity = false;
+ break;
+ case 'w':
+ bin->worker_prio = atoi(optarg);
+ break;
+ case 'W':
+ bin->worker_prio = 0;
+ break;
+ case 'l':
+ snprintf(bin->socket_path, sizeof(bin->socket_path), "%s", optarg);
+ break;
+ case 'n':
+ handle.server_name = optarg;
+ break;
+ case 'u':
+ handle.session_id = optarg;
+ break;
+ case 's':
+ handle.seq_size = MAX(SEQ_SIZE, atoi(optarg));
+ break;
+ case 'c':
+ if(atoi(optarg) < bin->num_slaves)
+ bin->num_slaves = atoi(optarg);
+ break;
+ case 'f':
+ bin->update_rate = atoi(optarg);
+ break;
+ case '?':
+ if( (optopt == 'n') || (optopt == 'u') || (optopt == 's') || (optopt == 'c')
+ || (optopt == 'l') || (optopt == 'f') )
+ fprintf(stderr, "Option `-%c' requires an argument.\n", optopt);
+ else if(isprint(optopt))
+ fprintf(stderr, "Unknown option `-%c'.\n", optopt);
+ else
+ fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ atomic_init(&handle.async, 0);
+
+ bin_init(bin, 48000); //FIXME
+
+ LV2_URID_Map *map = bin->map;
+
+ lv2_atom_forge_init(&handle.forge, map);
+ lv2_osc_urid_init(&handle.osc_urid, map);
+
+ handle.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ handle.time_position = map->map(map->handle, LV2_TIME__Position);
+ handle.time_barBeat = map->map(map->handle, LV2_TIME__barBeat);
+ handle.time_bar = map->map(map->handle, LV2_TIME__bar);
+ handle.time_beatUnit = map->map(map->handle, LV2_TIME__beatUnit);
+ handle.time_beatsPerBar = map->map(map->handle, LV2_TIME__beatsPerBar);
+ handle.time_beatsPerMinute = map->map(map->handle, LV2_TIME__beatsPerMinute);
+ handle.time_frame = map->map(map->handle, LV2_TIME__frame);
+ handle.time_framesPerSecond = map->map(map->handle, LV2_TIME__framesPerSecond);
+ handle.time_speed = map->map(map->handle, LV2_TIME__speed);
+
+ bin->app_driver.system_port_add = _system_port_add;
+ bin->app_driver.system_port_del = _system_port_del;
+
+ handle.osc_sched.osc2frames = _osc_schedule_osc2frames;
+ handle.osc_sched.frames2osc = _osc_schedule_frames2osc;
+ handle.osc_sched.handle = &handle;
+ bin->app_driver.osc_sched = &handle.osc_sched;
+
+ bin->app_driver.features = SP_APP_FEATURE_POWER_OF_2_BLOCK_LENGTH; // always true for JACK
+
+ // run
+ bin_run(bin, argv, &nsm_driver, _idle, &handle);
+
+ // stop
+ bin_stop(bin);
+
+ jack_session_event_t *async = (void *)atomic_load(&handle.async);
+ if(async)
+ jack_session_event_free(async);
+
+ // deinit JACK
+ _jack_deinit(&handle);
+
+ // deinit
+ bin_deinit(bin);
+
+ munlockall();
+
+ return 0;
+}
diff --git a/bin/synthpod_sandbox.1 b/bin/synthpod_sandbox.1
new file mode 100644
index 00000000..cea289e8
--- /dev/null
+++ b/bin/synthpod_sandbox.1
@@ -0,0 +1,72 @@
+.TH SYNTHPOD "1" "Feb 08, 2017"
+
+.SH NAME
+synthpod \- a lightweight nonlinear LV2 plugin container
+
+.SH SYNOPSIS
+.B synthpod_sandbox
+[\fIoptions\fR]
+
+.SH DESCRIPTION
+\fBsynthpod_sandbox\fP runs out-of-process plugin UIs for \fBsynthpod\fP.
+
+.SH OPTIONS
+.HP
+\fB\-n\fR plugin-urn
+.IP
+Plugin URN
+
+.SH OPTIONS
+.HP
+\fB\-p\fR plugin-uri
+.IP
+Plugin URI
+
+.HP
+\fB\-P\fR plugin-bundle
+.IP
+Plugin bundle path
+
+.HP
+\fB\-u\fR ui-uri
+.IP
+Plugin UI URI
+
+.HP
+\fB\-U\fR ui-bundle
+.IP
+Plugin UI bundle path
+
+.HP
+\fB\-s\fR socket-path
+.IP
+Socket link path, e.g. shm:///synthpod or tcp://localhost:9090
+
+.HP
+\fB\-w\fR window-title
+.IP
+Window title
+
+.HP
+\fB\-m\fR minimum-size
+.IP
+Minimum ringbuffer size
+
+.HP
+\fB\-r\fR sample-rate
+.IP
+Sample rate
+
+.HP
+\fB\-f\fR frames-per-second
+.IP
+Update rate in frames per second
+
+.SH LICENSE
+Artistic License 2.0.
+
+.SH AUTHOR
+Hanspeter Portner (dev@open-music-kontrollers.ch).
+
+.SH SEE ALSO
+synthpod_alsa(1), synthpod_jack(1), synthpod_dummy(1)
diff --git a/bin/synthpod_sandbox_gtk.c b/bin/synthpod_sandbox_gtk.c
new file mode 100644
index 00000000..d8932031
--- /dev/null
+++ b/bin/synthpod_sandbox_gtk.c
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sandbox_slave.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+#include <gtk/gtk.h>
+#include <glib-unix.h>
+
+typedef struct _wrap_t wrap_t;
+typedef struct _app_t app_t;
+
+struct _wrap_t {
+ sandbox_slave_t *sb;
+ app_t *app;
+};
+
+struct _app_t {
+ sandbox_slave_t *sb;
+
+ GtkWidget *win;
+ GtkWidget *widget;
+ guint timeout;
+ guint signal;
+};
+
+static gboolean
+_anim(void *data)
+{
+ wrap_t *wrap = data;
+
+ if(sandbox_slave_recv(wrap->sb))
+ {
+ gtk_main_quit();
+ wrap->app->win = NULL;
+ }
+
+ return true;
+}
+
+static void
+_destroy(GtkWidget *widget, void *data)
+{
+ app_t *app = data;
+
+ gtk_main_quit();
+ app->win = NULL;
+}
+
+static gboolean
+_sig(void *data)
+{
+ app_t *app = data;
+
+ gtk_main_quit();
+ app->win = NULL;
+
+ return true;
+}
+
+static inline int
+_init(sandbox_slave_t *sb, void *data)
+{
+ app_t *app= data;
+
+ app->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if(!app->win)
+ {
+ fprintf(stderr, "gtk_window_new failed\n");
+ goto fail;
+ }
+ g_signal_connect(G_OBJECT(app->win), "destroy",
+ G_CALLBACK(_destroy), app);
+
+ const char *title = sandbox_slave_title_get(sb);
+ if(title)
+ gtk_window_set_title(GTK_WINDOW(app->win), title);
+
+ const LV2_Feature parent_feature = {
+ .URI = LV2_UI__parent,
+ .data = app->win
+ };
+
+ if(!sandbox_slave_instantiate(sb, &parent_feature, &app->widget))
+ goto fail;
+ if(!app->widget)
+ goto fail;
+
+ gtk_widget_set_can_focus(app->widget, true);
+ gtk_widget_grab_focus(app->widget);
+
+ gtk_container_add(GTK_CONTAINER(app->win), app->widget);
+ gtk_widget_show_all(app->win);
+
+ app->signal = g_unix_signal_add(SIGINT, _sig, app);
+ if(!app->signal)
+ {
+ fprintf(stderr, "g_unix_signal_add failed\n");
+ goto fail;
+ }
+
+ return 0; //success
+
+fail:
+ return -1;
+}
+
+static inline void
+_run(sandbox_slave_t *sb, float update_rate, void *data)
+{
+ app_t *app = data;
+
+ wrap_t wrap = {
+ .app = app,
+ .sb = sb
+ };
+
+ app->timeout = g_timeout_add(1000 / update_rate, _anim, &wrap); //FIXME check
+ gtk_main();
+}
+
+static inline void
+_deinit(void *data)
+{
+ app_t *app = data;
+
+ if(app->timeout)
+ g_source_remove(app->timeout);
+
+ if(app->signal)
+ g_source_remove(app->signal);
+
+ if(app->win)
+ {
+ gtk_widget_hide(app->win);
+ gtk_widget_destroy(app->win);
+ }
+}
+
+static const sandbox_slave_driver_t driver = {
+ .init_cb = _init,
+ .run_cb = _run,
+ .deinit_cb = _deinit,
+ .resize_cb = NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+ int res;
+
+ gtk_init(&argc, &argv);
+
+ app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
+ if(app.sb)
+ {
+ sandbox_slave_run(app.sb);
+ sandbox_slave_free(app.sb);
+ return res;
+ }
+
+ return res;
+}
diff --git a/bin/synthpod_sandbox_kx.c b/bin/synthpod_sandbox_kx.c
new file mode 100644
index 00000000..cbec82db
--- /dev/null
+++ b/bin/synthpod_sandbox_kx.c
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#define CROSS_CLOCK_IMPLEMENTATION
+#include <cross_clock/cross_clock.h>
+
+#include <sandbox_slave.h>
+#include <lv2_external_ui.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+typedef struct _app_t app_t;
+
+struct _app_t {
+ sandbox_slave_t *sb;
+
+ LV2_External_UI_Host host;
+ LV2_External_UI_Widget *widget;
+ cross_clock_t clk_mono;
+};
+
+static atomic_bool done = ATOMIC_VAR_INIT(false);
+
+static inline void
+_sig(int signum)
+{
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+}
+
+static inline void
+_ui_closed(LV2UI_Controller controller)
+{
+ sandbox_slave_t *sb = controller;
+ (void)sb;
+
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+}
+
+static inline int
+_init(sandbox_slave_t *sb, void *data)
+{
+ app_t *app= data;
+
+ signal(SIGINT, _sig);
+
+ app->host.ui_closed = _ui_closed;
+ app->host.plugin_human_id = NULL; //FIXME
+
+ const LV2_Feature parent_feature = {
+ .URI = LV2_EXTERNAL_UI__Host,
+ .data = &app->host
+ };
+
+ if(!sandbox_slave_instantiate(sb, &parent_feature, &app->widget))
+ return -1;
+ if(!app->widget)
+ return -1;
+
+ LV2_EXTERNAL_UI_SHOW(app->widget);
+
+ cross_clock_init(&app->clk_mono, CROSS_CLOCK_MONOTONIC);
+
+ return 0; //success
+}
+
+static inline void
+_run(sandbox_slave_t *sb, float update_rate, void *data)
+{
+ app_t *app = data;
+ const unsigned ns = 1000000000 / update_rate;
+ struct timespec to;
+ cross_clock_gettime(&app->clk_mono, &to);
+
+ while(!atomic_load_explicit(&done, memory_order_relaxed))
+ {
+ to.tv_nsec += ns;
+ while(to.tv_nsec >= 1000000000)
+ {
+ to.tv_nsec -= 1000000000;
+ to.tv_sec += 1;
+ }
+
+ cross_clock_nanosleep(&app->clk_mono, true, &to);
+
+ if(sandbox_slave_recv(sb))
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ LV2_EXTERNAL_UI_RUN(app->widget);
+ }
+
+ LV2_EXTERNAL_UI_HIDE(app->widget);
+}
+
+static inline void
+_deinit(void *data)
+{
+ app_t *app = data;
+
+ cross_clock_deinit(&app->clk_mono);
+}
+
+static const sandbox_slave_driver_t driver = {
+ .init_cb = _init,
+ .run_cb = _run,
+ .deinit_cb = _deinit,
+ .resize_cb = NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+ int res;
+
+ app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
+ if(app.sb)
+ {
+ sandbox_slave_run(app.sb);
+ sandbox_slave_free(app.sb);
+ return res;
+ }
+
+ return res;
+}
diff --git a/bin/synthpod_sandbox_qt.cpp b/bin/synthpod_sandbox_qt.cpp
new file mode 100644
index 00000000..9bc12da6
--- /dev/null
+++ b/bin/synthpod_sandbox_qt.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <atomic>
+
+#include <sandbox_slave.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+#if (SYNTHPOD_SANDBOX_QT == 4)
+# include <QtGui/QApplication>
+# include <QtGui/QMainWindow>
+#elif (SYNTHPOD_SANDBOX_QT == 5)
+# include <QtWidgets/QApplication>
+# include <QtWidgets/QMainWindow>
+#else
+# error "SYNTHPOD_SANDBOX_QT is invalid"
+#endif
+
+typedef struct _app_t app_t;
+
+class MyWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MyWindow(sandbox_slave_t *_sb);
+ ~MyWindow();
+ void start(float update_rate);
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+protected:
+ sandbox_slave_t *sb;
+ int timer_id;
+};
+
+
+struct _app_t {
+ sandbox_slave_t *sb;
+
+ MyWindow *win;
+ QWidget *widget;
+};
+
+static QApplication *a;
+static std::atomic<bool> done = ATOMIC_VAR_INIT(false);
+
+MyWindow::MyWindow(sandbox_slave_t *_sb)
+ : QMainWindow(), sb(_sb)
+{
+}
+
+MyWindow::~MyWindow()
+{
+ killTimer(timer_id);
+}
+
+void
+MyWindow::start(float update_rate)
+{
+ timer_id = startTimer(1000 / update_rate);
+}
+
+void
+MyWindow::timerEvent(QTimerEvent *event)
+{
+ (void)event;
+
+ if(sandbox_slave_recv(sb))
+ done.store(true, std::memory_order_relaxed);
+
+ if(done.load(std::memory_order_relaxed))
+ a->quit();
+}
+
+static inline void
+_sig(int signum)
+{
+ (void)signum;
+ done.store(true, std::memory_order_relaxed);
+}
+
+static inline int
+_init(sandbox_slave_t *sb, void *data)
+{
+ app_t *app= (app_t *)data;
+
+ signal(SIGINT, _sig);
+
+ int argc = 0;
+ a = new QApplication(argc, NULL, true);
+ app->win = new MyWindow(sb);
+ if(!app->win)
+ return -1;
+
+ const char *title = sandbox_slave_title_get(sb);
+ if(title)
+ app->win->setWindowTitle(title);
+
+ const LV2_Feature parent_feature = {
+ .URI = LV2_UI__parent,
+ .data = (void *)app->win
+ };
+
+ if(!sandbox_slave_instantiate(sb, &parent_feature, (void *)&app->widget))
+ return -1;
+ if(!app->widget)
+ return -1;
+
+ app->widget->show();
+ app->win->setCentralWidget(app->widget);
+
+ app->win->adjustSize();
+ app->win->show();
+
+ return 0;
+}
+
+static inline void
+_run(sandbox_slave_t *sb, float update_rate, void *data)
+{
+ app_t *app = (app_t *)data;
+ (void)sb;
+
+ app->win->start(update_rate);
+ a->exec();
+}
+
+static inline void
+_deinit(void *data)
+{
+ app_t *app = (app_t *)data;
+
+ app->win->hide();
+ delete app->win;
+
+ delete a;
+}
+
+static const sandbox_slave_driver_t driver = {
+ .init_cb = _init,
+ .run_cb = _run,
+ .deinit_cb = _deinit,
+ .resize_cb = NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+ int res;
+
+ app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
+ if(app.sb)
+ {
+ sandbox_slave_run(app.sb);
+ sandbox_slave_free(app.sb);
+ return res;
+ }
+
+ return res;
+}
+
+#include <synthpod_sandbox_qt.moc>
diff --git a/bin/synthpod_sandbox_show.c b/bin/synthpod_sandbox_show.c
new file mode 100644
index 00000000..50902dde
--- /dev/null
+++ b/bin/synthpod_sandbox_show.c
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#define CROSS_CLOCK_IMPLEMENTATION
+#include <cross_clock/cross_clock.h>
+
+#include <sandbox_slave.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+typedef struct _app_t app_t;
+
+struct _app_t {
+ sandbox_slave_t *sb;
+ LV2UI_Handle *handle;
+ const LV2UI_Idle_Interface *idle_iface;
+ const LV2UI_Show_Interface *show_iface;
+ cross_clock_t clk_mono;
+};
+
+static atomic_bool done = ATOMIC_VAR_INIT(false);
+
+static inline void
+_sig(int signum)
+{
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+}
+
+static inline int
+_init(sandbox_slave_t *sb, void *data)
+{
+ app_t *app= data;
+
+ signal(SIGINT, _sig);
+
+ void *widget = NULL;
+ if(!(app->handle = sandbox_slave_instantiate(sb, NULL, &widget)))
+ return -1;
+
+ app->idle_iface = sandbox_slave_extension_data(sb, LV2_UI__idleInterface);
+ app->show_iface = sandbox_slave_extension_data(sb, LV2_UI__showInterface);
+
+ if(app->show_iface)
+ app->show_iface->show(app->handle);
+
+ cross_clock_init(&app->clk_mono, CROSS_CLOCK_MONOTONIC);
+
+ return 0; //success
+}
+
+static inline void
+_run(sandbox_slave_t *sb, float update_rate, void *data)
+{
+ app_t *app = data;
+ const unsigned ns = 1000000000 / update_rate;
+ struct timespec to;
+ cross_clock_gettime(&app->clk_mono, &to);
+
+ while(!atomic_load_explicit(&done, memory_order_relaxed))
+ {
+ to.tv_nsec += ns;
+ while(to.tv_nsec >= 1000000000)
+ {
+ to.tv_nsec -= 1000000000;
+ to.tv_sec += 1;
+ }
+
+ cross_clock_nanosleep(&app->clk_mono, true, &to);
+
+ if(sandbox_slave_recv(sb))
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ if(app->idle_iface)
+ {
+ if(app->idle_iface->idle(app->handle))
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ }
+ }
+
+ if(app->show_iface)
+ app->show_iface->hide(app->handle);
+}
+
+static inline void
+_deinit(void *data)
+{
+ app_t *app = data;
+
+ cross_clock_deinit(&app->clk_mono);
+}
+
+static const sandbox_slave_driver_t driver = {
+ .init_cb = _init,
+ .run_cb = _run,
+ .deinit_cb = _deinit,
+ .resize_cb = NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+ int res;
+
+ app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
+ if(app.sb)
+ {
+ sandbox_slave_run(app.sb);
+ sandbox_slave_free(app.sb);
+ return res;
+ }
+
+ return res;
+}
diff --git a/bin/synthpod_sandbox_x11.c b/bin/synthpod_sandbox_x11.c
new file mode 100644
index 00000000..ec0ca57f
--- /dev/null
+++ b/bin/synthpod_sandbox_x11.c
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define CROSS_CLOCK_IMPLEMENTATION
+#include <cross_clock/cross_clock.h>
+
+#include <sandbox_slave.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+#include <signal.h>
+
+typedef struct _app_t app_t;
+
+struct _app_t {
+ sandbox_slave_t *sb;
+ LV2UI_Handle *handle;
+ const LV2UI_Idle_Interface *idle_iface;
+ const LV2UI_Resize *resize_iface;
+
+ xcb_connection_t *conn;
+ xcb_screen_t *screen;
+ xcb_drawable_t win;
+ xcb_drawable_t widget;
+ xcb_intern_atom_cookie_t cookie;
+ xcb_intern_atom_reply_t* reply;
+ xcb_intern_atom_cookie_t cookie2;
+ xcb_intern_atom_reply_t* reply2;
+ int w;
+ int h;
+ cross_clock_t clk_real;
+};
+
+static atomic_bool done = ATOMIC_VAR_INIT(false);
+
+static inline void
+_sig(int signum)
+{
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+}
+
+static inline int
+_resize(void *data, int w, int h)
+{
+ app_t *app= data;
+
+ app->w = w;
+ app->h = h;
+
+ const uint32_t values [2] = {app->w, app->h};
+ xcb_configure_window(app->conn, app->win,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
+ xcb_flush(app->conn);
+
+ return 0;
+}
+
+static inline int
+_init(sandbox_slave_t *sb, void *data)
+{
+ app_t *app= data;
+
+ signal(SIGINT, _sig);
+
+ app->conn = xcb_connect(NULL, NULL);
+ app->screen = xcb_setup_roots_iterator(xcb_get_setup(app->conn)).data;
+ app->win = xcb_generate_id(app->conn);
+ const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
+ const uint32_t values [2] = {
+ app->screen->white_pixel,
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY
+ };
+
+ app->w = 640;
+ app->h = 360;
+ xcb_create_window(app->conn, XCB_COPY_FROM_PARENT, app->win, app->screen->root,
+ 0, 0, app->w, app->h, 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, app->screen->root_visual, mask, values);
+
+ const char *title = sandbox_slave_title_get(sb);
+ if(title)
+ xcb_change_property(app->conn, XCB_PROP_MODE_REPLACE, app->win,
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
+ strlen(title), title);
+
+ app->cookie = xcb_intern_atom(app->conn, 1, 12, "WM_PROTOCOLS");
+ app->reply = xcb_intern_atom_reply(app->conn, app->cookie, 0);
+
+ app->cookie2 = xcb_intern_atom(app->conn, 0, 16, "WM_DELETE_WINDOW");
+ app->reply2 = xcb_intern_atom_reply(app->conn, app->cookie2, 0);
+
+ xcb_change_property(app->conn,
+ XCB_PROP_MODE_REPLACE, app->win, (*app->reply).atom, 4, 32, 1, &(*app->reply2).atom);
+
+ xcb_map_window(app->conn, app->win);
+ xcb_flush(app->conn);
+
+ const LV2_Feature parent_feature = {
+ .URI = LV2_UI__parent,
+ .data = (void *)(uintptr_t)app->win
+ };
+
+ if(!(app->handle = sandbox_slave_instantiate(sb, &parent_feature, (uintptr_t *)&app->widget)))
+ return -1;
+ if(!app->widget)
+ return -1;
+
+ // clone size hints from widget to parent window
+ xcb_get_property_cookie_t reply = xcb_icccm_get_wm_size_hints(app->conn,
+ app->widget, XCB_ATOM_WM_NORMAL_HINTS);
+ xcb_size_hints_t size_hints;
+ xcb_icccm_get_wm_size_hints_reply(app->conn, reply, &size_hints, NULL);
+ xcb_icccm_set_wm_size_hints(app->conn, app->win, XCB_ATOM_WM_NORMAL_HINTS, &size_hints);
+ xcb_flush(app->conn);
+
+ app->idle_iface = sandbox_slave_extension_data(sb, LV2_UI__idleInterface);
+ app->resize_iface = sandbox_slave_extension_data(sb, LV2_UI__resize);
+
+ // work-around for broken lsp-plugins
+ if((uintptr_t)app->resize_iface == (uintptr_t)app->idle_iface)
+ {
+ app->resize_iface = NULL;
+ }
+
+ cross_clock_init(&app->clk_real, CROSS_CLOCK_REALTIME);
+
+ return 0;
+}
+
+static inline void
+_run(sandbox_slave_t *sb, float update_rate, void *data)
+{
+ app_t *app = data;
+ const unsigned ns = 1000000000 / update_rate;
+ struct timespec to;
+ cross_clock_gettime(&app->clk_real, &to);
+
+ while(!atomic_load_explicit(&done, memory_order_relaxed))
+ {
+ xcb_generic_event_t *e;
+ while((e = xcb_poll_for_event(app->conn)))
+ {
+ switch(e->response_type & ~0x80)
+ {
+ case XCB_CONFIGURE_NOTIFY:
+ {
+ const xcb_configure_notify_event_t *ev = (const xcb_configure_notify_event_t *)e;
+ if( (app->w != ev->width) || (app->h != ev->height) )
+ {
+ app->w = ev->width;
+ app->h = ev->height;
+
+ const uint32_t values [2] = {app->w, app->h};
+ xcb_configure_window(app->conn, app->widget,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ &values);
+ xcb_flush(app->conn);
+
+ if(app->resize_iface)
+ {
+ app->resize_iface->ui_resize(app->handle, app->w, app->h);
+ }
+ }
+ break;
+ }
+ case XCB_CLIENT_MESSAGE:
+ {
+ const xcb_client_message_event_t *ev = (const xcb_client_message_event_t *)e;
+ if(ev->data.data32[0] == (*app->reply2).atom)
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ break;
+ }
+ }
+ free(e);
+ }
+
+ if(sandbox_slave_timedwait(sb, &to)) // timedout
+ {
+ struct timespec tf;
+
+ cross_clock_gettime(&app->clk_real, &tf);
+ const uint64_t dd = (tf.tv_sec - to.tv_sec) * 1000000000
+ + (tf.tv_nsec - to.tv_nsec);
+
+#if 0
+ printf(":: %i, %lums\n", getpid(), dd/1000000);
+#endif
+
+ if(dd <= ns)
+ {
+ if(app->idle_iface)
+ {
+ if(app->idle_iface->idle(app->handle))
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ }
+ }
+
+ to.tv_nsec += ns;
+ while(to.tv_nsec >= 1000000000)
+ {
+ to.tv_nsec -= 1000000000;
+ to.tv_sec += 1;
+ }
+ }
+ else
+ {
+ if(sandbox_slave_recv(sb))
+ atomic_store_explicit(&done, true, memory_order_relaxed);
+ }
+ }
+}
+
+static inline void
+_deinit(void *data)
+{
+ app_t *app = data;
+
+ xcb_destroy_subwindows(app->conn, app->win);
+ xcb_destroy_window(app->conn, app->win);
+ xcb_disconnect(app->conn);
+
+ cross_clock_deinit(&app->clk_real);
+}
+
+static const sandbox_slave_driver_t driver = {
+ .init_cb = _init,
+ .run_cb = _run,
+ .deinit_cb = _deinit,
+ .resize_cb = _resize
+};
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+ int res;
+
+ app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
+ if(app.sb)
+ {
+ sandbox_slave_run(app.sb);
+ sandbox_slave_free(app.sb);
+ return res;
+ }
+
+ return res;
+}
diff --git a/bin/synthpod_ui b/bin/synthpod_ui
new file mode 100644
index 00000000..f4b509b4
--- /dev/null
+++ b/bin/synthpod_ui
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+socket=$1
+
+# guess pid of running daemon
+if [ -z "${socket}" ]; then
+ pid=$(ps -A | grep synthpod | grep -v sand | head -n 1 | awk '{print $1}')
+
+ if [ -z "${pid}" ]; then
+ exit 1
+ fi
+
+ socket="shm:///synthpod-${pid}"
+fi
+
+uri='http://open-music-kontrollers.ch/lv2/synthpod#stereo'
+ui='http://open-music-kontrollers.ch/lv2/synthpod#root_4_nk'
+bundle=$(lv2info ${uri} | grep Bundle | head -n1 | sed -e 's/.*file:\/\///g')
+
+exec ${GDB} synthpod_sandbox_x11 \
+ -p ${uri} \
+ -P ${bundle} \
+ -u ${ui} \
+ -U ${bundle} \
+ -s ${socket} \
+ -w "Synthpod - ${socket}" \
+ -m $((0x1000000)) \
+ -r 48000 \
+ -f 30 \
+ $*
diff --git a/bundle/manifest.ttl.in b/bundle/manifest.ttl.in
new file mode 100644
index 00000000..229a8468
--- /dev/null
+++ b/bundle/manifest.ttl.in
@@ -0,0 +1,94 @@
+# 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#> .
+
+@prefix synthpod: <http://open-music-kontrollers.ch/lv2/synthpod#> .
+
+# Synthpod System Source
+synthpod:source
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+# Synthpod System Sink
+synthpod:sink
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+
+# Synthpod OSC Source
+synthpod:osc_source
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+# Synthpod OSC Sink
+synthpod:osc_sink
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+
+# Synthpod MIDI Source
+synthpod:midi_source
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+# Synthpod MIDI Sink
+synthpod:midi_sink
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+
+# Synthpod Audio Source
+synthpod:audio_source
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+# Synthpod Audio Sink
+synthpod:audio_sink
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+
+# Synthpod CV Source
+synthpod:cv_source
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
+# Synthpod CV Sink
+synthpod:cv_sink
+ a lv2:Plugin ;
+ lv2:minorVersion @MINOR_VERSION@ ;
+ lv2:microVersion @MICRO_VERSION@ ;
+ lv2:binary <synthpod_bundle@MODULE_SUFFIX@> ;
+ rdfs:seeAlso <synthpod_bundle.ttl> .
diff --git a/bundle/meson.build b/bundle/meson.build
new file mode 100644
index 00000000..d1da20e4
--- /dev/null
+++ b/bundle/meson.build
@@ -0,0 +1,20 @@
+bndl_srcs = ['synthpod_bundle.c']
+
+bndl = shared_module('synthpod_bundle', bndl_srcs,
+ c_args : c_args,
+ name_prefix : '',
+ dependencies : [m_dep, lv2_dep],
+ install : true,
+ install_dir : bndl_dir)
+
+configure_file(input : 'manifest.ttl.in', output : 'manifest.ttl',
+ configuration : conf_data,
+ install : true,
+ install_dir : bndl_dir)
+
+custom_target('synthpod_bundle_ttl',
+ input : 'synthpod_bundle.ttl',
+ output : 'synthpod_bundle.ttl',
+ command : clone,
+ install : true,
+ install_dir : bndl_dir)
diff --git a/bundle/synthpod_bundle.c b/bundle/synthpod_bundle.c
new file mode 100644
index 00000000..df41b763
--- /dev/null
+++ b/bundle/synthpod_bundle.c
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+
+#include <synthpod_bundle.h>
+
+typedef struct _plughandle_t plughandle_t;
+
+struct _plughandle_t {
+ int dummy;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ // nothing
+}
+
+__realtime static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ // nothing
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static const LV2_Descriptor synthpod_audio_sink = {
+ .URI = SYNTHPOD_AUDIO_SINK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_audio_source = {
+ .URI = SYNTHPOD_AUDIO_SOURCE_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_cv_sink = {
+ .URI = SYNTHPOD_CV_SINK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_cv_source = {
+ .URI = SYNTHPOD_CV_SOURCE_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_midi_sink = {
+ .URI = SYNTHPOD_MIDI_SINK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_midi_source = {
+ .URI = SYNTHPOD_MIDI_SOURCE_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_osc_sink = {
+ .URI = SYNTHPOD_OSC_SINK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_osc_source = {
+ .URI = SYNTHPOD_OSC_SOURCE_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_sink = {
+ .URI = SYNTHPOD_SINK_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+static const LV2_Descriptor synthpod_source = {
+ .URI = SYNTHPOD_SOURCE_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = NULL
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &synthpod_source;
+ case 1:
+ return &synthpod_sink;
+
+ case 2:
+ return &synthpod_osc_source;
+ case 3:
+ return &synthpod_osc_sink;
+
+ case 4:
+ return &synthpod_cv_source;
+ case 5:
+ return &synthpod_cv_sink;
+
+ case 6:
+ return &synthpod_audio_source;
+ case 7:
+ return &synthpod_audio_sink;
+
+ case 8:
+ return &synthpod_midi_source;
+ case 9:
+ return &synthpod_midi_sink;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/bundle/synthpod_bundle.h b/bundle/synthpod_bundle.h
new file mode 100644
index 00000000..dc6be380
--- /dev/null
+++ b/bundle/synthpod_bundle.h
@@ -0,0 +1,52 @@
+/*
+ * 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 _SYNTHPOD_LV2_H
+#define _SYNTHPOD_LV2_H
+
+#ifdef _WIN32
+# define mlock(...)
+# define munlock(...)
+#else
+# include <sys/mman.h> // mlock
+#endif
+
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#define __realtime __attribute__((annotate("realtime")))
+#define __non_realtime __attribute__((annotate("non-realtime")))
+
+// bundle uri
+#define SYNTHPOD_URI "http://open-music-kontrollers.ch/lv2/synthpod"
+
+// plugin uris
+#define SYNTHPOD_SOURCE_URI SYNTHPOD_URI"#source"
+#define SYNTHPOD_SINK_URI SYNTHPOD_URI"#sink"
+
+#define SYNTHPOD_OSC_SOURCE_URI SYNTHPOD_URI"#osc_source"
+#define SYNTHPOD_OSC_SINK_URI SYNTHPOD_URI"#osc_sink"
+
+#define SYNTHPOD_CV_SOURCE_URI SYNTHPOD_URI"#cv_source"
+#define SYNTHPOD_CV_SINK_URI SYNTHPOD_URI"#cv_sink"
+
+#define SYNTHPOD_AUDIO_SOURCE_URI SYNTHPOD_URI"#audio_source"
+#define SYNTHPOD_AUDIO_SINK_URI SYNTHPOD_URI"#audio_sink"
+
+#define SYNTHPOD_MIDI_SOURCE_URI SYNTHPOD_URI"#midi_source"
+#define SYNTHPOD_MIDI_SINK_URI SYNTHPOD_URI"#midi_sink"
+
+#endif // _SYNTHPOD_LV2_H
diff --git a/bundle/synthpod_bundle.ttl b/bundle/synthpod_bundle.ttl
new file mode 100644
index 00000000..3946bbac
--- /dev/null
+++ b/bundle/synthpod_bundle.ttl
@@ -0,0 +1,593 @@
+# 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 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 lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix bufsz: <http://lv2plug.in/ns/ext/buf-size#> .
+@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix synthpod: <http://open-music-kontrollers.ch/lv2/synthpod#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+# system-port definitions
+synthpod:systemPorts
+ a lv2:Feature .
+
+synthpod:ControlPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+synthpod:AudioPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+synthpod:CVPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+synthpod:MIDIPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+synthpod:OSCPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+synthpod:ComPort
+ a rdfs:Class ;
+ rdfs:subClassOf lv2:Port .
+
+# 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:synthpod
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Synthpod bundle" ,
+ "Synthpod Bündel"@de ,
+ "Synthpod fasko"@eo , # esperanto
+ "Synthpod Böndu"@gsw , # allemannic
+ "  "@tlh . # klingon
+
+# Audio groups
+synthpod:group_sink
+ a pg:InputGroup ,
+ pg:StereoGroup ;
+ lv2:name "Sink" ,
+ "Ziel"@de ,
+ "Sinki"@eo ,
+ "Ziiu"@gsw ;
+ lv2:symbol "sink" .
+
+synthpod:group_source
+ a pg:OutputGroup ,
+ pg:StereoGroup ;
+ lv2:name "Source" ,
+ "Quelle"@de ,
+ "Fonto"@eo ,
+ "Quöue"@gsw ;
+ lv2:symbol "source" .
+
+# Sink Plugin
+synthpod:sink
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod System Sink" ,
+ "Synthpod System Ziel"@de ,
+ "Synthpod Syschtem Ziiu"@gsw ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, synthpod:systemPorts ;
+
+ lv2:port [
+ # event in
+ a lv2:InputPort ,
+ synthpod:MIDIPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ atom:supports osc:Event ;
+ atom:supports atom:Object ;
+ atom:supports time:Position ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_sink" ;
+ lv2:name "Event" ,
+ "Ereignissequenz"@de ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ # audio in 1
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 1 ;
+ lv2:symbol "audio_sink_1" ;
+ lv2:name "Audio 1" ,
+ "Audio 1"@de ;
+ lv2:designation pg:left ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ # audio in 2
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 2 ;
+ lv2:symbol "audio_sink_2" ;
+ lv2:name "Audio 2" ,
+ "Audio 2"@de ;
+ lv2:designation pg:right ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ # control in
+ a lv2:InputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 3 ;
+ lv2:symbol "control_sink_1" ;
+ lv2:name "Control 1" ,
+ "Kontroller 1"@de ;
+ lv2:default 0.0 ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 4 ;
+ lv2:symbol "control_sink_2" ;
+ lv2:name "Control 2" ,
+ "Kontroller 2"@de ;
+ lv2:default 0.0 ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 5 ;
+ lv2:symbol "control_sink_3" ;
+ lv2:name "Control 3" ,
+ "Kontroller 3"@de ;
+ lv2:default 0.0 ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 6 ;
+ lv2:symbol "control_sink_4" ;
+ lv2:name "Control 4" ,
+ "Kontroller 4"@de ;
+ lv2:default 0.0 ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ # com in
+ a lv2:InputPort ,
+ synthpod:ComPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ lv2:index 7 ;
+ lv2:symbol "com_sink" ;
+ lv2:name "Communication" ,
+ "Kommunikation"@de ;
+ pg:group synthpod:group_sink ;
+ ] .
+
+# Source Plugin
+synthpod:source
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod System Source" ,
+ "Synthpod System Quelle"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable, synthpod:systemPorts ;
+
+ lv2:port [
+ # event in
+ a lv2:OutputPort ,
+ synthpod:MIDIPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ atom:supports osc:Event ;
+ atom:supports atom:Object ;
+ atom:supports time:Position ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_source" ;
+ lv2:name "Event" ,
+ "Ereignissequenz"@de ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_source ;
+ ] , [
+ # audio in 1
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 1 ;
+ lv2:symbol "audio_source_1" ;
+ lv2:name "Audio 1" ,
+ "Audio 1"@de ;
+ lv2:designation pg:left ;
+ pg:group synthpod:group_source ;
+ ] , [
+ # audio in 2
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 2 ;
+ lv2:symbol "audio_source_2" ;
+ lv2:name "Audio 2" ,
+ "Audio 2"@de ;
+ lv2:designation pg:right ;
+ pg:group synthpod:group_source ;
+ ] , [
+ # control in
+ a lv2:OutputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 3 ;
+ lv2:symbol "control_source_1" ;
+ lv2:name "Control 1" ,
+ "Kontroller 1"@de ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 4 ;
+ lv2:symbol "control_source_2" ;
+ lv2:name "Control 2" ,
+ "Kontroller 2"@de ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 5 ;
+ lv2:symbol "control_source_3" ;
+ lv2:name "Control 3" ,
+ "Kontroller 3"@de ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:ControlPort ,
+ lv2:ControlPort ;
+ lv2:index 6 ;
+ lv2:symbol "control_source_4" ;
+ lv2:name "Control 4" ,
+ "Kontroller 4"@de ;
+ pg:group synthpod:group_source ;
+ ] , [
+ # com out
+ a lv2:OutputPort ,
+ synthpod:ComPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Object ;
+ lv2:index 7 ;
+ lv2:symbol "com_source" ;
+ lv2:name "Communication" ,
+ "Kommunikation"@de ;
+ pg:group synthpod:group_source ;
+ ] .
+
+# MIDI Sink Plugin
+synthpod:midi_sink
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod MIDI Sink" ,
+ "Synthpod MIDI Ziel"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ synthpod:MIDIPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 0 ;
+ lv2:symbol "midi_sink" ;
+ lv2:name "MIDI Sink" ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_sink ;
+ ] .
+
+# MIDI Source Plugin
+synthpod:midi_source
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod MIDI Source" ,
+ "Synthpod MIDI Quelle"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:OutputPort ,
+ synthpod:MIDIPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ;
+ lv2:index 0 ;
+ lv2:symbol "midi_source" ;
+ lv2:name "MIDI Source" ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_source ;
+ ] .
+
+# OSC Sink Plugin
+synthpod:osc_sink
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod OSC Sink" ,
+ "Synthpod OSC Ziel"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ synthpod:OSCPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ;
+ atom:supports atom:Object ;
+ lv2:index 0 ;
+ lv2:symbol "osc_sink" ;
+ lv2:name "OSC Sink" ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_sink ;
+ ] .
+
+# OSC Source Plugin
+synthpod:osc_source
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod OSC Source" ,
+ "Synthpod OSC Quelle"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:OutputPort ,
+ synthpod:OSCPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ;
+ atom:supports atom:Object ;
+ lv2:index 0 ;
+ lv2:symbol "osc_source" ;
+ lv2:name "OSC Source" ;
+ lv2:designation lv2:control ;
+ pg:group synthpod:group_source ;
+ ] .
+
+# CV Sink Plugin
+synthpod:cv_sink
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod CV Sink" ,
+ "Synthpod CV Ziel"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 0 ;
+ lv2:symbol "cv_sink_1" ;
+ lv2:name "CV Sink 1" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 1 ;
+ lv2:symbol "cv_sink_2" ;
+ lv2:name "CV Sink 2" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 2 ;
+ lv2:symbol "cv_sink_3" ;
+ lv2:name "CV Sink 3" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 3 ;
+ lv2:symbol "cv_sink_4" ;
+ lv2:name "CV Sink 4" ;
+ pg:group synthpod:group_sink ;
+ ] .
+
+# CV Source Plugin
+synthpod:cv_source
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod CV Source" ,
+ "Synthpod CV Quelle"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:OutputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 0 ;
+ lv2:symbol "cv_source_1" ;
+ lv2:name "CV Source 1" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 1 ;
+ lv2:symbol "cv_source_2" ;
+ lv2:name "CV Source 2" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 2 ;
+ lv2:symbol "cv_source_3" ;
+ lv2:name "CV Source 3" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:CVPort ,
+ lv2:CVPort ;
+ lv2:index 3 ;
+ lv2:symbol "cv_source_4" ;
+ lv2:name "CV Source 4" ;
+ pg:group synthpod:group_source ;
+ ] .
+
+# Audio Sink Plugin
+synthpod:audio_sink
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod Audio Sink" ,
+ "Synthpod Audio Ziel"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 0 ;
+ lv2:symbol "audio_sink_1" ;
+ lv2:name "Audio Sink 1" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 1 ;
+ lv2:symbol "audio_sink_2" ;
+ lv2:name "Audio Sink 2" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 2 ;
+ lv2:symbol "audio_sink_3" ;
+ lv2:name "Audio Sink 3" ;
+ pg:group synthpod:group_sink ;
+ ] , [
+ a lv2:InputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 3 ;
+ lv2:symbol "audio_sink_4" ;
+ lv2:name "Audio Sink 4" ;
+ pg:group synthpod:group_sink ;
+ ] .
+
+# AudioSource Plugin
+synthpod:audio_source
+ a lv2:Plugin ,
+ lv2:InstrumentPlugin ;
+ doap:name "Synthpod Audio Source" ,
+ "Synthpod Audio Quelle"@de ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:synthpod ;
+ lv2:requiredFeature bufsz:boundedBlockLength, synthpod:systemPorts ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 0 ;
+ lv2:symbol "audio_source_1" ;
+ lv2:name "Audio Source 1" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 1 ;
+ lv2:symbol "audio_source_2" ;
+ lv2:name "Audio Source 2" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 2 ;
+ lv2:symbol "audio_source_3" ;
+ lv2:name "Audio Source 3" ;
+ pg:group synthpod:group_source ;
+ ] , [
+ a lv2:OutputPort ,
+ synthpod:AudioPort ,
+ lv2:AudioPort ;
+ lv2:index 3 ;
+ lv2:symbol "audio_source_4" ;
+ lv2:name "Audio Source 4" ;
+ pg:group synthpod:group_source ;
+ ] .
diff --git a/canvas.lv2/COPYING b/canvas.lv2/COPYING
new file mode 100644
index 00000000..ddb9a463
--- /dev/null
+++ b/canvas.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source