Sunday, February 5, 2012

TouchOSC with Processing, iPhone and Arduino.


I've seen alot of projects that use iPhones or similair devices to interface with processing, and sometimes then control the arduino using the format sketch. This protocol is called OSC; a popular iOS/android app that implements this is called TouchOSC. It turned out that I couldn't fnd much documentation on how to use TouchOSC with processing, so it took a little fidling to successfuly read the values from the iPod Touch.

TouchOSC has a computer based layout creator which lays out the position, address, and value range of the objects in the layout. Some of the objects are push and toggle buttons, rotarys, encoders, XY pads and a few others. The TouchOSC program on the iPod then sends the OSC messages to a certain IP address(a computer with processing running). The Processing code then reads the OSC messages and can do whatever depending on what the value is.Processing needs the oscP5 library and the arduino library if you want to send commands to a Arduino from processing. MyFullExOSC.touchosc is the layout file for TouchOSC.

The following Processing program reads from device:
-toggle buttons
-push buttons
-XY pad
-Faders
-Rotaries
-Encoders
-multipush button matrixes
-multitoggle button matrixes
-multiXY pad (up to 5 touch points)
-multifader
-accelerometer
Also, the program sends back the highest and lowest accelerometer XYZ values that it has received from the iPod and displays them in labels. All of these readings have some sort of graphical representation in processing to test for:)





import oscP5.*;        //  Load OSC P5 library
import netP5.*;        //  Load net P5 library
import processing.serial.*;    // Load serial library
import cc.arduino.*;  //load arduino interface library

OscP5 oscP5;            //  Set oscP5 as OSC connection
NetAddress iPod; //holds address for sending data to iPod
Arduino arduino; // create new arudino device

PFont f; //initailize a font

//determine tab pos
boolean [] TabStat = new boolean [5];

//tab1
float [] ToggleStat = new float [3]; //Toggle: btn1, btn2, btn3
float [] PushStat = new float [3]; //Push: btn1, btn2, btn3
float [] XYpad = new float [2]; //x, y
//tab2
float [] TabII = new float [5]; //fader, rotary, encoder, Enc Previous value, Enc Pos(software based increase/decrease based on encoder)
//tab3
float [] MultiPush = new float [9]; //3x3 multi push button matrix
float [] MultiToggle = new float [9]; //3x3 multi toggle button matrix
//tab4
float [] MultiXYpad = new float [10]; //up to 5 xy coordinate pairs
float [] MultiFader = new float [5]; //multi fader with 5 faders
//sine wave
float WaveWidth = 500+16;              // Width of entire wave
float theta = 0.0;  // Start angle at 0
float dx;  // Value for incrementing X, a function of period and xspacing
float[] yvalues = new float[1];  // Using an array to store height values for the wave
//tab5
float [] Accel = new float [13]; //x, y, z, Center press button, x center, y center, z center, final x/y/z, prev final x/y/z
float [] AccelMM = new float [6]; //stores min/max values for xyz

void setup()
{
  //println(Serial.list()); //shows list of avaliable serial ports, put number for you port in Arduino.list()[X]

  size(500, 500);        // Processing screen size
  frameRate(60);
  smooth();

  f = loadFont("ComicSansMS-48.vlw"); //Load Font
  textFont(f, 25);                 //Specify font type

  oscP5 = new OscP5(this, 8000);  // Start oscP5, listening for incoming messages at port 8000
  iPod = new NetAddress("192.168.1.4", 9000); //address for sending messages to iPod(also the iPod sending messages to this computer), not needed if iPod is only sending
  arduino = new Arduino(this, Arduino.list()[0], 115200); //arduino connected, use the standard firmata example sketch
}

void draw()
{
  if (TabStat[0]) //if we are on the first tab
  {
    background(50); //color of background

    fill(0);
    text("Toggle:", 218, 25);
    text("Push:", 45, 25);

    for (int x = 0; x<3; x++)
    {
      fill(0); //fill font with black
      text(x+1, 110, 75+(x * 50)); //label buttons
      text(x+1, 310, 75+(x * 50));

      //draw buttons
      if (ToggleStat[x] == 1)  //if x button is pressed
      {
        fill(0, 255, 0); //R,G,B/0,255,0
        ellipse(146, 65+(x * 50), 30, 30); //x,y,width.height
        arduino.digitalWrite(x+2, 1); //turns on(HIGH) digital pin x+2(2->5) on the arduino
      }
      else
      {
        fill(255, 0, 0);
        ellipse(146, 65+(x * 50), 30, 30);
        arduino.digitalWrite(x+2, 0); //turns off(LOW) digital pin x+2(2->5) on the arduino
      }

      if (PushStat[x] == 1)
      {
        fill(0, 255, 0);
        ellipse(342, 65+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(342, 65+(x * 50), 30, 30);
      }
    }

    //draw XY Pad box
    fill(0); //fill with black
    stroke(255, 0, 0); //outline box with red
    rectMode(CORNER); //measure x,y from top left corner
    rect(150, 250, 200, 200);  //x,y,width,height

    //level bar
    fill(0, 255, 0);
    stroke(0); //black outline
    ellipse((150+XYpad[0]), (450-XYpad[1]), 5, 5);  //150(from top of program) + how far from top left corner my finger is on the xy pad, dido for y, dot size
  }

  else if (TabStat[1]) //if we are on the second tab
  {
    rectMode(CORNER);
    background(50);

    //fader box
    fill(0);
    text("Fader", 30, 65); //this text, x ,y
    stroke(255, 0, 0);
    rect(50, 75, 25, 350);    // 50 pixels from left, 75 from top, width 25, height 350
    //fader fill
    fill(255, 0, 0);
    rect(50, 75+350, 25, -TabII[0]); //75 is how far the bars are from the top of the window, then 350 for length to the bottom of bar


    //rotary, ~270deg
    fill(0);
    text("Rotary", 125, 175);
    fill(0, 0, 255);
    stroke(0, 0, 255);
    ellipse(300, 175, TabII[1], TabII[1]); //set size of ellipse based on rotary


    //encoder, free turning, alternates between two values, ex 01010101011111111111,LRLRLRLRLRLRLRRRRRRRRRRR
    //have fun with this below :D
    fill(0);
    text("Encoder", 125, 300);

    if (mouseX >= 200 && mouseX <= 400 && mouseY >= 200 && mouseY <= 400)  //if mouse over encoder circle, stop
    {
      TabII[4]=TabII[3]; //keep circle the same size
    }
    else //continue changing
    {
      if (TabII[2] == 1) //if this way
      {
        TabII[4]+=1; //add one
      }
      else if (TabII[2] == 0)
      {
        TabII[4]-=1;
      }
    }

    fill(200, 0, 100);
    stroke(200, 0, 150);
    ellipse(300, 300, TabII[4], TabII[4]); //draw ellipse based on encoder

    TabII[3] = TabII[4];
  }

  else if (TabStat[2]) //if we are on the third tab
  {
    background(50);
    stroke(0);
    fill(0);
    text("Toggle:", 218, 25);
    text("Push:", 45, 25);

    for (int x = 0; x<9; x++) //test all the buttons for on/off and set them green/red accordingly
    {
      fill(0);
      text(x+1, 107, 55+(x * 50)); //print button numbers
      text(x+1, 305, 55+(x * 50));

      if (MultiPush[x] == 1)  //draw buttons
      {
        fill(0, 255, 0);
        ellipse(146, 45+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(146, 45+(x * 50), 30, 30);
      }

      if (MultiToggle[x] == 1)
      {
        fill(0, 255, 0);
        ellipse(342, 45+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(342, 45+(x * 50), 30, 30);
      }
    }
  }

  else if (TabStat[3]) //if we are on the fourth tab
  {
    background(50);

    //draw multi XY Pad box
    fill(0);
    stroke(255, 0, 0);
    rectMode(CORNER);
    rect(150, 275, 200, 200);

    fill(0, 255, 0);
    stroke(0);
    ellipse((150+MultiXYpad[1]), (475-MultiXYpad[0]), 5, 5); //draw touch 1, 150 is the box x coordinate then + the x coordinate of touch, 475 is box y coordinate plus y length of box - the y value of touch
    ellipse((150+MultiXYpad[3]), (475-MultiXYpad[2]), 5, 5); //touch 2
    ellipse((150+MultiXYpad[5]), (475-MultiXYpad[4]), 5, 5); //...
    ellipse((150+MultiXYpad[7]), (475-MultiXYpad[6]), 5, 5);
    ellipse((150+MultiXYpad[9]), (475-MultiXYpad[8]), 5, 5);


    //sine wave maker based on fader
    theta += MultiFader[1]; //angular velocity
    // For every x value, calculate a y value with sine function
    float x = theta;
    for (int i = 0; i < yvalues.length; i++)
    {
      yvalues[i] = sin(x)* MultiFader[2];
      x+=dx;
    }

    for (int b = 0; b < yvalues.length; b++)
    {
      noStroke();
      fill(255, 0, 0);  //
      rectMode(CENTER);
      rect(b*MultiFader[0], 450/3+yvalues[b], (MultiFader[4]/15), (MultiFader[4]/15)); //x,y, width, height
    }
  }

  else if (TabStat[4]) //if we are on the fith tab
  {
    background(50);
    if (Accel[3]==1) //when zeroing the accel, make font green
    {
      fill(0, 255, 0);
      text("3-Axis Accelerometer", 130, 75);
    }
    else
    {
      fill(255, 0, 0);
      text("3-Axis Accelerometer", 130, 75);
    }

    Accel[7] = (Accel[0] - Accel[4]); //x final val = current val - center val
    Accel[8] = (Accel[1] - Accel[5]); //y
    Accel[9] = (Accel[2] - Accel[6]); //z

    fill(255, 0, 0);
    text("X Axis: ", 175, 125); //label axis in program
    text(Accel[7], 265, 125); //then write values next to label

    text("Y Axis: ", 175, 175);
    text(Accel[8], 265, 175);

    text("Z Axis: ", 175, 225);
    text(Accel[9], 265, 225);

    //all of this is for sending the highest and lowest value so far back to the ipod!
 
    //x
    if (Accel[10] < Accel[7] && (Accel[7] - Accel[10]) > AccelMM[0])  //if prev final val is less than current final val && current max is higher than prev max
    {
      //from here
      AccelMM[0] = Accel[7]; //set new max
      OscMessage Xmax = new OscMessage("/5/Xmax"); //new message called Xmax going to /5/Xmax label
      Xmax.add(Accel[7]); //add value to Xmax message that is going to be sent
      oscP5.send(Xmax, iPod); //oscP5.send(Xmax, iPod); Xmax is name of message, iPod in this line is the address, see top of code
      //to here is how you send info to device
    }
    else if (Accel[10] > Accel[7] && (Accel[10] - Accel[7]) < AccelMM[1]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[1] = Accel[10]; //set new min
      OscMessage Xmin = new OscMessage("/5/Xmin");
      Xmin.add(-Accel[10]);
      oscP5.send(Xmin, iPod);
    }

    //y
    if (Accel[11] < Accel[8] && (Accel[8] - Accel[11]) > AccelMM[2])  //if prev final val is less than current final val && current max is higher than prev max
    {
      AccelMM[2] = Accel[8]; //set new max
      OscMessage Ymax = new OscMessage("/5/Ymax");
      Ymax.add(Accel[8]);
      oscP5.send(Ymax, iPod);
    }
    else if (Accel[11] > Accel[8] && (Accel[11] - Accel[8]) < AccelMM[3]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[3] = Accel[11]; //set new min
      OscMessage Ymin = new OscMessage("/5/Ymin");
      Ymin.add(-Accel[11]);
      oscP5.send(Ymin, iPod);
    }

    //z
    if (Accel[12] < Accel[9] && (Accel[9] - Accel[12]) > AccelMM[4])  //if prev final val is less than current final val && current max is higher than prev max
    {
      AccelMM[4] = Accel[8]; //set new max
      OscMessage Zmax = new OscMessage("/5/Zmax");
      Zmax.add(Accel[9]);
      oscP5.send(Zmax, iPod);
    }
    else if (Accel[12] > Accel[9] && (Accel[12] - Accel[9]) < AccelMM[5]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[5] = Accel[12]; //set new max
      OscMessage Zmin = new OscMessage("/5/Zmin");
      Zmin.add(-Accel[12]);
      oscP5.send(Zmin, iPod);
    }
 
    /*this is also how you turn on a virtual LED on the iPod:
 
      OscMessage LEDmsg = new OscMessage("/1/LED1"); //new message going to the "/1/LED1" led
      LEDmsg.add(0); //add value to message that is going to be sent , 0 for LED off, 1 for LED on
      oscP5.send(LEDmsg, iPod); //send the message
    */

    Accel[10] = Accel[7]; //to be previous val = current final val
    Accel[11] = Accel[8];
    Accel[12] = Accel[9];
  }
}

void oscEvent(OscMessage theOscMessage) //  This runs whenever there is a new OSC message
{
  /*
  OSC message looks like this: /1/toggle1 or /1/multitoggle/2/1.
   /tab/"object name"   /tab/"object name"/row pos/column pos
 
   multitoggle 3x3 example:
       1  2  3   Y
       __ __ __
X   3  |      |
    2  |      |
    1  |      |
       __ __ __
   /1/multitoggle1/X/Y
 
   */

  String addr = theOscMessage.addrPattern();  //  Creates a string out of the OSC message
  if (addr.equals("/1")) //search string, if we are on the first tab
  {
    TabStat[0] = true; //so we know what part of the draw function to run
    TabStat[1] = false; //and not to run
    TabStat[2] = false; //...
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/2")) //tab 2
  {
    TabStat[0] = false;
    TabStat[1] = true;
    TabStat[2] = false;
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/3")) //tab 3
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = true;
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/4")) //tab 4
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = false;
    TabStat[3] = true;
    TabStat[4] = false;
  }
  else if (addr.equals("/5")) //tab 5
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = false;
    TabStat[3] = false;
    TabStat[4] = true;
  }

  float val = theOscMessage.get(0).floatValue();  //get the value

  if (TabStat[0]) //if we are on the first tab
  {
    if (addr.equals("/1/toggle1")) //if we find this address
    {
      ToggleStat[0] = val; //write the value to it, 0 or 1
    }
    if (addr.equals("/1/toggle2"))
    {
      ToggleStat[1] = val;
    }
    if (addr.equals("/1/toggle3"))
    {
      ToggleStat[2] = val;
    }

    if (addr.equals("/1/push1"))
    {
      PushStat[0] = val;
    }
    if (addr.equals("/1/push2"))
    {
      PushStat[1] = val;
    }  
    if (addr.equals("/1/push3"))
    {
      PushStat[2] = val;
    }

    if (addr.equals("/1/xyPad"))
    {  //xy pad writes to values to one address
      XYpad[1] = val;  //y
      XYpad[0] = theOscMessage.get(1).floatValue(); //buttons dont work if i put this(get(1)) into a float at begining of function...???
    }
  }

  else if (TabStat[1]) //if we are on the second tab
  {
    if (addr.equals("/2/fader"))
    {
      // print("fader recv");  //just checking
      // println(val);
      TabII[0] = val;
    }
    if (addr.equals("/2/rotary"))
    {
      TabII[1] = val;
    }
    if (addr.equals("/2/encoder"))
    {
      TabII[2] = val;
    }
  }

  else if (TabStat[2]) //if we are on the third tab
  {
    //MULTIPUSH-------------------------
    //Row3
    if (addr.equals("/3/multipush/1/1"))  //remember weird row numbering
    {
      MultiPush[6] = val;
    }
    if (addr.equals("/3/multipush/1/2"))
    {
      MultiPush[7] = val;
    }
    if (addr.equals("/3/multipush/1/3"))
    {
      MultiPush[8] = val;
    }
    //Row2
    if (addr.equals("/3/multipush/2/1"))
    {
      MultiPush[3] = val;
    }
    if (addr.equals("/3/multipush/2/2"))
    {
      MultiPush[4] = val;
    }
    if (addr.equals("/3/multipush/2/3"))
    {
      MultiPush[5] = val;
    }
    //Row1
    if (addr.equals("/3/multipush/3/1"))
    {
      MultiPush[0] = val;
    }
    if (addr.equals("/3/multipush/3/2"))
    {
      MultiPush[1] = val;
    }
    if (addr.equals("/3/multipush/3/3"))
    {
      MultiPush[2] = val;
    }


    //MULTITOGGLE-------------------------
    //Row1
    if (addr.equals("/3/multitoggle/1/1"))
    {
      MultiToggle[6] = val;
    }
    if (addr.equals("/3/multitoggle/1/2"))
    {
      MultiToggle[7] = val;
    }
    if (addr.equals("/3/multitoggle/1/3"))
    {
      MultiToggle[8] = val;
    }
    //Row2
    if (addr.equals("/3/multitoggle/2/1"))
    {
      MultiToggle[3] = val;
    }
    if (addr.equals("/3/multitoggle/2/2"))
    {
      MultiToggle[4] = val;
    }
    if (addr.equals("/3/multitoggle/2/3"))
    {
      MultiToggle[5] = val;
    }
    //Row3
    if (addr.equals("/3/multitoggle/3/1"))
    {
      MultiToggle[0] = val;
    }
    if (addr.equals("/3/multitoggle/3/2"))
    {
      MultiToggle[1] = val;
    }
    if (addr.equals("/3/multitoggle/3/3"))
    {
      MultiToggle[2] = val;
    }
  }

  else if (TabStat[3]) //if we are on the fourth tab
  {  
    //Multi touch XY, up to 5 x/y points
    if (addr.equals("/4/multixy/1")) //point 1 x and y values
    {
      MultiXYpad[0] = val; //y
      MultiXYpad[1] = theOscMessage.get(1).floatValue();  //x
    }

    if (addr.equals("/4/multixy/2")) //point 2 x and y values
    {
      MultiXYpad[2] = val;  //y
      MultiXYpad[3] = theOscMessage.get(1).floatValue();  //x
    }

    if (addr.equals("/4/multixy/3")) //point 3 x and y values
    {
      MultiXYpad[4] = val;
      MultiXYpad[5] = theOscMessage.get(1).floatValue();
    }

    if (addr.equals("/4/multixy/4")) //point 4 x and y values
    {
      MultiXYpad[6] = val;
      MultiXYpad[7] = theOscMessage.get(1).floatValue();
    }

    if (addr.equals("/4/multixy/5")) //point 5 x and y values
    {
      MultiXYpad[8] = val;
      MultiXYpad[9] = theOscMessage.get(1).floatValue();
    }


/* what multi faders look like
--------------- fader 4
--------------- 3
--------------- 2
--------------- 1


*/
    if (addr.equals("/4/multifader/1"))  //if multi fader 1 has been moved
    {
      // divide by 40 to get smaller value that fits this application
      MultiFader[0] = (val/40.0)+1; //How far apart should each horizontal location be spaced,
      yvalues = new float[int(WaveWidth/MultiFader[0])]; //height values for the wave
    }
    if (addr.equals("/4/multifader/2"))
    {
      MultiFader[1] = (val/400.0); //angular velocity, theta
    }
    if (addr.equals("/4/multifader/3"))
    {
      MultiFader[2] = val/2.5;  //amplitude -- Height of wave
    }
    if (addr.equals("/4/multifader/4"))
    {
      //MultiFader[3] = val = period -- How many pixels before the wave repeats
      dx = (TWO_PI / val) * MultiFader[0];  //dx Value for incrementing X, a function of period and xspacing
    }
    if (addr.equals("/4/multifader/5"))
    {
      MultiFader[4] = val;   //line thickness
    }
  }
  else if (TabStat[4]) //if we are on the fith tab
  {
    //read Accelerometer
    if (addr.equals("/accxyz"))
    {
      Accel[0] = val;  //x
      Accel[1] = theOscMessage.get(1).floatValue();  //y
      Accel[2] = theOscMessage.get(2).floatValue();  //z
    }

    //Accel Center
    if (addr.equals("/5/AccCenter"))
    {
      Accel[3] = val;
      for (int x = 0; x<3; x++)
      {
        Accel[x+4] = Accel[x]; //save current x, y, z values as center values
      }
    }
  }
}







8 comments:

  1. Hi, first thanks alot for your code ! Could you repost the touchosc layout, your link is bad,
    thanks !

    ReplyDelete
  2. Hi there - i also wanted to thank you for taking the time to post this.

    I'm trying to do a simple d-pad type control interface in touchOsc to control an arduino robot over bluetooth. I've got all the other pieces into place but i'm having troubles with what i imagine is a simple bit of code in processing. I just want 4 push buttons, and when one is held down, i'll have processing send an ascii code over bluetooth to the robot. I can already send the ascii code over its just the actual touchosc to processing programming i'm struggling with.

    Think you could pass along any tips?
    thanks!

    ReplyDelete
  3. Oh - and that link still seems broken?

    ReplyDelete
  4. moved it to another host, should work now.

    this is a basic 4 button OSC sketch. label your buttons as /1/push2 or /1/push1 etc. you also need to create a comic sans font


    import oscP5.*; // Load OSC P5 library
    import netP5.*; // Load net P5 library
    import processing.serial.*; // Load serial library

    OscP5 oscP5; // Set oscP5 as OSC connection
    PFont f; //initailize a font
    float [] PushStat = new float [4]; //Push: btn1, btn2, btn3

    void setup()
    {
    //println(Serial.list()); //shows list of avaliable serial ports, put number for you port in Arduino.list()[X]

    size(250, 300); // Processing screen size
    smooth();

    f = loadFont("ComicSansMS-48.vlw"); //Load Font
    textFont(f, 25); //Specify font type

    oscP5 = new OscP5(this, 8000); // Start oscP5, listening for incoming messages at port 8000
    }

    void draw()
    {
    background(50); //color of background

    fill(0);
    text("Push:", 45, 25);

    for (int x = 0; x<4; x++) //draws all the buttons
    {
    fill(0); //fill font with black
    text(x+1, 110, 75+(x * 50));

    if (PushStat[x] == 1)
    {
    fill(0, 255, 0);
    ellipse(142, 65+(x * 50), 30, 30);
    //put serial code here or below--------------------------------
    }
    else
    {
    fill(255, 0, 0);
    ellipse(142, 65+(x * 50), 30, 30);
    }
    }
    }

    void oscEvent(OscMessage theOscMessage) // This runs whenever there is a new OSC message
    {
    String addr = theOscMessage.addrPattern(); // Creates a string out of the OSC message

    float val = theOscMessage.get(0).floatValue(); //get the value

    if (addr.equals("/1/push1"))
    {
    PushStat[0] = val;
    //or put your code here------------------------
    }
    if (addr.equals("/1/push2"))
    {
    PushStat[1] = val;
    }
    if (addr.equals("/1/push3"))
    {
    PushStat[2] = val;
    }
    if (addr.equals("/1/push4"))
    {
    PushStat[3] = val;
    }
    }

    ReplyDelete
  5. great tutorial that inspire me to write an article about how to develop iOS applications to control robots. the article could be read here http://www.intorobotics.com/tutorials-how-to-build-ios-applications-to-control-a-robot/

    ReplyDelete
    Replies
    1. Awesome! i once tried IOS programming on my OSX86 PC :D

      Delete