// LoopState tab has code for the state of display, OSC messages, // and note looping. 1/4/2020 import java.net.*; import java.util.* ; import java.util.concurrent.* ; import oscP5.*; import netP5.*; enum LoopType { CLEAR, RECORD, PLAY }; LoopType loopState = LoopType.CLEAR ; boolean enterLoopMenu = false ; long loopStartTime = 0L ; class LoopData { final long notetime ; // in msecs final String command ; final int pitch ; final int velocity ; final int channel ; LoopData(long notetime, String command, int pitch, int velocity, int channel) { this.notetime = notetime ; this.command = command ; this.pitch = pitch ; this.velocity = velocity ; this.channel = channel ; } } final List loopSequence = new LinkedList(); int loopSequenceStep = 0 ; boolean DEBUGINCOMINGOSC = false ; final int networkInterfaceIndex = -1 ; final String CommandPrefix = "/csc480S20Midi1/" ; OscP5 oscP5 = null ; // null until client gets a UDP listen port. NetAddress myRemoteLocation; NetAddress serverRemoteLocation; String MYIPADDR = null ; int MYPORT = 12000 ; /* Adjust if it fails. */ String SERVERIPADDR = null ; int SERVERPORT = -1 ; /* Have to enter as data. */ boolean printingScreenSize = true ; PFont globalFont = null ; int globalPointSize = 64 ; // 64 works OK for Android Galaxy of 1536 x 2048 pixels volatile int timeToRunInstrument = 0 ; // CommandMenu gets made 1 sec. after last response from server volatile Menu globalMenu = null ; volatile boolean showingKeyboard = false ; volatile boolean showedKeyboard = false ; // was it previously shown? volatile Thread guiThread = null ; int oscClientMidiChan = -1 ; volatile boolean isChord = false ; // STUDENT Parson's code will toggle true/false. void startOSC() { /* myRemoteLocation is a NetAddress. a NetAddress takes 2 parameters, * an ip address and a port number. myRemoteLocation is used as parameter in * oscP5.send() when sending osc packets to another computer, device, * application. usage see below. for testing purposes the listening port * and the port of the remote location address are the same, hence you will * send messages back to this sketch. */ globalPointSize = height / 24 ; // Use 24 for Galaxy tablet in landscape. // The CSC Galaxy tablets use 1536 x 2048 pixels, need bigger pointsize // fullScreen(); // globalPointSize = 64 ; // height / 32 ; // point size 64 on the CSC Android tablet's // Get the IP address of this client. try { int nix = 0 ; Enumeration e = NetworkInterface.getNetworkInterfaces(); while (e.hasMoreElements()) { NetworkInterface n = (NetworkInterface) e.nextElement(); Enumeration ee = n.getInetAddresses(); while (ee.hasMoreElements()) { InetAddress i = (InetAddress) ee.nextElement(); String ipaddr = i.getHostAddress().toString(); if (ipaddr.indexOf(".") > 0) { // It is an IP address, not a MAC address. println("One client IPADDR: " + ipaddr); if (! (MYIPADDR != null || ipaddr.equals("127.0.0.1") || ipaddr.indexOf("localhost") > -1) || networkInterfaceIndex > nix) { println("SETTING MYIPADDR to " + ipaddr); printDEBUG("SETTING MYIPADDR to " + ipaddr); MYIPADDR = ipaddr ; } nix++ ; } } } } catch (SocketException sx) { printDEBUG("ERROR, SocketException checking IP addresses: " + sx.getMessage()); exit(); } /* myRemoteLocation is a NetAddress. a NetAddress takes 2 parameters, * an ip address and a port number. myRemoteLocation is used as parameter in * oscP5.send() when sending osc packets to another computer, device, * application. usage see below. for testing purposes the listening port * and the port of the remote location address are the same, hence you will * send messages back to this sketch. */ if (MYIPADDR != null) { myRemoteLocation = new NetAddress(MYIPADDR, MYPORT); /* start oscP5, listening for incoming messages at port 12000 */ try { oscP5 = new OscP5(this, MYPORT/*12000*/); } catch (Exception xxx) { printDEBUG("ERROR, cannot open port " + MYPORT); printDEBUG("Change global MYPORT to another value & try again."); if (oscP5 != null) { oscP5.stop(); } oscP5 = null ; exit(); } } else { printDEBUG("ERROR, Cannot get IP address for this client device."); exit(); } } int [] scaleBoundingBox = new int [4]; // when mouse releases within bounding box int [] tonicBoundingBox = new int [4]; int [] channelBoundingBox = new int [4]; int [] loopBoundingBox = new int [4]; int [] chordBoundingBox = new int [4]; /* Return true if connection to server is established, else false. */ boolean connectionReady() { guiThread = Thread.currentThread(); colorMode(HSB, 360, 100, 100, 100); if (showingKeyboard) { fill(0, 0, 99); textSize(globalPointSize); textAlign(LEFT, TOP); int textHeight = round(textAscent() + textDescent()); // if (scaleix < 0 || scaleix >= scaleName.length) println("DEBUG SCALEIX = " + scaleix); String textScale = (scaleix >= 0 && scaleix < scaleName.length) ? scaleName[scaleix] : "?" ; int textScaleWidth = round(textWidth(textScale)); String textTonic = "tonic = " + ((tonic >= 0 && tonic < noteName.length) ? noteName[tonic] : "?") ; int textTonicWidth = round(textWidth(textTonic)); String textChannel = "chan = " + oscClientMidiChan ; int textChannelWidth = round(textWidth(textChannel)); String textLoop = (loopState == LoopType.RECORD ? "record loop" : (loopState == LoopType.PLAY ? "play loop" : (loopState == LoopType.CLEAR ? "clear loop" : "unknown loop"))); int textLoopWidth = round(textWidth(textLoop)); String textChord = "chord" ; int textChordWidth = round(textWidth(textChord)); scaleBoundingBox[0] = 10 ; scaleBoundingBox[1] = globalPointSize ; scaleBoundingBox[2] = 10 + textScaleWidth ; scaleBoundingBox[3] = globalPointSize + textHeight; tonicBoundingBox[0] = 10 ; tonicBoundingBox[1] = globalPointSize*3 ; tonicBoundingBox[2] = 10 + textTonicWidth ; tonicBoundingBox[3] = globalPointSize*3 + textHeight; channelBoundingBox[0] = 10 ; channelBoundingBox[1] = globalPointSize*5 ; channelBoundingBox[2] = 10 + textChannelWidth ; channelBoundingBox[3] = globalPointSize*5 + textHeight; loopBoundingBox[0] = 10 ; loopBoundingBox[1] = globalPointSize*7 ; loopBoundingBox[2] = 10 + textLoopWidth ; loopBoundingBox[3] = globalPointSize*7 + textHeight; chordBoundingBox[0] = 10 ; chordBoundingBox[1] = globalPointSize*9 ; chordBoundingBox[2] = 10 + textChordWidth ; chordBoundingBox[3] = globalPointSize*9 + textHeight; if (isWithin(mouseX, mouseY, scaleBoundingBox)) { fill(180, 99, 99) ; // cyan } else { fill(0, 0, 99) ; // white } text(textScale, 10, globalPointSize); if (isWithin(mouseX, mouseY, tonicBoundingBox)) { fill(180, 99, 99) ; } else { fill(0, 0, 99) ; // white } text(textTonic, 10, globalPointSize*3); if (isWithin(mouseX, mouseY, channelBoundingBox)) { fill(180, 99, 99) ; } else { fill(0, 0, 99) ; // white } text(textChannel, 10, globalPointSize*5); if (isWithin(mouseX, mouseY, loopBoundingBox)) { fill(180, 99, 99) ; } else { fill(0, 0, 99) ; // white } text(textLoop, 10, globalPointSize*7); if (isChord) { fill(0, 99, 99) ; // red textChord = "CHORD" ; } else { fill(0, 0, 99) ; // white } text(textChord, 10, globalPointSize*9); fill(0, 0, 99) ; // white return true ; } if (printingScreenSize) { background(0); fill(0, 0, 99); // HSB WHITE stroke(0, 0, 99); displayScreenSize(); return false ; } if (oscClientMidiChan < 1 || oscClientMidiChan > 15) { if (! (globalMenu instanceof SetClientMidiChannel)) { globalMenu = new SetClientMidiChannel(); println("DEBUG setting globalMenu = new SetClientMidiChannel()"); } globalMenu.display(); return false ; } // SetClientTonic if (tonic < 0 || tonic > 11) { if (! (globalMenu instanceof SetClientTonic)) { globalMenu = new SetClientTonic(); println("DEBUG setting globalMenu = new SetClientTonic()"); } globalMenu.display(); return false ; } // SetClientScale if (scaleix < 0 || scaleix >= scaleName.length) { if (! (globalMenu instanceof SetClientScale)) { globalMenu = new SetClientScale(); println("DEBUG setting globalMenu = new SetClientScale()"); } globalMenu.display(); return false ; } // SetClientLoop if (enterLoopMenu) { if (! (globalMenu instanceof SetClientLoop)) { globalMenu = new SetClientLoop(); println("DEBUG setting globalMenu = new SetClientLoop()"); } globalMenu.display(); return false ; } if (MYIPADDR == null) { printDEBUG("NO CLIENT IP ADDRESS, CANNOT RUN."); throw new RuntimeException("NO CLIENT IP ADDRESS, CANNOT RUN."); } else if (oscP5 == null) { printDEBUG("CANNOT USE MYPORT, CHANGE SETTING & TRY AGAIN: " + MYPORT); throw new RuntimeException("CANNOT USE MYPORT, CHANGE SETTING & TRY AGAIN: " + MYPORT); } else if (SERVERIPADDR == null) { setServerAddressPort(); } if (timeToRunInstrument != 0 && frameCount >= timeToRunInstrument) { globalMenu = null ; timeToRunInstrument = 0 ; return true ; } if (globalMenu != null) { // println("DEBUG ENTERING globalMenu.display()"); globalMenu.display(); } return false ; } /* PARSON // This function is only for initially registering the client with // the server. void sendOSCMessage(String command, String clientIP, int clientPort) { if (oscP5 != null && SERVERIPADDR != null && SERVERPORT > -1) { OscMessage myMessage = new OscMessage(command); myMessage.add(clientIP); myMessage.add(new Integer(clientPort)); myMessage.add(new Integer(clientPort)); // channel ignored on connection myMessage.add(new Integer(clientPort)); // pitch ignored on connection myMessage.add(new Integer(clientPort)); // velocity ignored on connection oscP5.send(myMessage, serverRemoteLocation); } } PARSON */ // All outgoing OSC messages go through this function. // Command must be one of: // "client" "noteon" "noteoff" "clearmine" "clearall" // See STUDENT comment below. void sendOSCMessage(String command, int midiChannel, int pitch, int velocity) { if (oscP5 != null && SERVERIPADDR != null && SERVERPORT > -1) { OscMessage myMessage = new OscMessage(CommandPrefix + command); myMessage.add(MYIPADDR); myMessage.add(new Integer(MYPORT)); myMessage.add(new Integer(midiChannel)); // channel ignored on connection myMessage.add(new Integer(pitch)); // pitch ignored on connection myMessage.add(new Integer(velocity)); // velocity ignored on connection // STUDENT: If global variable isChord is true, pass the values of // global variables tonic and scaleix as two additional Integer // objects before calling oscP5.send(); if global variable isChord is false, // pass two Integer objects with values of -1 in these two new positions. if (isChord) { myMessage.add(new Integer(tonic)); myMessage.add(new Integer(scaleix)); } else { myMessage.add(new Integer(-1)); myMessage.add(new Integer(-1)); } oscP5.send(myMessage, serverRemoteLocation); if (loopState == LoopType.RECORD && (command.equals("noteon") || command.equals("noteoff"))) { long loopNowTime = System.currentTimeMillis(); LoopData levent = new LoopData(loopNowTime-loopStartTime, command, pitch, velocity, midiChannel); loopSequence.add(levent); } } } void pollLooper() { if (loopState == LoopType.PLAY && loopSequence.size() > 0) { long curms = System.currentTimeMillis() ; long curTime = curms - loopStartTime ; while (loopSequenceStep < loopSequence.size()) { LoopData ready = loopSequence.get(loopSequenceStep) ; if (ready.notetime <= curTime) { sendOSCMessage(ready.command, ready.channel, ready.pitch, ready.velocity); loopSequenceStep++ ; } else { break ; } } if (loopSequenceStep >= loopSequence.size()) { loopSequenceStep = 0 ; loopStartTime = curms ; } } } /* incoming osc message are forwarded to the oscEvent method. */ /* THIS RUNS ON A SEPARATE THREAD, DO NOT INTERACT WITH GUI! */ void oscEvent(OscMessage theOscMessage) { Object [] args = theOscMessage.arguments(); // oscEvent runs in its own OSC thread, so be careful!!! String addr = theOscMessage.addrPattern(); println("DEBUG RECVD OSC addr: " + addr); if ((CommandPrefix + "server").equals(addr)) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { println("DEBUG RECVD OSC args[" + i + "]: " + args[i].toString()); } else { println("DEBUG RECVD OSC args[" + i + "]: null"); } } } else { println("DEBUG RECVD OSC INVALID addr: " + addr); } if (globalMenu instanceof SetServerAddressPortMenu) { globalMenu = null ; // a reply came from server, so it's address is set } timeToRunInstrument = round(frameCount + frameRate) ; // wait a second before populating screen showingKeyboard = true ; showedKeyboard = true ; } void displayScreenSize() { textAlign(LEFT); textSize(64); text("display size: " + width + " x " + height, 10, height/2); } void setServerAddressPort() { if (! (globalMenu instanceof SetServerAddressPortMenu)) { globalMenu = new SetServerAddressPortMenu(); } } void printDEBUG(String txt) { // proxy for println on tablet if (guiThread == Thread.currentThread()) { textSize(globalPointSize); textAlign(LEFT, TOP); text(txt, 10, height/2); } else { println(txt); } } void printDEBUG(String txt, int ylocation) { // proxy for println on tablet if (guiThread == Thread.currentThread()) { textSize(globalPointSize); textAlign(LEFT, TOP); text(txt, 10, ylocation); } else { println(txt); } } void mousePressed() { if (printingScreenSize) { printingScreenSize = false ; return ; } if (globalMenu != null) { // printDEBUG("DEBUG CALL globalMenu.respondToMouseEvent: " + mouseX + "," + mouseY); globalMenu.respondToMouseEvent(mouseX, mouseY); } else if (showingKeyboard) { // println("DEBUG responding to mousePressed in main display"); if (isWithin(mouseX, mouseY, scaleBoundingBox)) { scaleix = -1 ; println("DEBUG mousePressed() set scale to " + scaleix); showingKeyboard = false ; println("DEBUG mousePressed scaleBoundingBox"); } else if (isWithin(mouseX, mouseY, tonicBoundingBox)) { tonic = -1 ; showingKeyboard = false ; println("DEBUG mousePressed tonicBoundingBox"); } else if (isWithin(mouseX, mouseY, channelBoundingBox)) { oscClientMidiChan = -1 ; showingKeyboard = false ; println("DEBUG mousePressed channelBoundingBox"); } else if (isWithin(mouseX, mouseY, loopBoundingBox)) { enterLoopMenu = true ; showingKeyboard = false ; println("DEBUG mousePressed loopBoundingBox"); } else if (isWithin(mouseX, mouseY, chordBoundingBox)) { isChord = ! isChord ; println("DEBUG isChord = " + isChord); } } }