├── .clang-format ├── .clang-tidy ├── .clant.json ├── .gitignore ├── .includes.imp ├── .reuse └── dep5 ├── .suppress.cppcheck ├── AUTHORS ├── COPYING ├── INSTALL.md ├── LICENSES ├── 0BSD.txt └── ISC.txt ├── NEWS ├── README.md ├── doc ├── jalv.1 ├── jalv.gtk3.1 ├── jalv.qt5.1 ├── jalv.qt6.1 ├── mandoc.css └── meson.build ├── jalv.desktop.in ├── jalv.ttl ├── meson.build ├── meson_options.txt ├── src ├── attributes.h ├── backend.h ├── comm.c ├── comm.h ├── control.c ├── control.h ├── dumper.c ├── dumper.h ├── features.c ├── features.h ├── frontend.h ├── jack.c ├── jack_impl.h ├── jack_internal.c ├── jalv.c ├── jalv.h ├── jalv_config.h ├── jalv_console.c ├── jalv_gtk.c ├── jalv_qt.cpp ├── jalv_qt.hpp ├── log.c ├── log.h ├── lv2_evbuf.c ├── lv2_evbuf.h ├── macros.h ├── main.c ├── mapper.c ├── mapper.h ├── nodes.c ├── nodes.h ├── options.h ├── port.h ├── portaudio.c ├── process.c ├── process.h ├── process_setup.c ├── process_setup.h ├── query.c ├── query.h ├── settings.h ├── state.c ├── state.h ├── string_utils.c ├── string_utils.h ├── symap.c ├── symap.h ├── types.h ├── urids.c ├── urids.h ├── worker.c └── worker.h ├── subprojects ├── lilv.wrap ├── lv2.wrap ├── serd.wrap ├── sord.wrap ├── sratom.wrap ├── suil.wrap └── zix.wrap └── test └── meson.build /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignConsecutiveAssignments: true 3 | AlignConsecutiveDeclarations: true 4 | AlignEscapedNewlines: Left 5 | AttributeMacros: 6 | - ZIX_MALLOC_FUNC 7 | - ZIX_PURE_FUNC 8 | BasedOnStyle: Mozilla 9 | BraceWrapping: 10 | AfterNamespace: false 11 | AfterClass: true 12 | AfterEnum: false 13 | AfterExternBlock: false 14 | AfterFunction: true 15 | AfterStruct: false 16 | SplitEmptyFunction: false 17 | SplitEmptyRecord: false 18 | BreakBeforeBraces: Custom 19 | Cpp11BracedListStyle: true 20 | FixNamespaceComments: true 21 | ForEachMacros: 22 | - LILV_FOREACH 23 | - LV2_ATOM_OBJECT_BODY_FOREACH 24 | - LV2_ATOM_OBJECT_FOREACH 25 | - LV2_ATOM_SEQUENCE_BODY_FOREACH 26 | - LV2_ATOM_SEQUENCE_FOREACH 27 | - LV2_ATOM_TUPLE_BODY_FOREACH 28 | - LV2_ATOM_TUPLE_FOREACH 29 | IndentCaseLabels: false 30 | IndentPPDirectives: AfterHash 31 | KeepEmptyLinesAtTheStartOfBlocks: false 32 | SpacesInContainerLiterals: false 33 | StatementMacros: 34 | - Q_OBJECT 35 | ... 36 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | *, 3 | -*-c-arrays, 4 | -*-macro-to-enum, 5 | -*-magic-numbers, 6 | -*-named-parameter, 7 | -*-narrowing-conversions, 8 | -altera-*, 9 | -boost-*, 10 | -bugprone-assignment-in-if-condition, 11 | -bugprone-casting-through-void, 12 | -bugprone-easily-swappable-parameters, 13 | -bugprone-multi-level-implicit-pointer-conversion, 14 | -cert-err33-c, 15 | -cert-err34-c, 16 | -clang-analyzer-optin.core.EnumCastOutOfRange, 17 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, 18 | -clang-analyzer-valist.Uninitialized, 19 | -concurrency-mt-unsafe, 20 | -cppcoreguidelines-avoid-non-const-global-variables, 21 | -cppcoreguidelines-macro-usage, 22 | -cppcoreguidelines-owning-memory, 23 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 24 | -cppcoreguidelines-pro-type-reinterpret-cast, 25 | -fuchsia-default-arguments-calls, 26 | -google-readability-todo, 27 | -hicpp-signed-bitwise, 28 | -llvm-header-guard, 29 | -llvmlibc-*, 30 | -misc-include-cleaner, 31 | -misc-no-recursion, 32 | -modernize-use-nodiscard, 33 | -modernize-use-trailing-return-type, 34 | -modernize-use-using, 35 | -performance-enum-size, 36 | -readability-function-cognitive-complexity, 37 | -readability-identifier-length, 38 | -readability-implicit-bool-conversion, 39 | -readability-redundant-casting, 40 | -readability-static-accessed-through-instance, 41 | CheckOptions: 42 | - key: hicpp-uppercase-literal-suffix.NewSuffixes 43 | value: 'L;U;UL;ULL' 44 | - key: readability-uppercase-literal-suffix.NewSuffixes 45 | value: 'L;U;UL;ULL' 46 | WarningsAsErrors: '*' 47 | HeaderFilterRegex: '.*' 48 | FormatStyle: file 49 | -------------------------------------------------------------------------------- /.clant.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "mapping_files": [".includes.imp", "qt5_11.imp"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | /build/ 5 | /subprojects/lilv-0.24.26/ 6 | /subprojects/lv2/ 7 | /subprojects/packagecache/ 8 | /subprojects/serd/ 9 | /subprojects/sord-0.16.18/ 10 | /subprojects/sphinxygen-1.0.10/ 11 | /subprojects/sratom-0.6.18/ 12 | /subprojects/suil-0.10.22/ 13 | /subprojects/zix-0.6.2/ 14 | -------------------------------------------------------------------------------- /.includes.imp: -------------------------------------------------------------------------------- 1 | [ 2 | { "symbol": [ "QT_VERSION", "private", "", "public" ] }, 3 | { "symbol": [ "QT_VERSION_CHECK", "private", "", "public" ] }, 4 | { "symbol": [ "Q_OBJECT", "private", "", "public" ] }, 5 | { "symbol": [ "Q_SLOT", "private", "", "public" ] }, 6 | { "symbol": [ "qMax", "private", "", "public" ] } 7 | ] 8 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: jalv 3 | Upstream-Contact: David Robillard 4 | Source: https://gitlab.com/drobilla/jalv 5 | 6 | Files: .clang* .clant.json .gitignore .includes.imp .suppress.cppcheck AUTHORS NEWS jalv.desktop.in jalv.ttl 7 | Copyright: 2010-2022 David Robillard 8 | License: 0BSD OR ISC 9 | 10 | Files: INSTALL.md README.md doc/*.1 11 | Copyright: 2011-2020 David Robillard 12 | License: ISC 13 | -------------------------------------------------------------------------------- /.suppress.cppcheck: -------------------------------------------------------------------------------- 1 | cstyleCast 2 | normalCheckLevelMaxBranches 3 | unusedStructMember 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Author: 2 | David Robillard 3 | 4 | Original GTK generic UI: 5 | Nick Lanham 6 | 7 | JACK latency support and other improvements: 8 | Robin Gareus 9 | 10 | Preset menu support for Qt: 11 | Timo Westkämper 12 | 13 | Original Qt generic UI: 14 | Amadeus Folego 15 | 16 | GTK plugin selector UI: 17 | Alexandros Theodotou 18 | 19 | Original man pages: 20 | Jaromír Mikes 21 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2011-2022 David Robillard 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ========================= 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | To build from source, you will need: 8 | 9 | * A relatively modern C compiler (GCC, Clang, and MSVC are known to work). 10 | 11 | * [Meson](http://mesonbuild.com/), which depends on 12 | [Python](http://python.org/). 13 | 14 | This is a brief overview of building this project with meson. See the meson 15 | documentation for more detailed information. 16 | 17 | Configuration 18 | ------------- 19 | 20 | The build is configured with the `setup` command, which creates a new build 21 | directory with the given name: 22 | 23 | meson setup build 24 | 25 | Some environment variables are read during `setup` and stored with the 26 | configuration: 27 | 28 | * `CC`: Path to C compiler. 29 | * `CFLAGS`: C compiler options. 30 | * `CXX`: Path to C++ compiler. 31 | * `CXXFLAGS`: C++ compiler options. 32 | * `LDFLAGS`: Linker options. 33 | 34 | However, it is better to use meson options for configuration. All options can 35 | be inspected with the `configure` command from within the build directory: 36 | 37 | cd build 38 | meson configure 39 | 40 | Options can be set by passing C-style "define" options to `configure`: 41 | 42 | meson configure -Dc_args="-march=native" -Dprefix="/opt/mypackage/" 43 | 44 | Note that some options, such as `strict` and `werror` are for 45 | developer/maintainer use only. Please don't file issues about anything that 46 | happens when they are enabled. 47 | 48 | Building 49 | -------- 50 | 51 | From within a configured build directory, everything can be built with the 52 | `compile` command: 53 | 54 | meson compile 55 | 56 | Similarly, tests can be run with the `test` command: 57 | 58 | meson test 59 | 60 | Meson can also generate a project for several popular IDEs, see the `backend` 61 | option for details. 62 | 63 | Installation 64 | ------------ 65 | 66 | A compiled project can be installed with the `install` command: 67 | 68 | meson install 69 | 70 | You may need to acquire root permissions to install to a system-wide prefix. 71 | For packaging, the installation may be staged to a directory using the 72 | `DESTDIR` environment variable or the `--destdir` option: 73 | 74 | DESTDIR=/tmp/mypackage/ meson install 75 | 76 | meson install --destdir=/tmp/mypackage/ 77 | -------------------------------------------------------------------------------- /LICENSES/0BSD.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011-2022 David Robillard 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 8 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /LICENSES/ISC.txt: -------------------------------------------------------------------------------- 1 | ../COPYING -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | jalv (1.6.9) unstable; urgency=medium 2 | 3 | * Add Qt6 version 4 | * Add missing short versions of command line options 5 | * Add option to install tool man pages 6 | * Add support for control outputs with lv2:latency designation 7 | * Build Qt UI with -fPIC 8 | * Clean up and strengthen code 9 | * Clean up command line help output 10 | * Cleanly separate audio thread from the rest of the application 11 | * Fix Jack latency recomputation when plugin latency changes 12 | * Fix clashing command line options 13 | * Fix minor memory leaks 14 | * Make help and version commands exit successfully 15 | * Only send control messages to designated lv2:control ports 16 | * Reduce Jack process callback overhead 17 | * Remove Gtk2 interface 18 | * Remove limits on the size of messages sent from plugin to UI 19 | * Remove transport position dumping from Jack process callback 20 | * Replace use of deprecated Gtk interfaces 21 | * Rewrite man pages in mdoc 22 | * Switch to external zix dependency 23 | * Use Gtk switches instead of checkboxes for toggle controls 24 | * Use fewer platform-specific APIs 25 | * Use portable zix filesystem API 26 | 27 | -- David Robillard Fri, 20 Dec 2024 00:45:28 +0000 28 | 29 | jalv (1.6.8) stable; urgency=medium 30 | 31 | * Add Gtk plugin selector UI and desktop file 32 | * Add missing option in console help output 33 | * Add version option to console executable 34 | * Build Qt interface as C++14 35 | * Change no-menu short option to m to avoid clash with jack-name 36 | * Clean up and modernize code 37 | * Fix "preset" console command when "presets" hasn't been called 38 | * Fix MSVC build 39 | * Fix atom buffer alignment 40 | * Fix crash when running jalv without arguments 41 | * Fix man page headers 42 | * Fix memory leaks 43 | * Fix outdated man pages 44 | * Fix spurious transport messages 45 | * Fix thread-safety of plugin/UI communication rings 46 | * Flush stdout after printing control values in console interface 47 | * Print status information consistently to stdout 48 | * Propagate worker errors to the scheduler when possible 49 | * Remove Gtkmm interface 50 | * Remove Qt4 support 51 | * Support both rdfs:label and lv2:name for port group labels 52 | * Switch to meson build system 53 | 54 | -- David Robillard Sat, 10 Sep 2022 00:43:05 +0000 55 | 56 | jalv (1.6.6) stable; urgency=medium 57 | 58 | * Add a command line argument to select a specific UI 59 | * Explicitly support lv2:inPlaceBroken 60 | * Ignore ports with nonsense lv2:control designations 61 | * Remove Jack session support 62 | * Support port events for ui:showInterface UIs 63 | 64 | -- David Robillard Thu, 07 Jan 2021 22:05:38 +0000 65 | 66 | jalv (1.6.4) stable; urgency=medium 67 | 68 | * Support rdfs:label for port groups 69 | * Use screen refresh rate with Gtk3 and Qt5 70 | 71 | -- David Robillard Sun, 10 Nov 2019 21:56:49 +0000 72 | 73 | jalv (1.6.2) stable; urgency=medium 74 | 75 | * Add jalv -i option to ignore stdin for background use 76 | * Add several commands to console interface 77 | * Add support for running as an internal Jack client (thanks Timo Wischer) 78 | * Add support for underscore in port names on command line (thanks Jośe 79 | Fernando Moyano) 80 | * Fix Jack deactivation 81 | * Fix compilation with recent Gtkmm versions that require C++11 82 | * Fix potential crash when closed with worker (thanks JP Cimalando) 83 | * Fix potential hang after Ctrl-c in console interface (thanks Laxmi Devi) 84 | * Make Suil dependency optional 85 | * Remove support for deprecated event and uri-map extensions 86 | 87 | -- David Robillard Thu, 06 Jun 2019 20:38:01 +0000 88 | 89 | jalv (1.6.0) stable; urgency=medium 90 | 91 | * Add PortAudio backend (compile time option, audio only) 92 | * Add Qt5 version 93 | * Add command prompt to console version for changing controls 94 | * Add generic Qt control UI from Amadeus Folego 95 | * Add option to print plugin trace messages 96 | * Allow Jack client name to be set from command line (thanks Adam Avramov) 97 | * Exit GUI versions on interrupt 98 | * Exit on Jack shutdown (patch from Robin Gareus) 99 | * Fix memory error on preset save resulting in odd bundle names 100 | * Fix semaphore correctness issues 101 | * Fix unreliable UI state initialization (patch from Hanspeter Portner) 102 | * Improve preset support 103 | * Print colorful log if output is a terminal 104 | * Report Jack latency (patch from Robin Gareus) 105 | * Set Jack port order metadata 106 | * Support CV ports if Jack metadata is enabled (patch from Hanspeter 107 | Portner) 108 | * Support numeric and string plugin properties (event-based control) 109 | * Support thread-safe state restoration 110 | * Update UI when internal plugin state is changed during preset load 111 | * Use moc-qt4 if present for systems with multiple Qt versions 112 | 113 | -- David Robillard Wed, 04 Jan 2017 17:24:58 +0000 114 | 115 | jalv (1.4.6) stable; urgency=medium 116 | 117 | * Add option to print control output changes to stdout 118 | * Add support for data-access extension (based on patch by Filipe Coelho) 119 | * Generate Qt moc nonsense at build time for broader compatibility 120 | * Set port pretty names via new Jack metadata API 121 | * Show newly saved presets in the preset menu 122 | * Support new UI show/hide interface in console version 123 | * Support saving the same preset several times 124 | * Update for latest LV2 Atom Object simplifications 125 | * Update man pages and console jalv help output for new options 126 | * Upgrade to waf 1.7.16 127 | 128 | -- David Robillard Fri, 08 Aug 2014 22:30:28 +0000 129 | 130 | jalv (1.4.4) stable; urgency=medium 131 | 132 | * Add --no-menu option for jalv.gtk 133 | * Add -c option for setting controls from the command line 134 | * Don't expose non-MIDI event ports to Jack 135 | * Hide controls for ports with notOnGUI property in generic UI (based on 136 | patch from Robin Gareus) 137 | * Preset menu support for Qt (patch from Timo Westkämper) 138 | * Support ui:portMap feature to allow UIs to avoid hard-coded port indices 139 | (useful for compatibility and separately distributed UIs) 140 | 141 | -- David Robillard Sat, 04 Jan 2014 21:11:45 +0000 142 | 143 | jalv (1.4.2) stable; urgency=medium 144 | 145 | * Add command-line option to control UI update frequency 146 | * Fix crash when running "jalv" with bad command line arguments 147 | * Fix default setting for non-sequential enumeration ports (patch from Robin 148 | Gareus) 149 | * Fix parameter changes with Qt UI 150 | * Fix potential crash with UIs and debug printing 151 | * Nicer printing of atom messages with -d 152 | * Support rsz:minimumSize for atom and event ports 153 | * Upgrade to waf 1.7.11 154 | * Work around Gtk bug for labels on sliders (patch from Robin Gareus) 155 | 156 | -- David Robillard Fri, 09 Aug 2013 14:40:20 +0000 157 | 158 | jalv (1.4.0) stable; urgency=medium 159 | 160 | * Add menu bar and pass parent widget in Qt version for true UI embedding 161 | * Add spinbuttons for precisely setting control values 162 | * Group controls under headings if port group information is available 163 | * Make URI map thread-safe, fixing occasional crashes for plugins with UIs 164 | * Send time information to plugin when Jack tempo changes 165 | * Support state:loadDefaultState 166 | * Update to waf 1.7.8 and autowaf r90 167 | * Use a more efficient dense layout for controls 168 | 169 | -- David Robillard Sat, 23 Feb 2013 03:35:22 +0000 170 | 171 | jalv (1.2.0) stable; urgency=medium 172 | 173 | * Add Gtk3 UI 174 | * Fix Jack Session support 175 | * Notify plugins of Jack transport changes by sending events (an atom:Blank 176 | with properties from the LV2 time extension) 177 | * Port to MinGW 178 | * Refuse to instantiate plugins which require unsupported features 179 | * Support LV2 buf-size extension (with all features) 180 | * Support lv2:sampleRate control ports. 181 | * Tolerate loading presets with port values that aren't xsd:decimal 182 | * Tune UI update rate and ring size based on JACK rate and MIDI buffer size 183 | to handle the handle the maximum message rate the plugin can send. 184 | 185 | -- David Robillard Sun, 14 Oct 2012 22:38:53 +0000 186 | 187 | jalv (1.0.0) stable; urgency=medium 188 | 189 | * Initial release 190 | 191 | -- David Robillard Thu, 19 Apr 2012 22:42:42 +0000 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jalv 2 | ==== 3 | 4 | Jalv (JAck LV2) is a simple host for LV2 plugins. It runs a plugin, and 5 | exposes the plugin ports to the system, essentially making the plugin an 6 | application. For more information, see . 7 | 8 | Jalv can be built to run on JACK, where plugin ports are exposed directly as 9 | JACK ports, or via PortAudio, where the plugin is connected to the system 10 | inputs and outputs. 11 | 12 | Jalv is particularly useful as a simple test host for plugin development and 13 | testing. It runs plugins from the command line with no user interaction, is 14 | light enough to run with tools like sanitizers or valgrind, and is capable of 15 | dumping all communication between the plugin and its UI in a human readable 16 | format. Plugin UIs can be tested in different host toolkits by using different 17 | executables: jalv, jalv.gtk, jalv.gtk3, and jalv.qt5. 18 | 19 | -- David Robillard 20 | -------------------------------------------------------------------------------- /doc/jalv.1: -------------------------------------------------------------------------------- 1 | .\" # Copyright 2024 David Robillard 2 | .\" # SPDX-License-Identifier: ISC 3 | .Dd December 20, 2024 4 | .Dt JALV 1 5 | .Os 6 | .Sh NAME 7 | .Nm jalv 8 | .Nd run an LV2 plugin with a command-line interface 9 | .Sh SYNOPSIS 10 | .Nm jalv 11 | .Op Fl dhipstx 12 | .Op Fl b Ar size 13 | .Op Fl c Ar symbol=value 14 | .Op Fl U Ar ui_uri 15 | .Op Fl l Ar dir 16 | .Op Fl n Ar name 17 | .Ar plugin_uri 18 | .Sh DESCRIPTION 19 | .Nm 20 | is a simple LV2 host that runs one plugin. 21 | It has several versions, this one has an interactive command-line interface. 22 | .Pp 23 | .Nm 24 | has one positional argument, the URI of an installed LV2 plugin. 25 | .Pp 26 | The options are as follows: 27 | .Bl -tag -width 3n 28 | .It Fl b Ar bytes 29 | Buffer size for communication between plugin and UI. 30 | The default value should be enough, 31 | but if there are overflows, 32 | this option can be used to allocate more space. 33 | .It Fl c Ar symbol=value 34 | Set control value, for example, 35 | .Fl c Ar vol=1.4 36 | where 37 | .Dq vol 38 | is the symbol of a control port on the plugin. 39 | .It Fl d 40 | Dump communication between plugin and UI to 41 | .Dv stdout . 42 | Note that this may print an extreme amount of text, 43 | piping the output to a pager or file is recommended. 44 | .It Fl h 45 | Print the command line options and exit. 46 | .It Fl i 47 | Ignore input on 48 | .Dv stdin 49 | and run non-interactively. 50 | .It Fl l Ar dir 51 | Load state from the given directory before running the plugin. 52 | .It Fl n Ar name 53 | Use the given JACK client name. 54 | Note that JACK may adjust the name if necessary unless 55 | .Fl x 56 | is also given. 57 | .It Fl p 58 | Print control output changes to 59 | .Dv stdout . 60 | .It Fl s 61 | Show plugin UI if possible. 62 | This option only works when plugins provide a UI that uses the non-embeddable 63 | .Li showHide 64 | interface. 65 | For embeddable UIs, use 66 | .Xr jalv.gtk3 1 67 | instead. 68 | .It Fl t 69 | Print trace messages from plugin. 70 | This enables the 71 | .Dq trace 72 | log defined by LV2, which is used by some plugins to print debugging output. 73 | .It Fl U Ar uri 74 | Load the UI with the given URI. 75 | Usually only one suitable UI is available on a given platform, 76 | which is used by default. 77 | If there are several, this option can be used to select which is loaded. 78 | .It Fl V 79 | Print version information and exit. 80 | .It Fl x 81 | Use only the exact JACK client name given by 82 | .Fl n 83 | or exit if it's unavailable. 84 | .El 85 | .Sh COMMANDS 86 | The Jalv prompt supports several commands for interactive control: 87 | .Pp 88 | .Bl -tag -width 16n -compact 89 | .It Ic help 90 | Display help message. 91 | .It Ic controls 92 | Print settable control values. 93 | .It Ic monitors 94 | Print output control values. 95 | .It Ic presets 96 | Print available presets. 97 | .It Ic preset Ar uri 98 | Set preset. 99 | .It Ic set index value 100 | Set control value by port index. 101 | .It Ic set Ar symbol Ar value 102 | Set control value by symbol. 103 | .It Ar symbol Cm = Ar value 104 | Set control value by symbol. 105 | .El 106 | .Sh ENVIRONMENT 107 | .Bl -tag -width LV2_PATH 108 | .It Ev LV2_PATH 109 | Search path for LV2 bundles, in 110 | .Ev PATH 111 | format. 112 | .El 113 | .Sh SEE ALSO 114 | .Xr jalv.gtk3 1 , 115 | .Xr jalv.qt5 1 , 116 | .Xr lv2ls 1 117 | .Sh AUTHORS 118 | .Nm 119 | was written by 120 | .An David Robillard 121 | .Aq Mt d@drobilla.net , 122 | with contributions by 123 | Robin Gareus, 124 | Hanspeter Portner, 125 | and others. 126 | -------------------------------------------------------------------------------- /doc/jalv.gtk3.1: -------------------------------------------------------------------------------- 1 | .\" # Copyright 2024 David Robillard 2 | .\" # SPDX-License-Identifier: ISC 3 | .Dd December 20, 2024 4 | .Dt JALV.GTK3 1 5 | .Os 6 | .Sh NAME 7 | .Nm jalv.gtk3 8 | .Nd run an LV2 plugin with a GTK3 interface 9 | .Sh SYNOPSIS 10 | .Nm jalv.gtk3 11 | .Op Fl dghmpstx 12 | .Op Fl b , Fl Fl buffer-size Ns = Ns Ar size 13 | .Op Fl c , Fl Fl control Ns = Ns Ar setting 14 | .Op Fl l , Fl Fl load Ns = Ns Ar dir 15 | .Op Fl n , Fl Fl jack-name Ns = Ns Ar name 16 | .Op Fl P , Fl Fl preset Ns = Ns Ar uri 17 | .Op Fl r , Fl Fl update-frequency Ns = Ns Ar hz 18 | .Op Fl S , Fl Fl scale-factor Ns = Ns Ar scale 19 | .Op Fl U , Fl Fl ui-uri Ns = Ns Ar uri 20 | .Ar plugin_uri 21 | .Sh DESCRIPTION 22 | .Nm 23 | is a simple LV2 host that runs a single plugin. 24 | This version has a GTK3 interface that shows either generic controls or a custom plugin GUI. 25 | .Pp 26 | The options are as follows: 27 | .Bl -tag -width 3n 28 | .It Fl b Ar size 29 | Buffer size for plugin <=> UI communication. 30 | .It Fl c Ar symbol=value 31 | Set control value, for example, 32 | .Fl c Ar vol=1.4 33 | where 34 | .Dq vol 35 | is the symbol of some control port on the plugin. 36 | .It Fl d 37 | Dump plugin <=> UI communication. 38 | .It Fl g 39 | Show generic UI instead of custom plugin GUI. 40 | .It Fl h 41 | Print the command line options. 42 | .It Fl l Ar dir 43 | Load state from the given directory before running the plugin. 44 | .It Fl m 45 | Hide application menu, showing only the plugin interface. 46 | .It Fl n Ar name 47 | Use the given JACK client name. 48 | Note that JACK may adjust the name if necessary unless 49 | .Fl x 50 | is also given. 51 | .It Fl P uri 52 | Load the given preset before running plugin. 53 | .It Fl p 54 | Print control output changes to 55 | .Dv stdout . 56 | .It Fl r Ar hz 57 | Set the UI update frequency. 58 | By default the screen refresh rate is used, typically 30 or 60 Hz. 59 | .It Fl S Ar factor 60 | Override the UI scale factor. 61 | .It Fl s 62 | Show controls that are normally hidden. 63 | .It Fl t 64 | Print trace messages from plugin. 65 | .It Fl U 66 | URI Load the UI with the given URI. 67 | .It Fl x 68 | Use only the exact JACK client name given by 69 | .Fl n 70 | or exit if it's unavailable. 71 | .El 72 | .Sh COMMANDS 73 | The Jalv prompt supports several commands for interactive control: 74 | .Pp 75 | .Bl -tag -width 16n -compact 76 | .It Ic help 77 | Display help message. 78 | .It Ic controls 79 | Print settable control values. 80 | .It Ic monitors 81 | Print output control values. 82 | .It Ic presets 83 | Print available presets. 84 | .It Ic preset Ar uri 85 | Set preset. 86 | .It Ic set index value 87 | Set control value by port index. 88 | .It Ic set Ar symbol Ar value 89 | Set control value by symbol. 90 | .It Ar SYMBOL Cm = Ar VALUE 91 | Set control value by symbol. 92 | .El 93 | .Sh SEE ALSO 94 | .Xr jalv 1 , 95 | .Xr jalv.qt5 1 96 | .Sh AUTHORS 97 | jalv was written by 98 | .An David Robillard 99 | .Aq Mt d@drobilla.net 100 | with contributions by others. 101 | Most of the GTK interface was added by Alexandros Theodotou and Nick Lanham. 102 | -------------------------------------------------------------------------------- /doc/jalv.qt5.1: -------------------------------------------------------------------------------- 1 | .\" # Copyright 2024 David Robillard 2 | .\" # SPDX-License-Identifier: ISC 3 | .Dd December 20, 2024 4 | .Dt JALV.QT5 1 5 | .Os 6 | .Sh NAME 7 | .Nm jalv.qt5 8 | .Nd run an LV2 plugin with a Qt5 interface 9 | .Sh SYNOPSIS 10 | .Nm jalv.qt5 11 | .Ar plugin_uri 12 | .Sh DESCRIPTION 13 | .Nm 14 | is a simple LV2 host that runs a single plugin. 15 | This version has a Qt5 interface that shows either generic controls or a custom plugin GUI. 16 | .Pp 17 | This version has no options and requires the plugin URI to be given on the command line. 18 | For a fully graphical version with a plugin selector, see 19 | .Xr jalv.gtk3 1 . 20 | .Sh SEE ALSO 21 | .Xr jalv 1 , 22 | .Xr jalv.gtk3 1 , 23 | .Xr lv2ls 1 24 | .Sh AUTHORS 25 | jalv was written by 26 | .An David Robillard 27 | .Aq Mt d@drobilla.net 28 | with contributions by others. 29 | Most of the Qt interface was added by Amadeus Folego and Timo Westkämper. 30 | -------------------------------------------------------------------------------- /doc/jalv.qt6.1: -------------------------------------------------------------------------------- 1 | .\" # Copyright 2024 David Robillard 2 | .\" # SPDX-License-Identifier: ISC 3 | .Dd December 20, 2024 4 | .Dt JALV.QT6 1 5 | .Os 6 | .Sh NAME 7 | .Nm jalv.qt6 8 | .Nd run an LV2 plugin with a Qt6 interface 9 | .Sh SYNOPSIS 10 | .Nm jalv.qt6 11 | .Ar plugin_uri 12 | .Sh DESCRIPTION 13 | .Nm 14 | is a simple LV2 host that runs a single plugin. 15 | This version has a Qt6 interface that shows either generic controls or a custom plugin GUI. 16 | .Pp 17 | This version has no options and requires the plugin URI to be given on the command line. 18 | For a fully graphical version with a plugin selector, see 19 | .Xr jalv.gtk3 1 . 20 | .Sh SEE ALSO 21 | .Xr jalv 1 , 22 | .Xr jalv.gtk3 1 , 23 | .Xr lv2ls 1 24 | .Sh AUTHORS 25 | jalv was written by 26 | .An David Robillard 27 | .Aq Mt d@drobilla.net 28 | with contributions by others. 29 | Most of the Qt interface was added by Amadeus Folego and Timo Westkämper. 30 | -------------------------------------------------------------------------------- /doc/mandoc.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2022 David Robillard 3 | SPDX-License-Identifier: ISC 4 | */ 5 | 6 | /* Generic page style */ 7 | 8 | /* 9 | Smaller sizes: 0.236em 0.271em 0.382em 0.438em 0.618em 0.708em 10 | Larger sizes: 1.146em 1.618em 1.854em 2.618em 3em 4.236em 11 | */ 12 | 13 | html { 14 | margin: 0 1.618em; 15 | background: #fff; 16 | color: #000; 17 | } 18 | 19 | body { 20 | font-style: normal; 21 | line-height: 1.618em; 22 | margin: 0 auto auto; 23 | padding: 0; 24 | max-width: 60em; 25 | font-family: "SF Pro Text", Verdana, "DejaVu Sans", sans-serif; 26 | text-rendering: optimizelegibility; 27 | } 28 | 29 | h1 { 30 | font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; 31 | font-size: 1.854em; 32 | font-weight: 600; 33 | line-height: 114.6%; 34 | margin: 1.146em 0; 35 | } 36 | 37 | a { 38 | text-decoration: none; 39 | } 40 | 41 | h1 a, 42 | h2 a, 43 | h3 a, 44 | h4 a, 45 | h5 a, 46 | h6 a { 47 | color: #222; 48 | } 49 | 50 | a:hover { 51 | text-decoration: underline; 52 | } 53 | 54 | h1 a:link, 55 | h2 a:link, 56 | h3 a:link, 57 | h4 a:link, 58 | h5 a:link, 59 | h6 a:link { 60 | color: #222; 61 | } 62 | 63 | h1 a:visited, 64 | h2 a:visited, 65 | h3 a:visited, 66 | h4 a:visited, 67 | h5 a:visited, 68 | h6 a:visited { 69 | color: #222; 70 | } 71 | 72 | pre, 73 | tt, 74 | code { 75 | overflow: auto; 76 | font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; 77 | hyphens: none; 78 | white-space: nowrap; 79 | 80 | /* stylelint-disable property-no-vendor-prefix */ 81 | -epub-hyphens: none; 82 | -moz-hyphens: none; 83 | -ms-hyphens: none; 84 | -webkit-hyphens: none; 85 | /* stylelint-enable property-no-vendor-prefix */ 86 | } 87 | 88 | ul, 89 | ol, 90 | dl { 91 | margin: 0; 92 | padding: 0; 93 | } 94 | 95 | ul { 96 | padding: 0; 97 | hyphens: auto; 98 | } 99 | 100 | dt { 101 | font-weight: 600; 102 | padding: 0.618em 0 0; 103 | } 104 | 105 | dd { 106 | margin: 0 0 0 2.618em; 107 | hyphens: auto; 108 | } 109 | 110 | dd > ul:only-child, 111 | dd > ol:only-child { 112 | padding-left: 0; 113 | } 114 | 115 | li { 116 | margin-left: 2.618em; 117 | } 118 | 119 | dt:empty { 120 | margin: 0; 121 | display: none; 122 | } 123 | 124 | dd:empty { 125 | margin: 0; 126 | display: none; 127 | } 128 | 129 | dt:blank { 130 | margin: 0; 131 | display: none; 132 | } 133 | 134 | dd:blank { 135 | margin: 0; 136 | display: none; 137 | } 138 | 139 | /* Media-specific style */ 140 | 141 | /* Color links on screens */ 142 | @media screen { 143 | a { 144 | color: #546e00; 145 | } 146 | } 147 | 148 | @media print { 149 | body { 150 | color: #000; 151 | } 152 | 153 | a, 154 | h1 a, 155 | h2 a, 156 | h3 a, 157 | h4 a, 158 | h5 a, 159 | h6 a { 160 | color: #000; 161 | } 162 | 163 | a:link { 164 | color: #000; 165 | } 166 | 167 | a:visited { 168 | color: #000; 169 | } 170 | } 171 | 172 | /* Mandoc specific style */ 173 | 174 | /* stylelint-disable selector-class-pattern */ 175 | 176 | table.head { 177 | font-size: 0.708em; 178 | margin: 0.438em 0 1.854em; 179 | width: 100%; 180 | } 181 | 182 | table.foot { 183 | font-size: 0.708em; 184 | margin: 2.618em 0 0.438em; 185 | width: 100%; 186 | } 187 | 188 | td.head-rtitle, 189 | td.foot-os { 190 | text-align: right; 191 | } 192 | 193 | td.head-vol { 194 | text-align: center; 195 | } 196 | 197 | div.Pp { 198 | margin: 1ex 0; 199 | } 200 | 201 | a.permalink { 202 | color: #222; 203 | } 204 | 205 | div.Nd, 206 | div.Bf, 207 | div.Op { 208 | display: inline; 209 | } 210 | 211 | span.Pa, 212 | span.Ad { 213 | font-style: italic; 214 | } 215 | 216 | span.Ms { 217 | font-weight: bold; 218 | } 219 | 220 | dl.Bl-diag > dt { 221 | font-weight: bold; 222 | } 223 | 224 | table.Nm tbody tr { 225 | vertical-align: baseline; 226 | } 227 | 228 | code.Nm, 229 | code.Fl, 230 | code.Cm, 231 | code.Ic, 232 | code.In, 233 | code.Fd, 234 | code.Fn, 235 | code.Cd { 236 | font-weight: bold; 237 | color: #444; 238 | } 239 | 240 | code.Ev { 241 | font-weight: bold; 242 | color: #444; 243 | } 244 | 245 | code.Li { 246 | color: #333; 247 | } 248 | 249 | var.Ar { 250 | font-style: italic; 251 | } 252 | 253 | /* stylelint-enable selector-class-pattern */ 254 | 255 | /* Dark mode */ 256 | @media (prefers-color-scheme: dark) { 257 | html { 258 | background: #222; 259 | color: #ddd; 260 | } 261 | 262 | a { 263 | color: #b4c342; 264 | } 265 | 266 | a.permalink { 267 | color: #ddd; 268 | } 269 | 270 | h1 a, 271 | h2 a, 272 | h3 a, 273 | h4 a, 274 | h5 a, 275 | h6 a { 276 | color: #ddd; 277 | } 278 | 279 | h1 a:link, 280 | h2 a:link, 281 | h3 a:link, 282 | h4 a:link, 283 | h5 a:link, 284 | h6 a:link { 285 | color: #ddd; 286 | } 287 | 288 | h1 a:visited, 289 | h2 a:visited, 290 | h3 a:visited, 291 | h4 a:visited, 292 | h5 a:visited, 293 | h6 a:visited { 294 | color: #ddd; 295 | } 296 | 297 | /* stylelint-disable selector-class-pattern */ 298 | 299 | code.Nm, 300 | code.Fl, 301 | code.Cm, 302 | code.Ic, 303 | code.In, 304 | code.Fd, 305 | code.Fn, 306 | code.Cd { 307 | color: #aaa; 308 | } 309 | 310 | code.Ev { 311 | color: #aaa; 312 | } 313 | 314 | code.Li { 315 | color: #ccc; 316 | } 317 | 318 | /* stylelint-enable selector-class-pattern */ 319 | } 320 | 321 | /* Hard black for dark mode on mobile (since it's likely to be an OLED screen) */ 322 | @media only screen and (hover: none) and (pointer: coarse) and (prefers-color-scheme: dark) { 323 | html { 324 | background: #000; 325 | color: #ccc; 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /doc/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2024 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | docdir = get_option('datadir') / 'doc' 5 | 6 | if not get_option('man').disabled() 7 | install_man(files('jalv.1')) 8 | 9 | if not get_option('gtk3').disabled() 10 | install_man(files('jalv.gtk3.1')) 11 | endif 12 | 13 | if not get_option('qt5').disabled() 14 | install_man(files('jalv.qt5.1')) 15 | endif 16 | 17 | if not get_option('qt6').disabled() 18 | install_man(files('jalv.qt6.1')) 19 | endif 20 | endif 21 | 22 | # Build/install HTML man pages if mandoc is present 23 | mandoc = find_program('mandoc', required: get_option('man_html')) 24 | if mandoc.found() 25 | configure_file( 26 | copy: true, 27 | input: files('mandoc.css'), 28 | output: 'mandoc.css', 29 | install_dir: docdir / meson.project_name() / 'man', 30 | ) 31 | 32 | mandoc_html_command = [ 33 | mandoc, 34 | '-Kutf-8', 35 | '-Ostyle=mandoc.css,man=%N.html', 36 | '-Thtml', 37 | '-Wwarning,stop', '@INPUT@', 38 | ] 39 | 40 | html_mandir = docdir / meson.project_name() / 'man' 41 | foreach name : ['jalv', 'jalv.gtk3', 'jalv.qt5', 'jalv.qt6'] 42 | custom_target( 43 | name + '.html', 44 | capture: true, 45 | command: mandoc_html_command, 46 | input: files(name + '.1'), 47 | install: true, 48 | install_dir: html_mandir, 49 | output: name + '.html', 50 | ) 51 | endforeach 52 | 53 | if not meson.is_subproject() 54 | summary( 55 | 'HTML man pages', 56 | get_option('prefix') / html_mandir, 57 | section: 'Directories', 58 | ) 59 | endif 60 | endif 61 | -------------------------------------------------------------------------------- /jalv.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=@APP_HUMAN_NAME@ 3 | Comment=Simple LV2 plugin host 4 | Comment[fr]=Hôte de greffon LV2 simple 5 | Exec=@BINDIR@/@APP_INSTALL_NAME@ 6 | Terminal=false 7 | Type=Application 8 | Categories=AudioVideo;Audio; 9 | Keywords=music;midi;alsa;jack; 10 | -------------------------------------------------------------------------------- /jalv.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix rdfs: . 3 | @prefix doap: . 4 | @prefix foaf: . 5 | 6 | 7 | a foaf:Person ; 8 | rdfs:seeAlso ; 9 | foaf:mbox ; 10 | foaf:name "David Robillard" ; 11 | foaf:nick "drobilla" . 12 | 13 | 14 | a doap:Project ; 15 | doap:blog ; 16 | doap:bug-database ; 17 | doap:description "C library for hosting LV2 plugins" ; 18 | doap:developer ; 19 | doap:download-page ; 20 | doap:homepage ; 21 | doap:implements ; 22 | doap:license ; 23 | doap:maintainer ; 24 | doap:name "Jalv" ; 25 | doap:programming-language "C" ; 26 | doap:repository [ 27 | a doap:GitBranch ; 28 | doap:location 29 | ] . 30 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020-2024 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | option('checks', type: 'feature', value: 'enabled', yield: true, 5 | description: 'Check for platform-specific features') 6 | 7 | option('cxx', type: 'feature', yield: true, 8 | description: 'Build C++ programs') 9 | 10 | option('gtk3', type: 'feature', yield: true, 11 | description: 'Build Gtk3 GUI') 12 | 13 | option('portaudio', type: 'feature', yield: true, 14 | description: 'Build PortAudio driver') 15 | 16 | option('jack', type: 'feature', yield: true, 17 | description: 'Build JACK driver') 18 | 19 | option('lint', type: 'boolean', value: false, yield: true, 20 | description: 'Run code quality checks') 21 | 22 | option('man', type: 'feature', value: 'enabled', yield: true, 23 | description: 'Install man pages') 24 | 25 | option('man_html', type: 'feature', yield: true, 26 | description: 'Build HTML man pages') 27 | 28 | option('posix', type: 'feature', yield: true, 29 | description: 'Use POSIX system facilities') 30 | 31 | option('qt5', type: 'feature', yield: true, 32 | description: 'Build Qt5 GUI') 33 | 34 | option('qt5_moc', type: 'string', yield: true, 35 | description: 'Path to Qt5 moc executable') 36 | 37 | option('qt6', type: 'feature', yield: true, 38 | description: 'Build Qt6 GUI') 39 | 40 | option('qt6_moc', type: 'string', yield: true, 41 | description: 'Path to Qt6 moc executable') 42 | 43 | option('suil', type: 'feature', yield: true, 44 | description: 'Use suil to load plugin UIs') 45 | 46 | option('title', type: 'string', value: 'Jalv', 47 | description: 'Project title') 48 | 49 | option('tests', type: 'feature', yield: true, 50 | description: 'Build tests') 51 | -------------------------------------------------------------------------------- /src/attributes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_ATTRIBUTES_H 5 | #define JALV_ATTRIBUTES_H 6 | 7 | #ifdef __cplusplus 8 | # define JALV_BEGIN_DECLS extern "C" { 9 | # define JALV_END_DECLS } 10 | #else 11 | # define JALV_BEGIN_DECLS 12 | # define JALV_END_DECLS 13 | #endif 14 | 15 | #endif // JALV_ATTRIBUTES_H 16 | -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_BACKEND_H 5 | #define JALV_BACKEND_H 6 | 7 | #include "attributes.h" 8 | #include "process.h" 9 | #include "settings.h" 10 | #include "types.h" 11 | #include "urids.h" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | // Interface that must be implemented by audio/MIDI backends 20 | JALV_BEGIN_DECLS 21 | 22 | /// Allocate a new uninitialized backend 23 | ZIX_MALLOC_FUNC JalvBackend* 24 | jalv_backend_allocate(void); 25 | 26 | /// Free a backend allocated with jalv_backend_allocate() 27 | void 28 | jalv_backend_free(JalvBackend* backend); 29 | 30 | /// Open the audio/MIDI system 31 | int 32 | jalv_backend_open(JalvBackend* backend, 33 | const JalvURIDs* urids, 34 | JalvSettings* settings, 35 | JalvProcess* process, 36 | ZixSem* done, 37 | const char* name, 38 | bool exact_name); 39 | 40 | /// Close the audio/MIDI system 41 | void 42 | jalv_backend_close(JalvBackend* backend); 43 | 44 | /// Activate the backend and start processing audio 45 | void 46 | jalv_backend_activate(JalvBackend* backend); 47 | 48 | /// Deactivate the backend and stop processing audio 49 | void 50 | jalv_backend_deactivate(JalvBackend* backend); 51 | 52 | /// Expose a port to the system (if applicable) and connect it to its buffer 53 | void 54 | jalv_backend_activate_port(JalvBackend* backend, 55 | JalvProcess* process, 56 | uint32_t port_index); 57 | 58 | /// Recompute latencies based on plugin port latencies if necessary 59 | void 60 | jalv_backend_recompute_latencies(JalvBackend* backend); 61 | 62 | JALV_END_DECLS 63 | 64 | #endif // JALV_BACKEND_H 65 | -------------------------------------------------------------------------------- /src/comm.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "comm.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | ZixStatus 11 | jalv_write_split_message(ZixRing* const target, 12 | const void* const header, 13 | const uint32_t header_size, 14 | const void* const body, 15 | const uint32_t body_size) 16 | { 17 | ZixRingTransaction tx = zix_ring_begin_write(target); 18 | 19 | ZixStatus st = ZIX_STATUS_SUCCESS; 20 | if (!(st = zix_ring_amend_write(target, &tx, header, header_size)) && 21 | !(st = zix_ring_amend_write(target, &tx, body, body_size))) { 22 | st = zix_ring_commit_write(target, &tx); 23 | } 24 | 25 | return st; 26 | } 27 | 28 | ZixStatus 29 | jalv_write_event(ZixRing* const target, 30 | const uint32_t port_index, 31 | const uint32_t size, 32 | const LV2_URID type, 33 | const void* const body) 34 | { 35 | // TODO: Be more discriminate about what to send 36 | 37 | typedef struct { 38 | JalvMessageHeader message; 39 | JalvEventTransfer event; 40 | } Header; 41 | 42 | const Header header = {{EVENT_TRANSFER, sizeof(JalvEventTransfer) + size}, 43 | {port_index, {size, type}}}; 44 | 45 | return jalv_write_split_message(target, &header, sizeof(header), body, size); 46 | } 47 | 48 | ZixStatus 49 | jalv_write_control(ZixRing* const target, 50 | const uint32_t port_index, 51 | const float value) 52 | { 53 | typedef struct { 54 | JalvMessageHeader message; 55 | JalvControlChange control; 56 | } Message; 57 | 58 | const Message msg = {{CONTROL_PORT_CHANGE, sizeof(JalvControlChange)}, 59 | {port_index, value}}; 60 | 61 | return zix_ring_write(target, &msg, sizeof(msg)) == sizeof(msg) 62 | ? ZIX_STATUS_SUCCESS 63 | : ZIX_STATUS_ERROR; 64 | } 65 | -------------------------------------------------------------------------------- /src/comm.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_COMM_H 5 | #define JALV_COMM_H 6 | 7 | #include "attributes.h" 8 | #include "types.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // Communication between the audio and main threads via rings 18 | JALV_BEGIN_DECLS 19 | 20 | /// Type of an internal message in a communication ring 21 | typedef enum { 22 | NO_MESSAGE, ///< Sentinel type for uninitialized messages 23 | CONTROL_PORT_CHANGE, ///< Value change for a control port (float) 24 | EVENT_TRANSFER, ///< Event transfer for a sequence port (atom) 25 | LATENCY_CHANGE, ///< Change to plugin latency 26 | STATE_REQUEST, ///< Request for a plugin state update (no payload) 27 | RUN_STATE_CHANGE, ///< Request to pause or resume running 28 | } JalvMessageType; 29 | 30 | /** 31 | Message between the audio thread and the main thread. 32 | 33 | This is the general header for any type of message in a communication ring. 34 | The type determines how the message must be handled by the receiver. This 35 | header is followed immediately by `size` bytes of data in the ring. 36 | */ 37 | typedef struct { 38 | JalvMessageType type; ///< Type of this message 39 | uint32_t size; ///< Size of payload following this header in bytes 40 | } JalvMessageHeader; 41 | 42 | /** 43 | The payload of a CONTROL_PORT_CHANGE message. 44 | 45 | This message has a fixed size, this struct defines the entire payload. 46 | */ 47 | typedef struct { 48 | uint32_t port_index; ///< Control port index 49 | float value; ///< Control value 50 | } JalvControlChange; 51 | 52 | /** 53 | The start of the payload of an EVENT_TRANSFER message. 54 | 55 | This message has a variable size, the start, described by this struct, is 56 | followed immediately by `atom.size` bytes of data (the atom body). 57 | */ 58 | typedef struct { 59 | uint32_t port_index; ///< Sequence port index 60 | LV2_Atom atom; ///< Event payload header 61 | } JalvEventTransfer; 62 | 63 | /** 64 | The payload of a LATENCY_CHANGE message. 65 | 66 | This message has a fixed size, this struct defines the entire payload. 67 | */ 68 | typedef struct { 69 | float value; ///< Latency in frames at the current sample rate 70 | } JalvLatencyChange; 71 | 72 | /** 73 | The payload of a RUN_STATE_CHANGE message. 74 | 75 | This message has a fixed size, this struct defines the entire payload. 76 | */ 77 | typedef struct { 78 | JalvRunState state; ///< Run state to change to 79 | } JalvRunStateChange; 80 | 81 | /** 82 | Write a message in two parts to a ring. 83 | 84 | This is used to conveniently write a message with a fixed-size header and 85 | possibly variably-sized body in a single call. 86 | 87 | @param target Communication ring (jalv->plugin_to_ui or jalv->ui_to_plugin). 88 | @param header Pointer to start of header data. 89 | @param header_size Size of header in bytes. 90 | @param body Pointer to start of body data. 91 | @param body_size Size of body in bytes. 92 | @return 0 on success, non-zero on failure (overflow). 93 | */ 94 | ZixStatus 95 | jalv_write_split_message(ZixRing* target, 96 | const void* header, 97 | uint32_t header_size, 98 | const void* body, 99 | uint32_t body_size); 100 | 101 | /** 102 | Write a port event using the atom:eventTransfer protocol. 103 | 104 | This is used to transfer atoms between the plugin and UI via sequence ports. 105 | 106 | @param target Communication ring (jalv->plugin_to_ui or jalv->ui_to_plugin). 107 | @param port_index Index of the port this change is for. 108 | @param size Size of body in bytes. 109 | @param type Atom type URID. 110 | @param body Atom body. 111 | @return 0 on success, non-zero on failure (overflow). 112 | */ 113 | ZixStatus 114 | jalv_write_event(ZixRing* target, 115 | uint32_t port_index, 116 | uint32_t size, 117 | LV2_URID type, 118 | const void* body); 119 | 120 | /** 121 | Write a control port change using the default (0) protocol. 122 | 123 | This is used to transfer control port value changes between the plugin and 124 | UI. 125 | 126 | @param target Communication ring (jalv->plugin_to_ui or jalv->ui_to_plugin). 127 | @param port_index Index of the port this change is for. 128 | @param value New control port value. 129 | @return 0 on success, non-zero on failure (overflow). 130 | */ 131 | ZixStatus 132 | jalv_write_control(ZixRing* target, uint32_t port_index, float value); 133 | 134 | JALV_END_DECLS 135 | 136 | #endif // JALV_COMM_H 137 | -------------------------------------------------------------------------------- /src/control.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "control.h" 5 | 6 | #include "log.h" 7 | #include "nodes.h" 8 | #include "string_utils.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | /// Order scale points by value 20 | static int 21 | scale_point_cmp(const ScalePoint* a, const ScalePoint* b) 22 | { 23 | if (a->value < b->value) { 24 | return -1; 25 | } 26 | 27 | if (a->value == b->value) { 28 | return 0; 29 | } 30 | 31 | return 1; 32 | } 33 | 34 | ControlID* 35 | new_port_control(LilvWorld* const world, 36 | const LilvPlugin* const plugin, 37 | const LilvPort* const port, 38 | uint32_t port_index, 39 | const float sample_rate, 40 | const JalvNodes* const nodes, 41 | const LV2_Atom_Forge* const forge) 42 | { 43 | ControlID* id = (ControlID*)calloc(1, sizeof(ControlID)); 44 | 45 | id->type = PORT; 46 | id->id.index = port_index; 47 | id->node = lilv_node_duplicate(lilv_port_get_node(plugin, port)); 48 | id->symbol = lilv_node_duplicate(lilv_port_get_symbol(plugin, port)); 49 | id->label = lilv_port_get_name(plugin, port); 50 | id->group = lilv_port_get(plugin, port, nodes->pg_group); 51 | id->value_type = forge->Float; 52 | id->is_writable = lilv_port_is_a(plugin, port, nodes->lv2_InputPort); 53 | id->is_readable = lilv_port_is_a(plugin, port, nodes->lv2_OutputPort); 54 | id->is_toggle = lilv_port_has_property(plugin, port, nodes->lv2_toggled); 55 | id->is_integer = lilv_port_has_property(plugin, port, nodes->lv2_integer); 56 | 57 | id->is_enumeration = 58 | lilv_port_has_property(plugin, port, nodes->lv2_enumeration); 59 | 60 | id->is_logarithmic = 61 | lilv_port_has_property(plugin, port, nodes->pprops_logarithmic); 62 | 63 | lilv_port_get_range(plugin, port, &id->def, &id->min, &id->max); 64 | if (lilv_port_has_property(plugin, port, nodes->lv2_sampleRate)) { 65 | // Adjust range for lv2:sampleRate controls 66 | if (lilv_node_is_float(id->min) || lilv_node_is_int(id->min)) { 67 | const float min = lilv_node_as_float(id->min) * sample_rate; 68 | lilv_node_free(id->min); 69 | id->min = lilv_new_float(world, min); 70 | } 71 | if (lilv_node_is_float(id->max) || lilv_node_is_int(id->max)) { 72 | const float max = lilv_node_as_float(id->max) * sample_rate; 73 | lilv_node_free(id->max); 74 | id->max = lilv_new_float(world, max); 75 | } 76 | } 77 | 78 | // Get scale points 79 | LilvScalePoints* sp = lilv_port_get_scale_points(plugin, port); 80 | if (sp) { 81 | id->points = 82 | (ScalePoint*)malloc(lilv_scale_points_size(sp) * sizeof(ScalePoint)); 83 | 84 | size_t np = 0; 85 | LILV_FOREACH (scale_points, s, sp) { 86 | const LilvScalePoint* p = lilv_scale_points_get(sp, s); 87 | if (lilv_node_is_float(lilv_scale_point_get_value(p)) || 88 | lilv_node_is_int(lilv_scale_point_get_value(p))) { 89 | id->points[np].value = 90 | lilv_node_as_float(lilv_scale_point_get_value(p)); 91 | id->points[np].label = 92 | jalv_strdup(lilv_node_as_string(lilv_scale_point_get_label(p))); 93 | ++np; 94 | } 95 | // TODO: Non-float scale points? 96 | } 97 | 98 | qsort(id->points, 99 | np, 100 | sizeof(ScalePoint), 101 | (int (*)(const void*, const void*))scale_point_cmp); 102 | 103 | id->n_points = np; 104 | lilv_scale_points_free(sp); 105 | } 106 | 107 | return id; 108 | } 109 | 110 | static bool 111 | has_range(LilvWorld* const world, 112 | const JalvNodes* const nodes, 113 | const LilvNode* const subject, 114 | const char* const range_uri) 115 | { 116 | LilvNode* range = lilv_new_uri(world, range_uri); 117 | const bool result = lilv_world_ask(world, subject, nodes->rdfs_range, range); 118 | 119 | lilv_node_free(range); 120 | return result; 121 | } 122 | 123 | ControlID* 124 | new_property_control(LilvWorld* const world, 125 | const LilvNode* property, 126 | const JalvNodes* const nodes, 127 | LV2_URID_Map* const map, 128 | const LV2_Atom_Forge* const forge) 129 | { 130 | ControlID* id = (ControlID*)calloc(1, sizeof(ControlID)); 131 | id->type = PROPERTY; 132 | id->id.property = map->map(map->handle, lilv_node_as_uri(property)); 133 | id->node = lilv_node_duplicate(property); 134 | id->symbol = lilv_world_get_symbol(world, property); 135 | 136 | id->label = lilv_world_get(world, property, nodes->rdfs_label, NULL); 137 | id->min = lilv_world_get(world, property, nodes->lv2_minimum, NULL); 138 | id->max = lilv_world_get(world, property, nodes->lv2_maximum, NULL); 139 | id->def = lilv_world_get(world, property, nodes->lv2_default, NULL); 140 | 141 | const char* const types[] = {LV2_ATOM__Int, 142 | LV2_ATOM__Long, 143 | LV2_ATOM__Float, 144 | LV2_ATOM__Double, 145 | LV2_ATOM__Bool, 146 | LV2_ATOM__String, 147 | LV2_ATOM__Path, 148 | NULL}; 149 | 150 | for (const char* const* t = types; *t; ++t) { 151 | if (has_range(world, nodes, property, *t)) { 152 | id->value_type = map->map(map->handle, *t); 153 | break; 154 | } 155 | } 156 | 157 | id->is_toggle = (id->value_type == forge->Bool); 158 | id->is_integer = 159 | (id->value_type == forge->Int || id->value_type == forge->Long); 160 | 161 | if (!id->value_type) { 162 | jalv_log(JALV_LOG_WARNING, 163 | "Unknown value type for property <%s>\n", 164 | lilv_node_as_string(property)); 165 | } 166 | 167 | return id; 168 | } 169 | 170 | void 171 | free_control(ControlID* const control) 172 | { 173 | lilv_node_free(control->node); 174 | lilv_node_free(control->symbol); 175 | lilv_node_free(control->label); 176 | lilv_node_free(control->group); 177 | lilv_node_free(control->min); 178 | lilv_node_free(control->max); 179 | lilv_node_free(control->def); 180 | free(control); 181 | } 182 | 183 | void 184 | add_control(Controls* controls, ControlID* control) 185 | { 186 | ControlID** const new_controls = (ControlID**)realloc( 187 | controls->controls, (controls->n_controls + 1) * sizeof(ControlID*)); 188 | 189 | if (new_controls) { 190 | controls->controls = new_controls; 191 | controls->controls[controls->n_controls++] = control; 192 | } 193 | } 194 | 195 | ControlID* 196 | get_property_control(const Controls* controls, LV2_URID property) 197 | { 198 | for (size_t i = 0; i < controls->n_controls; ++i) { 199 | if (controls->controls[i]->type == PROPERTY && 200 | controls->controls[i]->id.property == property) { 201 | return controls->controls[i]; 202 | } 203 | } 204 | 205 | return NULL; 206 | } 207 | -------------------------------------------------------------------------------- /src/control.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_CONTROL_H 5 | #define JALV_CONTROL_H 6 | 7 | #include "attributes.h" 8 | #include "nodes.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | // Support for plugin controls (control port or event-based) 19 | JALV_BEGIN_DECLS 20 | 21 | /// Type of plugin control 22 | typedef enum { 23 | PORT, ///< Control port 24 | PROPERTY ///< Property (set via atom message) 25 | } ControlType; 26 | 27 | // "Interesting" value in a control's value range 28 | typedef struct { 29 | float value; 30 | char* label; 31 | } ScalePoint; 32 | 33 | /// Plugin control 34 | typedef struct { 35 | ControlType type; ///< Type of control 36 | union { 37 | LV2_URID property; ///< Iff type == PROPERTY 38 | uint32_t index; ///< Iff type == PORT 39 | } id; 40 | LilvNode* node; ///< Port or property 41 | LilvNode* symbol; ///< Symbol 42 | LilvNode* label; ///< Human readable label 43 | LilvNode* group; ///< Port/control group, or NULL 44 | void* widget; ///< Control Widget 45 | size_t n_points; ///< Number of scale points 46 | ScalePoint* points; ///< Scale points 47 | LV2_URID value_type; ///< Type of control value 48 | LilvNode* min; ///< Minimum value 49 | LilvNode* max; ///< Maximum value 50 | LilvNode* def; ///< Default value 51 | bool is_toggle; ///< Boolean (0 and 1 only) 52 | bool is_integer; ///< Integer values only 53 | bool is_enumeration; ///< Point values only 54 | bool is_logarithmic; ///< Logarithmic scale 55 | bool is_writable; ///< Writable (input) 56 | bool is_readable; ///< Readable (output) 57 | } ControlID; 58 | 59 | /// Set of plugin controls 60 | typedef struct { 61 | size_t n_controls; 62 | ControlID** controls; 63 | } Controls; 64 | 65 | /// Create a new ID for a control port 66 | ControlID* 67 | new_port_control(LilvWorld* world, 68 | const LilvPlugin* plugin, 69 | const LilvPort* port, 70 | uint32_t port_index, 71 | float sample_rate, 72 | const JalvNodes* nodes, 73 | const LV2_Atom_Forge* forge); 74 | 75 | /// Create a new ID for a property-based parameter 76 | ControlID* 77 | new_property_control(LilvWorld* world, 78 | const LilvNode* property, 79 | const JalvNodes* nodes, 80 | LV2_URID_Map* map, 81 | const LV2_Atom_Forge* forge); 82 | 83 | /// Free a control allocated with new_port_control() or new_property_control() 84 | void 85 | free_control(ControlID* control); 86 | 87 | /// Add a control to the given controls set, reallocating as necessary 88 | void 89 | add_control(Controls* controls, ControlID* control); 90 | 91 | /// Return a pointer to the control for the given property, or null 92 | ControlID* 93 | get_property_control(const Controls* controls, LV2_URID property); 94 | 95 | JALV_END_DECLS 96 | 97 | #endif // JALV_CONTROL_H 98 | -------------------------------------------------------------------------------- /src/dumper.c: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "dumper.h" 5 | 6 | #include "log.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | struct JalvDumperImpl { 20 | LV2_URID_Unmap* unmap; 21 | SerdEnv* env; 22 | Sratom* sratom; 23 | }; 24 | 25 | JalvDumper* 26 | jalv_dumper_new(LV2_URID_Map* const map, LV2_URID_Unmap* const unmap) 27 | { 28 | JalvDumper* const dumper = (JalvDumper*)calloc(1, sizeof(JalvDumper)); 29 | SerdEnv* const env = serd_env_new(NULL); 30 | Sratom* const sratom = sratom_new(map); 31 | if (!dumper || !env || !sratom) { 32 | jalv_dumper_free(dumper); 33 | return NULL; 34 | } 35 | 36 | serd_env_set_prefix_from_strings( 37 | env, (const uint8_t*)"patch", (const uint8_t*)LV2_PATCH_PREFIX); 38 | serd_env_set_prefix_from_strings( 39 | env, (const uint8_t*)"time", (const uint8_t*)LV2_TIME_PREFIX); 40 | serd_env_set_prefix_from_strings( 41 | env, (const uint8_t*)"xsd", (const uint8_t*)LILV_NS_XSD); 42 | 43 | dumper->env = env; 44 | dumper->sratom = sratom; 45 | dumper->unmap = unmap; 46 | return dumper; 47 | } 48 | 49 | void 50 | jalv_dumper_free(JalvDumper* const dumper) 51 | { 52 | if (dumper) { 53 | sratom_free(dumper->sratom); 54 | serd_env_free(dumper->env); 55 | free(dumper); 56 | } 57 | } 58 | 59 | void 60 | jalv_dump_atom(JalvDumper* const dumper, 61 | FILE* const stream, 62 | const char* const label, 63 | const LV2_Atom* const atom, 64 | const int color) 65 | { 66 | if (dumper) { 67 | char* const str = sratom_to_turtle(dumper->sratom, 68 | dumper->unmap, 69 | "jalv:", 70 | NULL, 71 | NULL, 72 | atom->type, 73 | atom->size, 74 | LV2_ATOM_BODY_CONST(atom)); 75 | 76 | jalv_ansi_start(stream, color); 77 | fprintf(stream, "\n# %s (%u bytes):\n%s\n", label, atom->size, str); 78 | jalv_ansi_reset(stream); 79 | free(str); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/dumper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_DUMPER_H 5 | #define JALV_DUMPER_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | // LV2 atom dumper 15 | JALV_BEGIN_DECLS 16 | 17 | /// Dumper for writing atoms as Turtle for debugging 18 | typedef struct JalvDumperImpl JalvDumper; 19 | 20 | /// Allocate, configure, and return a new atom dumper 21 | JalvDumper* 22 | jalv_dumper_new(LV2_URID_Map* map, LV2_URID_Unmap* unmap); 23 | 24 | /// Free memory allocated by jalv_init_dumper() 25 | void 26 | jalv_dumper_free(JalvDumper* dumper); 27 | 28 | /// Dump an atom to stdout 29 | void 30 | jalv_dump_atom(JalvDumper* dumper, 31 | FILE* stream, 32 | const char* label, 33 | const LV2_Atom* atom, 34 | int color); 35 | 36 | JALV_END_DECLS 37 | 38 | #endif // JALV_DUMPER_H 39 | -------------------------------------------------------------------------------- /src/features.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "features.h" 5 | 6 | #include "macros.h" 7 | #include "settings.h" 8 | #include "urids.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | void 16 | jalv_init_lv2_options(JalvFeatures* const features, 17 | const JalvURIDs* const urids, 18 | const JalvSettings* const settings) 19 | { 20 | const LV2_Options_Option options[ARRAY_SIZE(features->options)] = { 21 | {LV2_OPTIONS_INSTANCE, 22 | 0, 23 | urids->param_sampleRate, 24 | sizeof(float), 25 | urids->atom_Float, 26 | &settings->sample_rate}, 27 | {LV2_OPTIONS_INSTANCE, 28 | 0, 29 | urids->bufsz_minBlockLength, 30 | sizeof(int32_t), 31 | urids->atom_Int, 32 | &settings->block_length}, 33 | {LV2_OPTIONS_INSTANCE, 34 | 0, 35 | urids->bufsz_maxBlockLength, 36 | sizeof(int32_t), 37 | urids->atom_Int, 38 | &settings->block_length}, 39 | {LV2_OPTIONS_INSTANCE, 40 | 0, 41 | urids->bufsz_sequenceSize, 42 | sizeof(int32_t), 43 | urids->atom_Int, 44 | &settings->midi_buf_size}, 45 | {LV2_OPTIONS_INSTANCE, 46 | 0, 47 | urids->ui_updateRate, 48 | sizeof(float), 49 | urids->atom_Float, 50 | &settings->ui_update_hz}, 51 | {LV2_OPTIONS_INSTANCE, 52 | 0, 53 | urids->ui_scaleFactor, 54 | sizeof(float), 55 | urids->atom_Float, 56 | &settings->ui_scale_factor}, 57 | {LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL}}; 58 | 59 | memcpy(features->options, options, sizeof(features->options)); 60 | features->options_feature.URI = LV2_OPTIONS__options; 61 | features->options_feature.data = (void*)features->options; 62 | } 63 | -------------------------------------------------------------------------------- /src/features.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_FEATURES_H 5 | #define JALV_FEATURES_H 6 | 7 | #include "attributes.h" 8 | #include "settings.h" 9 | #include "urids.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // LV2 feature support 20 | JALV_BEGIN_DECLS 21 | 22 | /// LV2 features and associated data to be passed to plugins 23 | typedef struct { 24 | LV2_Feature map_feature; 25 | LV2_Feature unmap_feature; 26 | LV2_State_Make_Path make_path; 27 | LV2_Feature make_path_feature; 28 | LV2_Worker_Schedule sched; 29 | LV2_Feature sched_feature; 30 | LV2_Worker_Schedule ssched; 31 | LV2_Feature state_sched_feature; 32 | LV2_Log_Log llog; 33 | LV2_Feature log_feature; 34 | LV2_Options_Option options[7]; 35 | LV2_Feature options_feature; 36 | LV2_Feature safe_restore_feature; 37 | LV2UI_Request_Value request_value; 38 | LV2_Feature request_value_feature; 39 | LV2_Extension_Data_Feature ext_data; 40 | } JalvFeatures; 41 | 42 | /// Set LV2 options feature for passing to plugin after settings are determined 43 | void 44 | jalv_init_lv2_options(JalvFeatures* features, 45 | const JalvURIDs* urids, 46 | const JalvSettings* settings); 47 | 48 | JALV_END_DECLS 49 | 50 | #endif // JALV_FEATURES_H 51 | -------------------------------------------------------------------------------- /src/frontend.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_UI_H 5 | #define JALV_UI_H 6 | 7 | #include "attributes.h" 8 | #include "options.h" 9 | #include "types.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | // Interface that must be implemented by UIs 17 | JALV_BEGIN_DECLS 18 | 19 | /// Arbitrary return code for successful early exit (for --help and so on) 20 | #define JALV_EARLY_EXIT_STATUS (-431) 21 | 22 | /// Command-line arguments passed to an executable 23 | typedef struct { 24 | int* argc; ///< Pointer to `argc` like in `main` 25 | char*** argv; ///< Pointer to `argv` like in `main` 26 | } JalvFrontendArgs; 27 | 28 | /// Consume command-line arguments and set `opts` accordingly 29 | int 30 | jalv_frontend_init(JalvFrontendArgs* args, JalvOptions* opts); 31 | 32 | /// Return the URI of the "native" LV2 UI type 33 | const char* 34 | jalv_frontend_ui_type(void); 35 | 36 | /// Return true if an interactive frontend is available 37 | bool 38 | jalv_frontend_discover(const Jalv* jalv); 39 | 40 | /// Return the ideal refresh rate of the frontend in Hz 41 | float 42 | jalv_frontend_refresh_rate(const Jalv* jalv); 43 | 44 | /// Return the scale factor of the frontend (for example 2.0 for double sized) 45 | float 46 | jalv_frontend_scale_factor(const Jalv* jalv); 47 | 48 | /// Attempt to get a plugin URI selection from the user 49 | LilvNode* 50 | jalv_frontend_select_plugin(Jalv* jalv); 51 | 52 | /// Open and run the frontend interface, signalling jalv.done when finished 53 | int 54 | jalv_frontend_open(Jalv* jalv); 55 | 56 | /// Quit and close the frontend interface 57 | int 58 | jalv_frontend_close(Jalv* jalv); 59 | 60 | /// Called when a port event (control change or other message) is sent to the UI 61 | void 62 | jalv_frontend_port_event(Jalv* jalv, 63 | uint32_t port_index, 64 | uint32_t buffer_size, 65 | uint32_t protocol, 66 | const void* buffer); 67 | 68 | JALV_END_DECLS 69 | 70 | #endif // JALV_UI_H 71 | -------------------------------------------------------------------------------- /src/jack.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "backend.h" 5 | 6 | #include "comm.h" 7 | #include "jack_impl.h" 8 | #include "jalv_config.h" 9 | #include "log.h" 10 | #include "lv2_evbuf.h" 11 | #include "process.h" 12 | #include "process_setup.h" 13 | #include "settings.h" 14 | #include "string_utils.h" 15 | #include "types.h" 16 | #include "urids.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if USE_JACK_METADATA 30 | # include 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #ifdef __clang__ 40 | # define REALTIME __attribute__((annotate("realtime"))) 41 | #else 42 | # define REALTIME 43 | #endif 44 | 45 | /// Maximum supported latency in frames (at most 2^24 so all integers work) 46 | static const float max_latency = 16777216.0f; 47 | 48 | /// Jack buffer size callback 49 | static int 50 | buffer_size_cb(jack_nframes_t nframes, void* data) 51 | { 52 | JalvBackend* const backend = (JalvBackend*)data; 53 | JalvSettings* const settings = backend->settings; 54 | JalvProcess* const proc = backend->process; 55 | 56 | settings->block_length = nframes; 57 | #if USE_JACK_PORT_TYPE_GET_BUFFER_SIZE 58 | settings->midi_buf_size = 59 | jack_port_type_get_buffer_size(backend->client, JACK_DEFAULT_MIDI_TYPE); 60 | #endif 61 | if (proc->run_state == JALV_RUNNING) { 62 | jalv_process_activate(proc, backend->urids, proc->instance, settings); 63 | } 64 | return 0; 65 | } 66 | 67 | /// Jack shutdown callback 68 | static void 69 | shutdown_cb(void* data) 70 | { 71 | JalvBackend* const backend = (JalvBackend*)data; 72 | zix_sem_post(backend->done); 73 | } 74 | 75 | static void 76 | forge_position(LV2_Atom_Forge* const forge, 77 | const JalvURIDs* const urids, 78 | const jack_transport_state_t state, 79 | const jack_position_t pos) 80 | { 81 | LV2_Atom_Forge_Frame frame; 82 | lv2_atom_forge_object(forge, &frame, 0, urids->time_Position); 83 | lv2_atom_forge_key(forge, urids->time_frame); 84 | lv2_atom_forge_long(forge, pos.frame); 85 | lv2_atom_forge_key(forge, urids->time_speed); 86 | lv2_atom_forge_float(forge, (state == JackTransportRolling) ? 1.0 : 0.0); 87 | if ((pos.valid & JackPositionBBT)) { 88 | lv2_atom_forge_key(forge, urids->time_barBeat); 89 | lv2_atom_forge_float(forge, pos.beat - 1 + (pos.tick / pos.ticks_per_beat)); 90 | lv2_atom_forge_key(forge, urids->time_bar); 91 | lv2_atom_forge_long(forge, pos.bar - 1); 92 | lv2_atom_forge_key(forge, urids->time_beatUnit); 93 | lv2_atom_forge_int(forge, pos.beat_type); 94 | lv2_atom_forge_key(forge, urids->time_beatsPerBar); 95 | lv2_atom_forge_float(forge, pos.beats_per_bar); 96 | lv2_atom_forge_key(forge, urids->time_beatsPerMinute); 97 | lv2_atom_forge_float(forge, pos.beats_per_minute); 98 | } 99 | } 100 | 101 | static int 102 | process_silent(JalvProcess* const proc, const jack_nframes_t nframes) 103 | { 104 | for (uint32_t p = 0U; p < proc->num_ports; ++p) { 105 | const JalvProcessPort* const port = &proc->ports[p]; 106 | jack_port_t* const jport = (jack_port_t*)proc->ports[p].sys_port; 107 | if (jport && port->flow == FLOW_OUTPUT) { 108 | void* const buf = jack_port_get_buffer(jport, nframes); 109 | if (port->type == TYPE_EVENT) { 110 | jack_midi_clear_buffer(buf); 111 | } else { 112 | memset(buf, '\0', nframes * sizeof(float)); 113 | } 114 | } 115 | } 116 | 117 | return jalv_bypass(proc, nframes); 118 | } 119 | 120 | static bool 121 | process_transport(JalvProcess* const proc, 122 | const jack_transport_state_t state, 123 | const jack_position_t pos, 124 | const jack_nframes_t nframes) 125 | { 126 | // If transport state is not as expected, then something has changed 127 | const bool rolling = state == JackTransportRolling; 128 | const bool has_bbt = (pos.valid & JackPositionBBT); 129 | const bool xport_changed = 130 | (rolling != proc->rolling || pos.frame != proc->position || 131 | (has_bbt && pos.beats_per_minute != proc->bpm)); 132 | 133 | // Update transport state to expected values for next cycle 134 | proc->position = rolling ? pos.frame + nframes : pos.frame; 135 | proc->bpm = has_bbt ? pos.beats_per_minute : proc->bpm; 136 | proc->rolling = rolling; 137 | 138 | return xport_changed; 139 | } 140 | 141 | /// Jack process callback 142 | static REALTIME int 143 | process_cb(jack_nframes_t nframes, void* data) 144 | { 145 | JalvBackend* const backend = (JalvBackend*)data; 146 | const JalvURIDs* const urids = backend->urids; 147 | JalvProcess* const proc = backend->process; 148 | jack_client_t* const client = backend->client; 149 | uint64_t pos_buf[64] = {0U}; 150 | LV2_Atom* const lv2_pos = (LV2_Atom*)pos_buf; 151 | 152 | // If execution is paused, emit silence and return 153 | if (proc->run_state == JALV_PAUSED) { 154 | return process_silent(proc, nframes); 155 | } 156 | 157 | // Get transport state and position 158 | jack_position_t pos = {0U}; 159 | const jack_transport_state_t state = jack_transport_query(client, &pos); 160 | 161 | // Check if transport is discontinuous from last time and update state 162 | const bool xport_changed = process_transport(proc, state, pos, nframes); 163 | if (xport_changed) { 164 | // Build an LV2 position object to report change to plugin 165 | lv2_atom_forge_set_buffer(&proc->forge, (uint8_t*)pos_buf, sizeof(pos_buf)); 166 | forge_position(&proc->forge, urids, state, pos); 167 | } 168 | 169 | // Prepare port buffers 170 | for (uint32_t p = 0; p < proc->num_ports; ++p) { 171 | JalvProcessPort* const port = &proc->ports[p]; 172 | if (port->sys_port && (port->type == TYPE_AUDIO || port->type == TYPE_CV)) { 173 | // Connect plugin port directly to Jack port buffer 174 | lilv_instance_connect_port( 175 | proc->instance, p, jack_port_get_buffer(port->sys_port, nframes)); 176 | } else if (port->type == TYPE_EVENT && port->flow == FLOW_INPUT) { 177 | lv2_evbuf_reset(port->evbuf, true); 178 | LV2_Evbuf_Iterator iter = lv2_evbuf_begin(port->evbuf); 179 | 180 | if (port->is_primary && xport_changed) { 181 | // Write new transport position 182 | lv2_evbuf_write( 183 | &iter, 0, 0, lv2_pos->type, lv2_pos->size, LV2_ATOM_BODY(lv2_pos)); 184 | } 185 | 186 | if (port->sys_port) { 187 | // Write Jack MIDI input 188 | void* buf = jack_port_get_buffer(port->sys_port, nframes); 189 | for (uint32_t i = 0; i < jack_midi_get_event_count(buf); ++i) { 190 | jack_midi_event_t ev; 191 | jack_midi_event_get(&ev, buf, i); 192 | lv2_evbuf_write( 193 | &iter, ev.time, 0, urids->midi_MidiEvent, ev.size, ev.buffer); 194 | } 195 | } 196 | } else if (port->type == TYPE_EVENT) { 197 | // Clear event output for plugin to write to 198 | lv2_evbuf_reset(port->evbuf, false); 199 | } 200 | } 201 | 202 | // Run plugin for this cycle 203 | const bool send_ui_updates = jalv_run(proc, nframes); 204 | 205 | // Deliver MIDI output and UI events 206 | for (uint32_t p = 0; p < proc->num_ports; ++p) { 207 | JalvProcessPort* const port = &proc->ports[p]; 208 | if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && 209 | port->reports_latency) { 210 | // Get the latency in frames from the control output truncated to integer 211 | const float value = proc->controls_buf[p]; 212 | const uint32_t frames = 213 | (value >= 0.0f && value <= max_latency) ? (uint32_t)value : 0U; 214 | 215 | if (proc->plugin_latency != frames) { 216 | // Update the cached value and notify the UI if the latency changed 217 | proc->plugin_latency = frames; 218 | 219 | const JalvLatencyChange body = {frames}; 220 | const JalvMessageHeader header = {LATENCY_CHANGE, sizeof(body)}; 221 | jalv_write_split_message( 222 | proc->plugin_to_ui, &header, sizeof(header), &body, sizeof(body)); 223 | } 224 | } else if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { 225 | void* buf = NULL; 226 | if (port->sys_port) { 227 | buf = jack_port_get_buffer(port->sys_port, nframes); 228 | jack_midi_clear_buffer(buf); 229 | } 230 | 231 | for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf); 232 | lv2_evbuf_is_valid(i); 233 | i = lv2_evbuf_next(i)) { 234 | // Get event from LV2 buffer 235 | uint32_t frames = 0; 236 | uint32_t subframes = 0; 237 | LV2_URID type = 0; 238 | uint32_t size = 0; 239 | void* body = NULL; 240 | lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body); 241 | 242 | if (buf && type == urids->midi_MidiEvent) { 243 | // Write MIDI event to Jack output 244 | jack_midi_event_write(buf, frames, body, size); 245 | } 246 | 247 | if (proc->has_ui) { 248 | // Forward event to UI 249 | jalv_write_event(proc->plugin_to_ui, p, size, type, body); 250 | } 251 | } 252 | } else if (send_ui_updates && port->flow == FLOW_OUTPUT && 253 | port->type == TYPE_CONTROL) { 254 | jalv_write_control(proc->plugin_to_ui, p, proc->controls_buf[p]); 255 | } 256 | } 257 | 258 | return 0; 259 | } 260 | 261 | /// Jack latency callback 262 | static void 263 | latency_cb(const jack_latency_callback_mode_t mode, void* const data) 264 | { 265 | // Calculate latency assuming all ports depend on each other 266 | 267 | const JalvBackend* const backend = (JalvBackend*)data; 268 | const JalvProcess* const proc = backend->process; 269 | const PortFlow flow = 270 | ((mode == JackCaptureLatency) ? FLOW_INPUT : FLOW_OUTPUT); 271 | 272 | // First calculate the min/max latency of all feeding ports 273 | uint32_t ports_found = 0; 274 | jack_latency_range_t range = {UINT32_MAX, 0}; 275 | for (uint32_t p = 0; p < proc->num_ports; ++p) { 276 | JalvProcessPort* const port = &proc->ports[p]; 277 | if (port->sys_port && port->flow == flow) { 278 | jack_latency_range_t r; 279 | jack_port_get_latency_range(port->sys_port, mode, &r); 280 | if (r.min < range.min) { 281 | range.min = r.min; 282 | } 283 | if (r.max > range.max) { 284 | range.max = r.max; 285 | } 286 | ++ports_found; 287 | } 288 | } 289 | 290 | if (ports_found == 0) { 291 | range.min = 0; 292 | } 293 | 294 | // Add the plugin's own latency 295 | range.min += proc->plugin_latency; 296 | range.max += proc->plugin_latency; 297 | 298 | // Tell Jack about it 299 | for (uint32_t p = 0; p < proc->num_ports; ++p) { 300 | const JalvProcessPort* const port = &proc->ports[p]; 301 | if (port->sys_port && port->flow == flow) { 302 | jack_port_set_latency_range(port->sys_port, mode, &range); 303 | } 304 | } 305 | } 306 | 307 | static jack_client_t* 308 | create_client(const char* const name, const bool exact_name) 309 | { 310 | char* const jack_name = jalv_strdup(name); 311 | 312 | // Truncate client name to suit JACK if necessary 313 | if (strlen(jack_name) >= (unsigned)jack_client_name_size() - 1) { 314 | jack_name[jack_client_name_size() - 1] = '\0'; 315 | } 316 | 317 | // Connect to JACK 318 | jack_client_t* const client = jack_client_open( 319 | jack_name, (exact_name ? JackUseExactName : JackNullOption), NULL); 320 | 321 | free(jack_name); 322 | 323 | return client; 324 | } 325 | 326 | JalvBackend* 327 | jalv_backend_allocate(void) 328 | { 329 | return (JalvBackend*)calloc(1, sizeof(JalvBackend)); 330 | } 331 | 332 | void 333 | jalv_backend_free(JalvBackend* const backend) 334 | { 335 | free(backend); 336 | } 337 | 338 | int 339 | jalv_backend_open(JalvBackend* const backend, 340 | const JalvURIDs* const urids, 341 | JalvSettings* const settings, 342 | JalvProcess* const process, 343 | ZixSem* const done, 344 | const char* const name, 345 | const bool exact_name) 346 | { 347 | jack_client_t* const client = 348 | backend->client ? backend->client : create_client(name, exact_name); 349 | 350 | if (!client) { 351 | return 1; 352 | } 353 | 354 | jalv_log(JALV_LOG_INFO, "JACK name: %s\n", jack_get_client_name(client)); 355 | 356 | // Set audio engine properties 357 | settings->sample_rate = (float)jack_get_sample_rate(client); 358 | settings->block_length = jack_get_buffer_size(client); 359 | settings->midi_buf_size = 4096; 360 | #if USE_JACK_PORT_TYPE_GET_BUFFER_SIZE 361 | settings->midi_buf_size = 362 | jack_port_type_get_buffer_size(client, JACK_DEFAULT_MIDI_TYPE); 363 | #endif 364 | 365 | // Set JACK callbacks 366 | void* const arg = (void*)backend; 367 | jack_set_process_callback(client, &process_cb, arg); 368 | jack_set_buffer_size_callback(client, &buffer_size_cb, arg); 369 | jack_on_shutdown(client, &shutdown_cb, arg); 370 | jack_set_latency_callback(client, &latency_cb, arg); 371 | 372 | backend->urids = urids; 373 | backend->settings = settings; 374 | backend->process = process; 375 | backend->done = done; 376 | backend->client = client; 377 | backend->is_internal_client = false; 378 | return 0; 379 | } 380 | 381 | void 382 | jalv_backend_close(JalvBackend* const backend) 383 | { 384 | if (backend && backend->client && !backend->is_internal_client) { 385 | jack_client_close(backend->client); 386 | } 387 | } 388 | 389 | void 390 | jalv_backend_activate(JalvBackend* const backend) 391 | { 392 | jack_activate(backend->client); 393 | } 394 | 395 | void 396 | jalv_backend_deactivate(JalvBackend* const backend) 397 | { 398 | if (!backend->is_internal_client && backend->client) { 399 | jack_deactivate(backend->client); 400 | } 401 | } 402 | 403 | void 404 | jalv_backend_activate_port(JalvBackend* const backend, 405 | JalvProcess* const proc, 406 | const uint32_t port_index) 407 | { 408 | jack_client_t* const client = backend->client; 409 | JalvProcessPort* const port = &proc->ports[port_index]; 410 | 411 | // Connect unsupported ports to NULL (known to be optional by this point) 412 | if (port->flow == FLOW_UNKNOWN || port->type == TYPE_UNKNOWN) { 413 | lilv_instance_connect_port(proc->instance, port_index, NULL); 414 | return; 415 | } 416 | 417 | // Build Jack flags for port 418 | enum JackPortFlags jack_flags = 419 | (port->flow == FLOW_INPUT) ? JackPortIsInput : JackPortIsOutput; 420 | 421 | // Connect the port based on its type 422 | switch (port->type) { 423 | case TYPE_UNKNOWN: 424 | break; 425 | case TYPE_CONTROL: 426 | lilv_instance_connect_port( 427 | proc->instance, port_index, &proc->controls_buf[port_index]); 428 | break; 429 | case TYPE_AUDIO: 430 | port->sys_port = jack_port_register( 431 | client, port->symbol, JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); 432 | break; 433 | #if USE_JACK_METADATA 434 | case TYPE_CV: 435 | port->sys_port = jack_port_register( 436 | client, port->symbol, JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0); 437 | if (port->sys_port) { 438 | jack_set_property(client, 439 | jack_port_uuid(port->sys_port), 440 | "http://jackaudio.org/metadata/signal-type", 441 | "CV", 442 | "text/plain"); 443 | } 444 | break; 445 | #endif 446 | case TYPE_EVENT: 447 | if (port->supports_midi) { 448 | port->sys_port = jack_port_register( 449 | client, port->symbol, JACK_DEFAULT_MIDI_TYPE, jack_flags, 0); 450 | } 451 | break; 452 | } 453 | 454 | #if USE_JACK_METADATA 455 | if (port->sys_port) { 456 | // Set port order to index 457 | char index_str[16]; 458 | snprintf(index_str, sizeof(index_str), "%u", port_index); 459 | jack_set_property(client, 460 | jack_port_uuid(port->sys_port), 461 | "http://jackaudio.org/metadata/order", 462 | index_str, 463 | "http://www.w3.org/2001/XMLSchema#integer"); 464 | 465 | // Set port pretty name to label 466 | if (port->label) { 467 | jack_set_property(client, 468 | jack_port_uuid(port->sys_port), 469 | JACK_METADATA_PRETTY_NAME, 470 | port->label, 471 | "text/plain"); 472 | } 473 | } 474 | #endif 475 | } 476 | 477 | void 478 | jalv_backend_recompute_latencies(JalvBackend* const backend) 479 | { 480 | jack_recompute_total_latencies(backend->client); 481 | } 482 | -------------------------------------------------------------------------------- /src/jack_impl.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_JACK_IMPL_H 5 | #define JALV_JACK_IMPL_H 6 | 7 | #include "attributes.h" 8 | #include "process.h" 9 | #include "settings.h" 10 | #include "urids.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // Definition of Jack backend structure (private to implementation) 18 | JALV_BEGIN_DECLS 19 | 20 | struct JalvBackendImpl { 21 | const JalvURIDs* urids; ///< Application vocabulary 22 | JalvSettings* settings; ///< Run settings 23 | JalvProcess* process; ///< Process thread state 24 | ZixSem* done; ///< Shutdown semaphore 25 | jack_client_t* client; ///< Jack client 26 | bool is_internal_client; ///< Running inside jackd 27 | }; 28 | 29 | JALV_END_DECLS 30 | 31 | #endif // JALV_JACK_IMPL_H 32 | -------------------------------------------------------------------------------- /src/jack_internal.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "backend.h" 5 | #include "jack_impl.h" 6 | #include "jalv.h" 7 | #include "log.h" 8 | #include "types.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /// Internal Jack client initialization entry point 19 | int 20 | jack_initialize(jack_client_t* client, const char* load_init); 21 | 22 | /// Internal Jack client finalization entry point 23 | void 24 | jack_finish(void* arg); 25 | 26 | int 27 | jack_initialize(jack_client_t* const client, const char* const load_init) 28 | { 29 | #ifndef E2BIG 30 | # define E2BIG 7 31 | #endif 32 | #ifndef ENOMEM 33 | # define ENOMEM 12 34 | #endif 35 | 36 | const size_t args_len = strlen(load_init); 37 | if (args_len > JACK_LOAD_INIT_LIMIT) { 38 | jalv_log(JALV_LOG_ERR, "Too many arguments given\n"); 39 | return E2BIG; 40 | } 41 | 42 | Jalv* const jalv = (Jalv*)calloc(1, sizeof(Jalv)); 43 | if (!jalv) { 44 | return ENOMEM; 45 | } 46 | 47 | if (!(jalv->backend = jalv_backend_allocate())) { 48 | free(jalv); 49 | return ENOMEM; 50 | } 51 | 52 | jalv->backend->client = client; 53 | jalv->backend->is_internal_client = true; 54 | 55 | // Build full command line with "program" name for building argv 56 | const size_t cmd_len = strlen("jalv ") + args_len; 57 | char* const cmd = (char*)calloc(cmd_len + 1, 1); 58 | memcpy(cmd, "jalv ", strlen("jalv ") + 1); 59 | memcpy(cmd + 5, load_init, args_len + 1); 60 | 61 | // Build argv 62 | int argc = 0; 63 | char** argv = NULL; 64 | char* tok = cmd; 65 | int err = 0; 66 | for (size_t i = 0; !err && i <= cmd_len; ++i) { 67 | if (isspace(cmd[i]) || !cmd[i]) { 68 | char** const new_argv = (char**)realloc(argv, sizeof(char*) * ++argc); 69 | if (!new_argv) { 70 | err = ENOMEM; 71 | break; 72 | } 73 | 74 | argv = new_argv; 75 | cmd[i] = '\0'; 76 | argv[argc - 1] = tok; 77 | tok = cmd + i + 1; 78 | } 79 | } 80 | 81 | if (err || (err = jalv_open(jalv, &argc, &argv))) { 82 | jalv_close(jalv); 83 | free(jalv); 84 | } else { 85 | jalv_activate(jalv); 86 | } 87 | 88 | free(argv); 89 | free(cmd); 90 | return err; 91 | 92 | #undef ENOMEM 93 | #undef E2BIG 94 | } 95 | 96 | void 97 | jack_finish(void* const arg) 98 | { 99 | Jalv* const jalv = (Jalv*)arg; 100 | if (jalv) { 101 | jalv_deactivate(jalv); 102 | if (jalv_close(jalv)) { 103 | jalv_log(JALV_LOG_ERR, "Failed to close Jalv\n"); 104 | } 105 | 106 | jalv_backend_free(jalv->backend); 107 | free(jalv); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/jalv.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_JALV_H 5 | #define JALV_JALV_H 6 | 7 | #include "attributes.h" 8 | #include "control.h" 9 | #include "dumper.h" 10 | #include "features.h" 11 | #include "jalv_config.h" 12 | #include "log.h" 13 | #include "mapper.h" 14 | #include "nodes.h" 15 | #include "options.h" 16 | #include "port.h" 17 | #include "process.h" 18 | #include "settings.h" 19 | #include "types.h" 20 | #include "urids.h" 21 | 22 | #if USE_SUIL 23 | # include 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | // "Shared" internal application declarations 37 | JALV_BEGIN_DECLS 38 | 39 | /// Internal application state 40 | struct JalvImpl { 41 | JalvOptions opts; ///< Command-line options 42 | LilvWorld* world; ///< Lilv World 43 | JalvMapper* mapper; ///< URI mapper/unmapper 44 | JalvURIDs urids; ///< URIDs 45 | JalvNodes nodes; ///< Nodes 46 | JalvLog log; ///< Log for error/warning/debug messages 47 | LV2_Atom_Forge forge; ///< Atom forge 48 | JalvDumper* dumper; ///< Atom dumper (console debug output) 49 | JalvBackend* backend; ///< Audio system backend 50 | JalvSettings settings; ///< Processing settings 51 | void* ui_msg; ///< Buffer for messages in the UI thread 52 | ZixSem work_lock; ///< Lock for plugin work() method 53 | ZixSem done; ///< Exit semaphore 54 | char* temp_dir; ///< Temporary plugin state directory 55 | char* save_dir; ///< Plugin save directory 56 | const LilvPlugin* plugin; ///< Plugin class (RDF data) 57 | LilvState* preset; ///< Current preset 58 | LilvUIs* uis; ///< All plugin UIs (RDF data) 59 | const LilvUI* ui; ///< Plugin UI (RDF data) 60 | const LilvNode* ui_type; ///< Plugin UI type (unwrapped) 61 | JalvProcess process; ///< Process thread state 62 | #if USE_SUIL 63 | SuilHost* ui_host; ///< Plugin UI host support 64 | SuilInstance* ui_instance; ///< Plugin UI instance (shared library) 65 | #endif 66 | void* window; ///< Window (if applicable) 67 | JalvPort* ports; ///< Port array of size num_ports 68 | Controls controls; ///< Available plugin controls 69 | size_t ui_msg_size; ///< Maximum size of a single message 70 | uint32_t num_ports; ///< Total number of ports on the plugin 71 | bool safe_restore; ///< Plugin restore() is thread-safe 72 | JalvFeatures features; 73 | const LV2_Feature** feature_list; 74 | }; 75 | 76 | /// Load the plugin and set up the application 77 | int 78 | jalv_open(Jalv* jalv, int* argc, char*** argv); 79 | 80 | /// Shut down the application (counterpart to jalv_open) 81 | int 82 | jalv_close(Jalv* jalv); 83 | 84 | /// Activate audio processing 85 | int 86 | jalv_activate(Jalv* jalv); 87 | 88 | /// Deactivate audio processing 89 | int 90 | jalv_deactivate(Jalv* jalv); 91 | 92 | /// Find a port by symbol 93 | JalvPort* 94 | jalv_port_by_symbol(Jalv* jalv, const char* sym); 95 | 96 | /// Set a control to the given value 97 | void 98 | jalv_set_control(Jalv* jalv, 99 | const ControlID* control, 100 | uint32_t size, 101 | LV2_URID type, 102 | const void* body); 103 | 104 | /// Request and/or set initial control values to initialize the UI 105 | void 106 | jalv_init_ui(Jalv* jalv); 107 | 108 | /// Instantiate the UI instance using suil if available 109 | void 110 | jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent); 111 | 112 | /// Periodically update user interface 113 | int 114 | jalv_update(Jalv* jalv); 115 | 116 | JALV_END_DECLS 117 | 118 | #endif // JALV_JALV_H 119 | -------------------------------------------------------------------------------- /src/jalv_config.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | /* 5 | Configuration header that defines reasonable defaults at compile time. 6 | 7 | This allows compile-time configuration from the command line, while still 8 | allowing the source to be built "as-is" without any configuration. The idea 9 | is to support an advanced build system with configuration checks, while still 10 | allowing the code to be simply "thrown at a compiler" with features 11 | determined from the compiler or system headers. Everything can be 12 | overridden, so it should never be necessary to edit this file to build 13 | successfully. 14 | 15 | To ensure that all configure checks are performed, the build system can 16 | define JALV_NO_DEFAULT_CONFIG to disable defaults. In this case, it must 17 | define all HAVE_FEATURE symbols below to 1 or 0 to enable or disable 18 | features. Any missing definitions will generate a compiler warning. 19 | 20 | To ensure that this header is always included properly, all code that uses 21 | configuration variables includes this header and checks their value with #if 22 | (not #ifdef). Variables like USE_FEATURE are internal and should never be 23 | defined on the command line. 24 | */ 25 | 26 | #ifndef JALV_CONFIG_H 27 | #define JALV_CONFIG_H 28 | 29 | // Define version unconditionally so a warning will catch a mismatch 30 | #define JALV_VERSION "1.6.9" 31 | 32 | #if !defined(JALV_NO_DEFAULT_CONFIG) 33 | 34 | // We need unistd.h to check _POSIX_VERSION 35 | # ifndef JALV_NO_POSIX 36 | # ifdef __has_include 37 | # if __has_include() 38 | # include 39 | # endif 40 | # elif defined(__unix__) 41 | # include 42 | # endif 43 | # endif 44 | 45 | // POSIX.1-2001: fileno() 46 | # ifndef HAVE_FILENO 47 | # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L 48 | # define HAVE_FILENO 1 49 | # else 50 | # define HAVE_FILENO 0 51 | # endif 52 | # endif 53 | 54 | // POSIX.1-2001: isatty() 55 | # ifndef HAVE_ISATTY 56 | # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L 57 | # define HAVE_ISATTY 1 58 | # else 59 | # define HAVE_ISATTY 0 60 | # endif 61 | # endif 62 | 63 | // POSIX.1-2001: posix_memalign() 64 | # ifndef HAVE_POSIX_MEMALIGN 65 | # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L 66 | # define HAVE_POSIX_MEMALIGN 1 67 | # else 68 | # define HAVE_POSIX_MEMALIGN 0 69 | # endif 70 | # endif 71 | 72 | // POSIX.1-2001: sigaction() 73 | # ifndef HAVE_SIGACTION 74 | # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L 75 | # define HAVE_SIGACTION 1 76 | # else 77 | # define HAVE_SIGACTION 0 78 | # endif 79 | # endif 80 | 81 | // Suil 82 | # ifndef HAVE_SUIL 83 | # ifdef __has_include 84 | # if __has_include("suil/suil.h") 85 | # define HAVE_SUIL 1 86 | # else 87 | # define HAVE_SUIL 0 88 | # endif 89 | # endif 90 | # endif 91 | 92 | // JACK metadata API 93 | # ifndef HAVE_JACK_METADATA 94 | # ifdef __has_include 95 | # if __has_include("jack/metadata.h") 96 | # define HAVE_JACK_METADATA 1 97 | # else 98 | # define HAVE_JACK_METADATA 0 99 | # endif 100 | # endif 101 | # endif 102 | 103 | // JACK jack_port_type_get_buffer_size() function 104 | # ifndef HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE 105 | # ifdef __has_include 106 | # if __has_include("jack/midiport.h") 107 | # define HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE 1 108 | # else 109 | # define HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE 0 110 | # endif 111 | # endif 112 | # endif 113 | 114 | #endif // !defined(JALV_NO_DEFAULT_CONFIG) 115 | 116 | /* 117 | Make corresponding USE_FEATURE defines based on the HAVE_FEATURE defines from 118 | above or the command line. The code checks for these using #if (not #ifdef), 119 | so there will be an undefined warning if it checks for an unknown feature, 120 | and this header is always required by any code that checks for features, even 121 | if the build system defines them all. 122 | */ 123 | 124 | #if HAVE_FILENO 125 | # define USE_FILENO 1 126 | #else 127 | # define USE_FILENO 0 128 | #endif 129 | 130 | #if HAVE_ISATTY 131 | # define USE_ISATTY 1 132 | #else 133 | # define USE_ISATTY 0 134 | #endif 135 | 136 | #if HAVE_POSIX_MEMALIGN 137 | # define USE_POSIX_MEMALIGN 1 138 | #else 139 | # define USE_POSIX_MEMALIGN 0 140 | #endif 141 | 142 | #if HAVE_SIGACTION 143 | # define USE_SIGACTION 1 144 | #else 145 | # define USE_SIGACTION 0 146 | #endif 147 | 148 | #if HAVE_SUIL 149 | # define USE_SUIL 1 150 | #else 151 | # define USE_SUIL 0 152 | #endif 153 | 154 | #if HAVE_JACK_METADATA 155 | # define USE_JACK_METADATA 1 156 | #else 157 | # define USE_JACK_METADATA 0 158 | #endif 159 | 160 | #if HAVE_JACK_PORT_TYPE_GET_BUFFER_SIZE 161 | # define USE_JACK_PORT_TYPE_GET_BUFFER_SIZE 1 162 | #else 163 | # define USE_JACK_PORT_TYPE_GET_BUFFER_SIZE 0 164 | #endif 165 | 166 | #endif // JALV_CONFIG_H 167 | -------------------------------------------------------------------------------- /src/jalv_console.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "comm.h" 5 | #include "control.h" 6 | #include "frontend.h" 7 | #include "jalv.h" 8 | #include "jalv_config.h" 9 | #include "log.h" 10 | #include "options.h" 11 | #include "port.h" 12 | #include "state.h" 13 | #include "string_utils.h" 14 | #include "types.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #if USE_SUIL 22 | # include 23 | #endif 24 | 25 | #ifdef _WIN32 26 | # include 27 | #else 28 | # include 29 | #endif 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | static int 39 | print_usage(const char* name, bool error) 40 | { 41 | FILE* const os = error ? stderr : stdout; 42 | fprintf(os, "Usage: %s [OPTION...] PLUGIN_URI\n", name); 43 | fprintf(os, 44 | "Run an LV2 plugin as a Jack application.\n" 45 | " -b SIZE Buffer size for plugin <=> UI communication\n" 46 | " -c SYM=VAL Set control value (like \"vol=1.4\")\n" 47 | " -d Dump plugin <=> UI communication\n" 48 | " -h Display this help and exit\n" 49 | " -i Ignore keyboard input, run non-interactively\n" 50 | " -l DIR Load state from save directory\n" 51 | " -n NAME JACK client name\n" 52 | " -p Print control output changes to stdout\n" 53 | " -s Show plugin UI if possible\n" 54 | " -t Print trace messages from plugin\n" 55 | " -U URI Load the UI with the given URI\n" 56 | " -V Display version information and exit\n" 57 | " -x Exit if the requested JACK client name is taken\n"); 58 | return error ? 1 : JALV_EARLY_EXIT_STATUS; 59 | } 60 | 61 | static int 62 | print_version(void) 63 | { 64 | printf("jalv " JALV_VERSION " \n"); 65 | printf("Copyright 2011-2024 David Robillard \n" 66 | "License ISC: .\n" 67 | "This is free software; you are free to change and redistribute it." 68 | "\nThere is NO WARRANTY, to the extent permitted by law.\n"); 69 | return JALV_EARLY_EXIT_STATUS; 70 | } 71 | 72 | static int 73 | print_arg_error(const char* const command, const char* const msg) 74 | { 75 | fprintf(stderr, "%s: %s\n", command, msg); 76 | return 1; 77 | } 78 | 79 | static void 80 | print_control_port(const Jalv* const jalv, 81 | const JalvPort* const port, 82 | const float value) 83 | { 84 | const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port); 85 | jalv_log(JALV_LOG_INFO, "%s = %f\n", lilv_node_as_string(sym), value); 86 | } 87 | 88 | void 89 | jalv_frontend_port_event(Jalv* jalv, 90 | uint32_t port_index, 91 | uint32_t buffer_size, 92 | uint32_t protocol, 93 | const void* buffer) 94 | { 95 | #if USE_SUIL 96 | if (jalv->ui_instance) { 97 | suil_instance_port_event( 98 | jalv->ui_instance, port_index, buffer_size, protocol, buffer); 99 | } 100 | #else 101 | (void)buffer_size; 102 | #endif 103 | 104 | if (!protocol && jalv->opts.print_controls) { 105 | assert(buffer_size == sizeof(float)); 106 | print_control_port(jalv, &jalv->ports[port_index], *(float*)buffer); 107 | } 108 | } 109 | 110 | int 111 | jalv_frontend_init(JalvFrontendArgs* const args, JalvOptions* const opts) 112 | { 113 | const int argc = *args->argc; 114 | char** argv = *args->argv; 115 | 116 | const char* const cmd = argv[0]; 117 | 118 | int n_controls = 0; 119 | int a = 1; 120 | for (; a < argc && argv[a][0] == '-'; ++a) { 121 | if (argv[a][1] == 'h' || !strcmp(argv[a], "--help")) { 122 | return print_usage(cmd, false); 123 | } 124 | 125 | if (argv[a][1] == 'V' || !strcmp(argv[a], "--version")) { 126 | return print_version(); 127 | } 128 | 129 | if (argv[a][1] == 's') { 130 | opts->show_ui = true; 131 | } else if (argv[a][1] == 'p') { 132 | opts->print_controls = true; 133 | } else if (argv[a][1] == 'U') { 134 | if (++a == argc) { 135 | return print_arg_error(cmd, "option requires an argument -- 'U'"); 136 | } 137 | opts->ui_uri = jalv_strdup(argv[a]); 138 | } else if (argv[a][1] == 'l') { 139 | if (++a == argc) { 140 | return print_arg_error(cmd, "option requires an argument -- 'l'"); 141 | } 142 | opts->load = jalv_strdup(argv[a]); 143 | } else if (argv[a][1] == 'b') { 144 | if (++a == argc) { 145 | return print_arg_error(cmd, "option requires an argument -- 'b'"); 146 | } 147 | opts->ring_size = atoi(argv[a]); 148 | } else if (argv[a][1] == 'c') { 149 | if (++a == argc) { 150 | return print_arg_error(cmd, "option requires an argument -- 'c'"); 151 | } 152 | 153 | char** new_controls = 154 | (char**)realloc(opts->controls, (n_controls + 2) * sizeof(char*)); 155 | if (!new_controls) { 156 | fprintf(stderr, "Out of memory\n"); 157 | return 12; 158 | } 159 | 160 | opts->controls = new_controls; 161 | opts->controls[n_controls++] = argv[a]; 162 | opts->controls[n_controls] = NULL; 163 | } else if (argv[a][1] == 'i') { 164 | opts->non_interactive = true; 165 | } else if (argv[a][1] == 'd') { 166 | opts->dump = true; 167 | } else if (argv[a][1] == 't') { 168 | opts->trace = true; 169 | } else if (argv[a][1] == 'n') { 170 | if (++a == argc) { 171 | return print_arg_error(cmd, "option requires an argument -- 'n'"); 172 | } 173 | free(opts->name); 174 | opts->name = jalv_strdup(argv[a]); 175 | } else if (argv[a][1] == 'x') { 176 | opts->name_exact = 1; 177 | } else { 178 | fprintf(stderr, "%s: unknown option -- '%c'\n", cmd, argv[a][1]); 179 | return print_usage(argv[0], true); 180 | } 181 | } 182 | 183 | *args->argc = *args->argc - a; 184 | *args->argv = *args->argv + a; 185 | return 0; 186 | } 187 | 188 | const char* 189 | jalv_frontend_ui_type(void) 190 | { 191 | return NULL; 192 | } 193 | 194 | static void 195 | print_controls(const Jalv* const jalv, const bool writable, const bool readable) 196 | { 197 | for (size_t i = 0; i < jalv->controls.n_controls; ++i) { 198 | ControlID* const control = jalv->controls.controls[i]; 199 | if (control->type == PORT && ((control->is_writable && writable) || 200 | (control->is_readable && readable))) { 201 | jalv_log(JALV_LOG_INFO, 202 | "%s = %f\n", 203 | lilv_node_as_string(control->symbol), 204 | jalv->process.controls_buf[control->id.index]); 205 | } 206 | } 207 | 208 | fflush(stdout); 209 | } 210 | 211 | static int 212 | jalv_print_preset(Jalv* ZIX_UNUSED(jalv), 213 | const LilvNode* node, 214 | const LilvNode* title, 215 | void* ZIX_UNUSED(data)) 216 | { 217 | printf("%s (%s)\n", lilv_node_as_string(node), lilv_node_as_string(title)); 218 | return 0; 219 | } 220 | 221 | static void 222 | jalv_process_command(Jalv* jalv, const char* cmd) 223 | { 224 | char sym[1024]; 225 | uint32_t index = 0; 226 | float value = 0.0f; 227 | if (!strncmp(cmd, "help", 4)) { 228 | fprintf(stderr, 229 | "Commands:\n" 230 | " help Display this help message\n" 231 | " controls Print settable control values\n" 232 | " monitors Print output control values\n" 233 | " presets Print available presets\n" 234 | " preset URI Set preset\n" 235 | " set INDEX VALUE Set control value by port index\n" 236 | " set SYMBOL VALUE Set control value by symbol\n" 237 | " SYMBOL = VALUE Set control value by symbol\n"); 238 | } else if (strcmp(cmd, "presets\n") == 0) { 239 | jalv_unload_presets(jalv); 240 | jalv_load_presets(jalv, jalv_print_preset, NULL); 241 | } else if (sscanf(cmd, "preset %1023[a-zA-Z0-9_:/-.#]\n", sym) == 1) { 242 | LilvNode* preset = lilv_new_uri(jalv->world, sym); 243 | lilv_world_load_resource(jalv->world, preset); 244 | jalv_apply_preset(jalv, preset); 245 | lilv_node_free(preset); 246 | print_controls(jalv, true, false); 247 | } else if (strcmp(cmd, "controls\n") == 0) { 248 | print_controls(jalv, true, false); 249 | } else if (strcmp(cmd, "monitors\n") == 0) { 250 | print_controls(jalv, false, true); 251 | } else if (sscanf(cmd, "set %u %f", &index, &value) == 2) { 252 | if (index < jalv->num_ports) { 253 | jalv_write_control(jalv->process.ui_to_plugin, index, value); 254 | print_control_port(jalv, &jalv->ports[index], value); 255 | } else { 256 | fprintf(stderr, "error: port index out of range\n"); 257 | } 258 | } else if (sscanf(cmd, "set %1023[a-zA-Z0-9_] %f", sym, &value) == 2 || 259 | sscanf(cmd, "%1023[a-zA-Z0-9_] = %f", sym, &value) == 2) { 260 | const JalvPort* const port = jalv_port_by_symbol(jalv, sym); 261 | if (port) { 262 | jalv->process.controls_buf[port->index] = value; 263 | print_control_port(jalv, port, value); 264 | } else { 265 | fprintf(stderr, "error: no control named `%s'\n", sym); 266 | } 267 | } else { 268 | fprintf(stderr, "error: invalid command (try `help')\n"); 269 | } 270 | } 271 | 272 | bool 273 | jalv_frontend_discover(const Jalv* jalv) 274 | { 275 | return jalv->opts.show_ui; 276 | } 277 | 278 | static bool 279 | jalv_run_custom_ui(Jalv* jalv) 280 | { 281 | #if USE_SUIL 282 | const LV2UI_Idle_Interface* idle_iface = NULL; 283 | const LV2UI_Show_Interface* show_iface = NULL; 284 | if (jalv->ui && jalv->opts.show_ui) { 285 | jalv_ui_instantiate(jalv, jalv_frontend_ui_type(), NULL); 286 | idle_iface = (const LV2UI_Idle_Interface*)suil_instance_extension_data( 287 | jalv->ui_instance, LV2_UI__idleInterface); 288 | show_iface = (const LV2UI_Show_Interface*)suil_instance_extension_data( 289 | jalv->ui_instance, LV2_UI__showInterface); 290 | } 291 | 292 | if (show_iface && idle_iface) { 293 | show_iface->show(suil_instance_get_handle(jalv->ui_instance)); 294 | 295 | // Drive idle interface until interrupted 296 | while (zix_sem_try_wait(&jalv->done)) { 297 | jalv_update(jalv); 298 | if (idle_iface->idle(suil_instance_get_handle(jalv->ui_instance))) { 299 | break; 300 | } 301 | 302 | # ifdef _WIN32 303 | Sleep(33); 304 | # else 305 | const struct timespec delay = {0, 33333000}; 306 | nanosleep(&delay, NULL); 307 | # endif 308 | } 309 | 310 | show_iface->hide(suil_instance_get_handle(jalv->ui_instance)); 311 | return true; 312 | } 313 | #else 314 | (void)jalv; 315 | #endif 316 | 317 | return false; 318 | } 319 | 320 | float 321 | jalv_frontend_refresh_rate(const Jalv* ZIX_UNUSED(jalv)) 322 | { 323 | return 30.0f; 324 | } 325 | 326 | float 327 | jalv_frontend_scale_factor(const Jalv* ZIX_UNUSED(jalv)) 328 | { 329 | return 1.0f; 330 | } 331 | 332 | LilvNode* 333 | jalv_frontend_select_plugin(Jalv* jalv) 334 | { 335 | (void)jalv; 336 | return NULL; 337 | } 338 | 339 | int 340 | jalv_frontend_open(Jalv* jalv) 341 | { 342 | // Print initial control values 343 | for (size_t i = 0; i < jalv->controls.n_controls; ++i) { 344 | ControlID* control = jalv->controls.controls[i]; 345 | if (control->type == PORT && control->is_writable) { 346 | const JalvPort* const port = &jalv->ports[control->id.index]; 347 | print_control_port( 348 | jalv, port, jalv->process.controls_buf[control->id.index]); 349 | } 350 | } 351 | 352 | if (!jalv_run_custom_ui(jalv) && !jalv->opts.non_interactive) { 353 | // Primitive command prompt for setting control values 354 | while (zix_sem_try_wait(&jalv->done)) { 355 | char line[1024]; 356 | printf("> "); 357 | if (fgets(line, sizeof(line), stdin)) { 358 | jalv_process_command(jalv, line); 359 | } else { 360 | break; 361 | } 362 | } 363 | } else { 364 | zix_sem_wait(&jalv->done); 365 | } 366 | 367 | // Caller waits on the done sem, so increment it again to exit 368 | zix_sem_post(&jalv->done); 369 | 370 | return 0; 371 | } 372 | 373 | int 374 | jalv_frontend_close(Jalv* jalv) 375 | { 376 | zix_sem_post(&jalv->done); 377 | return 0; 378 | } 379 | -------------------------------------------------------------------------------- /src/jalv_qt.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "port.h" 5 | #include "state.h" 6 | #include "types.h" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | class QDial; 20 | class QLabel; 21 | class QWidget; 22 | 23 | class PresetAction final : public QAction 24 | { 25 | Q_OBJECT // NOLINT 26 | 27 | public: 28 | PresetAction(QObject* parent, Jalv* jalv, LilvNode* preset) 29 | : QAction(parent) 30 | , _jalv(jalv) 31 | , _preset(preset) 32 | { 33 | connect(this, SIGNAL(triggered()), this, SLOT(presetChosen())); 34 | } 35 | 36 | Q_SLOT void presetChosen() { jalv_apply_preset(_jalv, _preset); } 37 | 38 | private: 39 | Jalv* _jalv; 40 | LilvNode* _preset; 41 | }; 42 | 43 | struct PortContainer { 44 | Jalv* jalv; 45 | JalvPort* port; 46 | }; 47 | 48 | class Control final : public QGroupBox 49 | { 50 | Q_OBJECT // NOLINT 51 | 52 | public: 53 | explicit Control(PortContainer portContainer, QWidget* parent); 54 | 55 | Q_SLOT void dialChanged(int value); 56 | 57 | void setValue(float value); 58 | 59 | private: 60 | void setRange(float min, float max); 61 | QString getValueLabel(float value); 62 | float getValue(); 63 | int stringWidth(const QString& str); 64 | 65 | QDial* _dial; 66 | Jalv* _jalv; 67 | JalvPort* _port; 68 | 69 | QLabel* _label; 70 | QString _name; 71 | int _steps; 72 | float _max{1.0f}; 73 | float _min{0.0f}; 74 | bool _isInteger{}; 75 | bool _isEnum{}; 76 | bool _isLogarithmic{}; 77 | 78 | std::vector _scalePoints; 79 | std::map _scaleMap; 80 | }; 81 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "log.h" 5 | 6 | #include "jalv_config.h" 7 | 8 | #include 9 | #include 10 | 11 | #if USE_ISATTY 12 | # include 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | JALV_LOG_FUNC(2, 0) 20 | static int 21 | jalv_vlog(const JalvLogLevel level, const char* const fmt, va_list ap) 22 | { 23 | bool fancy = false; 24 | switch (level) { 25 | case JALV_LOG_ERR: 26 | fancy = jalv_ansi_start(stderr, 31); 27 | fprintf(stderr, "error: "); 28 | break; 29 | case JALV_LOG_WARNING: 30 | fancy = jalv_ansi_start(stderr, 33); 31 | fprintf(stderr, "warning: "); 32 | break; 33 | case JALV_LOG_INFO: 34 | break; 35 | case JALV_LOG_DEBUG: 36 | fancy = jalv_ansi_start(stderr, 32); 37 | fprintf(stderr, "trace: "); 38 | break; 39 | } 40 | 41 | const int st = vfprintf(stderr, fmt, ap); 42 | 43 | if (fancy) { 44 | jalv_ansi_reset(stderr); 45 | } 46 | 47 | return st; 48 | } 49 | 50 | int 51 | jalv_log(const JalvLogLevel level, const char* const fmt, ...) 52 | { 53 | va_list args; 54 | va_start(args, fmt); 55 | 56 | const int ret = jalv_vlog(level, fmt, args); 57 | 58 | va_end(args); 59 | return ret; 60 | } 61 | 62 | int 63 | jalv_vprintf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, va_list ap) 64 | { 65 | JalvLog* const log = (JalvLog*)handle; 66 | 67 | if (type == log->urids->log_Trace) { 68 | return log->tracing ? jalv_vlog(JALV_LOG_DEBUG, fmt, ap) : 0; 69 | } 70 | 71 | if (type == log->urids->log_Error) { 72 | return jalv_vlog(JALV_LOG_ERR, fmt, ap); 73 | } 74 | 75 | if (type == log->urids->log_Warning) { 76 | return jalv_vlog(JALV_LOG_WARNING, fmt, ap); 77 | } 78 | 79 | return vfprintf(stderr, fmt, ap); 80 | } 81 | 82 | int 83 | jalv_printf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...) 84 | { 85 | va_list args; 86 | va_start(args, fmt); 87 | const int ret = jalv_vprintf(handle, type, fmt, args); 88 | va_end(args); 89 | return ret; 90 | } 91 | 92 | bool 93 | jalv_ansi_start(FILE* stream, int color) 94 | { 95 | #if USE_ISATTY && USE_FILENO 96 | if (isatty(fileno(stream))) { 97 | return fprintf(stream, "\033[0;%dm", color); 98 | } 99 | #else 100 | (void)stream; 101 | (void)color; 102 | #endif 103 | return 0; 104 | } 105 | 106 | void 107 | jalv_ansi_reset(FILE* stream) 108 | { 109 | #if USE_ISATTY 110 | if (isatty(fileno(stream))) { 111 | fprintf(stream, "\033[0m"); 112 | fflush(stream); 113 | } 114 | #else 115 | (void)stream; 116 | #endif 117 | } 118 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_LOG_H 5 | #define JALV_LOG_H 6 | 7 | #include "attributes.h" 8 | #include "urids.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef __GNUC__ 18 | # define JALV_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) 19 | #else 20 | # define JALV_LOG_FUNC(fmt, arg1) 21 | #endif 22 | 23 | // String and log utilities 24 | JALV_BEGIN_DECLS 25 | 26 | typedef enum { 27 | JALV_LOG_ERR = 3, 28 | JALV_LOG_WARNING = 4, 29 | JALV_LOG_INFO = 6, 30 | JALV_LOG_DEBUG = 7, 31 | } JalvLogLevel; 32 | 33 | typedef struct { 34 | JalvURIDs* urids; 35 | bool tracing; 36 | } JalvLog; 37 | 38 | /// Print a log message to stderr with a GCC-like prefix and color 39 | JALV_LOG_FUNC(2, 3) 40 | int 41 | jalv_log(JalvLogLevel level, const char* fmt, ...); 42 | 43 | /// LV2 log vprintf function 44 | JALV_LOG_FUNC(3, 0) 45 | int 46 | jalv_vprintf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, va_list ap); 47 | 48 | /// LV2 log printf function 49 | JALV_LOG_FUNC(3, 4) 50 | int 51 | jalv_printf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...); 52 | 53 | /// Write an ANSI escape sequence to set the foreground color 54 | bool 55 | jalv_ansi_start(FILE* stream, int color); 56 | 57 | /// Write an ANSI escape sequence to reset the foreground color 58 | void 59 | jalv_ansi_reset(FILE* stream); 60 | 61 | JALV_END_DECLS 62 | 63 | #endif // JALV_LOG_H 64 | -------------------------------------------------------------------------------- /src/lv2_evbuf.c: -------------------------------------------------------------------------------- 1 | // Copyright 2008-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "lv2_evbuf.h" 5 | #include "jalv_config.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | struct LV2_Evbuf_Impl { 15 | uint32_t capacity; 16 | uint32_t atom_Chunk; 17 | uint32_t atom_Sequence; 18 | uint32_t pad; // So buf has correct atom alignment 19 | LV2_Atom_Sequence buf; 20 | }; 21 | 22 | LV2_Evbuf* 23 | lv2_evbuf_new(uint32_t capacity, uint32_t atom_Chunk, uint32_t atom_Sequence) 24 | { 25 | const size_t buffer_size = 26 | sizeof(LV2_Evbuf) + sizeof(LV2_Atom_Sequence) + capacity; 27 | 28 | #if USE_POSIX_MEMALIGN 29 | LV2_Evbuf* evbuf = NULL; 30 | const int st = posix_memalign((void**)&evbuf, 16, buffer_size); 31 | if (st) { 32 | return NULL; 33 | } 34 | 35 | assert((uintptr_t)evbuf % 8U == 0U); 36 | #else 37 | LV2_Evbuf* evbuf = (LV2_Evbuf*)malloc(buffer_size); 38 | #endif 39 | 40 | if (evbuf) { 41 | memset(evbuf, 0, sizeof(*evbuf)); 42 | evbuf->capacity = capacity; 43 | evbuf->atom_Chunk = atom_Chunk; 44 | evbuf->atom_Sequence = atom_Sequence; 45 | } 46 | 47 | return evbuf; 48 | } 49 | 50 | void 51 | lv2_evbuf_free(LV2_Evbuf* evbuf) 52 | { 53 | if (evbuf) { 54 | free(evbuf); 55 | } 56 | } 57 | 58 | void 59 | lv2_evbuf_reset(LV2_Evbuf* evbuf, bool input) 60 | { 61 | if (input) { 62 | evbuf->buf.atom.size = sizeof(LV2_Atom_Sequence_Body); 63 | evbuf->buf.atom.type = evbuf->atom_Sequence; 64 | } else { 65 | evbuf->buf.atom.size = evbuf->capacity; 66 | evbuf->buf.atom.type = evbuf->atom_Chunk; 67 | } 68 | } 69 | 70 | uint32_t 71 | lv2_evbuf_get_size(LV2_Evbuf* evbuf) 72 | { 73 | assert(evbuf->buf.atom.type != evbuf->atom_Sequence || 74 | evbuf->buf.atom.size >= sizeof(LV2_Atom_Sequence_Body)); 75 | return evbuf->buf.atom.type == evbuf->atom_Sequence 76 | ? evbuf->buf.atom.size - sizeof(LV2_Atom_Sequence_Body) 77 | : 0; 78 | } 79 | 80 | void* 81 | lv2_evbuf_get_buffer(LV2_Evbuf* evbuf) 82 | { 83 | return &evbuf->buf; 84 | } 85 | 86 | LV2_Evbuf_Iterator 87 | lv2_evbuf_begin(LV2_Evbuf* evbuf) 88 | { 89 | LV2_Evbuf_Iterator iter = {evbuf, 0}; 90 | return iter; 91 | } 92 | 93 | LV2_Evbuf_Iterator 94 | lv2_evbuf_end(LV2_Evbuf* evbuf) 95 | { 96 | const uint32_t size = lv2_evbuf_get_size(evbuf); 97 | const LV2_Evbuf_Iterator iter = {evbuf, lv2_atom_pad_size(size)}; 98 | return iter; 99 | } 100 | 101 | bool 102 | lv2_evbuf_is_valid(LV2_Evbuf_Iterator iter) 103 | { 104 | return iter.offset < lv2_evbuf_get_size(iter.evbuf); 105 | } 106 | 107 | LV2_Evbuf_Iterator 108 | lv2_evbuf_next(const LV2_Evbuf_Iterator iter) 109 | { 110 | if (!lv2_evbuf_is_valid(iter)) { 111 | return iter; 112 | } 113 | 114 | LV2_Atom_Sequence* aseq = &iter.evbuf->buf; 115 | const char* abuf = (const char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq); 116 | const LV2_Atom_Event* aev = (const LV2_Atom_Event*)(abuf + iter.offset); 117 | 118 | const uint32_t offset = 119 | iter.offset + lv2_atom_pad_size(sizeof(LV2_Atom_Event) + aev->body.size); 120 | 121 | LV2_Evbuf_Iterator next = {iter.evbuf, offset}; 122 | return next; 123 | } 124 | 125 | bool 126 | lv2_evbuf_get(LV2_Evbuf_Iterator iter, 127 | uint32_t* frames, 128 | uint32_t* subframes, 129 | uint32_t* type, 130 | uint32_t* size, 131 | void** data) 132 | { 133 | *frames = *subframes = *type = *size = 0; 134 | *data = NULL; 135 | 136 | if (!lv2_evbuf_is_valid(iter)) { 137 | return false; 138 | } 139 | 140 | LV2_Atom_Sequence* aseq = &iter.evbuf->buf; 141 | LV2_Atom_Event* aev = 142 | (LV2_Atom_Event*)((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + 143 | iter.offset); 144 | 145 | *frames = aev->time.frames; 146 | *subframes = 0; 147 | *type = aev->body.type; 148 | *size = aev->body.size; 149 | *data = LV2_ATOM_BODY(&aev->body); 150 | 151 | return true; 152 | } 153 | 154 | bool 155 | lv2_evbuf_write(LV2_Evbuf_Iterator* iter, 156 | uint32_t frames, 157 | uint32_t subframes, 158 | uint32_t type, 159 | uint32_t size, 160 | const void* data) 161 | { 162 | (void)subframes; 163 | 164 | LV2_Atom_Sequence* aseq = &iter->evbuf->buf; 165 | if (iter->evbuf->capacity - sizeof(LV2_Atom) - aseq->atom.size < 166 | sizeof(LV2_Atom_Event) + size) { 167 | return false; 168 | } 169 | 170 | LV2_Atom_Event* aev = 171 | (LV2_Atom_Event*)((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + 172 | iter->offset); 173 | 174 | aev->time.frames = frames; 175 | aev->body.type = type; 176 | aev->body.size = size; 177 | memcpy(LV2_ATOM_BODY(&aev->body), data, size); 178 | 179 | size = lv2_atom_pad_size(sizeof(LV2_Atom_Event) + size); 180 | aseq->atom.size += size; 181 | iter->offset += size; 182 | 183 | return true; 184 | } 185 | -------------------------------------------------------------------------------- /src/lv2_evbuf.h: -------------------------------------------------------------------------------- 1 | // Copyright 2008-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef LV2_EVBUF_H 5 | #define LV2_EVBUF_H 6 | 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #else 12 | # include 13 | #endif 14 | 15 | /// An abstract/opaque LV2 event buffer 16 | typedef struct LV2_Evbuf_Impl LV2_Evbuf; 17 | 18 | /// An iterator over an LV2_Evbuf 19 | typedef struct { 20 | LV2_Evbuf* evbuf; 21 | uint32_t offset; 22 | } LV2_Evbuf_Iterator; 23 | 24 | /** 25 | Allocate a new, empty event buffer. 26 | 27 | URIDs for atom:Chunk and atom:Sequence must be passed for LV2_EVBUF_ATOM. 28 | */ 29 | LV2_Evbuf* 30 | lv2_evbuf_new(uint32_t capacity, uint32_t atom_Chunk, uint32_t atom_Sequence); 31 | 32 | /// Free an event buffer allocated with lv2_evbuf_new 33 | void 34 | lv2_evbuf_free(LV2_Evbuf* evbuf); 35 | 36 | /** 37 | Clear and initialize an existing event buffer. 38 | 39 | The contents of buf are ignored entirely and overwritten, except capacity 40 | which is unmodified. If input is false and this is an atom buffer, the 41 | buffer will be prepared for writing by the plugin. This MUST be called 42 | before every run cycle. 43 | */ 44 | void 45 | lv2_evbuf_reset(LV2_Evbuf* evbuf, bool input); 46 | 47 | /// Return the total padded size of the events stored in the buffer 48 | uint32_t 49 | lv2_evbuf_get_size(LV2_Evbuf* evbuf); 50 | 51 | /** 52 | Return the actual buffer implementation. 53 | 54 | The format of the buffer returned depends on the buffer type. 55 | */ 56 | void* 57 | lv2_evbuf_get_buffer(LV2_Evbuf* evbuf); 58 | 59 | /// Return an iterator to the start of `evbuf` 60 | LV2_Evbuf_Iterator 61 | lv2_evbuf_begin(LV2_Evbuf* evbuf); 62 | 63 | /// Return an iterator to the end of `evbuf` 64 | LV2_Evbuf_Iterator 65 | lv2_evbuf_end(LV2_Evbuf* evbuf); 66 | 67 | /** 68 | Check if `iter` is valid. 69 | 70 | @return True if `iter` is valid, otherwise false (past end of buffer) 71 | */ 72 | bool 73 | lv2_evbuf_is_valid(LV2_Evbuf_Iterator iter); 74 | 75 | /** 76 | Advance `iter` forward one event. 77 | 78 | `iter` must be valid. 79 | 80 | @return True if `iter` is valid, otherwise false (reached end of buffer) 81 | */ 82 | LV2_Evbuf_Iterator 83 | lv2_evbuf_next(LV2_Evbuf_Iterator iter); 84 | 85 | /** 86 | Dereference an event iterator (i.e. get the event currently pointed to). 87 | 88 | `iter` must be valid. 89 | `type` Set to the type of the event. 90 | `size` Set to the size of the event. 91 | `data` Set to the contents of the event. 92 | @return True on success. 93 | */ 94 | bool 95 | lv2_evbuf_get(LV2_Evbuf_Iterator iter, 96 | uint32_t* frames, 97 | uint32_t* subframes, 98 | uint32_t* type, 99 | uint32_t* size, 100 | void** data); 101 | 102 | /** 103 | Write an event at `iter`. 104 | 105 | The event (if any) pointed to by `iter` will be overwritten, and `iter` 106 | incremented to point to the following event (i.e. several calls to this 107 | function can be done in sequence without twiddling iter in-between). 108 | 109 | @return True if event was written, otherwise false (buffer is full). 110 | */ 111 | bool 112 | lv2_evbuf_write(LV2_Evbuf_Iterator* iter, 113 | uint32_t frames, 114 | uint32_t subframes, 115 | uint32_t type, 116 | uint32_t size, 117 | const void* data); 118 | 119 | #ifdef __cplusplus 120 | } 121 | #endif 122 | 123 | #endif // LV2_EVBUF_H 124 | -------------------------------------------------------------------------------- /src/macros.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_MACROS_H 5 | #define JALV_MACROS_H 6 | 7 | #ifndef MIN 8 | # define MIN(a, b) (((a) < (b)) ? (a) : (b)) 9 | #endif 10 | 11 | #ifndef MAX 12 | # define MAX(a, b) (((a) > (b)) ? (a) : (b)) 13 | #endif 14 | 15 | #ifndef ARRAY_SIZE 16 | # define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 17 | #endif 18 | 19 | #endif // JALV_MACROS_H 20 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "backend.h" 5 | #include "frontend.h" 6 | #include "jalv.h" 7 | #include "jalv_config.h" 8 | #include "types.h" 9 | 10 | #include 11 | #include 12 | 13 | #if USE_SUIL 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | static ZixSem* exit_sem = NULL; ///< Exit semaphore used by signal handler 22 | 23 | static void 24 | signal_handler(int ZIX_UNUSED(sig)) 25 | { 26 | zix_sem_post(exit_sem); 27 | } 28 | 29 | static void 30 | setup_signals(Jalv* const jalv) 31 | { 32 | exit_sem = &jalv->done; 33 | 34 | #if !defined(_WIN32) && USE_SIGACTION 35 | struct sigaction action; 36 | sigemptyset(&action.sa_mask); 37 | action.sa_flags = 0; 38 | action.sa_handler = signal_handler; 39 | sigaction(SIGINT, &action, NULL); 40 | sigaction(SIGTERM, &action, NULL); 41 | #else 42 | // May not work in combination with fgets in the console interface 43 | signal(SIGINT, signal_handler); 44 | signal(SIGTERM, signal_handler); 45 | #endif 46 | } 47 | 48 | int 49 | main(int argc, char** argv) 50 | { 51 | Jalv jalv; 52 | memset(&jalv, '\0', sizeof(Jalv)); 53 | jalv.backend = jalv_backend_allocate(); 54 | 55 | // Initialize application 56 | const int orc = jalv_open(&jalv, &argc, &argv); 57 | if (orc) { 58 | jalv_close(&jalv); 59 | return orc == JALV_EARLY_EXIT_STATUS ? EXIT_SUCCESS : EXIT_FAILURE; 60 | } 61 | 62 | // Set up signal handlers and activate audio processing 63 | setup_signals(&jalv); 64 | jalv_activate(&jalv); 65 | 66 | // Run UI (or prompt at console) 67 | jalv_frontend_open(&jalv); 68 | 69 | // Wait for finish signal from UI or signal handler 70 | zix_sem_wait(&jalv.done); 71 | 72 | // Deactivate audio processing and tear down application 73 | jalv_deactivate(&jalv); 74 | const int crc = jalv_close(&jalv); 75 | jalv_backend_free(jalv.backend); 76 | return crc; 77 | } 78 | -------------------------------------------------------------------------------- /src/mapper.c: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "mapper.h" 5 | 6 | #include "symap.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct JalvMapperImpl { 15 | Symap* symap; 16 | ZixSem lock; 17 | LV2_URID_Map map; 18 | LV2_URID_Unmap unmap; 19 | }; 20 | 21 | static LV2_URID 22 | map_uri(LV2_URID_Map_Handle handle, const char* const uri) 23 | { 24 | JalvMapper* const mapper = (JalvMapper*)handle; 25 | zix_sem_wait(&mapper->lock); 26 | 27 | const LV2_URID id = symap_map(mapper->symap, uri); 28 | 29 | zix_sem_post(&mapper->lock); 30 | return id; 31 | } 32 | 33 | static const char* 34 | unmap_uri(LV2_URID_Unmap_Handle handle, const LV2_URID urid) 35 | { 36 | JalvMapper* const mapper = (JalvMapper*)handle; 37 | zix_sem_wait(&mapper->lock); 38 | 39 | const char* const uri = symap_unmap(mapper->symap, urid); 40 | 41 | zix_sem_post(&mapper->lock); 42 | return uri; 43 | } 44 | 45 | JalvMapper* 46 | jalv_mapper_new(void) 47 | { 48 | JalvMapper* const mapper = (JalvMapper*)calloc(1, sizeof(JalvMapper)); 49 | if (mapper) { 50 | mapper->symap = symap_new(); 51 | mapper->map.handle = mapper; 52 | mapper->map.map = map_uri; 53 | mapper->unmap.handle = mapper; 54 | mapper->unmap.unmap = unmap_uri; 55 | zix_sem_init(&mapper->lock, 1); 56 | } 57 | return mapper; 58 | } 59 | 60 | void 61 | jalv_mapper_free(JalvMapper* const mapper) 62 | { 63 | if (mapper) { 64 | zix_sem_destroy(&mapper->lock); 65 | symap_free(mapper->symap); 66 | free(mapper); 67 | } 68 | } 69 | 70 | LV2_URID_Map* 71 | jalv_mapper_urid_map(JalvMapper* const mapper) 72 | { 73 | return mapper ? &mapper->map : NULL; 74 | } 75 | 76 | LV2_URID_Unmap* 77 | jalv_mapper_urid_unmap(JalvMapper* const mapper) 78 | { 79 | return mapper ? &mapper->unmap : NULL; 80 | } 81 | 82 | LV2_URID 83 | jalv_mapper_map_uri(JalvMapper* const mapper, const char* const sym) 84 | { 85 | return symap_map(mapper->symap, sym); 86 | } 87 | 88 | const char* 89 | jalv_mapper_unmap_uri(const JalvMapper* mapper, uint32_t id) 90 | { 91 | return mapper ? symap_unmap(mapper->symap, id) : NULL; 92 | } 93 | -------------------------------------------------------------------------------- /src/mapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_MAPPER_H 5 | #define JALV_MAPPER_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | #include 11 | 12 | // URI to URID mapping and unmapping 13 | JALV_BEGIN_DECLS 14 | 15 | /// Opaque URI mapper implementation 16 | typedef struct JalvMapperImpl JalvMapper; 17 | 18 | /// Allocate, configure, and return a new URI mapper 19 | JalvMapper* 20 | jalv_mapper_new(void); 21 | 22 | /// Free memory allocated by jalv_mapper_new() 23 | void 24 | jalv_mapper_free(JalvMapper* mapper); 25 | 26 | /// Return a pointer to the mapper's LV2 URID map 27 | LV2_URID_Map* 28 | jalv_mapper_urid_map(JalvMapper* mapper); 29 | 30 | /// Return a pointer to the mapper's LV2 URID unmap 31 | LV2_URID_Unmap* 32 | jalv_mapper_urid_unmap(JalvMapper* mapper); 33 | 34 | /// Map a URI string to a URID 35 | LV2_URID 36 | jalv_mapper_map_uri(JalvMapper* mapper, const char* sym); 37 | 38 | /// Unmap a URID back to a URI string if possible, or return NULL 39 | ZIX_PURE_FUNC const char* 40 | jalv_mapper_unmap_uri(const JalvMapper* mapper, LV2_URID id); 41 | 42 | JALV_END_DECLS 43 | 44 | #endif // JALV_MAPPER_H 45 | -------------------------------------------------------------------------------- /src/nodes.c: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "nodes.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | void 21 | jalv_init_nodes(LilvWorld* const world, JalvNodes* const nodes) 22 | { 23 | #define MAP_NODE(uri) lilv_new_uri(world, (uri)) 24 | 25 | nodes->atom_AtomPort = MAP_NODE(LV2_ATOM__AtomPort); 26 | nodes->atom_Chunk = MAP_NODE(LV2_ATOM__Chunk); 27 | nodes->atom_Float = MAP_NODE(LV2_ATOM__Float); 28 | nodes->atom_Path = MAP_NODE(LV2_ATOM__Path); 29 | nodes->atom_Sequence = MAP_NODE(LV2_ATOM__Sequence); 30 | nodes->lv2_AudioPort = MAP_NODE(LV2_CORE__AudioPort); 31 | nodes->lv2_CVPort = MAP_NODE(LV2_CORE__CVPort); 32 | nodes->lv2_ControlPort = MAP_NODE(LV2_CORE__ControlPort); 33 | nodes->lv2_InputPort = MAP_NODE(LV2_CORE__InputPort); 34 | nodes->lv2_OutputPort = MAP_NODE(LV2_CORE__OutputPort); 35 | nodes->lv2_connectionOptional = MAP_NODE(LV2_CORE__connectionOptional); 36 | nodes->lv2_control = MAP_NODE(LV2_CORE__control); 37 | nodes->lv2_default = MAP_NODE(LV2_CORE__default); 38 | nodes->lv2_designation = MAP_NODE(LV2_CORE__designation); 39 | nodes->lv2_enumeration = MAP_NODE(LV2_CORE__enumeration); 40 | nodes->lv2_extensionData = MAP_NODE(LV2_CORE__extensionData); 41 | nodes->lv2_integer = MAP_NODE(LV2_CORE__integer); 42 | nodes->lv2_latency = MAP_NODE(LV2_CORE__latency); 43 | nodes->lv2_maximum = MAP_NODE(LV2_CORE__maximum); 44 | nodes->lv2_minimum = MAP_NODE(LV2_CORE__minimum); 45 | nodes->lv2_name = MAP_NODE(LV2_CORE__name); 46 | nodes->lv2_reportsLatency = MAP_NODE(LV2_CORE__reportsLatency); 47 | nodes->lv2_sampleRate = MAP_NODE(LV2_CORE__sampleRate); 48 | nodes->lv2_symbol = MAP_NODE(LV2_CORE__symbol); 49 | nodes->lv2_toggled = MAP_NODE(LV2_CORE__toggled); 50 | nodes->midi_MidiEvent = MAP_NODE(LV2_MIDI__MidiEvent); 51 | nodes->pg_group = MAP_NODE(LV2_PORT_GROUPS__group); 52 | nodes->pprops_logarithmic = MAP_NODE(LV2_PORT_PROPS__logarithmic); 53 | nodes->pprops_notOnGUI = MAP_NODE(LV2_PORT_PROPS__notOnGUI); 54 | nodes->pprops_rangeSteps = MAP_NODE(LV2_PORT_PROPS__rangeSteps); 55 | nodes->pset_Preset = MAP_NODE(LV2_PRESETS__Preset); 56 | nodes->pset_bank = MAP_NODE(LV2_PRESETS__bank); 57 | nodes->rdfs_comment = MAP_NODE(LILV_NS_RDFS "comment"); 58 | nodes->rdfs_label = MAP_NODE(LILV_NS_RDFS "label"); 59 | nodes->rdfs_range = MAP_NODE(LILV_NS_RDFS "range"); 60 | nodes->rsz_minimumSize = MAP_NODE(LV2_RESIZE_PORT__minimumSize); 61 | nodes->state_threadSafeRestore = MAP_NODE(LV2_STATE__threadSafeRestore); 62 | nodes->ui_showInterface = MAP_NODE(LV2_UI__showInterface); 63 | nodes->work_interface = MAP_NODE(LV2_WORKER__interface); 64 | nodes->work_schedule = MAP_NODE(LV2_WORKER__schedule); 65 | nodes->end = NULL; 66 | 67 | #undef MAP_NODE 68 | } 69 | 70 | void 71 | jalv_free_nodes(JalvNodes* const nodes) 72 | { 73 | for (LilvNode** n = (LilvNode**)nodes; *n; ++n) { 74 | lilv_node_free(*n); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/nodes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_NODES_H 5 | #define JALV_NODES_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | 11 | // Cached lilv nodes 12 | JALV_BEGIN_DECLS 13 | 14 | typedef struct { 15 | LilvNode* atom_AtomPort; 16 | LilvNode* atom_Chunk; 17 | LilvNode* atom_Float; 18 | LilvNode* atom_Path; 19 | LilvNode* atom_Sequence; 20 | LilvNode* lv2_AudioPort; 21 | LilvNode* lv2_CVPort; 22 | LilvNode* lv2_ControlPort; 23 | LilvNode* lv2_InputPort; 24 | LilvNode* lv2_OutputPort; 25 | LilvNode* lv2_connectionOptional; 26 | LilvNode* lv2_control; 27 | LilvNode* lv2_default; 28 | LilvNode* lv2_designation; 29 | LilvNode* lv2_enumeration; 30 | LilvNode* lv2_extensionData; 31 | LilvNode* lv2_integer; 32 | LilvNode* lv2_latency; 33 | LilvNode* lv2_maximum; 34 | LilvNode* lv2_minimum; 35 | LilvNode* lv2_name; 36 | LilvNode* lv2_reportsLatency; 37 | LilvNode* lv2_sampleRate; 38 | LilvNode* lv2_symbol; 39 | LilvNode* lv2_toggled; 40 | LilvNode* midi_MidiEvent; 41 | LilvNode* pg_group; 42 | LilvNode* pprops_logarithmic; 43 | LilvNode* pprops_notOnGUI; 44 | LilvNode* pprops_rangeSteps; 45 | LilvNode* pset_Preset; 46 | LilvNode* pset_bank; 47 | LilvNode* rdfs_comment; 48 | LilvNode* rdfs_label; 49 | LilvNode* rdfs_range; 50 | LilvNode* rsz_minimumSize; 51 | LilvNode* state_threadSafeRestore; 52 | LilvNode* ui_showInterface; 53 | LilvNode* work_interface; 54 | LilvNode* work_schedule; 55 | LilvNode* end; ///< NULL terminator for easy freeing of entire structure 56 | } JalvNodes; 57 | 58 | void 59 | jalv_init_nodes(LilvWorld* world, JalvNodes* nodes); 60 | 61 | void 62 | jalv_free_nodes(JalvNodes* nodes); 63 | 64 | JALV_END_DECLS 65 | 66 | #endif // JALV_NODES_H 67 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_OPTIONS_H 5 | #define JALV_OPTIONS_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | 11 | // Program options 12 | JALV_BEGIN_DECLS 13 | 14 | typedef struct { 15 | char* name; ///< Client name 16 | int name_exact; ///< Exit if name is taken 17 | char* load; ///< Path for state to load 18 | char* preset; ///< URI of preset to load 19 | char** controls; ///< Control values 20 | uint32_t ring_size; ///< Plugin <=> UI communication buffer size 21 | double update_rate; ///< UI update rate in Hz 22 | double scale_factor; ///< UI scale factor 23 | int dump; ///< Dump communication iff true 24 | int trace; ///< Print trace log iff true 25 | int generic_ui; ///< Use generic UI iff true 26 | int show_hidden; ///< Show controls for notOnGUI ports 27 | int no_menu; ///< Hide menu iff true 28 | int show_ui; ///< Show non-embedded UI 29 | int print_controls; ///< Print control changes to stdout 30 | int non_interactive; ///< Do not listen for commands on stdin 31 | char* ui_uri; ///< URI of UI to load 32 | } JalvOptions; 33 | 34 | JALV_END_DECLS 35 | 36 | #endif // JALV_OPTIONS_H 37 | -------------------------------------------------------------------------------- /src/port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_PORT_H 5 | #define JALV_PORT_H 6 | 7 | #include "attributes.h" 8 | #include "types.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | // Application port state 15 | JALV_BEGIN_DECLS 16 | 17 | typedef struct { 18 | const LilvPort* lilv_port; ///< LV2 port 19 | PortType type; ///< Data type 20 | PortFlow flow; ///< Data flow direction 21 | void* widget; ///< Control widget, if applicable 22 | uint32_t index; ///< Port index 23 | } JalvPort; 24 | 25 | JALV_END_DECLS 26 | 27 | #endif // JALV_PORT_H 28 | -------------------------------------------------------------------------------- /src/portaudio.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "backend.h" 5 | #include "comm.h" 6 | #include "log.h" 7 | #include "lv2_evbuf.h" 8 | #include "process.h" 9 | #include "settings.h" 10 | #include "types.h" 11 | #include "urids.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | struct JalvBackendImpl { 25 | PaStream* stream; 26 | }; 27 | 28 | static int 29 | process_silent(JalvProcess* const proc, 30 | void* const outputs, 31 | const unsigned long nframes) 32 | { 33 | for (uint32_t i = 0; i < proc->num_ports; ++i) { 34 | memset(((float**)outputs)[i], '\0', nframes * sizeof(float)); 35 | } 36 | 37 | return jalv_bypass(proc, nframes); 38 | } 39 | 40 | static int 41 | process_cb(const void* inputs, 42 | void* outputs, 43 | unsigned long nframes, 44 | const PaStreamCallbackTimeInfo* time, 45 | PaStreamCallbackFlags flags, 46 | void* handle) 47 | { 48 | (void)time; 49 | (void)flags; 50 | 51 | JalvProcess* const proc = (JalvProcess*)handle; 52 | 53 | // If execution is paused, emit silence and return 54 | if (proc->run_state == JALV_PAUSED) { 55 | return process_silent(proc, outputs, nframes); 56 | } 57 | 58 | // Prepare port buffers 59 | uint32_t in_index = 0; 60 | uint32_t out_index = 0; 61 | for (uint32_t i = 0; i < proc->num_ports; ++i) { 62 | JalvProcessPort* const port = &proc->ports[i]; 63 | if (port->type == TYPE_AUDIO) { 64 | if (port->flow == FLOW_INPUT) { 65 | lilv_instance_connect_port( 66 | proc->instance, i, ((float**)inputs)[in_index++]); 67 | } else if (port->flow == FLOW_OUTPUT) { 68 | lilv_instance_connect_port( 69 | proc->instance, i, ((float**)outputs)[out_index++]); 70 | } 71 | } else if (port->type == TYPE_EVENT) { 72 | lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); 73 | } 74 | } 75 | 76 | // Run plugin for this cycle 77 | const bool send_ui_updates = jalv_run(proc, nframes); 78 | 79 | // Deliver UI events 80 | for (uint32_t p = 0; p < proc->num_ports; ++p) { 81 | JalvProcessPort* const port = &proc->ports[p]; 82 | if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { 83 | for (LV2_Evbuf_Iterator i = lv2_evbuf_begin(port->evbuf); 84 | lv2_evbuf_is_valid(i); 85 | i = lv2_evbuf_next(i)) { 86 | // Get event from LV2 buffer 87 | uint32_t frames = 0U; 88 | uint32_t subframes = 0U; 89 | uint32_t type = 0U; 90 | uint32_t size = 0U; 91 | void* body = NULL; 92 | lv2_evbuf_get(i, &frames, &subframes, &type, &size, &body); 93 | 94 | if (proc->has_ui) { 95 | // Forward event to UI 96 | jalv_write_event(proc->plugin_to_ui, p, size, type, body); 97 | } 98 | } 99 | } else if (send_ui_updates && port->flow == FLOW_OUTPUT && 100 | port->type == TYPE_CONTROL) { 101 | jalv_write_control(proc->plugin_to_ui, p, proc->controls_buf[p]); 102 | } 103 | } 104 | 105 | return paContinue; 106 | } 107 | 108 | static int 109 | setup_error(const char* msg, PaError err) 110 | { 111 | jalv_log(JALV_LOG_ERR, "%s (%s)\n", msg, Pa_GetErrorText(err)); 112 | Pa_Terminate(); 113 | return 1; 114 | } 115 | 116 | JalvBackend* 117 | jalv_backend_allocate(void) 118 | { 119 | return (JalvBackend*)calloc(1, sizeof(JalvBackend)); 120 | } 121 | 122 | void 123 | jalv_backend_free(JalvBackend* const backend) 124 | { 125 | free(backend); 126 | } 127 | 128 | int 129 | jalv_backend_open(JalvBackend* const backend, 130 | const JalvURIDs* const ZIX_UNUSED(urids), 131 | JalvSettings* const settings, 132 | JalvProcess* const proc, 133 | ZixSem* const ZIX_UNUSED(done), 134 | const char* const ZIX_UNUSED(name), 135 | const bool ZIX_UNUSED(exact_name)) 136 | { 137 | PaStreamParameters inputParameters; 138 | PaStreamParameters outputParameters; 139 | PaStream* stream = NULL; 140 | PaError st = paNoError; 141 | 142 | if ((st = Pa_Initialize())) { 143 | return setup_error("Failed to initialize audio system", st); 144 | } 145 | 146 | // Get default input and output devices 147 | inputParameters.device = Pa_GetDefaultInputDevice(); 148 | outputParameters.device = Pa_GetDefaultOutputDevice(); 149 | if (inputParameters.device == paNoDevice) { 150 | return setup_error("No default input device", paDeviceUnavailable); 151 | } 152 | 153 | if (outputParameters.device == paNoDevice) { 154 | return setup_error("No default output device", paDeviceUnavailable); 155 | } 156 | 157 | const PaDeviceInfo* in_dev = Pa_GetDeviceInfo(inputParameters.device); 158 | const PaDeviceInfo* out_dev = Pa_GetDeviceInfo(outputParameters.device); 159 | 160 | // Count number of input and output audio ports/channels 161 | inputParameters.channelCount = 0; 162 | outputParameters.channelCount = 0; 163 | for (uint32_t i = 0; i < proc->num_ports; ++i) { 164 | if (proc->ports[i].type == TYPE_AUDIO) { 165 | if (proc->ports[i].flow == FLOW_INPUT) { 166 | ++inputParameters.channelCount; 167 | } else if (proc->ports[i].flow == FLOW_OUTPUT) { 168 | ++outputParameters.channelCount; 169 | } 170 | } 171 | } 172 | 173 | // Configure audio format 174 | inputParameters.sampleFormat = paFloat32 | paNonInterleaved; 175 | inputParameters.suggestedLatency = in_dev->defaultLowInputLatency; 176 | inputParameters.hostApiSpecificStreamInfo = NULL; 177 | outputParameters.sampleFormat = paFloat32 | paNonInterleaved; 178 | outputParameters.suggestedLatency = out_dev->defaultLowOutputLatency; 179 | outputParameters.hostApiSpecificStreamInfo = NULL; 180 | 181 | // Open stream 182 | if ((st = 183 | Pa_OpenStream(&stream, 184 | inputParameters.channelCount ? &inputParameters : NULL, 185 | outputParameters.channelCount ? &outputParameters : NULL, 186 | in_dev->defaultSampleRate, 187 | paFramesPerBufferUnspecified, 188 | 0, 189 | process_cb, 190 | proc))) { 191 | return setup_error("Failed to open audio stream", st); 192 | } 193 | 194 | // Set audio parameters 195 | settings->sample_rate = in_dev->defaultSampleRate; 196 | // settings->block_length = FIXME 197 | settings->midi_buf_size = 4096; 198 | 199 | backend->stream = stream; 200 | return 0; 201 | } 202 | 203 | void 204 | jalv_backend_close(JalvBackend* const backend) 205 | { 206 | if (backend) { 207 | PaError st = paNoError; 208 | if (backend->stream && (st = Pa_CloseStream(backend->stream))) { 209 | jalv_log(JALV_LOG_ERR, "Error closing audio (%s)\n", Pa_GetErrorText(st)); 210 | } 211 | 212 | if ((st = Pa_Terminate())) { 213 | jalv_log( 214 | JALV_LOG_ERR, "Error terminating audio (%s)\n", Pa_GetErrorText(st)); 215 | } 216 | } 217 | } 218 | 219 | void 220 | jalv_backend_activate(JalvBackend* const backend) 221 | { 222 | const PaError st = Pa_StartStream(backend->stream); 223 | if (st != paNoError) { 224 | jalv_log(JALV_LOG_ERR, "Error starting audio (%s)\n", Pa_GetErrorText(st)); 225 | } 226 | } 227 | 228 | void 229 | jalv_backend_deactivate(JalvBackend* const backend) 230 | { 231 | const PaError st = Pa_StopStream(backend->stream); 232 | if (st != paNoError) { 233 | jalv_log(JALV_LOG_ERR, "Error stopping audio (%s)\n", Pa_GetErrorText(st)); 234 | } 235 | } 236 | 237 | void 238 | jalv_backend_activate_port(JalvBackend* const ZIX_UNUSED(backend), 239 | JalvProcess* const proc, 240 | const uint32_t port_index) 241 | { 242 | JalvProcessPort* const port = &proc->ports[port_index]; 243 | 244 | if (port->type == TYPE_CONTROL) { 245 | lilv_instance_connect_port( 246 | proc->instance, port_index, &proc->controls_buf[port_index]); 247 | } 248 | } 249 | 250 | void 251 | jalv_backend_recompute_latencies(JalvBackend* const ZIX_UNUSED(backend)) 252 | {} 253 | -------------------------------------------------------------------------------- /src/process.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "process.h" 5 | 6 | #include "comm.h" 7 | #include "log.h" 8 | #include "lv2_evbuf.h" 9 | #include "types.h" 10 | #include "worker.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | static int 22 | ring_error(const char* const message) 23 | { 24 | jalv_log(JALV_LOG_ERR, "%s", message); 25 | return 1; 26 | } 27 | 28 | static int 29 | apply_ui_events(JalvProcess* const proc, const uint32_t nframes) 30 | { 31 | ZixRing* const ring = proc->ui_to_plugin; 32 | JalvMessageHeader header = {NO_MESSAGE, 0U}; 33 | const size_t space = zix_ring_read_space(ring); 34 | for (size_t i = 0; i < space; i += sizeof(header) + header.size) { 35 | // Read message header (which includes the body size) 36 | if (zix_ring_read(ring, &header, sizeof(header)) != sizeof(header)) { 37 | return ring_error("Failed to read header from UI ring\n"); 38 | } 39 | 40 | if (header.type == CONTROL_PORT_CHANGE) { 41 | assert(header.size == sizeof(JalvControlChange)); 42 | JalvControlChange msg = {0U, 0.0f}; 43 | if (zix_ring_read(ring, &msg, sizeof(msg)) != sizeof(msg)) { 44 | return ring_error("Failed to read control value from UI ring\n"); 45 | } 46 | 47 | assert(msg.port_index < proc->num_ports); 48 | proc->controls_buf[msg.port_index] = msg.value; 49 | 50 | } else if (header.type == EVENT_TRANSFER) { 51 | assert(header.size <= proc->process_msg_size); 52 | void* const body = proc->process_msg; 53 | if (zix_ring_read(ring, body, header.size) != header.size) { 54 | return ring_error("Failed to read event from UI ring\n"); 55 | } 56 | 57 | const JalvEventTransfer* const msg = (const JalvEventTransfer*)body; 58 | assert(msg->port_index < proc->num_ports); 59 | JalvProcessPort* const port = &proc->ports[msg->port_index]; 60 | LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); 61 | const LV2_Atom* const atom = &msg->atom; 62 | lv2_evbuf_write( 63 | &e, nframes, 0U, atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); 64 | 65 | } else if (header.type == STATE_REQUEST) { 66 | JalvProcessPort* const port = &proc->ports[proc->control_in]; 67 | assert(port->type == TYPE_EVENT); 68 | assert(port->flow == FLOW_INPUT); 69 | assert(port->evbuf); 70 | 71 | LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); 72 | lv2_evbuf_write(&e, 73 | nframes, 74 | 0U, 75 | proc->get_msg.atom.type, 76 | proc->get_msg.atom.size, 77 | &proc->get_msg.body); 78 | 79 | } else if (header.type == RUN_STATE_CHANGE) { 80 | assert(header.size == sizeof(JalvRunStateChange)); 81 | JalvRunStateChange msg = {JALV_RUNNING}; 82 | if (zix_ring_read(ring, &msg, sizeof(msg)) != sizeof(msg)) { 83 | return ring_error("Failed to read run state change from UI ring\n"); 84 | } 85 | 86 | proc->run_state = msg.state; 87 | if (msg.state == JALV_PAUSED) { 88 | zix_sem_post(&proc->paused); 89 | } 90 | 91 | } else { 92 | return ring_error("Unknown message type received from UI ring\n"); 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | bool 100 | jalv_run(JalvProcess* const proc, const uint32_t nframes) 101 | { 102 | // Read and apply control change events from UI 103 | apply_ui_events(proc, nframes); 104 | 105 | // Run plugin for this cycle 106 | lilv_instance_run(proc->instance, nframes); 107 | 108 | // Process any worker replies and end the cycle 109 | LV2_Handle handle = lilv_instance_get_handle(proc->instance); 110 | jalv_worker_emit_responses(proc->state_worker, handle); 111 | jalv_worker_emit_responses(proc->worker, handle); 112 | jalv_worker_end_run(proc->worker); 113 | 114 | // Check if it's time to send updates to the UI 115 | proc->pending_frames += nframes; 116 | if (proc->update_frames && proc->pending_frames > proc->update_frames) { 117 | proc->pending_frames = 0U; 118 | return true; 119 | } 120 | 121 | return false; 122 | } 123 | 124 | int 125 | jalv_bypass(JalvProcess* const proc, const uint32_t nframes) 126 | { 127 | // Read and apply control change events from UI 128 | apply_ui_events(proc, nframes); 129 | return 0; 130 | } 131 | -------------------------------------------------------------------------------- /src/process.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_PROCESS_H 5 | #define JALV_PROCESS_H 6 | 7 | #include "attributes.h" 8 | #include "lv2_evbuf.h" 9 | #include "types.h" 10 | #include "worker.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | // Code and data used in the realtime process thread 23 | JALV_BEGIN_DECLS 24 | 25 | /// Port state used in the process thread 26 | typedef struct { 27 | PortType type; ///< Data type 28 | PortFlow flow; ///< Data flow direction 29 | void* sys_port; ///< For audio/MIDI ports, otherwise NULL 30 | char* symbol; ///< Port symbol (stable/unique C-like identifier) 31 | char* label; ///< Human-readable label 32 | LV2_Evbuf* evbuf; ///< Sequence port event buffer 33 | uint32_t buf_size; ///< Custom buffer size, or 0 34 | bool reports_latency; ///< Whether control port reports latency 35 | bool is_primary; ///< True for main control/response channel 36 | bool supports_midi; ///< Whether event port supports MIDI 37 | } JalvProcessPort; 38 | 39 | /** 40 | State accessed in the process thread. 41 | 42 | Everything accessed by the process thread is stored here, to keep it 43 | somewhat insulated from the UI and to make references to it stand out in the 44 | code. 45 | */ 46 | typedef struct { 47 | LilvInstance* instance; ///< Plugin instance 48 | ZixRing* ui_to_plugin; ///< Messages from UI to plugin/process 49 | ZixRing* plugin_to_ui; ///< Messages from plugin/process to UI 50 | JalvWorker* worker; ///< Worker thread implementation 51 | JalvWorker* state_worker; ///< Synchronous worker for state restore 52 | JalvProcessPort* ports; ///< Port array of size num_ports 53 | LV2_Atom_Forge forge; ///< Atom forge 54 | LV2_Atom_Object get_msg; ///< General patch:Get message 55 | float* controls_buf; ///< Control port buffers array 56 | size_t process_msg_size; ///< Maximum size of a single message 57 | void* process_msg; ///< Buffer for receiving messages 58 | ZixSem paused; ///< Paused signal from process thread 59 | JalvRunState run_state; ///< Current run state 60 | uint32_t control_in; ///< Index of control input port 61 | uint32_t num_ports; ///< Total number of ports on the plugin 62 | uint32_t pending_frames; ///< Frames since last UI update sent 63 | uint32_t update_frames; ///< UI update period in frames, or zero 64 | uint32_t plugin_latency; ///< Latency reported by plugin (if any) 65 | uint32_t position; ///< Transport position in frames 66 | float bpm; ///< Transport tempo in beats per minute 67 | bool rolling; ///< Transport speed (0=stop, 1=play) 68 | bool has_ui; ///< True iff a control UI is present 69 | } JalvProcess; 70 | 71 | /** 72 | Run the plugin for a block of frames. 73 | 74 | Applies any pending messages from the UI, runs the plugin instance, and 75 | processes any worker replies. 76 | 77 | @param proc Process thread state. 78 | @param nframes Number of frames to process. 79 | @return Whether output value updates should be sent to the UI now. 80 | */ 81 | bool 82 | jalv_run(JalvProcess* proc, uint32_t nframes); 83 | 84 | /** 85 | Bypass the plugin for a block of frames. 86 | 87 | This is like jalv_run(), but doesn't actually run the plugin and only does 88 | the minimum necessary internal work for the cycle. 89 | 90 | @param proc Process thread state. 91 | @param nframes Number of frames to bypass. 92 | @return Zero. 93 | */ 94 | int 95 | jalv_bypass(JalvProcess* proc, uint32_t nframes); 96 | 97 | JALV_END_DECLS 98 | 99 | #endif // JALV_PROCESS_H 100 | -------------------------------------------------------------------------------- /src/process_setup.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "process_setup.h" 5 | 6 | #include "jalv_config.h" 7 | #include "log.h" 8 | #include "lv2_evbuf.h" 9 | #include "macros.h" 10 | #include "mapper.h" 11 | #include "nodes.h" 12 | #include "process.h" 13 | #include "query.h" 14 | #include "settings.h" 15 | #include "string_utils.h" 16 | #include "types.h" 17 | #include "urids.h" 18 | #include "worker.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | int 32 | jalv_process_init(JalvProcess* const proc, 33 | const JalvURIDs* const urids, 34 | JalvMapper* const mapper, 35 | const uint32_t update_frames) 36 | { 37 | proc->get_msg.atom.size = sizeof(LV2_Atom_Object_Body); 38 | proc->get_msg.atom.type = urids->atom_Object; 39 | proc->get_msg.body.id = 0U; 40 | proc->get_msg.body.otype = urids->patch_Get; 41 | 42 | proc->instance = NULL; 43 | proc->ui_to_plugin = NULL; 44 | proc->plugin_to_ui = NULL; 45 | proc->worker = NULL; 46 | proc->state_worker = NULL; 47 | proc->ports = NULL; 48 | proc->process_msg_size = 1024U; 49 | proc->process_msg = NULL; 50 | proc->run_state = JALV_PAUSED; 51 | proc->control_in = UINT32_MAX; 52 | proc->num_ports = 0U; 53 | proc->pending_frames = 0U; 54 | proc->update_frames = update_frames; 55 | proc->position = 0U; 56 | proc->bpm = 120.0f; 57 | proc->rolling = false; 58 | proc->has_ui = false; 59 | 60 | zix_sem_init(&proc->paused, 0); 61 | lv2_atom_forge_init(&proc->forge, jalv_mapper_urid_map(mapper)); 62 | 63 | return 0; 64 | } 65 | 66 | void 67 | jalv_process_cleanup(JalvProcess* const proc) 68 | { 69 | zix_sem_destroy(&proc->paused); 70 | jalv_worker_free(proc->worker); 71 | jalv_worker_free(proc->state_worker); 72 | zix_ring_free(proc->ui_to_plugin); 73 | zix_ring_free(proc->plugin_to_ui); 74 | zix_aligned_free(NULL, proc->process_msg); 75 | 76 | for (uint32_t i = 0U; i < proc->num_ports; ++i) { 77 | jalv_process_port_cleanup(&proc->ports[i]); 78 | } 79 | } 80 | 81 | void 82 | jalv_process_activate(JalvProcess* const proc, 83 | const JalvURIDs* const urids, 84 | LilvInstance* const instance, 85 | const JalvSettings* const settings) 86 | { 87 | proc->instance = instance; 88 | 89 | for (uint32_t i = 0U; i < proc->num_ports; ++i) { 90 | JalvProcessPort* const port = &proc->ports[i]; 91 | if (port->type == TYPE_EVENT) { 92 | const size_t size = 93 | port->buf_size ? port->buf_size : settings->midi_buf_size; 94 | 95 | lv2_evbuf_free(port->evbuf); 96 | port->evbuf = 97 | lv2_evbuf_new(size, urids->atom_Chunk, urids->atom_Sequence); 98 | 99 | lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT); 100 | lilv_instance_connect_port( 101 | proc->instance, i, lv2_evbuf_get_buffer(port->evbuf)); 102 | 103 | if (port->flow == FLOW_INPUT) { 104 | proc->process_msg_size = MAX(proc->process_msg_size, port->buf_size); 105 | } 106 | } 107 | } 108 | 109 | // Allocate UI<=>process communication rings and process receive buffer 110 | proc->ui_to_plugin = zix_ring_new(NULL, settings->ring_size); 111 | proc->plugin_to_ui = zix_ring_new(NULL, settings->ring_size); 112 | proc->process_msg = zix_aligned_alloc(NULL, 8U, proc->process_msg_size); 113 | zix_ring_mlock(proc->ui_to_plugin); 114 | zix_ring_mlock(proc->plugin_to_ui); 115 | zix_ring_mlock(proc->process_msg); 116 | } 117 | 118 | void 119 | jalv_process_deactivate(JalvProcess* const proc) 120 | { 121 | zix_aligned_free(NULL, proc->process_msg); 122 | proc->process_msg = NULL; 123 | 124 | for (uint32_t i = 0U; i < proc->num_ports; ++i) { 125 | lv2_evbuf_free(proc->ports[i].evbuf); 126 | lilv_instance_connect_port(proc->instance, i, NULL); 127 | proc->ports[i].evbuf = NULL; 128 | } 129 | } 130 | 131 | int 132 | jalv_process_port_init(JalvProcessPort* const port, 133 | const JalvNodes* const nodes, 134 | const LilvPlugin* const lilv_plugin, 135 | const LilvPort* const lilv_port) 136 | { 137 | const LilvNode* const symbol = lilv_port_get_symbol(lilv_plugin, lilv_port); 138 | 139 | port->type = TYPE_UNKNOWN; 140 | port->flow = FLOW_UNKNOWN; 141 | port->sys_port = NULL; 142 | port->evbuf = NULL; 143 | port->buf_size = 0U; 144 | port->reports_latency = false; 145 | 146 | const bool optional = lilv_port_has_property( 147 | lilv_plugin, lilv_port, nodes->lv2_connectionOptional); 148 | 149 | // Set port flow (input or output) 150 | if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_InputPort)) { 151 | port->flow = FLOW_INPUT; 152 | } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_OutputPort)) { 153 | port->flow = FLOW_OUTPUT; 154 | } else if (!optional) { 155 | jalv_log(JALV_LOG_ERR, 156 | "Mandatory port \"%s\" is neither input nor output\n", 157 | lilv_node_as_string(symbol)); 158 | return 1; 159 | } 160 | 161 | // Set port type 162 | if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_ControlPort)) { 163 | port->type = TYPE_CONTROL; 164 | } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_AudioPort)) { 165 | port->type = TYPE_AUDIO; 166 | #if USE_JACK_METADATA 167 | } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->lv2_CVPort)) { 168 | port->type = TYPE_CV; 169 | #endif 170 | } else if (lilv_port_is_a(lilv_plugin, lilv_port, nodes->atom_AtomPort)) { 171 | port->type = TYPE_EVENT; 172 | } else if (!optional) { 173 | jalv_log(JALV_LOG_ERR, 174 | "Mandatory port \"%s\" has unknown data type\n", 175 | lilv_node_as_string(symbol)); 176 | return 1; 177 | } 178 | 179 | // Set symbol and label 180 | LilvNode* const name = lilv_port_get_name(lilv_plugin, lilv_port); 181 | port->symbol = symbol ? jalv_strdup(lilv_node_as_string(symbol)) : NULL; 182 | port->label = name ? jalv_strdup(lilv_node_as_string(name)) : NULL; 183 | lilv_node_free(name); 184 | 185 | // Set buffer size 186 | LilvNode* const min_size = 187 | lilv_port_get(lilv_plugin, lilv_port, nodes->rsz_minimumSize); 188 | if (min_size && lilv_node_is_int(min_size)) { 189 | port->buf_size = (uint32_t)MAX(lilv_node_as_int(min_size), 0); 190 | } 191 | lilv_node_free(min_size); 192 | 193 | // Set reports_latency flag 194 | if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && 195 | (lilv_port_has_property( 196 | lilv_plugin, lilv_port, nodes->lv2_reportsLatency) || 197 | jalv_port_has_designation( 198 | nodes, lilv_plugin, lilv_port, nodes->lv2_latency))) { 199 | port->reports_latency = true; 200 | } 201 | 202 | // Set supports_midi flag 203 | port->supports_midi = 204 | lilv_port_supports_event(lilv_plugin, lilv_port, nodes->midi_MidiEvent); 205 | 206 | return 0; 207 | } 208 | 209 | void 210 | jalv_process_port_cleanup(JalvProcessPort* const port) 211 | { 212 | if (port) { 213 | if (port->evbuf) { 214 | lv2_evbuf_free(port->evbuf); 215 | } 216 | free(port->label); 217 | free(port->symbol); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/process_setup.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_PROCESS_SETUP_H 5 | #define JALV_PROCESS_SETUP_H 6 | 7 | #include "attributes.h" 8 | #include "mapper.h" 9 | #include "nodes.h" 10 | #include "process.h" 11 | #include "settings.h" 12 | #include "urids.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | // Code for setting up the realtime process thread (but that isn't used in it) 19 | JALV_BEGIN_DECLS 20 | 21 | /** 22 | Initialize process thread and allocate necessary structures. 23 | 24 | This only initializes the state structure, it doesn't create any threads or 25 | start plugin execution. 26 | */ 27 | int 28 | jalv_process_init(JalvProcess* proc, 29 | const JalvURIDs* urids, 30 | JalvMapper* mapper, 31 | uint32_t update_frames); 32 | 33 | /** 34 | Clean up process thread. 35 | 36 | This frees everything allocated by jalv_process_init() and 37 | jalv_process_activate(). 38 | */ 39 | void 40 | jalv_process_cleanup(JalvProcess* proc); 41 | 42 | /** 43 | Allocate necessary buffers, connect the plugin to them, and prepare to run. 44 | 45 | @param proc Process thread state. 46 | @param urids Application vocabulary. 47 | @param instance Plugin instance to run. 48 | @param settings Process thread settings. 49 | */ 50 | void 51 | jalv_process_activate(JalvProcess* proc, 52 | const JalvURIDs* urids, 53 | LilvInstance* instance, 54 | const JalvSettings* settings); 55 | 56 | /** 57 | Clean up after jalv_process_activate() and disconnect plugin. 58 | 59 | @param proc Process thread state. 60 | */ 61 | void 62 | jalv_process_deactivate(JalvProcess* proc); 63 | 64 | /** 65 | Initialize the process thread state for a port. 66 | 67 | @return Zero on success. 68 | */ 69 | int 70 | jalv_process_port_init(JalvProcessPort* port, 71 | const JalvNodes* nodes, 72 | const LilvPlugin* lilv_plugin, 73 | const LilvPort* lilv_port); 74 | 75 | /** 76 | Free memory allocated by jalv_setup_init_port(). 77 | */ 78 | void 79 | jalv_process_port_cleanup(JalvProcessPort* port); 80 | 81 | JALV_END_DECLS 82 | 83 | #endif // JALV_PROCESS_SETUP_H 84 | -------------------------------------------------------------------------------- /src/query.c: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "query.h" 5 | 6 | #include "nodes.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | bool 13 | jalv_port_has_designation(const JalvNodes* const nodes, 14 | const LilvPlugin* const plugin, 15 | const LilvPort* const port, 16 | const LilvNode* const designation) 17 | { 18 | LilvNodes* const designations = 19 | lilv_port_get_value(plugin, port, nodes->lv2_designation); 20 | 21 | bool found = false; 22 | LILV_FOREACH (nodes, n, designations) { 23 | const LilvNode* const node = lilv_nodes_get(designations, n); 24 | if (lilv_node_equals(node, designation)) { 25 | found = true; 26 | break; 27 | } 28 | } 29 | 30 | lilv_nodes_free(designations); 31 | return found; 32 | } 33 | 34 | bool 35 | jalv_ui_is_resizable(LilvWorld* const world, const LilvUI* const ui) 36 | { 37 | if (!ui) { 38 | return false; 39 | } 40 | 41 | const LilvNode* s = lilv_ui_get_uri(ui); 42 | LilvNode* p = lilv_new_uri(world, LV2_CORE__optionalFeature); 43 | LilvNode* fs = lilv_new_uri(world, LV2_UI__fixedSize); 44 | LilvNode* nrs = lilv_new_uri(world, LV2_UI__noUserResize); 45 | 46 | LilvNodes* fs_matches = lilv_world_find_nodes(world, s, p, fs); 47 | LilvNodes* nrs_matches = lilv_world_find_nodes(world, s, p, nrs); 48 | 49 | lilv_nodes_free(nrs_matches); 50 | lilv_nodes_free(fs_matches); 51 | lilv_node_free(nrs); 52 | lilv_node_free(fs); 53 | lilv_node_free(p); 54 | 55 | return !fs_matches && !nrs_matches; 56 | } 57 | -------------------------------------------------------------------------------- /src/query.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_MODEL_H 5 | #define JALV_MODEL_H 6 | 7 | #include "attributes.h" 8 | #include "nodes.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | // Lilv query utilities 15 | JALV_BEGIN_DECLS 16 | 17 | /// Return whether a port has a given designation 18 | bool 19 | jalv_port_has_designation(const JalvNodes* nodes, 20 | const LilvPlugin* plugin, 21 | const LilvPort* port, 22 | const LilvNode* designation); 23 | 24 | /// Return whether a UI is described as resizable 25 | bool 26 | jalv_ui_is_resizable(LilvWorld* world, const LilvUI* ui); 27 | 28 | JALV_END_DECLS 29 | 30 | #endif // JALV_MODEL_H 31 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_SETTINGS_H 5 | #define JALV_SETTINGS_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | #include 11 | 12 | // Process thread settings 13 | JALV_BEGIN_DECLS 14 | 15 | /// System and/or configuration settings for the execution process 16 | typedef struct { 17 | float sample_rate; ///< Sample rate 18 | uint32_t block_length; ///< Audio buffer length in frames 19 | size_t midi_buf_size; ///< MIDI buffer size in bytes 20 | uint32_t ring_size; ///< Communication ring size in bytes 21 | float ui_update_hz; ///< Frequency of UI updates 22 | float ui_scale_factor; ///< UI scale factor 23 | } JalvSettings; 24 | 25 | JALV_END_DECLS 26 | 27 | #endif // JALV_SETTINGS_H 28 | -------------------------------------------------------------------------------- /src/state.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "state.h" 5 | 6 | #include "comm.h" 7 | #include "jalv.h" 8 | #include "log.h" 9 | #include "mapper.h" 10 | #include "port.h" 11 | #include "process.h" 12 | #include "types.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | ZIX_MALLOC_FUNC char* 30 | jalv_make_path(LV2_State_Make_Path_Handle handle, const char* path) 31 | { 32 | Jalv* jalv = (Jalv*)handle; 33 | 34 | // Create in save directory if saving, otherwise use temp directory 35 | const char* const dir = jalv->save_dir ? jalv->save_dir : jalv->temp_dir; 36 | return zix_path_join(NULL, dir, path); 37 | } 38 | 39 | static const void* 40 | get_port_value(const char* port_symbol, 41 | void* user_data, 42 | uint32_t* size, 43 | uint32_t* type) 44 | { 45 | Jalv* const jalv = (Jalv*)user_data; 46 | const JalvPort* const port = jalv_port_by_symbol(jalv, port_symbol); 47 | if (port && port->flow == FLOW_INPUT && port->type == TYPE_CONTROL) { 48 | *size = sizeof(float); 49 | *type = jalv->forge.Float; 50 | return &jalv->process.controls_buf[port->index]; 51 | } 52 | *size = *type = 0; 53 | return NULL; 54 | } 55 | 56 | void 57 | jalv_save(Jalv* jalv, const char* dir) 58 | { 59 | LV2_URID_Map* const map = jalv_mapper_urid_map(jalv->mapper); 60 | LV2_URID_Unmap* const unmap = jalv_mapper_urid_unmap(jalv->mapper); 61 | 62 | jalv->save_dir = zix_path_join(NULL, dir, NULL); 63 | 64 | LilvState* const state = 65 | lilv_state_new_from_instance(jalv->plugin, 66 | jalv->process.instance, 67 | map, 68 | jalv->temp_dir, 69 | dir, 70 | dir, 71 | dir, 72 | get_port_value, 73 | jalv, 74 | LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, 75 | NULL); 76 | 77 | lilv_state_save(jalv->world, map, unmap, state, NULL, dir, "state.ttl"); 78 | lilv_state_free(state); 79 | free(jalv->save_dir); 80 | jalv->save_dir = NULL; 81 | } 82 | 83 | int 84 | jalv_load_presets(Jalv* jalv, PresetSink sink, void* data) 85 | { 86 | LilvNodes* presets = 87 | lilv_plugin_get_related(jalv->plugin, jalv->nodes.pset_Preset); 88 | LILV_FOREACH (nodes, i, presets) { 89 | const LilvNode* preset = lilv_nodes_get(presets, i); 90 | lilv_world_load_resource(jalv->world, preset); 91 | if (!sink) { 92 | continue; 93 | } 94 | 95 | LilvNodes* labels = 96 | lilv_world_find_nodes(jalv->world, preset, jalv->nodes.rdfs_label, NULL); 97 | if (labels) { 98 | const LilvNode* label = lilv_nodes_get_first(labels); 99 | sink(jalv, preset, label, data); 100 | lilv_nodes_free(labels); 101 | } else { 102 | jalv_log(JALV_LOG_WARNING, 103 | "Preset <%s> has no rdfs:label\n", 104 | lilv_node_as_string(lilv_nodes_get(presets, i))); 105 | } 106 | } 107 | lilv_nodes_free(presets); 108 | 109 | return 0; 110 | } 111 | 112 | int 113 | jalv_unload_presets(Jalv* jalv) 114 | { 115 | LilvNodes* presets = 116 | lilv_plugin_get_related(jalv->plugin, jalv->nodes.pset_Preset); 117 | LILV_FOREACH (nodes, i, presets) { 118 | const LilvNode* preset = lilv_nodes_get(presets, i); 119 | lilv_world_unload_resource(jalv->world, preset); 120 | } 121 | lilv_nodes_free(presets); 122 | 123 | return 0; 124 | } 125 | 126 | static void 127 | set_port_value(const char* port_symbol, 128 | void* user_data, 129 | const void* value, 130 | uint32_t ZIX_UNUSED(size), 131 | uint32_t type) 132 | { 133 | Jalv* const jalv = (Jalv*)user_data; 134 | JalvProcess* const proc = &jalv->process; 135 | const JalvPort* const port = jalv_port_by_symbol(jalv, port_symbol); 136 | if (!port) { 137 | jalv_log(JALV_LOG_ERR, "Preset port `%s' is missing\n", port_symbol); 138 | return; 139 | } 140 | 141 | float fvalue = 0.0f; 142 | if (type == jalv->forge.Float) { 143 | fvalue = *(const float*)value; 144 | } else if (type == jalv->forge.Double) { 145 | fvalue = *(const double*)value; 146 | } else if (type == jalv->forge.Int) { 147 | fvalue = *(const int32_t*)value; 148 | } else if (type == jalv->forge.Long) { 149 | fvalue = *(const int64_t*)value; 150 | } else { 151 | jalv_log(JALV_LOG_ERR, 152 | "Preset `%s' value has bad type <%s>\n", 153 | port_symbol, 154 | jalv_mapper_unmap_uri(jalv->mapper, type)); 155 | return; 156 | } 157 | 158 | ZixStatus st = ZIX_STATUS_SUCCESS; 159 | if (proc->run_state != JALV_RUNNING) { 160 | // Set value on port struct directly 161 | proc->controls_buf[port->index] = fvalue; 162 | } else { 163 | // Send value to plugin (as if from UI) 164 | st = jalv_write_control(proc->ui_to_plugin, port->index, fvalue); 165 | } 166 | 167 | if (proc->has_ui) { 168 | // Update UI (as if from plugin) 169 | st = jalv_write_control(proc->plugin_to_ui, port->index, fvalue); 170 | } 171 | 172 | if (st) { 173 | jalv_log( 174 | JALV_LOG_ERR, "Failed to write control change (%s)\n", zix_strerror(st)); 175 | } 176 | } 177 | 178 | void 179 | jalv_apply_state(Jalv* jalv, const LilvState* state) 180 | { 181 | JalvProcess* const proc = &jalv->process; 182 | 183 | typedef struct { 184 | JalvMessageHeader head; 185 | JalvRunStateChange body; 186 | } PauseMessage; 187 | 188 | const bool must_pause = 189 | !jalv->safe_restore && proc->run_state == JALV_RUNNING; 190 | if (must_pause) { 191 | const PauseMessage pause_msg = { 192 | {RUN_STATE_CHANGE, sizeof(JalvRunStateChange)}, {JALV_PAUSED}}; 193 | zix_ring_write(proc->ui_to_plugin, &pause_msg, sizeof(pause_msg)); 194 | 195 | zix_sem_wait(&proc->paused); 196 | } 197 | 198 | const LV2_Feature* state_features[9] = { 199 | &jalv->features.map_feature, 200 | &jalv->features.unmap_feature, 201 | &jalv->features.make_path_feature, 202 | &jalv->features.state_sched_feature, 203 | &jalv->features.safe_restore_feature, 204 | &jalv->features.log_feature, 205 | &jalv->features.options_feature, 206 | NULL, 207 | }; 208 | 209 | lilv_state_restore( 210 | state, proc->instance, set_port_value, jalv, 0, state_features); 211 | 212 | if (must_pause) { 213 | const JalvMessageHeader state_msg = {STATE_REQUEST, 0U}; 214 | zix_ring_write(proc->ui_to_plugin, &state_msg, sizeof(state_msg)); 215 | 216 | const PauseMessage run_msg = { 217 | {RUN_STATE_CHANGE, sizeof(JalvRunStateChange)}, {JALV_RUNNING}}; 218 | zix_ring_write(proc->ui_to_plugin, &run_msg, sizeof(run_msg)); 219 | } 220 | } 221 | 222 | int 223 | jalv_apply_preset(Jalv* jalv, const LilvNode* preset) 224 | { 225 | lilv_state_free(jalv->preset); 226 | jalv->preset = lilv_state_new_from_world( 227 | jalv->world, jalv_mapper_urid_map(jalv->mapper), preset); 228 | if (jalv->preset) { 229 | jalv_apply_state(jalv, jalv->preset); 230 | } 231 | return 0; 232 | } 233 | 234 | int 235 | jalv_save_preset(Jalv* jalv, 236 | const char* dir, 237 | const char* uri, 238 | const char* label, 239 | const char* filename) 240 | { 241 | LV2_URID_Map* const map = jalv_mapper_urid_map(jalv->mapper); 242 | LV2_URID_Unmap* const unmap = jalv_mapper_urid_unmap(jalv->mapper); 243 | 244 | LilvState* const state = 245 | lilv_state_new_from_instance(jalv->plugin, 246 | jalv->process.instance, 247 | map, 248 | jalv->temp_dir, 249 | dir, 250 | dir, 251 | dir, 252 | get_port_value, 253 | jalv, 254 | LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, 255 | NULL); 256 | 257 | if (label) { 258 | lilv_state_set_label(state, label); 259 | } 260 | 261 | int ret = lilv_state_save(jalv->world, map, unmap, state, uri, dir, filename); 262 | 263 | lilv_state_free(jalv->preset); 264 | jalv->preset = state; 265 | 266 | return ret; 267 | } 268 | 269 | int 270 | jalv_delete_current_preset(Jalv* jalv) 271 | { 272 | if (!jalv->preset) { 273 | return 1; 274 | } 275 | 276 | lilv_world_unload_resource(jalv->world, lilv_state_get_uri(jalv->preset)); 277 | lilv_state_delete(jalv->world, jalv->preset); 278 | lilv_state_free(jalv->preset); 279 | jalv->preset = NULL; 280 | return 0; 281 | } 282 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_STATE_H 5 | #define JALV_STATE_H 6 | 7 | #include "attributes.h" 8 | #include "types.h" 9 | 10 | #include 11 | #include 12 | 13 | // State and preset utilities 14 | JALV_BEGIN_DECLS 15 | 16 | typedef int (*PresetSink)(Jalv* jalv, 17 | const LilvNode* node, 18 | const LilvNode* title, 19 | void* data); 20 | 21 | int 22 | jalv_load_presets(Jalv* jalv, PresetSink sink, void* data); 23 | 24 | int 25 | jalv_unload_presets(Jalv* jalv); 26 | 27 | int 28 | jalv_apply_preset(Jalv* jalv, const LilvNode* preset); 29 | 30 | int 31 | jalv_delete_current_preset(Jalv* jalv); 32 | 33 | int 34 | jalv_save_preset(Jalv* jalv, 35 | const char* dir, 36 | const char* uri, 37 | const char* label, 38 | const char* filename); 39 | 40 | void 41 | jalv_save(Jalv* jalv, const char* dir); 42 | 43 | char* 44 | jalv_make_path(LV2_State_Make_Path_Handle handle, const char* path); 45 | 46 | void 47 | jalv_apply_state(Jalv* jalv, const LilvState* state); 48 | 49 | JALV_END_DECLS 50 | 51 | #endif // JALV_STATE_H 52 | -------------------------------------------------------------------------------- /src/string_utils.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "string_utils.h" 5 | 6 | #include 7 | #include 8 | 9 | char* 10 | jalv_strdup(const char* const str) 11 | { 12 | const size_t len = strlen(str); 13 | char* copy = (char*)malloc(len + 1); 14 | memcpy(copy, str, len + 1); 15 | return copy; 16 | } 17 | -------------------------------------------------------------------------------- /src/string_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_STRING_UTILS_H 5 | #define JALV_STRING_UTILS_H 6 | 7 | #include "attributes.h" 8 | 9 | // String utilities 10 | JALV_BEGIN_DECLS 11 | 12 | /// Return a newly allocated copy of a string 13 | char* 14 | jalv_strdup(const char* str); 15 | 16 | JALV_END_DECLS 17 | 18 | #endif // JALV_STRING_UTILS_H 19 | -------------------------------------------------------------------------------- /src/symap.c: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "symap.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | @file symap.c Implementation of Symap, a basic symbol map (string interner). 13 | 14 | This implementation is primitive, but has some desirable qualities: good 15 | (O(lg(n)) lookup performance for already-mapped symbols, minimal space 16 | overhead, extremely fast (O(1)) reverse mapping (ID to string), simple code, 17 | no dependencies. 18 | 19 | The tradeoff is that mapping new symbols may be quite slow. In other words, 20 | this implementation is ideal for use cases with a relatively limited set of 21 | symbols, or where most symbols are mapped early. It will not fare so well 22 | with very dynamic sets of symbols. For that, you're better off with a 23 | tree-based implementation (and the associated space cost, especially if you 24 | need reverse mapping). 25 | */ 26 | 27 | struct SymapImpl { 28 | char** symbols; ///< String array where symbols[i] is the symbol for ID i 29 | uint32_t* index; ///< ID array sorted by corresponding string in `symbols` 30 | uint32_t size; ///< Number of symbols (length of both symbols and index) 31 | uint32_t pad; ///< Unused padding 32 | }; 33 | 34 | Symap* 35 | symap_new(void) 36 | { 37 | Symap* const map = (Symap*)calloc(1, sizeof(Symap)); 38 | 39 | if (map) { 40 | map->symbols = NULL; 41 | map->index = NULL; 42 | map->size = 0U; 43 | } 44 | 45 | return map; 46 | } 47 | 48 | void 49 | symap_free(Symap* const map) 50 | { 51 | if (map) { 52 | for (uint32_t i = 0U; i < map->size; ++i) { 53 | free(map->symbols[i]); 54 | } 55 | 56 | free(map->symbols); 57 | free(map->index); 58 | free(map); 59 | } 60 | } 61 | 62 | static char* 63 | symap_strdup(const char* const str) 64 | { 65 | const size_t len = strlen(str); 66 | char* const copy = (char*)malloc(len + 1); 67 | memcpy(copy, str, len + 1); 68 | return copy; 69 | } 70 | 71 | /** 72 | Return the index into map->index (not the ID) corresponding to `sym`, 73 | or the index where a new entry for `sym` should be inserted. 74 | */ 75 | static uint32_t 76 | symap_search(const Symap* const map, const char* const sym, bool* const exact) 77 | { 78 | *exact = false; 79 | 80 | if (map->size == 0) { 81 | return 0; // Empty map, insert at 0 82 | } 83 | 84 | if (strcmp(map->symbols[map->index[map->size - 1] - 1], sym) < 0) { 85 | return map->size; // Greater than last element, append 86 | } 87 | 88 | uint32_t lower = 0; 89 | uint32_t upper = map->size - 1; 90 | uint32_t i = upper; 91 | 92 | while (upper >= lower) { 93 | i = lower + ((upper - lower) / 2); 94 | 95 | const int cmp = strcmp(map->symbols[map->index[i] - 1], sym); 96 | if (cmp == 0) { 97 | *exact = true; 98 | return i; 99 | } 100 | 101 | if (cmp > 0) { 102 | if (i == 0) { 103 | break; // Avoid underflow 104 | } 105 | upper = i - 1; 106 | } else { 107 | lower = ++i; 108 | } 109 | } 110 | 111 | assert(!*exact || strcmp(map->symbols[map->index[i] - 1], sym) > 0); 112 | return i; 113 | } 114 | 115 | uint32_t 116 | symap_try_map(const Symap* const map, const char* const sym) 117 | { 118 | bool exact = false; 119 | const uint32_t index = symap_search(map, sym, &exact); 120 | if (exact) { 121 | return map->index[index]; 122 | } 123 | 124 | return 0; 125 | } 126 | 127 | uint32_t 128 | symap_map(Symap* const map, const char* sym) 129 | { 130 | // Search for existing symbol 131 | bool exact = false; 132 | const uint32_t index = symap_search(map, sym, &exact); 133 | if (exact) { 134 | assert(!strcmp(map->symbols[map->index[index] - 1], sym)); 135 | return map->index[index]; 136 | } 137 | 138 | // Claim a new highest ID 139 | const uint32_t id = map->size + 1; 140 | 141 | // Grow symbol array 142 | char** new_symbols = (char**)realloc(map->symbols, id * sizeof(sym)); 143 | if (!new_symbols) { 144 | return 0; 145 | } 146 | 147 | map->symbols = new_symbols; 148 | 149 | // Grow index array 150 | uint32_t* new_index = (uint32_t*)realloc(map->index, id * sizeof(index)); 151 | if (!new_index) { 152 | return 0; 153 | } 154 | 155 | map->index = new_index; 156 | 157 | // Append new symbol to symbols array 158 | map->size = id; 159 | map->symbols[id - 1] = symap_strdup(sym); 160 | 161 | // Insert new index element into sorted index 162 | map->index = new_index; 163 | if (index < map->size - 1) { 164 | memmove(map->index + index + 1, 165 | map->index + index, 166 | (map->size - index - 1) * sizeof(uint32_t)); 167 | } 168 | 169 | map->index[index] = id; 170 | 171 | return id; 172 | } 173 | 174 | const char* 175 | symap_unmap(const Symap* const map, const uint32_t id) 176 | { 177 | if (id == 0) { 178 | return NULL; 179 | } 180 | 181 | if (id <= map->size) { 182 | return map->symbols[id - 1]; 183 | } 184 | 185 | return NULL; 186 | } 187 | 188 | #ifdef SYMAP_STANDALONE 189 | 190 | # include 191 | 192 | static void 193 | symap_dump(const Symap* const map) 194 | { 195 | fprintf(stderr, "{\n"); 196 | for (uint32_t i = 0; i < map->size; ++i) { 197 | fprintf( 198 | stderr, "\t%u = %s\n", map->index[i], map->symbols[map->index[i] - 1]); 199 | } 200 | fprintf(stderr, "}\n"); 201 | } 202 | 203 | static int 204 | symap_test(Symap* const map) 205 | { 206 | # define N_SYMS 5 207 | 208 | static const char* const syms[N_SYMS] = { 209 | "hello", "bonjour", "goodbye", "aloha", "salut"}; 210 | 211 | for (int i = 0; i < N_SYMS; ++i) { 212 | if (symap_try_map(map, syms[i])) { 213 | return fprintf(stderr, "error: Symbol already mapped\n"); 214 | } 215 | 216 | const uint32_t id = symap_map(map, syms[i]); 217 | if (!id) { 218 | return fprintf(stderr, "error: Failed to insert ID\n"); 219 | } 220 | 221 | if (!!strcmp(map->symbols[id - 1], syms[i])) { 222 | return fprintf(stderr, "error: Corrupt symbol table\n"); 223 | } 224 | 225 | if (symap_map(map, syms[i]) != id) { 226 | return fprintf(stderr, "error: Remapped symbol to a different ID\n"); 227 | } 228 | 229 | symap_dump(map); 230 | } 231 | 232 | return 0; 233 | 234 | # undef N_SYMS 235 | } 236 | 237 | int 238 | main(void) 239 | { 240 | Symap* const map = symap_new(); 241 | const int st = symap_test(map); 242 | 243 | symap_free(map); 244 | return st; 245 | } 246 | 247 | #endif // SYMAP_STANDALONE 248 | -------------------------------------------------------------------------------- /src/symap.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | /** 5 | @file symap.h API for Symap, a basic symbol map (string interner). 6 | 7 | Particularly useful for implementing LV2 URI mapping. 8 | 9 | @see LV2 URID 10 | */ 11 | 12 | #ifndef SYMAP_H 13 | #define SYMAP_H 14 | 15 | #include 16 | 17 | #include 18 | 19 | typedef struct SymapImpl Symap; 20 | 21 | /// Create a new symbol map 22 | ZIX_MALLOC_FUNC Symap* 23 | symap_new(void); 24 | 25 | /// Free a symbol map 26 | void 27 | symap_free(Symap* map); 28 | 29 | /// Map a string to a symbol if it is already mapped, otherwise return 0 30 | ZIX_PURE_FUNC uint32_t 31 | symap_try_map(const Symap* map, const char* sym); 32 | 33 | /// Map a string to a symbol 34 | uint32_t 35 | symap_map(Symap* map, const char* sym); 36 | 37 | /// Unmap a symbol back to a string if possible, otherwise return NULL 38 | ZIX_PURE_FUNC const char* 39 | symap_unmap(const Symap* map, uint32_t id); 40 | 41 | #endif // SYMAP_H 42 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_TYPES_H 5 | #define JALV_TYPES_H 6 | 7 | #include "attributes.h" 8 | 9 | // Basic internal type declarations 10 | JALV_BEGIN_DECLS 11 | 12 | /// Process thread running state 13 | typedef enum { 14 | JALV_RUNNING, ///< Active and running the plugin 15 | JALV_PAUSED, ///< Active but bypassing the plugin (silent) 16 | } JalvRunState; 17 | 18 | /// "Global" application state 19 | typedef struct JalvImpl Jalv; 20 | 21 | /// Audio/MIDI backend 22 | typedef struct JalvBackendImpl JalvBackend; 23 | 24 | /// Plugin port "direction" 25 | typedef enum { FLOW_UNKNOWN, FLOW_INPUT, FLOW_OUTPUT } PortFlow; 26 | 27 | /// Plugin port type 28 | typedef enum { 29 | TYPE_UNKNOWN, 30 | TYPE_CONTROL, 31 | TYPE_AUDIO, 32 | TYPE_EVENT, 33 | TYPE_CV 34 | } PortType; 35 | 36 | JALV_END_DECLS 37 | 38 | #endif // JALV_TYPES_H 39 | -------------------------------------------------------------------------------- /src/urids.c: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2024 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "urids.h" 5 | 6 | #include "mapper.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void 18 | jalv_init_urids(JalvMapper* const mapper, JalvURIDs* const urids) 19 | { 20 | #define MAP_URI(uri) jalv_mapper_map_uri(mapper, (uri)) 21 | 22 | urids->atom_Chunk = MAP_URI(LV2_ATOM__Chunk); 23 | urids->atom_Float = MAP_URI(LV2_ATOM__Float); 24 | urids->atom_Int = MAP_URI(LV2_ATOM__Int); 25 | urids->atom_Object = MAP_URI(LV2_ATOM__Object); 26 | urids->atom_Path = MAP_URI(LV2_ATOM__Path); 27 | urids->atom_Sequence = MAP_URI(LV2_ATOM__Sequence); 28 | urids->atom_String = MAP_URI(LV2_ATOM__String); 29 | urids->atom_eventTransfer = MAP_URI(LV2_ATOM__eventTransfer); 30 | urids->bufsz_maxBlockLength = MAP_URI(LV2_BUF_SIZE__maxBlockLength); 31 | urids->bufsz_minBlockLength = MAP_URI(LV2_BUF_SIZE__minBlockLength); 32 | urids->bufsz_sequenceSize = MAP_URI(LV2_BUF_SIZE__sequenceSize); 33 | urids->log_Error = MAP_URI(LV2_LOG__Error); 34 | urids->log_Trace = MAP_URI(LV2_LOG__Trace); 35 | urids->log_Warning = MAP_URI(LV2_LOG__Warning); 36 | urids->midi_MidiEvent = MAP_URI(LV2_MIDI__MidiEvent); 37 | urids->param_sampleRate = MAP_URI(LV2_PARAMETERS__sampleRate); 38 | urids->patch_Get = MAP_URI(LV2_PATCH__Get); 39 | urids->patch_Put = MAP_URI(LV2_PATCH__Put); 40 | urids->patch_Set = MAP_URI(LV2_PATCH__Set); 41 | urids->patch_body = MAP_URI(LV2_PATCH__body); 42 | urids->patch_property = MAP_URI(LV2_PATCH__property); 43 | urids->patch_value = MAP_URI(LV2_PATCH__value); 44 | urids->time_Position = MAP_URI(LV2_TIME__Position); 45 | urids->time_bar = MAP_URI(LV2_TIME__bar); 46 | urids->time_barBeat = MAP_URI(LV2_TIME__barBeat); 47 | urids->time_beatUnit = MAP_URI(LV2_TIME__beatUnit); 48 | urids->time_beatsPerBar = MAP_URI(LV2_TIME__beatsPerBar); 49 | urids->time_beatsPerMinute = MAP_URI(LV2_TIME__beatsPerMinute); 50 | urids->time_frame = MAP_URI(LV2_TIME__frame); 51 | urids->time_speed = MAP_URI(LV2_TIME__speed); 52 | urids->ui_scaleFactor = MAP_URI(LV2_UI__scaleFactor); 53 | urids->ui_updateRate = MAP_URI(LV2_UI__updateRate); 54 | 55 | #undef MAP_URI 56 | } 57 | -------------------------------------------------------------------------------- /src/urids.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_URIDS_H 5 | #define JALV_URIDS_H 6 | 7 | #include "attributes.h" 8 | #include "mapper.h" 9 | 10 | #include 11 | 12 | // Cached LV2 URIDs 13 | JALV_BEGIN_DECLS 14 | 15 | typedef struct { 16 | LV2_URID atom_Chunk; 17 | LV2_URID atom_Float; 18 | LV2_URID atom_Int; 19 | LV2_URID atom_Object; 20 | LV2_URID atom_Path; 21 | LV2_URID atom_Sequence; 22 | LV2_URID atom_String; 23 | LV2_URID atom_eventTransfer; 24 | LV2_URID bufsz_maxBlockLength; 25 | LV2_URID bufsz_minBlockLength; 26 | LV2_URID bufsz_sequenceSize; 27 | LV2_URID log_Error; 28 | LV2_URID log_Trace; 29 | LV2_URID log_Warning; 30 | LV2_URID midi_MidiEvent; 31 | LV2_URID param_sampleRate; 32 | LV2_URID patch_Get; 33 | LV2_URID patch_Put; 34 | LV2_URID patch_Set; 35 | LV2_URID patch_body; 36 | LV2_URID patch_property; 37 | LV2_URID patch_value; 38 | LV2_URID time_Position; 39 | LV2_URID time_bar; 40 | LV2_URID time_barBeat; 41 | LV2_URID time_beatUnit; 42 | LV2_URID time_beatsPerBar; 43 | LV2_URID time_beatsPerMinute; 44 | LV2_URID time_frame; 45 | LV2_URID time_speed; 46 | LV2_URID ui_scaleFactor; 47 | LV2_URID ui_updateRate; 48 | } JalvURIDs; 49 | 50 | void 51 | jalv_init_urids(JalvMapper* mapper, JalvURIDs* urids); 52 | 53 | JALV_END_DECLS 54 | 55 | #endif // JALV_URIDS_H 56 | -------------------------------------------------------------------------------- /src/worker.c: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #include "worker.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #define MAX_PACKET_SIZE 4096U 17 | 18 | typedef enum { 19 | STATE_SINGLE_THREADED, ///< Single-threaded worker (only state) 20 | STATE_STOPPED, ///< Thread is exited 21 | STATE_LAUNCHED, ///< Thread is running 22 | STATE_MUST_EXIT, ///< Thread exit requested 23 | } WorkerState; 24 | 25 | struct JalvWorkerImpl { 26 | ZixRing* requests; ///< Requests to the worker 27 | ZixRing* responses; ///< Responses from the worker 28 | void* response; ///< Worker response buffer 29 | ZixSem* lock; ///< Lock for plugin work() method 30 | ZixSem sem; ///< Worker semaphore 31 | WorkerState state; ///< Worker state 32 | ZixThread thread; ///< Worker thread 33 | LV2_Handle handle; ///< Plugin handle 34 | const LV2_Worker_Interface* iface; ///< Plugin worker interface 35 | }; 36 | 37 | static LV2_Worker_Status 38 | jalv_worker_write_packet(ZixRing* const target, 39 | const uint32_t size, 40 | const void* const data) 41 | { 42 | ZixRingTransaction tx = zix_ring_begin_write(target); 43 | if (zix_ring_amend_write(target, &tx, &size, sizeof(size)) || 44 | zix_ring_amend_write(target, &tx, data, size)) { 45 | return LV2_WORKER_ERR_NO_SPACE; 46 | } 47 | 48 | zix_ring_commit_write(target, &tx); 49 | return LV2_WORKER_SUCCESS; 50 | } 51 | 52 | static LV2_Worker_Status 53 | jalv_worker_respond(LV2_Worker_Respond_Handle handle, 54 | const uint32_t size, 55 | const void* data) 56 | { 57 | return jalv_worker_write_packet(((JalvWorker*)handle)->responses, size, data); 58 | } 59 | 60 | static ZixThreadResult ZIX_THREAD_FUNC 61 | worker_func(void* const data) 62 | { 63 | JalvWorker* const worker = (JalvWorker*)data; 64 | void* buf = NULL; 65 | 66 | while (true) { 67 | // Wait for a request 68 | zix_sem_wait(&worker->sem); 69 | if (worker->state == STATE_MUST_EXIT) { 70 | break; 71 | } 72 | 73 | // Read the size header of the request 74 | uint32_t size = 0; 75 | zix_ring_read(worker->requests, &size, sizeof(size)); 76 | 77 | // Reallocate buffer to accommodate request if necessary 78 | void* const new_buf = realloc(buf, size); 79 | if (new_buf) { 80 | // Read request into buffer 81 | buf = new_buf; 82 | zix_ring_read(worker->requests, buf, size); 83 | 84 | // Lock and dispatch request to plugin's work handler 85 | zix_sem_wait(worker->lock); 86 | worker->iface->work( 87 | worker->handle, jalv_worker_respond, worker, size, buf); 88 | zix_sem_post(worker->lock); 89 | 90 | } else { 91 | // Reallocation failed, skip request to avoid corrupting ring 92 | zix_ring_skip(worker->requests, size); 93 | } 94 | } 95 | 96 | free(buf); 97 | worker->state = STATE_STOPPED; 98 | return ZIX_THREAD_RESULT; 99 | } 100 | 101 | JalvWorker* 102 | jalv_worker_new(ZixSem* const lock, const bool threaded) 103 | { 104 | JalvWorker* const worker = (JalvWorker*)calloc(1, sizeof(JalvWorker)); 105 | ZixRing* const requests = zix_ring_new(NULL, MAX_PACKET_SIZE); 106 | ZixRing* const responses = zix_ring_new(NULL, MAX_PACKET_SIZE); 107 | void* const response = calloc(1, MAX_PACKET_SIZE); 108 | 109 | if (worker && responses && response) { 110 | worker->requests = requests; 111 | worker->responses = responses; 112 | worker->response = response; 113 | worker->lock = lock; 114 | worker->state = threaded ? STATE_STOPPED : STATE_SINGLE_THREADED; 115 | 116 | zix_ring_mlock(requests); 117 | zix_ring_mlock(responses); 118 | return worker; 119 | } 120 | 121 | free(response); 122 | zix_ring_free(responses); 123 | zix_ring_free(requests); 124 | free(worker); 125 | return NULL; 126 | } 127 | 128 | void 129 | jalv_worker_free(JalvWorker* const worker) 130 | { 131 | if (worker) { 132 | jalv_worker_exit(worker); 133 | zix_ring_free(worker->requests); 134 | zix_ring_free(worker->responses); 135 | free(worker->response); 136 | free(worker); 137 | } 138 | } 139 | 140 | ZixStatus 141 | jalv_worker_launch(JalvWorker* const worker) 142 | { 143 | ZixStatus st = ZIX_STATUS_SUCCESS; 144 | if (worker->state == STATE_STOPPED) { 145 | if ((st = zix_sem_init(&worker->sem, 0))) { 146 | return st; 147 | } 148 | 149 | if ((st = zix_thread_create(&worker->thread, 4096U, worker_func, worker))) { 150 | zix_sem_destroy(&worker->sem); 151 | return st; 152 | } 153 | 154 | worker->state = STATE_LAUNCHED; 155 | } 156 | return st; 157 | } 158 | 159 | void 160 | jalv_worker_exit(JalvWorker* const worker) 161 | { 162 | if (worker && worker->state == STATE_LAUNCHED) { 163 | worker->state = STATE_MUST_EXIT; 164 | zix_sem_post(&worker->sem); 165 | zix_thread_join(worker->thread); 166 | } 167 | } 168 | 169 | void 170 | jalv_worker_attach(JalvWorker* const worker, 171 | const LV2_Worker_Interface* const iface, 172 | LV2_Handle handle) 173 | { 174 | if (worker) { 175 | worker->iface = iface; 176 | worker->handle = handle; 177 | } 178 | } 179 | 180 | LV2_Worker_Status 181 | jalv_worker_schedule(LV2_Worker_Schedule_Handle handle, 182 | const uint32_t size, 183 | const void* const data) 184 | { 185 | JalvWorker* const worker = (JalvWorker*)handle; 186 | LV2_Worker_Status st = LV2_WORKER_SUCCESS; 187 | 188 | if (!worker || !size || worker->state == STATE_STOPPED) { 189 | st = LV2_WORKER_ERR_UNKNOWN; 190 | 191 | } else if (worker->state == STATE_LAUNCHED) { 192 | // Schedule a request to be executed by the worker thread 193 | if (!(st = jalv_worker_write_packet(worker->requests, size, data))) { 194 | zix_sem_post(&worker->sem); 195 | } 196 | 197 | } else if (worker->state == STATE_SINGLE_THREADED) { 198 | // Execute work immediately in this thread 199 | zix_sem_wait(worker->lock); 200 | st = worker->iface->work( 201 | worker->handle, jalv_worker_respond, worker, size, data); 202 | zix_sem_post(worker->lock); 203 | } 204 | 205 | return st; 206 | } 207 | 208 | void 209 | jalv_worker_emit_responses(JalvWorker* const worker, LV2_Handle lv2_handle) 210 | { 211 | static const uint32_t size_size = (uint32_t)sizeof(uint32_t); 212 | 213 | if (worker && worker->responses) { 214 | uint32_t size = 0U; 215 | while (zix_ring_read(worker->responses, &size, size_size) == size_size) { 216 | if (zix_ring_read(worker->responses, worker->response, size) == size) { 217 | worker->iface->work_response(lv2_handle, size, worker->response); 218 | } 219 | } 220 | } 221 | } 222 | 223 | void 224 | jalv_worker_end_run(JalvWorker* const worker) 225 | { 226 | if (worker && worker->iface && worker->iface->end_run) { 227 | worker->iface->end_run(worker->handle); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/worker.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007-2022 David Robillard 2 | // SPDX-License-Identifier: ISC 3 | 4 | #ifndef JALV_WORKER_H 5 | #define JALV_WORKER_H 6 | 7 | #include "attributes.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | // Worker implementation 18 | JALV_BEGIN_DECLS 19 | 20 | /** 21 | A worker for running non-realtime tasks for plugins. 22 | 23 | The worker can be used in threaded mode, which allows non-realtime work to be 24 | done with latency when running realtime, or non-threaded mode, which performs 25 | work immediately for state restoration or offline rendering. 26 | */ 27 | typedef struct JalvWorkerImpl JalvWorker; 28 | 29 | /** 30 | Allocate a new worker and launch its thread if necessary. 31 | 32 | @param lock Pointer to lock used to guard doing work. 33 | @param threaded If true, launch a thread to perform work in. 34 | @return A newly allocated worker, or null on error. 35 | */ 36 | JalvWorker* 37 | jalv_worker_new(ZixSem* lock, bool threaded); 38 | 39 | /** 40 | Free a worker allocated with jalv_worker_new(). 41 | 42 | Calls jalv_worker_exit() to terminate the running thread if necessary. 43 | */ 44 | void 45 | jalv_worker_free(JalvWorker* worker); 46 | 47 | /** 48 | Launch the worker's thread. 49 | 50 | For threaded workers, this launches the thread if it isn't already running. 51 | For non-threaded workers, this does nothing. 52 | 53 | @return Zero on success, or a non-zero error code if launching failed. 54 | */ 55 | ZixStatus 56 | jalv_worker_launch(JalvWorker* worker); 57 | 58 | /** 59 | Terminate the worker's thread if necessary. 60 | 61 | For threaded workers, this blocks until the thread has exited. For 62 | non-threaded workers, this does nothing. 63 | */ 64 | void 65 | jalv_worker_exit(JalvWorker* worker); 66 | 67 | /** 68 | Attach the worker to a plugin instance. 69 | 70 | This must be called before scheduling any work. 71 | 72 | @param worker Worker to activate and attach to a plugin. 73 | @param iface Worker interface from plugin. 74 | @param handle Handle to the LV2 plugin this worker is for. 75 | */ 76 | void 77 | jalv_worker_attach(JalvWorker* worker, 78 | const LV2_Worker_Interface* iface, 79 | LV2_Handle handle); 80 | 81 | /** 82 | Schedule work to be performed by the worker in the audio thread. 83 | 84 | For threaded workers, this enqueues a request that will be handled by the 85 | worker thread asynchronously. For non-threaded workers, the work is 86 | performed immediately before returning. 87 | */ 88 | LV2_Worker_Status 89 | jalv_worker_schedule(LV2_Worker_Schedule_Handle handle, 90 | uint32_t size, 91 | const void* data); 92 | 93 | /** 94 | Emit any pending responses to the plugin in the audio thread. 95 | 96 | This dispatches responses from work that has been completed since the last 97 | call, so the plugin knows it is finished and can apply the changes. 98 | */ 99 | void 100 | jalv_worker_emit_responses(JalvWorker* worker, LV2_Handle lv2_handle); 101 | 102 | /** 103 | Notify the plugin that the run() cycle is finished. 104 | 105 | This must be called near the end of every cycle, after any potential calls 106 | to jalv_worker_emit_responses(). 107 | */ 108 | void 109 | jalv_worker_end_run(JalvWorker* worker); 110 | 111 | JALV_END_DECLS 112 | 113 | #endif // JALV_WORKER_H 114 | -------------------------------------------------------------------------------- /subprojects/lilv.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-file] 5 | directory = lilv-0.24.26 6 | source_url = https://download.drobilla.net/lilv-0.24.26.tar.xz 7 | source_filename = lilv-0.24.26.tar.xz 8 | source_hash = 22feed30bc0f952384a25c2f6f4b04e6d43836408798ed65a8a934c055d5d8ac 9 | 10 | # [wrap-git] 11 | # url = https://gitlab.com/lv2/lilv.git 12 | # push-url = ssh://git@gitlab.com:lv2/lilv.git 13 | # revision = v0.24.26 14 | # depth = 1 15 | -------------------------------------------------------------------------------- /subprojects/lv2.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-git] 5 | url = https://gitlab.com/lv2/lv2.git 6 | push-url = ssh://git@gitlab.com:lv2/lv2.git 7 | revision = main 8 | depth = 1 9 | -------------------------------------------------------------------------------- /subprojects/serd.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | # [wrap-file] 5 | # directory = serd-0.32.4 6 | # source_url = https://download.drobilla.net/serd-0.32.4.tar.xz 7 | # source_filename = serd-0.32.4.tar.xz 8 | # source_hash = cbefb569e8db686be8c69cb3866a9538c7cb055e8f24217dd6a4471effa7d349 9 | 10 | [wrap-git] 11 | url = https://gitlab.com/drobilla/serd.git 12 | push-url = ssh://git@gitlab.com:drobilla/serd.git 13 | revision = main 14 | depth = 1 15 | -------------------------------------------------------------------------------- /subprojects/sord.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-file] 5 | directory = sord-0.16.18 6 | source_url = https://download.drobilla.net/sord-0.16.18.tar.xz 7 | source_filename = sord-0.16.18.tar.xz 8 | source_hash = 4f398b635894491a4774b1498959805a08e11734c324f13d572dea695b13d3b3 9 | 10 | # [wrap-git] 11 | # url = https://gitlab.com/drobilla/sord.git 12 | # push-url = ssh://git@gitlab.com:drobilla/sord.git 13 | # revision = v0.16.18 14 | # depth = 1 15 | -------------------------------------------------------------------------------- /subprojects/sratom.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-file] 5 | directory = sratom-0.6.18 6 | source_url = https://download.drobilla.net/sratom-0.6.18.tar.xz 7 | source_filename = sratom-0.6.18.tar.xz 8 | source_hash = 4c6a6d9e0b4d6c01cc06a8849910feceb92e666cb38779c614dd2404a9931e92 9 | 10 | # [wrap-git] 11 | # url = https://gitlab.com/lv2/sratom.git 12 | # push-url = ssh://git@gitlab.com:lv2/sratom.git 13 | # revision = v0.6.18 14 | # depth = 1 15 | -------------------------------------------------------------------------------- /subprojects/suil.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-file] 5 | directory = suil-0.10.22 6 | source_url = https://download.drobilla.net/suil-0.10.22.tar.xz 7 | source_filename = suil-0.10.22.tar.xz 8 | source_hash = d720969e0f44a99d5fba35c733a43ed63a16b0dab867970777efca4b25387eb7 9 | 10 | # [wrap-git] 11 | # url = https://gitlab.com/lv2/suil.git 12 | # push-url = ssh://git@gitlab.com:lv2/suil.git 13 | # revision = v0.10.22 14 | # depth = 1 15 | -------------------------------------------------------------------------------- /subprojects/zix.wrap: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | [wrap-file] 5 | directory = zix-0.6.2 6 | source_url = https://download.drobilla.net/zix-0.6.2.tar.xz 7 | source_filename = zix-0.6.2.tar.xz 8 | source_hash = 4bc771abf4fcf399ea969a1da6b375f0117784f8fd0e2db356a859f635f616a7 9 | 10 | # [wrap-git] 11 | # url = https://gitlab.com/drobilla/zix.git 12 | # push-url = ssh://git@gitlab.com:drobilla/zix.git 13 | # revision = v0.6.2 14 | # depth = 1 15 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2024 David Robillard 2 | # SPDX-License-Identifier: 0BSD OR ISC 3 | 4 | ######## 5 | # Lint # 6 | ######## 7 | 8 | all_sources = ( 9 | common_sources + files( 10 | '../src/attributes.h', 11 | '../src/backend.h', 12 | '../src/control.h', 13 | '../src/frontend.h', 14 | '../src/jack.c', 15 | '../src/jalv.h', 16 | '../src/jalv_config.h', 17 | '../src/jalv_console.c', 18 | '../src/jalv_gtk.c', 19 | '../src/jalv_qt.cpp', 20 | '../src/jalv_qt.hpp', 21 | '../src/log.h', 22 | '../src/lv2_evbuf.h', 23 | '../src/nodes.h', 24 | '../src/options.h', 25 | '../src/port.h', 26 | '../src/portaudio.c', 27 | '../src/state.h', 28 | '../src/symap.h', 29 | '../src/types.h', 30 | '../src/urids.h', 31 | '../src/worker.h', 32 | ) 33 | ) 34 | 35 | if get_option('lint') 36 | if not meson.is_subproject() 37 | # Check release metadata 38 | autoship = find_program('autoship', required: get_option('tests')) 39 | if autoship.found() 40 | test( 41 | 'autoship', 42 | autoship, 43 | args: ['test', jalv_src_root], 44 | suite: 'data', 45 | ) 46 | endif 47 | 48 | # Check code with cppcheck 49 | cppcheck = find_program('cppcheck', required: false) 50 | if cppcheck.found() 51 | compdb_path = join_paths(jalv_build_root, 'compile_commands.json') 52 | suppress_path = join_paths(jalv_src_root, '.suppress.cppcheck') 53 | test( 54 | 'cppcheck', 55 | cppcheck, 56 | args: [ 57 | '--enable=warning,style,performance,portability', 58 | '--error-exitcode=1', 59 | '--project=' + compdb_path, 60 | '--suppressions-list=' + suppress_path, 61 | '-q', 62 | ['-i', jalv_build_root], 63 | ], 64 | suite: 'code', 65 | ) 66 | endif 67 | endif 68 | 69 | # Check licensing metadata 70 | reuse = find_program('reuse', required: false) 71 | if reuse.found() 72 | test( 73 | 'REUSE', 74 | reuse, 75 | args: ['--root', jalv_src_root, 'lint'], 76 | suite: 'data', 77 | ) 78 | endif 79 | 80 | # Check code formatting 81 | clang_format = find_program('clang-format', required: false) 82 | if clang_format.found() 83 | test( 84 | 'format', 85 | clang_format, 86 | args: ['--Werror', '--dry-run'] + all_sources, 87 | suite: 'code', 88 | ) 89 | endif 90 | endif 91 | 92 | ############## 93 | # Unit Tests # 94 | ############## 95 | 96 | test( 97 | 'test_symap', 98 | executable( 99 | 'test_symap', 100 | files('../src/symap.c'), 101 | c_args: c_suppressions + ['-DSYMAP_STANDALONE'], 102 | dependencies: [zix_dep], 103 | ), 104 | ) 105 | --------------------------------------------------------------------------------