//////////////////////////////////////////////////////////////////////////////// // Title: TI Launchpad Controller // Author: Don Beckstein // // Description: Intermediary program to pass midi commands // received from custom TI Launchpad controller to Virtual DJ. // Receives serial input (over USB) from Launchpad and hardware // and sends commands to VDJ through LoopBe1 Intenal MIDI port. // // Special Thanks: Thank you to whoever made proMIDI (1.0) for Processing, // Andreas Schlegel for ControlP5, Daniel Schmitt for LoopBe1, // and to the whole Launchpad team! /////////////////////////////////////////////////////////////////////////////// //Libraries import processing.serial.*; import promidi.*; import controlP5.*; //Controller Classes ControlP5 cp5; //GUI MidiIO midiIO; //Midi controller MidiOut midiOut; //Midi output Serial COMPort; //Serial data bus (USB line to Launchpad) //GUI elements DropdownList d1, d2; Button b1, b2; Textlabel l1, l2; //Globals int connectedMIDI; int connectedCOM; boolean midiConnected = false; boolean comConnected = false; color red = color(128, 0, 0); color green = color(0, 200, 0); /*@todo Go through @debug Todo: -Test everything! Make a full prototype on the breadboard! -Read through code! */ static int keypadOffset = 20; //We start the notes for the keypad at 20 static int controlChangeOffset = 70; //We start the control changes (analog inputs in this case) at 20 //CC's 20-31 are not specifically reserved for anything. static int[] rotaryNotes = {120, 121, 122}; //These are the notes used by the rotary encoder for CW, CCW, and Double Click static int superSpeedMultiplier = 5; //This is how many rotation commands will be sent per detent with Superspeed static int[] modeAndDeck = {0, 2, 4, 1, 3, 5}; //@debug //Converts a 10-bit ADC value (0 - 1023) to a 7-bit MIDI value (0 - 127) int toMIDIValue(int value) { float returnVal = (Float.valueOf(value)/1023)*127; return int(returnVal); } //This is where the translation from a 2-byte code into a MIDI message begins //All messages follow this format: // [00000] [0] [0000000000] // ID Num Type Value //The ID Number identifies the sending control and the type determines if it's digital (0) or analog (1) //The last 10 bits contain the actual control data, from a 10-bit ADC value to //a simple true or false. The first 6 bits determine how we interpret the last 10. void ParseMessage(int message) { //println(binary(message)); int IDnum = message & 0xF8F2; //Get rid of the bits we don't need (0b1111100000000000) IDnum >>= 11; //Shift them to the right 11 bits so we have the true number //message & 0x0400 - Get rid of the bits we don't need (0b0000010000000000) // >> 10 - Shift it to the right 10 bits so we have the type value boolean type = boolean(((message & 0x0400)>>10)); //Bit 11 converted to boolean //@debug //print("ID Number: "); println(IDnum); //print("Type: "); println(type ? "Analog" : "Digital"); if(!type) //Digital control { if(IDnum == 0) //Reserved for Keypad ProcessKeypad(message); else if(IDnum == 1) //Reserved for Rotary Encoder ProcessRotary(message); else //General Digital Input ProcessDigital(IDnum, message); } else //Analog control { ProcessAnalog(IDnum, message); } } //The ADCs we used in this project are 10-bit resolution //Which is why this protocol was designed the way it is //The last 10 bits converts to a number between 0 and 1023 (2^10 - 1) void ProcessAnalog(int IDnum, int message) { int value = message & 0x03FF; //Shave off everything but the last 10 bits (10-bit ADC value) //@todo Test - Prints a certain contrl's value based on its ID number //int select = 13; //if(IDnum == select) // println(value); int MIDIvalue = toMIDIValue(value); //Convert it to a value between 0 and 127 //@debug //println(MIDIvalue); SendControlChange(IDnum, MIDIvalue); //Send it off! } //The last 10 bits of a digital message are either true or false, so we really only care about the last bit void ProcessDigital(int IDnum, int message) { boolean value = boolean(message & 0x0001); //Converts the value (last 10 bits of message) to either true or false //@debug /*int select = 6; if(IDnum == select) {print("Value: "); println(value ? "Off" : "On"); }//We have pull-ups on the hardware, so digital is inverted (low = pressed)*/ println("Button press"); //We have all the information we need, send the note! SendNote(IDnum, !value); //Invert the value and send } /* Keypad Message Format Keypad messages are formatted as follows: [00000] [0] [0000] [0] [00] [0] [00] ID Num Type Key ID On/Off Mode Deck Don't Care The key ID identifies what key was pressed. We use 0-11 but more are possible. The additional keys would actually be interpreted correctly but there is no way we can receive them as input However, we have chosen to use key 15 (1111) to signify a deck change. The On/Off bit indicates if the button was pressed or released The Mode identifies what mode we're in (0-2), and deck gives us what deck is selected We don't care about the last 2 bits. */ void ProcessKeypad(int message) { println("Keypad Press"); int keyPress = message & 0x03C0; //Get the bits that correspond to the key ID keyPress >>= 6; //Shift bits right to get meaningful number boolean oorf = boolean((message & 0x0020)>>5); //Converts the on/off bit to a boolean int mode = message & 0x0018; //Get the bits that correspond to the mode number mode >>= 3; //Shift right to get a meaningful number int deck = message & 0x0004; //Get the bit that corresponds to the deck number deck >>= 2; //Shift right to get a meaningful number /*print("Key Pressed: "); println(keyPress); print("On or Off: "); println(oorf ? "On" : "Off"); print("Mode: "); println(mode); print("Deck: "); println(deck);*/ //This next little algorithm converts all keycodes into distinct notes depending on keyPressed, the mode, and the deck //It ranges from note 20 (the keypadOffset) to note 115 (or 16*3*2 = 96 above 20). int note = keypadOffset + (keyPress*6) + mode + (deck * 3); //print("Note: "); println(note); SendNote(note, oorf); //Send the keypad note! //println("----------------------"); } /* Rotary Encoder Message Format Rotary Encoder messages are formatted as follows: [00000] [0] [00] [0] [0000000] ID Num Type Value SuperSpd Don't Care The Value ranges from 0 to 2 with these meanings: 0 - CW Rotation, 1 - CCW Rotation, 2 - Double Click The Super Speed bit indicates if the integrated button is pressed down while rotation. It allows this program to send many more messages per rotation to scroll faster. The rotary encoder is meant to scroll through music libraries without using a mouse. We don't care about the last 6 bits. */ void ProcessRotary(int message) { int value = (message & 0x0300) >> 8; //Get the bits we want and shift it right to make it the true number boolean superSpeed = boolean((message & 0x0080) >> 6); //Convert the 7th bit to a boolean if(value >= 0 && value <= 2) //Within range { int note = rotaryNotes[value]; //Note that superSpeed will never be on for double click messages (coded into firmware) so this will not cause problems if(superSpeed) //Superspeed is on! Send a specified number (superSpeedMultiplier) more commands per detent { //println("Superspeed"); for(int i=0;i<=superSpeedMultiplier;i++) { SendNote(note, true); //Turn it on SendNote(note, false); //Then turn it off immediately } } else //The superspeed is off, just send one { //println("normal"); SendNote(note, true); SendNote(note, false); } } //println(note); } void SendNote(int note, boolean OORF) //To send a note, we need to know what note and if it's On Or Off { if(midiConnected) { if(note <= 127 && note >= 0) //Between 0 and 127 { if(OORF) //This means send a note on message { midiOut.sendNoteOn(new promidi.Note(0, note, 100)); //Channel 0, with our note, and velocity = 100 } else //Send note off { midiOut.sendNoteOff(new promidi.Note(0, note, 100)); } } //@debug //print("Note: "); println(note); //print("On or off: "); println(OORF ? "On" : "Off"); } } //To send a control change, we need to know the control change number (ccNum) and a value for it (from 0 - 127) void SendControlChange(int ccNum, int value) { //println(value); if(midiConnected) { if(value <= 127 && value >=0) //Value is between 0 and 127 { //int cc = ccNum+controlChangeOffset; //if(cc != 76 && cc != 74) //{ midiOut.sendController(new promidi.Controller(0, (ccNum + controlChangeOffset), value)); //On Channel 0 //} } } } void setup() { size(500, 250); //Controller Initialization //GUI cp5 = new ControlP5(this); //MIDI midiIO = MidiIO.getInstance(this); connectedMIDI = -1; //Initial value connectedCOM = -1; //GUI elements initialization //Create the drop down lists d1 = cp5.addDropdownList("midi-out") .setPosition(25, 50) .setSize(200, 200); d1.captionLabel().set("Select MIDI Out"); customize(d1); d2 = cp5.addDropdownList("com-port") .setPosition(275, 50) .setSize(200, 200); d2.captionLabel().set("Select Serial Port"); customize(d2); generateMIDIDropdownList(); generateCOMDropdownList(); //Add the buttons cp5.addButton("Refresh") .setPosition(200, 200) .setSize(100, 20); b1 = cp5.addButton("Connect_MIDI") .setPosition(75, 200) .setSize(100, 20); b2 = cp5.addButton("Connect_COM") .setPosition(325, 200) .setSize(100, 20); l1 = cp5.addTextlabel("MIDI-Status") .setText("Disconnected") .setPosition(70, 222) .setFont(createFont("Calibri", 12)) .setColorValue(red); l2 = cp5.addTextlabel("COM-Status") .setText("Disconnected") .setPosition(320, 222) .setFont(createFont("Calibri", 12)) .setColorValue(red); //Test code for keypad (generates all possible key press messages) /*int code = 0; for(int i=0; i<16; i++) { int keyPress = i; keyPress <<= 6; for(int j=0; j<6; j++) { code = 0; code |= keyPress; int mAD = modeAndDeck[j]; mAD <<= 2; code |= mAD; ParseMessage(code); code ^= 0x0020; //Toggle the on/off bit ParseMessage(code); } }*/ } void draw() { background(128); } void customize(DropdownList ddl) { // a convenience function to customize a DropdownList ddl.setBackgroundColor(color(190)); ddl.setItemHeight(20); ddl.setBarHeight(15); //ddl.captionLabel().set("Select"); ddl.captionLabel().style().marginTop = 3; ddl.captionLabel().style().marginLeft = 5; ddl.valueLabel().style().marginTop = 3; //ddl.scroll(0); ddl.setColorBackground(color(60)); ddl.setColorActive(color(255, 128)); } void generateMIDIDropdownList() { d1.clear(); //Get list of MIDI devices int numOutputs = midiIO.getNumberOfOutputs(); //println("Num of MIDI Out:" + numOutputs); //Store all outputs in dropdown list for(int i=0; i