Contributions are welcome! However, please open an issue first and discuss the details with me, preferably before you start working on a pull request! And if possible, avoid very large PRs. I usually can’t find the time to process large PRs.
ReaLearn is written in the programming language Rust. It makes heavy use of reaper-rs, which provides Rust bindings for the REAPER C++ API. reaper-rs was developed together with ReaLearn but is designed as independent library that can be used for REAPER plug-ins of all sorts.
Another noteworthy dependency and byproduct of ReaLearn is helgoboss-learn, a crate which provides DAW-agnostic MIDI learn logic (basically the source and mode parts of ReaLearn). Like reaper-rs, it’s designed as independent library and could be used to provide similar MIDI-learn functionality in other DAWs.
See here.
Directory entry | Content |
---|---|
|
Workspace root |
|
A custom global allocator for deferring deallocation in real-time threads |
|
Data structures of ReaLearn’s preset API (which is e.g. used in the Lua-based ReaLearn Script) |
|
Very generic utility code used by many crates in the workspace |
|
Code for interfacing with the ControlSurfaceIntegrator (CSI) project |
|
The single source of truth for ReaLearn’s GUI dialogs |
|
Documentation |
|
Helgobox REAPER extension (provides some additional convenience around the actual ReaLearn plug-in) |
|
Contains code for license processing (currently a private submodule) |
|
Various Rust macros for usage in this project only |
|
Main crate: The actual ReaLearn instrument plug-in ( |
|
Playtime data structures for describing e.g. clip engine presets |
|
Playtime Clip Engine for playing/recording clips (currently a private submodule). Is a workspace member because that makes it much simpler to use the same dependency versions everywhere. |
|
A placeholder crate for the Playtime Clip Engine. Users who don’t have access to
the Playtime Clip Engine submodule, must rename this directory to |
|
Core logic behind Pot Browser, also powers the Pot targets |
|
The actual Pot Browser user interface |
|
REAPER projects for manual testing, controller preset files, etc. |
|
Some reactive programming helpers |
|
Minimalistic UI framework based on SWELL ( |
cd main/lib/WDL
git checkout main
git pull
# After updating Cockos WDL, regenerate Rust bindings (because we use WDL's EEL code, for example)
cargo build --features generate
cargo fmt
Luau language bindings should be regenerated from Rust after changing something in api or playtime-api. This is done simply by executing all tests like this:
RUST_MIN_STACK=5242880 cargo test --package helgobox-api --lib bindings::luau::export_luau
Artwork such as toolbar icons can be regenerated by running a crate:
cargo run helgobox-artwork-processor
3 different approaches for generating code … yes, maybe it’s time to unify this ;)
This is about the diagrams in the Antora documentation, e.g. the glue signal flow.
sh regenerate-doc-diagrams.sh
In the following, you will find the complete instructions for Windows 10/11, including Rust setup. Points where you have to consider the target architecture (for example, REAPER 32-bit vs. 64-bit) are marked with ⭐.
-
Enable "Developer mode" in the Windows settings (this is needed because ReaLearn uses Symlinks within its Git repository)
-
Setup "Visual Studio" (currently tested with version 2022)
-
Rust uses native build toolchains. On Windows, it’s necessary to use the MSVC (Microsoft Visual Studio C++) toolchain because REAPER plug-ins only work with that.
-
Visual Studio downloads → All downloads → Tools for Visual Studio 2022 → Build Tools for Visual Studio 2022
-
Start it and follow the installer instructions
-
Required components
-
Workloads tab
-
"C++ build tools" (large box on the left)
-
Make sure "Windows 10 SDK" is checked on the right side (usually it is)
-
If on Windows ARM64: Make sure "C++ Clang-Tools" is checked on the right side (normally not checked!, currently necessary for compilation of
ring
dependency)
-
-
Language packs
-
English
-
-
-
-
Setup Rust
-
Download and execute
rustup-init.exe
-
Accept the defaults
-
Set the correct toolchain default ⭐
rustup default 1.77.2-x86_64-pc-windows-msvc
-
-
Download and install Git for Windows
-
Clone the ReaLearn Git repository
git clone https://github.com/helgoboss/helgobox.git` cd helgobox git checkout v2.16.0 # or any other release tag # ONLY IF YOU ARE HELGOBOSS git submodule update --init # OTHERWISE git submodule update --init main/lib/WDL main/lib/helgoboss-learn rmdir playtime-clip-engine rename playtime-clip-engine-placeholder playtime-clip-engine
-
Build ReaLearn (after that you should have a
helgobox.dll
intarget\debug
)cargo build --features egui
Complete instructions to build ReaLearn from a fresh Ubuntu 18.04.3 LTS installation, including Rust setup:
# Install native dependencies
sudo apt update
sudo apt install -y curl git build-essential pkg-config php nasm llvm-dev libclang-dev clang libudev-dev libxdo-dev libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev libspeechd-dev libgtk-3-dev
# Install Rust (copied from the official Linux installation instructions)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # choose 1 (default)
source $HOME/.cargo/env
# Set the correct toolchain default
rustup default 1.81.0-x86_64-unknown-linux-gnu
# Clone ReaLearn repository
git clone https://github.com/helgoboss/helgobox.git
cd helgobox
git checkout v2.16.0 # or any other release tag
# ONLY IF YOU ARE HELGOBOSS
git submodule update --init
# OTHERWISE
git submodule update --init main/lib/WDL main/lib/helgoboss-learn
rmdir playtime-clip-engine
mv playtime-clip-engine-placeholder playtime-clip-engine
# Build (after that you should have a "libhelgobox.so" in "target/debug")
cargo build --features egui
Some words about the native dependencies:
-
curl git build-essential pkg-config
are bare essentials. -
php
is needed to translate the ReaLearn dialog resource file to C so it can be processed by the SWELL dialog generator. It's also necessary for generating the 64-bit EEL assembler code. All of this is the typical WDL C way of doing things, no Rust specifics here. -
nasm
is needed for assembling the 64-bit EEL assembler code to produceasm-nseel-x64.o
, which is necessary to make the custom EEL control and feedback transformations in ReaLearn’s absolute mode work. -
llvm-dev libclang-dev clang
are necessary for building with featuregenerate
(to generate bindings to C). -
libxdo-dev
is needed to control the mouse (see target "Global: Mouse") -
libudev-dev
is needed for connecting to Stream Deck via HID API -
libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev
are necessary for egui-baseview (egui is the GUI framework used for ReaLearn’s control transformation editor) -
libspeechd-dev
is necessary for the speech source -
libgtk-3-dev
is necessary to obtain the X window and X display from a SWELL OS window, in order to fire up OpenGL/egui in it
The following instructions include Rust setup. However, it’s very well possible that some native toolchain setup instructions are missing, because I don’t have a bare macOS installation at my disposal. The Rust installation script should provide you with the necessary instructions if something is missing.
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # choose 1 (default)
source $HOME/.cargo/env
rustup default 1.81.0-x86_64-apple-darwin
# Clone ReaLearn
cd Downloads
git clone https://github.com/helgoboss/helgobox.git
cd helgobox
git checkout v2.16.0 # or any other release tag
# ONLY IF YOU ARE HELGOBOSS
git submodule update --init
# OTHERWISE
git submodule update --init main/lib/WDL main/lib/helgoboss-learn
rmdir playtime-clip-engine
mv playtime-clip-engine-placeholder playtime-clip-engine
# Install build dependencies
brew install php
# Build ReaLearn
cargo build --features egui
The GUI dialogs are defined in the dialogs
directory.
Whenever ReaLearn is built, the code there generates an old-school Windows dialog resource file (target/generated/msvc.rc
) and a Rust file which contains all the resource ID constants (main/src/infrastructure/ui/bindings.rs
).
Previously I used the Visual Studio C++ 2019 resource editor to WYSIWYG-edit this file as part of the solution msvc.sln, but this was too tedious.
Warning
|
You can still preview the generated file in Visual Studio but don’t edit the RC file, the changes will be overwritten at build time!
Adjust the Rust code in the dialogs directory instead.
|
On macOS and Linux, an extra step will happen at build time: It will try to use a PHP script (part of Cockos SWELL) to generate
target/generated/msvc.rc_mac_dlg
, which is a translation of the RC file to C code using SWELL.
So make sure you have PHP installed on these platforms!
Yes, there are tests but there should be more. While ReaLearn’s basic building blocks helgoboss-learn and reaper-rs are tested pretty thoroughly, ReaLearn itself has room for improvement in that aspect.
Unit tests should be executed with a higher stack size because there’s one unit test that generates and formats Lua code and this currently overflows the stack in debug builds.
RUST_MIN_STACK=104857600 cargo test
It’s possible to make ReaLearn output log messages to stdout
by setting the HELGOBOX_LOG
environment variable, e.g. to debug,vst=info
.
It follows this format.
Beware that e.g. on Windows, stdout
is not shown, not even when executing REAPER from the command line.
One way to make it visible is to execute REAPER with a debugger.
It’s possible to make ReaLearn expose execution metrics.
-
If the projection server is running, metrics will then be exposed at
/realearn/metrics
in the popular Prometheus format. That’s great for visualization.-
Just add this to your
prometheus.yml
(you might need to adjust the port):
-
scrape_configs: - job_name: 'realearn' metrics_path: '/realearn/metrics' static_configs: - targets: ['localhost:39080']
-
If you don’t have any metrics enabled, this will show zeros only.
Prometheus is usually available at http://localhost:9090/.
-
You can turn on ReaLearn metrics by setting the environment variable
HELGOBOX_METRICS
(value doesn’t matter). -
If this environment variable is set (value doesn’t matter), ReaLearn will record some metrics and expose them on the Prometheus endpoint mentioned above.
-
If ReaLearn is built with the Playtime Clip Engine, this flag will also enable Clip Engine metrics. This can negatively effect clip playing performance because many clip engine metrics are captured in real-time threads.
Set vst_scan=1
in the [reaper]
section of reaper.ini
.
That makes the debugged REAPER process itself do the scanning.
Debug symbols are stripped from release builds but stored as build artifact of the GitHub Actions "Create release" workflow. If you want the symbols for a specific build, proceed as follows:
-
Use the branch filter to show all releases builds made for a specific version, e.g. "v1.11.0".
-
Click the desired workflow.
-
GitHub seems to do a fuzzy search, so if there are pre-releases (e.g. "v1.11.0-pre2"), you will see them, too.
-
In that case, just choose the latest one.
-
-
You will see a list of artifacts, one for each OS-architecture combination.
-
Download the one you need and unzip it.
-
You will find both the library file and the symbol file (e.g.
realearn.pdb
for a Windows build).
-
As soon as you have the debug symbols, you can make ReaLearn print full backtraces (including line number etc.) in the REAPER ReaScript console. Here’s how you do it.
The problem with release builds is that they don’t contain debug symbols and therefore backtraces usually contain not much more than memory addresses. Especially backtraces generated by Windows release builds leave a lot to be desired.
ReaLearn has a built-in REAPER action which attempts to look up symbol information for a given error report: "ReaLearn: Resolve symbols from clipboard". Works on Windows only. To be used like this:
-
Make sure the PDB for the release build in question is on the search path (see section above).
-
Fire up an ReaLearn using exactly that release build.
-
Copy the error report to the clipboard.
-
Execute the action.
Insights:
-
The size difference between
debug = 1
anddebug = 2
is almost nothing (both 58 MB), and there’s nothing to gain fromdebug = 2
in terms of stack traces. -
The size difference between
debug = 0
anddebug = 1
is around 5 MB (53 MB vs. 58 MB), anddebug = 1
only makes a difference if the source files exist (showing line numbers), and only for panics. -
Hard crash stack traces are completely independent of the
debug
value. They are always helpful except when stripping the symbols. -
strip = symbols
leads to the smallest binaries (38 MB) but also to completely useless stack traces, both in soft and hard crashes. However,split-debuginfo = "packed" seems to fix this at least for hard crashes, at a similar-sized binary (not for panics though) … even if the DSYM directories are not on disk. What also fixes this for hard crashes is stripping via `strip -u -r
. We shouldn’t do that anymore! -
strip = debuginfo
leads to an okay size reduction (53 MB) but removes line numbers even if source files exist. However,split-debuginfo = "packed"
solves this by creating dSYM directories, as long as they are there.
Takeaway:
-
We should build with
debug = 2
-
While
debug = 1
is actually enough for most purposes, it can’t hurt building withdebug = 2
since we strip debuginfo anyway. So there’s no size difference for the final binary. That way we have more debuginfo on the server whenever we need it.
-
-
strip = debuginfo
is the max we can strip away if we want panics to contain something useful -
strip = symbols
is only okay if we are fine with bogus stack traces in panics. In that case, we must usesplit-debuginfo = "packed"
to get at least detailed stack traces in case of hard crashes. -
We should use
split-debuginfo = "packed"
in all cases. -
Would be good to find a way to leverage symbols for panic stack traces, but I think there is none.
7: 0x120531450 - helgobox::infrastructure::plugin::sandbox::execute::h9608b1370cb08106
4 helgobox-arm64.vst.dylib 0x120527df4 _$LT$helgobox..domain..targets..track_volume_target..TrackVolumeTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::hff7df2fe68cec5d5 + 20
7: 0x13071ffdc - helgobox::infrastructure::plugin::sandbox::execute::hb564445c2d9211ee at /Users/helgoboss/Documents/projects/dev/realearn/main/src/infrastructure/plugin/sandbox.rs:3:5
7: 0x140f1ffdc - helgobox::infrastructure::plugin::sandbox::execute::hb564445c2d9211ee
6 helgobox-arm64.vst.dylib 0x1302d4a64 _$LT$helgobox..domain..mapping..CompoundMappingTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::hadfcb1be993900b3 + 20 (mapping.rs:2498) [inlined]
7: 0x12160dafc - helgobox::infrastructure::plugin::sandbox::execute::h18fb689d4112e2d3 at /Users/helgoboss/Documents/projects/dev/realearn/main/src/infrastructure/plugin/sandbox.rs:3:5
7: 0x153f81afc - helgobox::infrastructure::plugin::sandbox::execute::h18fb689d4112e2d3
6 helgobox-arm64.vst.dylib 0x1212d8148 _$LT$helgobox..domain..mapping..CompoundMappingTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::h15041e3226fa455d + 20 (mapping.rs:2498) [inlined]
7: 0x138616570 - helgobox::infrastructure::plugin::sandbox::execute::hd0d406afe4d62df9
4 helgobox-arm64.vst.dylib 0x13885aab4 _$LT$helgobox..domain..targets..track_volume_target..TrackVolumeTarget$u20$as$u20$helgoboss_learn..mode..target..Target$GT$::current_value::h35c07d80eb0a312b + 20
0: 0x14bdef0ec - _NSEEL_HOSTSTUB_EnterMutex 1: 0x14bd56f08 - _NSEEL_HOSTSTUB_EnterMutex 2: 0x14bfbe4e4 - _cpp_to_rust_ProjectStateContext_SetTempFlag 3: 0x14bfbddcc - _cpp_to_rust_ProjectStateContext_SetTempFlag 4: 0x14bfbca08 - _cpp_to_rust_ProjectStateContext_SetTempFlag 5: 0x14bfbdabc - _cpp_to_rust_ProjectStateContext_SetTempFlag 6: 0x14c075710 - _cpp_to_rust_ProjectStateContext_SetTempFlag 7: 0x14af50aa0 - _ReaperPluginEntry 8: 0x14bd58fcc - _NSEEL_HOSTSTUB_EnterMutex 9: 0x14bd82844 - _NSEEL_HOSTSTUB_EnterMutex
7 helgobox-arm64.vst.dylib 0x14b34b5e0 0x14a65c000 + 13563360
All documentation is written in AsciiDoc.
Some SVGs embedded in the architecture documentation are generated via NodeJS / SVG.js in doc/svg-gen/index.js. After modifying this file, you need to execute the following command in the project root:
node doc/svg-gen/index.js
It’s important to make sure that the licenses of all dependencies are compatible with the final license. We use cargo-deny for this.
Installation:
cargo install --locked cargo-deny
Check:
cargo deny check licenses
It’s important to make sure that the licenses of all dependencies are compatible with the final license. We use cargo-deny for this.
Installation:
cargo install --locked cargo-about
Generate the report:
cargo about generate --fail --workspace --all-features --threshold 0.93 about.hbs > about.html
This serves mainly as a checklist for Helgobox’s author.
-
Check licenses via
cargo deny check licenses
and make sure the outcome is "licenses ok" -
Update license report (see above)
-
Take care of app versioning
-
Plug-in repository: Adjust
HOST_API_VERSION
andMIN_APP_API_VERSION
-
App repository: Adjust
appApiVersion
(macOS, Swift),APP_API_VERSION
(Windows, C++) and_minHostApiVersionString
(Dart)
-
-
Bump up the app version number in
pubspec.yaml
. -
Bump up the plug-in version number in main/Cargo.toml.
-
Either to a prerelease (e.g.
2.0.0-pre1
) or a final release (e.g.2.0.0
). -
This is important for having the correct version number displayed in ReaLearn UI.
-
-
Build at least once via
cargo build --features playtime,egui
.-
This updates
Cargo.lock
and is important for not having the-dirty
display in ReaLearn UI.
-
-
Update the user guide if not done already.
-
Create a version tag via
git tag v2.0.0-pre1
. -
Push via
git push origin master --tags
. -
While GitHub Actions executes the release job, take care of the following.
-
Can only be done by @helgoboss because it needs access to the helgoboss.org website repository.
-
If it’s a prerelease, make sure we are on a prerelease cycle branch of the website repository.
-
Add a changelog entry in data.yaml.
-
In
src/snippets/projects/realearn/repo
, entergit checkout master
andgit pull
to pull the latest user guide changes. -
Push via
git push origin HEAD
and wait until Netlify deployed the page. -
All the following stuff needs to be done using Netlify’s branch preview if it’s a prerelease!
-
Update helgoboss ReaPack index.
-
Generate ReaLearn-only ReaPack index by requesting /projects/realearn/reapack.txt.
-
Integrate the generated index by copying everything from
<category name="Extensions">
and pasting it to the helgoboss ReaPack index without overwriting the preset categories on the top of the file. -
Don’t push the index yet!
-
-
Author a REAPER forum ReaLearn thread entry with help of /projects/realearn/reaper-forum.txt but don’t submit yet!
-
Download the user guide by requesting /projects/realearn/user-guide.
-
Copy the corresponding changelog entry in markdown format by requesting /projects/realearn/changelog.md.
-
-
Once the release job has finished successfully, edit the not-yet-published release that has been created.
-
Paste the copied changelog entry to the release notes.
-
Manually add the previously downloaded user guide as release artifact named
realearn-user-guide.pdf
.
-
-
Publish the release.
-
Push the helgoboss ReaPack index.
-
Submit the REAPER forum ReaLearn thread entry.
-
Check if synchronization of the ReaPack repository works.
In REAPER for Windows it’s possible to enable complete unload of VST plug-ins (Preferences → Plug-ins → VST → Allow complete unload of VST plug-ins). This also affects ReaLearn. Removing the last ReaLearn instance should work with and without this flag enabled, it’s important to test this.
I ran into a case in which Windows was not unloading ReaLearn even though that option was enabled. The reason turned out to be a registry entry that Windows must have created automatically at some point:
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
→ C:\REAPER\reaper.exe
with value $ IgnoreFreeLibrary<realearn.dll>
Removing this entry made unloading work again. What a nasty trap!