Monome 64 (8x8 grid) as osc SL controller

Discuss using the OSC interface to control SL

Moderator: jesse

Post Reply
tylerwalker
Posts: 1
Joined: Wed Jul 27, 2016 10:28 am

Monome 64 (8x8 grid) as osc SL controller

Post by tylerwalker »

been working for a few days to get the Monome controlling SL and giving feedback for each track. i learned Processing to make this, so don't be surprised at clunky code. Monome folks prefer Max for programming, but after years of trying, it still makes my head spin. this code should be portable to any grid controller that has a Processing library supporting it. hopefully this saves somebody some time and headache. if you see any possible improvements, please let me know. the functions are somewhat barebones, but very easy to modify.

Code: Select all

//Processing sketch to use Monome64 (8x8 grid) as controller and visual feedback for SooperLooper
//Written in Processing 2.0.3 using SooperLooper 1.6.8 with 8 x stereo loops (working on Snow Leopard 10.6)
//using Monome OSC library from http://monome.org/docs/grid-studies/processing/
//by Tyler Walker 2016

//still have a problem with array errors if something is already recorded or playing when program starts.
//osc also starts and stops 3 times every time the program loads
//neither issue affects function

//buttons are assigned in the key() function.  each horizontal row is an independant track.  
//currently mapped as
//record - 0
//undo - 1  (long press for undo-all)
//overdub - 2
//trigger - 6
//mute - 7

//program will give visual feedback along the row for current playback position if playing (or overdubbing)
//light zero is solid if recording
//light zero should blink if "waiting" but this doesn't seem to work (believe this is fixed in later version of SL)
//light 7 blinks if track is muted (and isnt' empty)

import org.monome.Monome;
import oscP5.*;
import netP5.*;

Monome m;
int[][] button_state;
boolean dirty;
OscP5 oscP5;
float[] state;
float[] loop_position;
float[] loop_length;
byte[] timeline;
float previous_blink;
long blink_interval;
int blink_state;

NetAddress sooper;

public void setup() {
  oscP5 = new OscP5(this, 12000);
  sooper = new NetAddress("127.0.0.1", 10051);
  m = new Monome(this);

  button_state = new int[8][8];  //stores state of every button on monome
  state = new float[8];  //stores state of each loop, ie recording, playing, etc
  loop_position = new float[8];
  loop_length = new float[8];
  timeline = new byte[8]; //position of each loop 0-7
  float previous_blink = 0;  //for use with millis() to make a blink timer
  blink_interval = 500; //time in ms for blinks

  dirty = true; //tells draw() that led states have changed, triggering a refresh

  //auto_update only sends loop length when it changes.  
  //if a loop is already set, you'll get zero from update making a divide by zero error.  
  //these two functions get the loop lengths and states up top
  getValueGlobal("loop_len", "/length"); 
  getValueGlobal("state", "/state");

  registerAutoUpdate("state", "/state");
  registerAutoUpdate("loop_pos", "/length");
  registerAutoUpdate("loop_len", "/length");
}

public void draw() {
  int[][] led = new int[8][8]; //remake led array each time, avoiding the need to clear all previous states manually

  for (int y=0;y<8;y++) { // for each loop
    switch (int(state[y])) { 
      case 1: //waiting to start - this does not seem to work
        println("waiting");
        led[y][0] = blink();
        dirty = true;
      case 2: //recording
        led[y][0] = 1;
        dirty = true;
        break;
      case 4: // if state = playing (4.0)
        timeline[y]=timelinePosition(y);
        led[y][timeline[y]] = 1;
        dirty = true;
        break; 
      case 5: //overdubbing
        led[y][0] = 1;
        timeline[y]=timelinePosition(y);
        led[y][timeline[y]] = 1;
        dirty = true;
        break;          
      case 10: //muted 
        if (loop_length[y] > 0 ) led[y][7]=blink();
        dirty = true;
        break;
      }
  }

  if (dirty) {
    for (int y=0;y<8;y++) {  //add all currently pressed buttons to the refresh
      for (int x=0;x<8;x++) {
        if (button_state[y][x] == 1) led[y][x] = 1;
      }
    }
    m.refresh(led);
    dirty = false;
  }
}

public void key(int x, int y, int s) { 
  button_state[y][x] = s; //record state of all buttons  
  if (s==1) { //button is pressed     
    switch(x) {
      case 0: //record
        oscSend(y, "hit", "record");
        break;
      case 1: //undo
        oscSend(y, "down", "undo");
        break;
      case 2: //overdub
        oscSend(y, "hit", "overdub");
        break;
      case 6: //trigger
        oscSend(y, "hit", "trigger");
        break;
      case 7:  //mute
        oscSend(y, "hit", "mute");
        break;
      }
  }    

  else { //s == 0  used for key up commands for things like long presses    
    if (x==1) oscSend(y, "up", "undo"); //undo
  }
  dirty = true;
}


//incoming osc message are forwarded to the oscEvent method. 
void oscEvent(OscMessage theOscMessage) {
  //read incoming state messages
  if (theOscMessage.checkAddrPattern("/state")==true) {
    if (theOscMessage.checkTypetag("isf")) {
      int     loopNumber  = theOscMessage.get(0).intValue();
      String  controlName = theOscMessage.get(1).stringValue();
      float   controlValue  = theOscMessage.get(2).floatValue(); 
      //auto update of loop 0 also sends state for -3 and -1 (selected track and all tracks)
      if (loopNumber>=0) {
        state[loopNumber] = controlValue;
        //println("loop: "+loopNumber+" state: "+state[loopNumber]);
      }
    }
  }

  //read incoming length or position message
  if (theOscMessage.checkAddrPattern("/length")==true) {
    if (theOscMessage.checkTypetag("isf")) {
      int     loopNumber  = theOscMessage.get(0).intValue();
      String  controlName = theOscMessage.get(1).stringValue();
      float   controlValue  = theOscMessage.get(2).floatValue(); 
      //print(theOscMessage);
      if (loopNumber>=0) {
        if (controlName.equals("loop_len") == true) {
          loop_length[loopNumber] = controlValue;
          //loop_position[loopNumber] = 1.121254;
        }
        if (controlName.equals("loop_pos") == true) {
          loop_position[loopNumber] = controlValue;
        }
        //println("loop: "+loopNumber+" length: "+loop_length[loopNumber]+" position: "+loop_position[loopNumber]);
      }
    }
  }

  //print incoming messages for debugging
  if (theOscMessage.checkAddrPattern("/print")==true) {
    if (theOscMessage.checkTypetag("isf")) {
      int     loopNumber  = theOscMessage.get(0).intValue();
      String  controlName = theOscMessage.get(1).stringValue();
      float   controlValue  = theOscMessage.get(2).floatValue(); 

      //println(theOscMessage);
      println(" message: loopNumber= "+loopNumber+ " controlName= "
        +controlName+" controlValue "+controlValue);
    }
  }

  //the firehose.  all osc incoming spit back out.
  /*
    if (theOscMessage.checkTypetag("isf")) {
       int     loopNumber  = theOscMessage.get(0).intValue();
       String  controlName = theOscMessage.get(1).stringValue();
       float   controlValue  = theOscMessage.get(2).floatValue(); 
       
       //println(theOscMessage);
       println(" message: loopNumber= "+loopNumber+ " controlName= "
       +controlName+" controlValue "+controlValue);
       
    }
   */
}

byte timelinePosition (int loopnum) {  //takes in current loop number and returns a byte of its position 0-7  
  return byte(8*(loop_position[loopnum]/loop_length[loopnum]));
}

int blink() { //returns a 1 or 0 depending on current blink timing.  blink interval is set in setup.
  long currentMillis = millis();
  if ((currentMillis-previous_blink) >= blink_interval) {
    previous_blink = currentMillis;
    if (blink_state==0) blink_state = 1;    
    else blink_state = 0;
  }
  return blink_state;
}

void oscSend(int loopnum, String type, String command) {
  OscMessage msg = new OscMessage("/sl/" + loopnum + "/" + type); 
  msg.add(command);
  oscP5.send(msg, sooper);
}

//couldn't get global auto updates to work, so use a for loop to do them individually
void getValueGlobal (String control, String path) {
  for (int i=0;i<8;i++) {
    OscMessage msg = new OscMessage("/sl/" + i + "/get");
    msg.add(control);
    msg.add("osc.udp://127.0.0.1:12000");
    msg.add(path);
    oscP5.send(msg, sooper);
  }
}

//couldn't get global auto updates to work, so use a for loop to do them individually
void registerAutoUpdate (String control, String path) {
  for (int i=0;i<8;i++) {
    OscMessage msg = new OscMessage("/sl/" + i + "/register_auto_update");
    msg.add(control);
    msg.add(100);
    msg.add("osc.udp://127.0.0.1:12000");
    msg.add(path);
    oscP5.send(msg, sooper);
  }
}

//couldn't get global auto updates to work, so use a for loop to do them individually
void unRegisterAutoUpdate (String control, String path) {
  for (int i=0;i<8;i++) {
    OscMessage msg = new OscMessage("/sl/" + i + "/unregister_auto_update");
    msg.add(control);
    msg.add("osc.udp://127.0.0.1:12000");
    msg.add(path);
    oscP5.send(msg, sooper);
  }
}

Post Reply