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.
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.
The received data is parsed and stored in structs modelling the track structure (tracks, sends, inserts, parameters):
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:
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
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