JAMES BRITT

Current album
Loosies

Get it here ...

OSC Commander part two

In part one I explained why I was working with OSC (Open Sound Control) and Processing. Basically, for the E-book Just the Best Parts: OSC for Artists. I also wrote about some things to consider when writing code meant for a (partly, at least) non-technical audience.

While doing so I found myself reconsidering some of the code choices I made and decided to change a few things.

Here in part two I will describe the code as it is now.

Here’s the GUI:

OSC Commander

The editable fields (Server address, server port, client port, and OSC message) are loaded from a file, config.txt. If you edit these and click the Update button those values get written back out.

In part one I showed how the code uses those config values to create an OscP5 instance that is both a client and a server.

Sending messages

The sketch gives you two ways to send OSC messages. The first is to put the OSC address pattern and arguments in the OSC message field and click the send button.

The other is to use the five slider controls.

When you use the first approach, the code parses the text field contents, pulls out the address pattern, then tries to figure out the data types of any arguments there may be.


void sendOSC(){

  println("client port: "+ int(clientPortText.getText())  );
  println("Send OSC message " +  trim(oscCommandText.getText() ));

  oscResponseText.setText("");  

  String rawMessageText = trim(oscCommandText.getText() );
  String[] messageParts = stringToArgs(rawMessageText);

  OscMessage m = new OscMessage(trim(messageParts[0]));

  println("\nmessageParts: " + join(messageParts, " : ")  + "\n");

  String arg;
  for(int i=1; i < messageParts.length; i++) {
    arg = trim(messageParts[i]);
    switch ( getDataType( arg ) ) {
      case STRING:
        arg = removeWrappingQuotes(arg);
        m.add(arg); 
        println("Text: " + arg );     
        break;

      case INTEGER:
        m.add(int(arg)); 
        println("Int: " + arg );     
        break;

      case FLOAT:
        m.add(float(arg)); 
        println("Float: " + arg );     
        break;

      case TRUE:
        m.add(true); 
        println("TRUE: " + arg );     
        break;

      case FALSE:
        m.add(false); 
        println("FALSE: " + arg );     
        break;

      case UNKNOWN:
        println("Unknown!: " + arg);   
        break;

    } 
  } 
  println("Send message to " +  oscServerAddress );
  clientOscP5.send(m, oscServerAddress); 
}


Basically, to send an OSC message with OscP5, you need the IP address and port of the server (that is, whatever is to receive this message) and an address pattern. You use these to create the OscMessage instance.

If there are any arguments, you add() them to the message.

Since the user is entering all this as text there are some input rules that help the code make some assumptions about what it is parsing. For example, all arguments of type String have to be in double quotes. Anything not in quotes is then assumed to be either an Integer, Float, or Boolean. Or unknown, if it fails all heuristics.

The heuristics are pretty basic. Stuff in quotes is text. Else, if it’s a set of digits, it’s an integer. Unless it has a decimal point; then it’s a floating-point number. If it fails the number test then it has to be an unquoted single-character T or F, for true or false boolean values. If not that, then it’s considered noise.

This data-type detection is handled by getDataType(). It takes a string and does a series of regular expression pattern matches.

At first I had all that code in-line with the sendOSC() method. I was going to leave it that way. You could just follow the code, see what happens, and be done. For whatever reason I thought that would make it easier for someone not-too-familiar with Processing to follow. But, it irked me. Whike the Kinect book has some code shown and explained, the OSC Commander was meant as a resource, not an example (though the code would be available). Still. Once I start writing about my code and putting it out there I much prefer it be reasonably clean.

I moved all that regex stuff out to a separate method, defined some ersatz enums, and lo!, the code was not only nicer but easier to follow, novice or not. Go figure.

Interesting thing about Processing: it doesn’t have built-in support for enums. I don’t like magic numbers, so I wanted meaningful values to use in the switch statement. You can’t just define variables for this, though. I tried. You can’t use variables with switch. Now, you can create a Java class to define your enums, but I instead just added static to my variables.

Sliders

The other way to send OSC is to use the sliders. The first version of the Processing app only had the OSC message field for sending whatever message you wanted. However, I was using TouchOSC to control a demo app for the book and realized not everyone would be able (or want) to run it. I then added some sliders controls to the sketch, and code them to send the same /fader[n] address patterns as TouchOSC.

Later, when I had a demo sketch that responded to TouchOSC /rotary[n] messages I added the option to toggle the address pattern.

The [n] in that address pattern refers to a number that is part of the address pattern. Any given TouchOSC layout has one or more screens, each with assorted controls, such as toggle buttons, faders, and rotaries. The address pattern for any of these is a combination of the screen number and the index for that type of control. So, for example, the first fader on the first screen would /1/fader1. The third rotary on the second screen would use /2/rotary3. They all send floating-point values between 0.0 and 1.0.

The OSC Commander sliders do the same. The code is trivial.


void sendSliderOscValue(String addressPattern, float value ){
  OscMessage m = new OscMessage(addressPattern);
  m.add( value );   
  println("Send to " + oscServerAddress + ": " + addressPattern + " " + value );
  clientOscP5.send(m, oscServerAddress); 
}


What’s more interesting is knowing what slider is instigating the message.

In G4P you can write methods to catch component events. For example, if you want to react when someone manipulates a slider, you implement handleSliderEvents(). This method is passed a reference to the slider being used. Thing is, if you have multiple slider components you have to do some cheesey object comparisons:



void handleSliderEvents(GSlider slider) {
  if (slider == somePreviouslyDefinedSliderThing ) {
    // ... do stuff 
  } 

  if (slider == yetAnotherPreviouslyDefinedSliderThing ) {
    // ... do other stuff 
  } 

   ...

Ick. I wanted to be able to just ask the damn slider who it was. Specifically, I wanted to know it’s index value among all the sliders. There’s no innate property for that on a GSlider instance. Luckily, that was fixable by creating my own subclass:


class NGSlider extends GWSlider {

  private int index = -1; 

  NGSlider(PApplet owner, int x, int y, int w ){
    super(owner, x, y,  w);
  }

  NGSlider(PApplet owner, int x, int y, int w, int i ){
    super(owner, x, y,  w);
    index = i;
  }

  int index(){
    return index;
  }

  void index(int i){
    index = i;
  }
}

Now creating the sliders allowed me to assign index values as well:


for (int i = 1; i< 6; i++) {
    y = y + offsetHeight + basicHeight*3;
    NGSlider oscFader = new NGSlider(this, x+10, y, width - 50, i);
    oscFader.setValueType(GWSlider.DECIMAL);
    oscFader.setLimits(0.5f, 0f, 1.0f);
    oscFader.setPrecision(3);
    oscFader.setValue(0.0f);
  }

Notice anything weird there? I’m re-initializing NGSlider oscFader on each pass of the loop. You might think that would cause a problem, but it doesn’t. It works just fine. After defining an instance and setting properties there’s no more need to hold on to the reference; it’s been added to the UI. Of course, if I later wanted to reference any specific slider I’d have a problem, in which case I could switch to storing them in an array.

Now my slider event handler is quite clean:


void handleSliderEvents(GSlider slider) {
  NGSlider s = (NGSlider) slider;
  String addressPattern = defaultSliderScreenPattern + sliderPattern + s.index();
  sendSliderOscValue(addressPattern, s.getValuef() );
}

I need to do that (NGSlider) slider cast in order to be able to invoke the index() method.

Handling incoming OSC

To respond to incoming OSc messsages you need to implement oscEvent. It is passed an OscMessage instance for the received message; what happens next is up to you.

For OSC Commander I wanted to display the message and any arguments. Getting the address pattern is easy. Getting each of the arguments is easy as well, though they are presented as an array of type Object. There’s always toString() to just show them, but I wanted to wrap any string values in quote marks.

To figure out the proper datatype of each object in the arguments() array you can look at the value of typetag(); it’s a string, so the first char is the datatype of the first argument, the second char is the type of the second argument, and so on.

(Note: type tags use single-character abbreviations for different data types. ‘s’ for string, ‘i’ for integer, etc. For example, if an OSC message is received and it has the arguments "Foo" 12 123.09 then the type tag would be “sif”.)

Using an index value while walking through the arguments array makes it easy to make them up; if an ‘s’ is found then the argument is wrapped in quotes before appending to the “OSC response” field.


void oscEvent(OscMessage oscMessage) {
  println("* * *  OSC Commander received a message * * * ");
  print(" addrpattern: " + oscMessage.addrPattern());
  println("; typetag: " + oscMessage.typetag());

  String value = "";

  for (int i = 0; i < oscMessage.arguments().length; i++ ) {
    if (oscMessage.typetag().charAt(i) == 's' ) {
      value += "\"" + oscMessage.arguments()[i].toString() + "\" ";
    } else {
      value += oscMessage.arguments()[i].toString() + " ";
    }
  }
  oscResponseText.setText( oscMessage.addrPattern() + " " + 
                           trim(value) );  
}

Summing up

And that’s it. The code evolved over time based on what was needed. I’m pretty close to wrapping up the content of OSC for Artists so there’s no reason to add new behavior. I’m tempted to add one more slider to bring the number to six; I’d have to sort out the spacing. If I wanted to get fancy I’d make each slider configurable as to type and index. I’m afraid, though, that it would end up bloated; it’s primarily a basic testing/demo tool that works well for its intended task.

You can download a zip file of the code from neurogami.com/downloads/osc_commander.zip

If you found this interesting, or have somehting to say, please leave a comment, and please go check out Just the Best Parts: OSC for Artists and Just the Best Parts: Kinect Hacking for Artists

EDIT: The source code is now also on GitHub: https://github.com/JustTheBestParts/OscCommander

My Music

Loosies


American Electronic


Small Guitar Pieces

Maximum R&D



Love me on Tidal!!111!

Neurogami: Dance Noise


Neurogami: Maximum R&D