Introduction
REAPER is a powerful Digital Audio Workstation (DAW) with enormous customization possibilities. Its scripting support, external control capabilities, support for many DAW plugin formats, and compatibility with MacOS and Windows make it an obvious choice for building all sorts of integrations and automation. At Sonarworks, we use REAPER as a plugin host as part of our DAW plugin test automation framework.
Despite its great functionality, REAPER features minimal documentation, which makes it challenging to unleash its full potential.
In this article, I’ll provide a complete guide on how to extend REAPER’s control possibilities when interacting with it via the Open Sound Control (OSC) interface.
Out-of-the-Box Functionality
Out of the box, REAPER supports a limited set of functions via the OSC interface, which are defined in .ReaperOSC
files located in ~/Library/Application\ Support/REAPER/OSC/
on MacOS. You can modify the address patterns of OSC messages REAPER will react to and call pre-defined functions, but you can’t define your own functions directly.
Defining and Calling Custom Actions
Fortunately, REAPER supports calling custom actions via /action/_COMMAND_ID
OSC messages.
Below, I’ll demonstrate the setup. Please note that I’ll be modifying some of REAPER’s configuration files directly, as certain modifications via REAPER itself are not possible or are very unhandy. Always backup your files before making any modifications to avoid losing your work or breaking your existing setup.
Custom actions are stored in ~/Library/Application\ Support/REAPER/reaper-kb.ini
on MacOS. The file format description can be found here.
For my use case, I only need my custom actions, so I started with an empty file and added the following line:
SCR 4 0 MY_COMPANY_TEST_SIMPLE_OSC "MyCompany: test simple OSC trigger" "MyCompany/test_simple_osc.lua"
This defines a custom action. For the meaning of SCR 4 0
, see the reaper-kb.ini documentation. _MY_COMPANY_TEST_SIMPLE_OSC
(note the underscore) is the Command ID of the test_simple_osc.lua
script, which should be placed in the ~/Library/Application\ Support/REAPER/Scripts/MyCompany/
directory.
Content of test_simple_osc.lua
:
reaper.ShowMessageBox("This is a simple script that can be invoked via OSC", "Info", 0)
After manually editing files, REAPER must be restarted for changes to take effect!
To verify the script and reaper-kb.ini
are correct, run REAPER, and from the Actions
dialog (Shift
+ ?
), find the MyCompany: test simple OSC trigger
action and run it. You should see a message box with the text “This is a simple script that can be invoked via OSC”.
If not done already, from REAPER Preferences
(Command
+ ,
), navigate to the Control/OSC/web
section and add a new OSC (Open Sound Control)
control surface. Leave the Default
pattern config, select Local port
mode, and note the Local listen port
. Other settings can remain at their default values for now, as shown in the screenshot.
Now you can send an OSC message to trigger the custom action. For testing purposes, you can use sendosc (which can be installed on MacOS using brew install yoggy/tap/sendosc
).
From the terminal, run: sendosc localhost 8000 /action/_MY_COMPANY_TEST_SIMPLE_OSC
If everything worked correctly, you should see the same message box with the text “This is a simple script that can be invoked via OSC”.
Troubleshooting OSC Communication
To ensure OSC messages are reaching REAPER, navigate to Preferences
(Command
+ ,
), then the Control/OSC/web
section. Select the existing OSC
control from the list and press Edit
.
From the Control Surface Settings
window, press Listen...
, and observe incoming OSC messages. Each line represents a single received OSC message, for example: /test_message_with_multi_arg [sf] "test string" 123.449997
, where:
/test_message_with_multi_arg
is the message address;[sf]
describes argument types, s - string, f - float number, i - integer number. In this example, two arguments were passed: the first of type string, the second a float;- followed by argument values
"test string" 123.449997
.
Passing Arguments to Custom Actions
The above method works well for many use cases, but it has one downside: it’s impossible to pass any arguments when calling actions. If the variance of possible argument values is low, one can create multiple actions. For example, to enable or disable a certain option: /action/_ENABLE_OPTION
, /action/_DISABLE_OPTION
. This is fine, but such an approach is not suitable for passing numeric or string arguments to an action.
Is there a way to pass an argument to an action? Yes, as REAPER supports binding OSC messages to trigger actions. When an action is invoked this way, a single (first) OSC message argument of string or float type can be retrieved from the script.
To start, make sure binding OSC messages to actions is allowed: In REAPER, navigate to Preferences
(Command
+ ,
), then the Control/OSC/web
section. Select the existing OSC
control from the list and press Edit
.
Check the Allow binding messages to REAPER actions and FX learn
option and press Ok
.
Create osc.lua
in ~/Library/Application\ Support/REAPER/Scripts/MyCompany/
with the following content:
-- osc.lua
-- Utility functions to get and parse OSC message and argument from REAPER action context
local osc = {}
function osc.parse(context)
local msg = {}
-- Extract the message
msg.address = context:match("^osc:/([^:[]+)")
if msg.address == nil then
return nil
end
-- Extract float or string value
local value_type, value = context:match(":([fs])=([^%]]+)")
if value_type == "f" then
msg.arg = tonumber(value)
elseif value_type == "s" then
msg.arg = value
end
return msg
end
function osc.get()
local is_new, name, sec, cmd, rel, res, val, ctx = reaper.get_action_context()
if ctx == nil or ctx == '' then
return nil
end
return osc.parse(ctx)
end
return osc
Next to it, create test_osc_with_arg.lua
:
-- Retrieve the directory of the current script.
local script_path = debug.getinfo(1, "S").source:match("@?(.*/)")
-- Set the package path to include the other scripts in the directory
package.path = package.path .. ';' .. script_path .. '?.lua'
-- Require the osc module
local osc = require('osc')
local msg = osc.get()
if msg then
reaper.ShowMessageBox("OSC address: " .. msg.address .. ", argument: " .. (msg.arg and msg.arg or "(nil)"), "Info", 0)
else
reaper.ShowMessageBox("Invalid or no OSC message", "Error", 0)
end
Add the following line to ~/Library/Application\ Support/REAPER/reaper-kb.ini
:
SCR 4 0 MY_COMPANY_TEST_OSC_WITH_ARG "MyCompany: test OSC trigger with argument" "MyCompany/test_osc_with_arg.lua"
Finally, the newly created action can be mapped to OSC messages. This can be done in two ways:
- By editing the
~/Library/Application\ Support/REAPER/reaper-osc-actions.ini
file; - Or by creating a shortcut from the
Actions
dialog by listening for an incoming OSC message.
I prefer the former method. Add the following line to ~/Library/Application\ Support/REAPER/reaper-osc-actions.ini
:
"/test_message_with_arg" 0 0 _MY_COMPANY_TEST_OSC_WITH_ARG
This instructs REAPER to invoke the _MY_COMPANY_TEST_OSC_WITH_ARG
action when a "/test_message_with_arg"
OSC message is received.
Restart REAPER for changes to take effect.
To test that everything works, from the Terminal run: sendosc localhost 8000 /test_message_with_arg s "test string"
. This should invoke the test_osc_with_arg.lua
script in REAPER and display a message box with the text “OSC address: test_message_with_arg, argument: test string”.
Running sendosc localhost 8000 /test_message_with_arg f 123.456
will display the message “OSC address: test_message_with_arg, argument: 123.456001”.
Running sendosc localhost 8000 /test_message_with_arg
will display the message “OSC address: test_message_with_arg, argument: (nil)”.
Limitations
REAPER has the following limitations when it comes to working with OSC messages:
- Scripts can only receive the first argument of the OSC message. As a workaround, a string argument can be used to encapsulate any number of arguments.
- Only string and float argument types are supported. Again, a string can be used to hold any required datatype that can be represented as a string.
- Sending OSC messages from scripts is not supported. As a workaround,
sendosc
or a similar utility can be invoked usingos.execute()
, or third-party REAPER extensions can be used to accomplish this task.
Conclusion
REAPER is a powerful DAW offering scripting and extensible external control capabilities, which makes it suitable for use as part of various automation applications, including DAW plugin test automation frameworks. Unfortunately, its minimalistic documentation makes feature discovery problematic and requires additional effort to make things work. Some basic functionality is still missing and requires workarounds.