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);
}
}