OSC controller for Reaper digital audio workstation (DAW)

Status
Not open for further replies.

Gerrit

Well-known member
One of the issues with most commercially available control surfaces is the fact that they use the Mackie HUI or MCU MIDI protocol for communication. These protocols were designed to work with the 31kb/s midi bandwidth and as a result are limited when it comes to (text) feedback. On a Mackie control I often find myself guessing as to what it is I'm looking at. Open Sound Control (OSC) has none of these limitations but unfortunately OSC support is still rare. Reaper is one of the few DAWs offering comprehensive OSC support. To explore the available OSC interface I build a basic OSC controller.

Reaper_OSC_test.jpg

A Teensy 3.6 with a WIZ820_SD_ADAPTOR and WIZ850io module for wired ethernet, the connections are as in the example for the WIZ adapter. Two rotary encoders, one (with the small knob, indented) for selecting a parameter and one (big knob, not indented) for changing the value. A 4D Systems display showing the track number and name, plugin name, parameter name and parameter value as string and virtual knob display. The display also has vu meter showing the track volume. Not shown are five button for navigating tracks and plugins.
The code only uses libraries that come with the Teensyduino installation. The 4D Systems VisiGenie library is not used, relevant parts are used directly in the sketch and asynchronous communication with a kind of display queue is implemented.
During setup the controller sends the settings to Reaper and a 'refresh all control surfaces' action is triggered' which will cause Reaper to send all track data.
Code:
  // send bundle with controller definition & state
  OSCBundle bndl;
  bndl.add("/device/track/count").add(NUM_OF_TRACKS);
  bndl.add("/device/send/count").add(NUM_OF_SENDS);
  bndl.add("/device/fx/count").add(NUM_OF_INSERTS);
  bndl.add("/device/fxparam/count").add(NUM_OF_PARAMETERS);
  bndl.add("/device/track/select").add(selectedTrack.number);
  bndl.add("/device/fx/select").add(selectedInsert.number);
  bndl.add("/device/fxparam/bank/select").add(selectedParameterBank);

  Udp.beginPacket(outIp, destPort);
  bndl.send(Udp);
  Udp.endPacket(); // mark the end of the OSC Packet
  bndl.empty(); // empty the bundle to free room for a new one

  // send 'reset all controllers' message so Reaper will send all track data
  sendOscMessage("/action",41743);

The received data is parsed and stored in structs modelling the track structure (tracks, sends, inserts, parameters):

Code:
// struct containing OSC parameter data & values
struct oscParameter{
  char  name[NAME_LENGTH];                            // parameter long name
  int   number;                                       // parameter number
  char  valueStr[VALUE_LENGTH];                       // formatted string value e.g. '350Hz'
  float value;                                        // normalised value
};
typedef struct oscParameter OscParameter;

// struct for fx plugin
struct trackInsert{
  char  name[NAME_LENGTH];                            // plugin long name
  int   number;                                       // plugin number
  bool  bypass;                                       // bypass status
  //OscParameter parameter[NUM_OF_PARAMETERS];        // plugin parameters
};
typedef struct trackInsert TrackInsert;

// track send parameters
struct trackSend{
  char  name[NAME_LENGTH];                            // send long name
  int   number;                                       // send number
  char  volumeStr[VALUE_LENGTH];                      // volume string    
  float volume;                                       // normalised volume
  char  panStr[VALUE_LENGTH];                         // panning string    
  float pan;                                          // normalised panning
};
typedef struct trackSend TrackSend;

// struct containing Reaper track info & data
struct track{
  char        name[NAME_LENGTH];                      // track name
  int         number;                                 // track number
  char        volumeStr[VALUE_LENGTH];                // volume string    
  float       volume;                                 // normalised volume
  char        panStr[VALUE_LENGTH];                   // panning string    
  float       pan;                                    // normalised panning
  bool        rec;                                    // record arm
  bool        mute;                                   // track mute
  bool        solo;                                   // track solo
  bool        monitor;                                // track monitor
  TrackSend   send[NUM_OF_SENDS];                     // track sends
  TrackInsert insert[NUM_OF_INSERTS];                 // track effects/instrument plugins
};
typedef struct track Track;

Using these data the controller maintains the state of the tracks (for the number of tracks as defined in settings). This is a major improvement over HUI or MCU controllers which are essentially stateless and all functionality is implemented on the DAW. Maintaining state allows for context switching on the controller (e.g. select sends or pan for editing) without having to rely on the DAW to provide this function.
The state of the plugin parameters is only only maintained for the selected plugin, this will be expanded to the selected track. Maintaining state for all tracks requires lots of memory at higher track counts so I didn't implement this.
A print function is available to print the content of the structs to the serial monitor:

Code:
SELECTED TRACK PARAMETERS
	Name		EMT Plate
	Number		11
	Volume		0.00dB
	Pan		C
	Record		0
	Mute		0
	Solo		0
	Monitor		0

	Send 1		Send 1		-inf dB		center
	Send 2		Send 2		-inf dB		center
	Send 3		Send 3		-inf dB		center
	Send 4		Send 4		-inf dB		center
	Send 5		                                		                		                
	Send 6		                                		                		                
	Send 7		                                		                		                
	Send 8		                                		                		                

	Insert 1	1	EMT 140
	Insert 2	0	
	Insert 3	0	
	Insert 4	0	
	Insert 5	0	
	Insert 6	0	
	Insert 7	0	
	Insert 8	0	

SELECTED INSERT PARAMETERS
	Plate		B
	Damping A	2.2 s
	Damping B	2.4 s
	Damping C	1.2 s
	EQ		Out
	LFreq		0.0000
	LGain		-8.5 dB
	HFreq		1.0000
	HGain		0.0 dB
	PreDly		0.0000
	ModRate		0.0000
	ModDepth	0.0000
	Width		1.0000
	WetSolo		Solo
	Mix		0.2474
	LowCut		M 90Hz


TRACK OVERVIEW
	1	-8.42dB		C	SEM		Oberheim SEM V		Lexicon
	2	0.00dB		C	Marshall	Big Muff		Lexicon
	3	0.00dB		C	Clean Guitar	ReaTuner		EMT Plate
	4	0.00dB		C	Piano		ReaInsert (Cockos)	Lexicon
	5	-9.72dB		C	Moog		ReaInsert (Cockos)	Lexicon
	6	0.00dB		C	ARP		ReaInsert (Cockos)	Lexicon
	7	-0.29dB		C	OB-6		ReaInsert (Cockos)	Send 1
	8	0.00dB		C	Prophet		ReaInsert (Cockos)	Send 1
	9	+1.13dB		C	Oppo					Lexicon
	10	0.00dB		C	Lexicon		Lexicon 224		Send 1
	11	0.00dB		C	EMT Plate	EMT 140			Send 1
	12	0.00dB		C	Time Cube	Cooper Time Cube	Send 1
	13	-inf dB		C	Track 13				Send 1
	14	-inf dB		C	Track 14				Send 1
	15	-inf dB		C	Track 15				Send 1
	16	-inf dB		C	Track 16				Send 1

Next up will be some more testing and figuring out how to best use the available functionality.

Here's the complete sketch: View attachment OSC_Controller_20180902.zip

Hopefully this helps a little to get people started with building OSC controllers.


Suggestions for improving the code are welcome. I'm still learning C(++), just starting to get the hang of structs and pointers. Next up: classes:)
 
I missed your posts when you published them, but thanks for the description and the code too. Very inspiring (and very useful too, since I'm looking to wrap my head around OSC...)

In the last years I was more on building MIDI controllers ( one and two ) for live use with hardware instruments, but a day will come when I'll turn my attention on my Reaper-powerd-DAW ...

Thank you!

Stefano
 
You're welcome, glad to hear that the code is of use to someone :)

Here's an updated version of the code: View attachment OSC_Controller_20180923.zip

I created a basic Reaper class to hold all the track data etc. The controller configuration is in Reaper.h:

Code:
#define NUM_OF_TRACKS     32    // number of tracks in a bank = number of tracks Reaper will send
#define NUM_OF_PARAMETERS 48    // number of effect/instrument parameters in parameter bank = number of fx parameters Reaper will send
#define NUM_OF_SENDS      8     // number of sends, linked to Reaper OSC config = the number of sends Reaper will send.
#define NUM_OF_INSERTS    8     // number of fx inserts, linked to Reaper OSC config = the number of fx inserts Reaper will send.
#define NAME_LENGTH       32    // length of string for name strings
#define VALUE_LENGTH      16    // length of string for value strings

The idea here is to get all track data and browse through pages on the controller so the controller can act completely independent from the focus (selected track) in Reaper. Something I want to try is to build a simple controller dedicated to a single plugin. The controller should scan all tracks for the occurrence of the plugin and present a list of track names to easily select the plugin for editing.

PluginMap.h is a start of a parameter definition setup for plugins. Reaper currently does not send text feedback for continuous plugin parameters so I'll have to come up with a workaround for that. While I'm at it I'll also add some metadata that currently isn't available through OSC (or MIDI) like the step count of a parameter.
 
Wow and brilliant is an understatement - I'm really exited right now, as I have struggled for some time building almost the exact same thing :)

Unfortunately line 51:
Code:
HardwareSerial  displaySerial =Serial1;             // serial port for display
causes
Code:
'Serial1' was not declared in this scope

I'm sure I'll figure it out though :)

Edit: I was too quick - I wasn't aware I needed PJRC Teensy hardware to run this, only have Arduino right now. Will buy one, for sure :)
 
Will read everything you type sir! Steep learning curve for me, but the code you share here makes it way easier :) What's the main reason for choosing Teensy over Arduino? Flash & Ram?
 
Will read everything you type sir! Steep learning curve for me, but the code you share here makes it way easier :) What's the main reason for choosing Teensy over Arduino? Flash & Ram?

Both and the MIDI USB capabilities. If you want to do anything with MIDI the Teensy is an absolute no-brainer. My OSC controller doubles as a MIDI controller for hardware synths, it can receive bulk program dumps and store them on SDcard so I can display program names and parameter values for presets on the controller.
For most controllers the Teensy will be a small part of the total cost, I don't think it makes sense to skimp on that. What is important is if you need 5V tolerance on the pins, the Teensy 3.6 is NOT 5V tolerant but the Teensy 3.5 is.
 
I don't plan on MIDI for my project, but good to know. Yes, I figured Teensy 3.5 would be preferable because of the 5V tolerance. Can you use any expander cards like MCP23017 for more I/O's with Teensy like on Arduino? Or let's say - a W5100 ethernet shield (I have lots of them already)?
 
I don't plan on MIDI for my project, but good to know. Yes, I figured Teensy 3.5 would be preferable because of the 5V tolerance. Can you use any expander cards like MCP23017 for more I/O's with Teensy like on Arduino? Or let's say - a W5100 ethernet shield (I have lots of them already)?

Probably, but I don't know for sure. You'll have to delve into that yourself.
 
Status
Not open for further replies.
Back
Top