- CMake 3.15+
- C++17 compiler (GCC 7+, Clang 5+, MSVC 2017+)
- Go 1.21+ (for integration tests only)
make # Build release
make test # Build + run unit tests
make install # Copy plugin to REAPER UserPlugins directoryRun make help to see all targets.
make # Release build
make debug # Debug build
make rebuild # Clean + release buildOr using CMake directly:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config ReleaseThe plugin binary is output as:
- Linux:
build/reaper_reaserve.so - macOS:
build/reaper_reaserve.dylib - Windows:
build/reaper_reaserve.dll
make installThis copies the plugin to your REAPER UserPlugins/ directory. Override the path with:
make install USERPLUGINS=/path/to/UserPluginsRestart REAPER after installing to load the updated plugin.
Tests use plain assert() — no external test framework required.
make testOr directly:
ctest --test-dir build --output-on-failure
# Individual test binaries
./build/test_json_rpc
./build/test_command_queue
./build/test_framingTo disable building tests:
cmake -B build -DREASERVE_BUILD_TESTS=OFFIntegration tests run every method in PROTOCOL.md against a live REAPER instance. They require Go 1.21+.
Setup:
- Build and install the plugin (
make install) - Open REAPER with a new, blank project (no tracks, items, or markers)
- Confirm ReaServe is running (startup message in the console)
Run:
make test-integrationOr directly:
cd tests/integration && go test -tags integration -v -count=1 .To connect to a non-default address:
REASERVE_ADDR=192.168.1.10:9876 make test-integrationThe tests create tracks, items, FX, MIDI notes, markers, sends, and envelope points — then clean everything up, leaving the project blank.
The integration build tag ensures these tests are never compiled during normal go test runs or CI.
The version is defined in a single place: the project(VERSION ...) line in CMakeLists.txt.
At configure time, CMake generates build/generated/version.h from the template src/version.h.in. This header provides:
#define REASERVE_VERSION "0.1.0" // full version string
#define REASERVE_VERSION_MAJOR 0
#define REASERVE_VERSION_MINOR 1
#define REASERVE_VERSION_PATCH 0The version appears in:
- The Reaper console on plugin load (
ReaServe v0.1.0: TCP server started...) - The
pingJSON-RPC response ({"pong": true, "version": "0.1.0"})
To bump the version, edit the single line in CMakeLists.txt:
project(reaserve VERSION 0.2.0 LANGUAGES CXX)Then re-run cmake -B build to regenerate version.h. Update CHANGELOG.md manually.
Runs on every push and PR to main. Builds on Linux, macOS, and Windows in parallel, runs tests, and uploads the binaries as workflow artifacts (downloadable from the Actions tab, expire after 90 days).
Triggered by pushing a v* tag. Builds and tests all three platforms, then creates a GitHub Release with platform-specific binaries attached. The release body is extracted from CHANGELOG.md automatically.
# 1. Bump version in CMakeLists.txt
# project(reaserve VERSION 0.2.0 LANGUAGES CXX)
# 2. Update CHANGELOG.md — move items from [staging] to the new version heading
# 3. Commit
git add CMakeLists.txt CHANGELOG.md
git commit -m "Release v0.2.0"
# 4. Tag and push
git tag v0.2.0
git push origin main --tags
The tag push triggers the release workflow. Once it completes, a GitHub Release named "ReaServe v0.2.0" will appear with reaper_reaserve-linux.so, reaper_reaserve-macos.dylib, and reaper_reaserve-windows.dll attached for download.
CMakeLists.txt # Build system — single source of truth for version
src/
main.cpp # Plugin entry point (ReaperPluginEntry)
version.h.in # Version header template (CMake substitutes values)
tcp_server.{h,cpp} # TCP listener, per-client threads, length-prefixed framing
json_rpc.{h,cpp} # JSON-RPC 2.0 parsing and response construction
command_queue.{h,cpp} # Thread-safe queue bridging TCP threads to main thread
command_registry.{h,cpp} # Method name -> handler dispatch
reaper_api.{h,cpp} # REAPER API function pointer resolution
config.{h,cpp} # INI config loading (reaserve.ini)
undo.{h,cpp} # Undo block helpers
commands/ # One file per command group (ping, tracks, fx, etc.)
tests/ # Unit tests (assert-based, no framework)
vendor/ # Third-party headers (nlohmann/json)
examples/ # Client examples in Python, Node.js, Go
All REAPER API calls must happen on the main thread. The plugin uses a producer-consumer pattern:
- TCP client sends a JSON-RPC request
TcpServerparses it on a background thread and pushes aPendingCommand(with astd::promise) onto theCommandQueue- A REAPER timer callback (
~30ms) pops commands from the queue and dispatches them viaCommandRegistry - The handler calls REAPER's C API, returns a result
- The promise is fulfilled, unblocking the TCP thread which sends the response
- Create
src/commands/your_command.cpp - Implement a
register_your_command(CommandRegistry&)function - Add the source file to
CMakeLists.txtin theadd_librarylist - Add the forward declaration and registration call in
src/main.cpp - Document the method in
PROTOCOL.md