Skip to content

Instantly share code, notes, and snippets.

@yrrah
Last active December 16, 2015 09:49
Show Gist options
  • Save yrrah/5415948 to your computer and use it in GitHub Desktop.
Save yrrah/5415948 to your computer and use it in GitHub Desktop.
#include <SD.h>
#include <MIDI.h>
#include <TimerOne.h>
#include <DmxSimple.h>
#include <TinyXML.h>
#define DEBUG 1
#define MIDI_BAUDRATE 31250
#define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input.
#define COMPILE_MIDI_OUT 0 // Set this setting to 1 to use the MIDI output.
#define COMPILE_MIDI_THRU 0 // Set this setting to 1 to use the MIDI Soft Thru feature
#define USE_SERIAL_PORT Serial
#define USE_RUNNING_STATUS 0
#define USE_1BYTE_PARSING 1
#define USE_CALLBACKS 0
#define MIDI_SYSEX_ARRAY_SIZE 64
#define FRAME_GROUP 0
#define LISTEN_CHAN 1
#define CASE_VAL_L 2
#define CASE_VAL_H 3
#define MIDI_VAR 4
#define MSG_TYPE 5
#define FG_ADDR 1
#define FG_LENGTH 0
#define CURRENT_F 2
#define PITCH 0
#define VELOCITY 1
#define POT1 41
#define POT2 42
#define POT3 43
#define POT4 44
#define FADER 45
#define ledground1 8 //hole 2
#define ledground2 5 //hole 4
#define ledground3 24 //hole 13
#define ledground4 16 //hole 11
#define blueled 10 //hole 17
#define greenled 11 //hole 18
#define redled 12 // this goes to hole 19 on the ribbon cable
#define whitegreenled 14 //hole 15
#define whiteblueled 13
#define whiteredled 15 //hole 16
#define switchground1 18 //hole 1
#define switchground2 19 //hole 3
#define switchground3 0 //hole 14
#define switchground4 1 //hole 12
#define switch1 26 //hole 7
#define switch2 38 //hole 8
#define switch3 39 //hole 9
#define switch4 40 //hole 10
#define SW_DELAY 60 //milliseconds
#define CB_COUNT 50
#define INIT_WAIT 200
File myFile;
volatile int milliSec = 0;
char* temp = "";
int** caseArray;
int caseCount;
int** caseInfo;
int** fades;
int fgcount; //number of frame groups & corresponding cases
int maxChannel = 20;
double global_note, global_velocity;
volatile int period = 0;
int step = 1;
int counte = 0;
boolean XMLdone = 0;
int numScenes = 0;
char** fileNames;
int selectedScene = 0;
boolean S1_EN = 0;
boolean S2_EN = 0;
boolean S3_EN = 0;
boolean S4_EN = 0;
boolean S1_P = 0;
boolean S2_P = 0;
boolean S3_P = 0;
boolean S4_P = 0;
int S1_count = INIT_WAIT;
int S2_count = INIT_WAIT;
int S3_count = INIT_WAIT;
int S4_count = INIT_WAIT;
int S1_n = 0;
int S2_n = 0;
int S3_n = 0;
int S4_n = 0;
int clockType = -1;
int clockChan = -1;
int clockNote = -1;
int clockVelocity = -1;
int curRow = 0;
int curCol = 0;
int files = 0;
int CB = INIT_WAIT;
void XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen) {
char ns[] = "num-scenes";
char fn[] = "filename";
char ct[] = "clockType";
char cc[] = "clockChan";
char cn[] = "clockNote";
char cv[] = "clockVelocity";
if (statusflags & STATUS_ATTR_TEXT) {
if (!strcmp(ns, tagName)) {
numScenes = asciiHexToInt(data, 2);
Serial.print("numScenes: ");
Serial.println(numScenes);
fileNames = (char**) malloc(numScenes * sizeof (char*));
}
if (!strcmp(fn, tagName)) {
Serial.print("Filename: ");
fileNames[files] = (char*) malloc((strlen(data) + 1) * sizeof (char));
strcpy(fileNames[files], data);
Serial.println(fileNames[files]);
if (numScenes == ++files) {
XMLdone = true;
}
}
if (!strcmp(ct, tagName)) {
Serial.print("Clock MIDI Message Type: ");
clockType = asciiHexToInt(data, 2);
Serial.println(clockType);
}
if (!strcmp(cc, tagName)) {
Serial.print("Clock MIDI Channel: ");
clockChan = asciiHexToInt(data, 2);
Serial.println(clockChan);
}
if (!strcmp(cn, tagName)) {
Serial.print("Clock MIDI Note: ");
clockNote = asciiHexToInt(data, 2);
Serial.println(clockNote);
}
if (!strcmp(cv, tagName)) {
Serial.print("Clock MIDI Velocity: ");
clockVelocity = asciiHexToInt(data, 2);
Serial.println(clockVelocity);
}
}
}
static void readXML() {
TinyXML xml;
uint8_t buffer[100];
uint16_t buflen = 100;
xml.init((uint8_t*) & buffer, buflen, &XML_callback);
while (myFile.available() && !XMLdone) {
char c = myFile.read();
xml.processChar(c);
}
}
void loadFrame(int FrameID) {
int i;
int chan, val, upVal;
char in, in2;
#ifdef DEBUG
Serial.print("Load Frame Group:");
Serial.print(FrameID);
Serial.print(" Frame:");
Serial.println(caseInfo[FrameID][CURRENT_F]);
#endif
//seek to location of frame group on SD card
myFile.seek(caseInfo[FrameID][FG_ADDR]);
//find location of frame
i = 0;
while (i <= caseInfo[FrameID][CURRENT_F]) {
while (myFile.read() != '%');
i++;
}
//read and execute DMX instructions
in = myFile.read();
while (myFile.available() && in != '%' && in != '#') {
switch (in) {
case 'S':
case 'P':
case 'F':
case 'M':
in2 = myFile.read();
myFile.readBytes(temp, 2);
chan = asciiHexToInt(temp, 2);
myFile.readBytes(temp, 2);
val = asciiHexToInt(temp, 2);
myFile.readBytes(temp, 2);
upVal = asciiHexToInt(temp, 2);
#ifdef DEBUG
Serial.flush();
Serial.print("DMX Instruction: chan ");
Serial.print(chan, HEX);
Serial.print(", ");
Serial.print(val, HEX);
Serial.print(", ");
Serial.println(upVal, HEX);
#endif
double potValue;
int out;
switch (in) {
case 'S'://ST = Static
DmxSimple.write(chan, val);
break;
case 'P':
switch (in2) {
case '1'://P1 = Potentiometer 1
potValue = analogRead(POT1);
break;
case '2'://P1 = Potentiometer 2
potValue = analogRead(POT2);
break;
case '3'://P1 = Potentiometer 3
potValue = analogRead(POT3);
break;
case '4'://P1 = Potentiometer 4
potValue = analogRead(POT4);
break;
case '5'://P1 = Fader / Slider
potValue = analogRead(FADER);
break;
}
out = (int) ((potValue / 1023)*(upVal - val) + val);
DmxSimple.write(chan, out);
break;
case 'F':
switch (in2) {
case 'U'://FU = Fade Up
fades[chan][0] = 1;
DmxSimple.write(chan, val);
break;
case 'D'://FD = Fade Down
fades[chan][0] = 2;
DmxSimple.write(chan, upVal);
break;
}
fades[chan][1] = val;
fades[chan][2] = upVal;
break;
case 'M':
/* Serial.print(val);
Serial.print(" ");
Serial.println(upVal);
*/ switch (in2) {
case '1'://M1 = MIDI Note
out = (int) ((global_note / 127)*(upVal - val) + val);
break;
case '2'://M2 = MIDI Velocity
out = (int) ((global_velocity / 127)*(upVal - val) + val);
break;
}
// Serial.println(float2s(out, 5));
DmxSimple.write(chan, out);
break;
}
break;
default:
#ifdef DEBUG
Serial.println("DMX Instuction Type not implemented yet");
Serial.print(caseInfo[FrameID][CURRENT_F]);
Serial.print(" ");
Serial.print(FrameID);
Serial.print(" ");
Serial.print(in);
Serial.println(in2);
#endif
break;
}
in = myFile.read();
}
//increment frame counter
if (++caseInfo[FrameID][CURRENT_F] >= caseInfo[FrameID][FG_LENGTH])
caseInfo[FrameID][CURRENT_F] = 0;
}
void loadScene(char* fileName) {
int i;
int looplength;
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
myFile.close();
myFile = SD.open(fileName, FILE_READ);
if (!myFile) {
// if the file didn't open, print an error:
#ifdef DEBUG
Serial.print("error opening scene: ");
Serial.println(fileName);
#endif
} else {
Serial.print("Opening Scene: ");
Serial.println(fileName);
//free the memory for the old case array if it exists
if (fgcount != 0) {
for (i = 0; i < fgcount; i++)
free(caseArray[i]);
free(caseArray);
}
myFile.readBytes(temp, 2);
fgcount = asciiHexToInt(temp, 2);
myFile.readBytes(temp, 2);
looplength = asciiHexToInt(temp, 2);
myFile.readBytes(temp, 2);
maxChannel = asciiHexToInt(temp, 2);
fades = (int**) malloc(maxChannel * sizeof (int*));
for (i = 0; i < maxChannel; i++) {
fades[i] = (int*) malloc(3 * sizeof (int));
fades[i][0] = 0;
}
#ifdef DEBUG
Serial.print("fgcount ");
Serial.println(fgcount);
Serial.print("looplength ");
Serial.println(looplength);
Serial.print("maxChannel ");
Serial.println(maxChannel);
#endif
caseInfo = (int**) malloc(fgcount * sizeof (int*));
for (i = 0; i < fgcount; i++) {
caseInfo[i] = (int*) malloc(3 * sizeof (int));
while (myFile.read() != '#');
myFile.readBytes(temp, 2);
caseInfo[i][FG_LENGTH] = asciiHexToInt(temp, 2);
caseInfo[i][FG_ADDR] = myFile.position();
caseInfo[i][CURRENT_F] = 0;
#ifdef DEBUG
Serial.flush();
Serial.print("------ Case Info ");
Serial.print(i);
Serial.println(" -------");
Serial.print("FG Length: ");
Serial.println(caseInfo[i][FG_LENGTH]);
Serial.print("FG Address: ");
Serial.println(caseInfo[i][FG_ADDR]);
Serial.print("Current Frame: ");
Serial.println(caseInfo[i][CURRENT_F]);
#endif
}
while (myFile.read() != '$');
myFile.readBytes(temp, 2);
caseCount = asciiHexToInt(temp, 2);
int fg = 0;
caseArray = (int**) malloc(caseCount * sizeof (int*));
for (i = 0; i < caseCount; i++) {
caseArray[i] = (int*) malloc(6 * sizeof (int));
char c = myFile.read();
if (c == '$') {
fg++;
}
caseArray[i][FRAME_GROUP] = fg;
//MIDI Listen Channel
myFile.readBytes(temp, 2);
caseArray[i][LISTEN_CHAN] = asciiHexToInt(temp, 2);
//MIDI low case value
myFile.readBytes(temp, 2);
caseArray[i][CASE_VAL_L] = asciiHexToInt(temp, 2);
//MIDI high case value
myFile.readBytes(temp, 2);
caseArray[i][CASE_VAL_H] = asciiHexToInt(temp, 2);
//MIDI varible (pitch,velocity,etc)
myFile.readBytes(temp, 1);
caseArray[i][MIDI_VAR] = asciiHexToInt(temp, 1);
//MIDI message type (note on, note off, etc)
myFile.readBytes(temp, 1);
caseArray[i][MSG_TYPE] = asciiHexToInt(temp, 1);
#ifdef DEBUG
Serial.flush();
Serial.print("------ Case Array Index ");
Serial.print(i);
Serial.println(" -------");
Serial.print("Frame Group ");
Serial.println(caseArray[i][FRAME_GROUP]);
Serial.print("MIDI Listen Channel ");
Serial.println(caseArray[i][LISTEN_CHAN]);
Serial.print("MIDI low case value ");
Serial.println(caseArray[i][CASE_VAL_L]);
Serial.print("MIDI high case value ");
Serial.println(caseArray[i][CASE_VAL_H]);
Serial.print("MIDI varible ");
Serial.println(caseArray[i][MIDI_VAR]);
Serial.print("MIDI message type ");
Serial.println(caseArray[i][MSG_TYPE]);
Serial.flush();
#endif
}
}
}
void count() {
milliSec++;
// Serial.println(milliSec);
step = 255 / period;
if (step == 0)step = 1;
if (S1_count-- == 0) {
S1_EN = 1;
}
if (S2_count-- == 0) {
S2_EN = 1;
}
if (S3_count-- == 0) {
S3_EN = 1;
}
if (S4_count-- == 0) {
S4_EN = 1;
}
if (CB-- == 0) {
buttonled(curRow, curCol);
}
}
int asciiHexToInt(char* a, int length) {
//converts acii representation of hex eg 'ff' to 0xFF
int val;
int in;
int out = 0;
for (int i = length - 1; i >= 0; i--) {
in = a[i];
if (in >= 'A' && in <= 'F') {
val = in - 55;
} else if (in >= 'a' && in <= 'f') {
val = in - 87;
} else if (in >= '0' & in <= '9') {
val = in - 48;
} else return -1;
out += val * (int) ceil((pow(16, length - 1 - i)));
}
return out;
}
boolean handleMIDI(int midiType, int midiChan, int note, int velocity) {
global_note = note;
global_velocity = velocity;
int midiValue = 0;
int i;
//MIDI Clock Signal
if (((midiType == clockType) || (clockType == -1)) && ((midiChan == clockChan) || (clockChan == -1))
&& ((note == clockNote) || (clockNote == -1)) && ((velocity == clockVelocity) || (clockVelocity == -1))) {
#ifdef DEBUG
Serial.print("Clock... Period: ");
Serial.println(period);
#endif
period = milliSec;
milliSec = 0;
}
#ifdef DEBUG
Serial.print("Type: ");
Serial.print(midiType);
Serial.print(" Chan: ");
Serial.print(midiChan);
Serial.print(" note: ");
Serial.print(note);
Serial.print(" velocity: ");
Serial.println(velocity);
#endif
//loop through all cases
for (int i = 0; i < caseCount; i++) {
//check to see if correct message type (Note on, Note off, etc)
if (caseArray[i][MSG_TYPE] == midiType
&& caseArray[i][LISTEN_CHAN] == midiChan) {
//get relevant incoming midi data
switch (caseArray[i][MIDI_VAR]) {
case PITCH:
midiValue = note;
break;
case VELOCITY:
midiValue = velocity;
break;
}
//do comparison and load frame if true
if (midiValue >= caseArray[i][CASE_VAL_L]
&& midiValue <= caseArray[i][CASE_VAL_H]) {
loadFrame(caseArray[i][FRAME_GROUP]);
return 1;
}
}
}
return 0;
}
void blackout() {
digitalWrite(ledground1, HIGH);
digitalWrite(ledground2, HIGH);
digitalWrite(ledground3, HIGH);
digitalWrite(ledground4, HIGH);
digitalWrite(redled, LOW);
digitalWrite(greenled, LOW);
digitalWrite(blueled, LOW);
digitalWrite(whiteredled, LOW);
digitalWrite(whitegreenled, LOW);
digitalWrite(whiteblueled, LOW);
}
void down1() {
if (S1_EN) {
S1_EN = 0;
S1_P = 1;
S1_count = SW_DELAY;
}
}
void down2() {
if (S2_EN) {
S2_EN = 0;
S2_P = 1;
S2_count = SW_DELAY;
}
}
void down3() {
if (S3_EN) {
S3_EN = 0;
S3_P = 1;
S3_count = SW_DELAY;
}
}
void down4() {
if (S4_EN) {
S4_EN = 0;
S4_P = 1;
S4_count = 50;
}
}
void buttonled(int row, int collumn) {
blackout();
switch (row) {
case 0:
digitalWrite(ledground1, LOW);
break;
case 1:
digitalWrite(ledground2, LOW);
break;
case 2:
digitalWrite(ledground3, LOW);
break;
case 3:
digitalWrite(ledground4, LOW);
break;
}
switch (collumn) {
case -1:
digitalWrite(whiteredled, HIGH);
break;
case 0:
digitalWrite(whiteredled, HIGH);
digitalWrite(whitegreenled, HIGH);
digitalWrite(whiteblueled, HIGH);
break;
case 1:
digitalWrite(blueled, HIGH);
digitalWrite(whiteblueled, HIGH);
break;
case 2:
digitalWrite(greenled, HIGH);
digitalWrite(whitegreenled, HIGH);
break;
case 3:
digitalWrite(redled, HIGH);
digitalWrite(whiteredled, HIGH);
break;
}
}
void loop() {
boolean dmx = 0;
int i;
int newScene = 0;
boolean pressed = 0;
if (S1_P) {
S1_P = 0;
pressed = 1;
if (++S1_n > 3 || S1_n >= numScenes) {
S1_n = 0;
}
newScene = S1_n;
Serial.println(newScene);
if (newScene < numScenes) {
selectedScene = newScene;
buttonled(0, S1_n);
curRow = 0;
curCol = S1_n;
} else {
S1_n = 0;
buttonled(0, -1);
CB = CB_COUNT;
}
} else if (S2_P) {
S2_P = 0;
if (++S2_n > 3 || 4 + S2_n >= numScenes) {
S2_n = 0;
}
newScene = 4 + S2_n;
if (newScene < numScenes) {
pressed = 1;
selectedScene = newScene;
buttonled(1, S2_n);
curRow = 1;
curCol = S2_n;
} else {
S2_n = 0;
buttonled(1, -1);
CB = CB_COUNT;
}
} else if (S3_P) {
S3_P = 0;
if (++S3_n > 3 || 8 + S3_n >= numScenes) {
S3_n = 0;
}
newScene = 8 + S3_n;
if (newScene < numScenes) {
pressed = 1;
selectedScene = newScene;
buttonled(2, S3_n);
curRow = 2;
curCol = S3_n;
} else {
S3_n = 0;
buttonled(2, -1);
CB = CB_COUNT;
}
} else if (S4_P) {
S4_P = 0;
if (++S4_n > 3 || 12 + S4_n >= numScenes) {
S4_n = 0;
}
newScene = 12 + S4_n;
if (newScene < numScenes) {
pressed = 1;
selectedScene = newScene;
buttonled(3, S4_n);
curRow = 3;
curCol = S4_n;
} else {
S4_n = 0;
buttonled(3, -1);
CB = CB_COUNT;
}
} else {
pressed = 0;
}
if (pressed) {
Serial.print("New scene selected: [");
Serial.print(selectedScene);
Serial.print("]");
Serial.println(fileNames[selectedScene]);
loadScene(fileNames[selectedScene]);
}
if (usbMIDI.read())
dmx = handleMIDI(usbMIDI.getType(), usbMIDI.getChannel(), usbMIDI.getData1(), usbMIDI.getData2());
else if (MIDI.read())
handleMIDI(MIDI.getType(), MIDI.getChannel(), MIDI.getData1(), MIDI.getData2());
if (step) {
for (i = 0; i < maxChannel; i++) {
if (fades[i][0] == 1) {//fade up
fades[i][1] = fades[i][1] + step; //increase fade
if (fades[i][1] < fades[i][2]) {//if val < max
DmxSimple.write(i, fades[i][1]);
/* Serial.print(i);
Serial.print(" fade up ");
Serial.print(period);
Serial.print(" x ");
Serial.println(fades[i][1]);
*/ } else {
fades[i][0] = 0; //disable fade
DmxSimple.write(i, 0);
}
} else if (fades[i][0] == 2) {//fade down
fades[i][2] = fades[i][2] - step; //decrease fade
if (fades[i][2] > fades[i][1]) {//if val > min
DmxSimple.write(i, fades[i][2]);
/* Serial.print(i);
Serial.print(" fade dn ");
Serial.print(period);
Serial.print(" x ");
Serial.println(fades[i][2]);
*/ } else {
fades[i][0] = 0; //disable fade
DmxSimple.write(i, 0);
}
} else {
// DmxSimple.write(i, 0);
}
}
step = 0;
}
}
char * float2s(double f, unsigned int digits) {
int index = 0;
static char s[16]; // buffer to build string representation
// handle sign
if (f < 0.0) {
s[index++] = '-';
f = -f;
}
// handle infinite values
if (isinf(f)) {
strcpy(&s[index], "INF");
return s;
}
// handle Not a Number
if (isnan(f)) {
strcpy(&s[index], "NaN");
return s;
}
// max digits
if (digits > 9) digits = 9;
long multiplier = pow(10, digits); // fix int => long
int exponent = int(log10(f));
double g = f / pow(10, exponent);
if ((g < 1.0) && (g != 0.0)) {
g *= 10;
exponent--;
}
long whole = long(g); // single digit
long part = long((g - whole) * multiplier); // # digits
char format[16];
sprintf(format, "%%ld.%%0%dld E%%+d", digits);
sprintf(&s[index], format, whole, part, exponent);
return s;
}
void setup() {
DmxSimple.usePin(7);
DmxSimple.maxChannel(8);
MIDI.begin(MIDI_CHANNEL_OMNI);
pinMode(redled, OUTPUT);
pinMode(greenled, OUTPUT);
pinMode(blueled, OUTPUT);
pinMode(whiteredled, OUTPUT);
pinMode(whitegreenled, OUTPUT);
pinMode(whiteblueled, OUTPUT);
pinMode(ledground1, OUTPUT);
pinMode(ledground2, OUTPUT);
pinMode(ledground3, OUTPUT);
pinMode(ledground4, OUTPUT);
pinMode(switchground1, INPUT_PULLUP);
pinMode(switchground2, INPUT_PULLUP);
pinMode(switchground3, INPUT_PULLUP);
pinMode(switchground4, INPUT_PULLUP);
pinMode(switch1, OUTPUT);
pinMode(switch2, OUTPUT);
pinMode(switch3, OUTPUT);
pinMode(switch4, OUTPUT);
Timer1.initialize(10000); //in microseconds (1000000us = 1s)
Timer1.attachInterrupt(count); //runs every millisecond
Timer1.start();
#ifdef DEBUG
Serial.print("Initializing SD card...");
#endif
// On the Ethernet Shield, CS is pin 4. It's set as an output by default.*/
// Note that even if it's not used as the CS pin, the hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left as an output
// or the SD library functions will not work.
pinMode(20, OUTPUT);
if (!SD.begin(20)) {
#ifdef DEBUG
Serial.println("initialization failed!");
#endif
return;
}
myFile = SD.open("config.xml", FILE_READ);
if (!myFile) {
// if the file didn't open, print an error:
#ifdef DEBUG
Serial.println("error opening config file");
#endif
} else {
#ifdef DEBUG
Serial.println("initialization done.");
#endif
readXML();
if (numScenes > 0)
loadScene(fileNames[0]);
else
Serial.println("No scenes to load!");
}
attachInterrupt(switchground1, down1, FALLING);
attachInterrupt(switchground2, down2, FALLING);
attachInterrupt(switchground3, down3, FALLING);
attachInterrupt(switchground4, down4, FALLING);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment