• jon@schemawound.com
Misc Tutorial
rePatcher to OSC

rePatcher to OSC

As soon as I saw the announcement of rePatcher I was interested. It seemed like a very affordable way to add some physical control over your code. I am personally not a big fan of PD or Max so I decided to interface it to Supercollider and Reaktor instead. I quickly realized that the windows implementation of the Serial class in Supercollider did not work and Reaktor did not support serial communication at all. Both languages have very good support for OSC so I decided to write a small processing app to translate the serial info from the rePatcher into OSC.

Download the rePatcher Arduino code from the Open Music Labs Wiki and delete the following line before loading the code to your arduino:
while (Serial.read() != 0xab); // wait for turn on signal
In the PD patch that Open Music Labs provided they had a functionality where you could start and stop the rePatcher serial stream. It was not a bad idea but without a hardware button for the function it just seems like an extra step in trying to get the rePatcher set up properly.

From within your Processing IDE you will need to install the ControlP5 and OscP5 libraries. Once they have been installed properly you can run the following code from the IDE. The window will ask you for the COM port your arduino is connected to as well as the IP and port you wish to send OSC data on. Once you are satisfied with the settings hit the TX button and leave the window running. If you need to change these settings simply close the window and restart the program.

The knobs will send a message in the format of /repatcher/knobN where N is a number between 0 and 5 representing the current knob. The knobs will send a single argument of a float between 0 and 1 for the current value.

The patchbay will send a message in the format of /repatcher/outputN where N is a number between 0 and 5 representing the current output. The patchbay will send six integers with a value of 0 or 1 to represent which inputs are patched to the current output.

In the future I will post about how to read the generated OSC inside of Reaktor.

import oscP5.*;
import netP5.*;
import controlP5.*;
import processing.serial.*;

final int MESSAGE_STOP = 0xba;
final int MESSAGE_START = 0xab;
final int MESSAGE_BUFFERSTART = 0xc0;

//System
PApplet app = this;
Serial myPort;
OscP5 oscP5;
NetAddress myRemoteLocation;

//GUI
ControlP5 cp5;
Toggle transmitToggle;
DropdownList serialPortList;
Textfield ip;
Textfield ip_port;

void setup(){
  //Setup display
  size(190, 70);
  smooth();
  noStroke();
  cp5 = new ControlP5(this);
  transmitToggle = createTransmitToggle();
  serialPortList = createSerialPortList();
  ip = cp5.addTextfield("IP").setPosition(70, 10).setSize(50, 15).setText("127.0.0.1");
  ip_port = cp5.addTextfield("IP_Port").setPosition(70, 40).setSize(50, 15).setText("12000");
  oscP5 = new OscP5(this,12001);
}

void draw(){
  background(100);
 
  if (transmitToggle.getState()){
    //Read and process the buffer as long as the TX button is pressed
    readBuffer();
  };
  
}

Toggle createTransmitToggle(){
  Toggle transmitToggle;
  CallbackListener toggleCallback = new CallbackListener() {
    public void controlEvent(CallbackEvent theEvent) {
      if (theEvent.getAction()==ControlP5.ACTION_PRESSED) {connectSerialPort();}
    }
  };
  transmitToggle = cp5.addToggle("TX")
    .setPosition(130,10)
    .setSize(45,45)
    .addCallback(toggleCallback)
  ;
  return transmitToggle;
}

DropdownList createSerialPortList(){
  DropdownList serialPortList = cp5.addDropdownList("Port")
    .setPosition(10,22)
    .setSize(50,60)
    .addItems(Serial.list())
    .setValue(0);
  ;
  return serialPortList;
}

void connectSerialPort(){
  if (myPort == null){
    //If the serial port is not connected then connect it.  
    myPort = new Serial(app, Serial.list()[(int)serialPortList.getValue()], 38400);
    serialPortList.disableCollapse();
    
    //Connect port for OSC data
    ip.setLock(true);
    ip_port.setLock(true);
    myRemoteLocation = new NetAddress(ip.getText(),int(ip_port.getText()));
    
    //Lock all controls
    transmitToggle.setLock(true);
  }
}

void readBuffer(){
  //If the buffer is empty then bail out
  if (myPort.available() == 0) {return;}
  
  //Read all incoming serial data.  When we find the start of a buffer we grab the next 18 characters and process them.
  byte[] buffer = new byte[18];
  char inByte = myPort.readChar();
  if (inByte == MESSAGE_BUFFERSTART) {
    myPort.readBytes(buffer);
    myPort.clear(); //Data is coming in so fast we need to clear the pipe to avoid serious lag
    readKnobs(buffer);
    readPatchBay(buffer);
  };
}

void readKnobs(byte[] buffer){
  //Parse knob data out of the buffer and pass it to the repatcherControls object  
  for(int i = 0; i < 12; i = i+2){
    int knobNumber = 5 - (i / 2);  //Knobs are packed in the buffer in reverse order, this flips the numbering
    float knobInValue = (buffer[i+1] << 7) | buffer[i];
    float knobMapValue = map(knobInValue, 0, 1024, 0, 1);
    OscMessage myMessage = new OscMessage("/repatcher/knob" + knobNumber);
    myMessage.add(knobMapValue);
    oscP5.send(myMessage, myRemoteLocation); 
  };
}

void readPatchBay(byte[] buffer){
  //Parse patchbay data out of the buffer and pass it to the repatcherControls object
  for(int i = 12; i < 18; i++){
    int outPin = 5 - (i - 12);
    OscMessage myMessage = new OscMessage("/repatcher/output" + outPin);
    if ((buffer[i] & 32) == 32) {myMessage.add(1);} else {myMessage.add(0);};
    if ((buffer[i] & 16) == 16) {myMessage.add(1);} else {myMessage.add(0);};
    if ((buffer[i] &  8) ==  8) {myMessage.add(1);} else {myMessage.add(0);};
    if ((buffer[i] &  4) ==  4) {myMessage.add(1);} else {myMessage.add(0);};
    if ((buffer[i] &  2) ==  2) {myMessage.add(1);} else {myMessage.add(0);};
    if ((buffer[i] &  1) ==  1) {myMessage.add(1);} else {myMessage.add(0);};
    oscP5.send(myMessage, myRemoteLocation); 
  };
}