//This takes a midi input and outputs the triggers needed for the DR-110 #include typedef unsigned char pin; typedef unsigned char MIDIvalue; //The pins: pin pinCY = 2; //DR-110 9 pin pinOH = 3; //DR-110 10 pin pinCH = 4; //DR-110 11 pin pinBD = 5; //DR-110 12 pin pinSD = 6; //DR-110 13 pin pinC2 = 7; //DR-110 14 pin pinC1 = 8; //DR-110 15 pin pinAC = 9; //DR-110 16 pin pinChannelA = 10; pin pinChannelB = 11; pin pinChannelC = 12; pin pinChannelD = 14; pin pinLED = 13; unsigned int velocities[128] = {0}; byte channel = 0 ; //midi channel 0-15 bool omniMode = false; MIDIvalue serialBuffer = 0 ;//Used to save the contents of Serial on each iteration of loop. MIDIvalue lastSerialBuffer = 128 ; byte channelReceived = 128 ; //The CCs that change the notenumbers and also the positions in EEPROM namespace CCs { enum CC{//Also used as the EEPROM locations changeChannel = 9, BD = 36, RS = 37, SD = 38, CP = 39, BA = 40, SDh = 41, CH = 42, PH = 44, CHh = 45, OH = 46, ACh = 47, AC = 48, CY = 49, CYh = 51, CPh = 52, allNotesOff = 123, //Not saved omniOff = 124, omniOn = 125 //Saved in position 124 }; } enum class MIDIMessage{ //channel noteOff = B10000000, noteOn = B10010000, polyPressure = B10100000, CC = B10110000, programChange = B11000000, channelPressure = B11010000, pitchbend = B11100000, //System sysex = B11110000, quarterFrame = B11110001, positionPointer = B11110010, songSelect = B11110011, reserved1 = B11110100, reserved2 = B11110101, tune = B11110110, sysexEnd = B11110111, //Real time clock = B11111000, undefined3 = B11111001, seqstart = B11111010, seqContinue = B11111011, seqStop = B11111100, undefined4 = B11111101, activeSensing = B11111110, reset = B11111111 }; MIDIMessage MIDIMessages = MIDIMessage::noteOn; templatevoid saveToEEPROM(T placeToSave,U dataToSave) { EEPROM.update(static_cast(placeToSave),static_cast(dataToSave)); } unsigned long timer = 0; bool LEDon = false; void flashLED(){ digitalWrite(pinLED, HIGH); timer = millis(); LEDon = true; } void checkLED(){ if(LEDon && millis() - timer >= 5){ digitalWrite(pinLED, LOW); LEDon = false; } } class BasicInstrument; const int numberOfInstruments = 15; int instrumentCounter = 0; BasicInstrument* instrumentList[numberOfInstruments]; class BasicInstrument{ public: BasicInstrument(pin, MIDIvalue); void pinHigh() {digitalWrite(m_pin, HIGH);} void pinLow() {digitalWrite(m_pin, LOW);} virtual void trigger(unsigned int /*duration*/)//The function that sets the output high { pinHigh(); //Sets the pin high //flashLED(); } virtual void checkEndTrig(){}//Ignored as basic sounds only ended by noteoff virtual void noteOff() { pinLow(); } virtual void endNote() { pinLow(); } void setNoteNum(MIDIvalue newNoteNum){ m_noteNum = newNoteNum; saveToEEPROM(m_CCNum,newNoteNum); } int getNoteNum(){ return m_noteNum;} int getCCNum(){ return m_CCNum;} protected: pin m_pin;//The arduino pin MIDIvalue m_noteNum = 36; MIDIvalue m_CCNum = 36; }; //c_noteNum is the CC which sets the noteNum and the position in EEPROM in which the noteNum is saved BasicInstrument::BasicInstrument (pin c_pin, MIDIvalue c_noteNum){ m_pin = c_pin; m_noteNum = EEPROM.read(c_noteNum); m_CCNum = c_noteNum; pinMode(m_pin,OUTPUT); instrumentList[instrumentCounter] = this ; instrumentCounter ++; //instrumentList.push_back(this); } class Instrument : public BasicInstrument{ public: Instrument (pin c_pin, MIDIvalue c_noteNum):BasicInstrument(c_pin, c_noteNum){}//Same constructor virtual void trigger(unsigned int duration)//The function that sets the output high { m_duration = duration; //Set the duration of the pulse pinHigh(); //Sets the pin high m_isActive = true; //Lets Arduino know that the pin is high m_trigTimer = micros(); //Start the time //flashLED(); } virtual void checkEndTrig()//This is done every loop but only sets the pin low when enough time has elapsed { if (m_isActive)//If the output is high, check to see if it needs to be low { if (micros() - m_trigTimer >= m_duration) { endNote(); }//Has enough time elapsed? } } void noteOff() { } virtual void endNote() { pinLow(); m_isActive = false; //flashLED(); } protected: pin m_pin;//The arduino pin unsigned long m_duration = 1000;//The duration of the pulse in uS unsigned long m_trigTimer = 0; //The timer that keeps track of how long has passed boolean m_isActive = false; //If the output is high or not }; class Pedal : public Instrument{//Triggers both open and closed hihat public: Pedal (pin c_pin, pin c_pin2, MIDIvalue c_noteNum):Instrument(c_pin,c_noteNum){ m_pin2 = c_pin2; } void trigger(unsigned int duration)//The function that sets the output high { m_duration = duration; //Set the duration of the pulse pinHigh(); //Sets the pin high pin2High(); //Sets the pin high m_isActive = true; //Lets Arduino know that the pin is high m_trigTimer = micros(); //Start the time } void pin2High() {digitalWrite(m_pin2, HIGH);} void pin2Low() {digitalWrite(m_pin2, LOW);} void noteOff(){ } void endNote(){ m_isActive = false; pinLow(); pin2Low(); } private: pin m_pin2; }; //Very similar to instrument but with some added complexity due to the crazy clap signal class Clap : public Instrument{ public: Clap (pin pin1, pin pin2, MIDIvalue c_noteNum):Instrument(pin1, c_noteNum) { m_pin2 = pin2; pinMode(m_pin2,OUTPUT); } void pin2High() {digitalWrite(m_pin2, HIGH);} void pin2Low() {digitalWrite(m_pin2, LOW);} void noteOff() { } void trigger(unsigned int duration) { pinHigh(); pin2Low(); m_duration = duration; m_isActive = true; m_trigTimer = micros(); } void endNote(){ m_isActive = false; pinLow(); pin2Low(); } void checkEndTrig() { if (m_isActive) { unsigned long m_timeElapsed = micros() - m_trigTimer; if (m_timeElapsed > 3*timeBetweenClaps + m_duration) {pin2Low(); pinLow(); m_isActive = false;} else if (m_timeElapsed > 3*timeBetweenClaps) {pin2High();} else if (m_timeElapsed > 2*timeBetweenClaps + m_duration) {pinLow();} else if (m_timeElapsed > 2*timeBetweenClaps) {pinHigh();} else if (m_timeElapsed > timeBetweenClaps + m_duration) {pinLow();} else if (m_timeElapsed > timeBetweenClaps) {pinHigh();} else if (m_timeElapsed > m_duration) {pinLow();} // C1: /¯\____/¯\____/¯\__________... // C2: ______________________/¯\__... } } private: pin m_pin2;//The pin for the pulse with reverb const unsigned long timeBetweenClaps = 11000; //uS }; BasicInstrument SDh {pinSD, CCs::SDh}; BasicInstrument CPh {pinC1, CCs::CPh}; BasicInstrument CHh {pinCH, CCs::CHh}; BasicInstrument ACh {pinAC, CCs::ACh}; BasicInstrument CYh {pinCY, CCs::CYh}; Instrument CY {pinCY, CCs::CY }; Instrument OH {pinOH, CCs::OH }; Instrument CH {pinCH, CCs::CH }; Instrument BD {pinBD, CCs::BD }; Instrument SD {pinSD, CCs::SD }; Instrument AC {pinAC, CCs::AC }; Instrument RS {pinC1, CCs::RS }; Instrument BA {pinC2, CCs::BA }; Pedal PH {pinCH, pinOH, CCs::PH }; Clap CP {pinC1, pinC2, CCs::CP }; //Check all the instruments to see if they can end void checkEndTrigs() { for(const auto &instrument : instrumentList) { instrument->checkEndTrig(); } } void endTrigs() { for(const auto &instrument : instrumentList) { instrument->endNote(); } } void setupVelocities(){ for(unsigned long i = 1; i<128; i++){ //Don't need a zero value velocities[i] = ((i*i*i) / 1111) + i; } } void setChannelFromSwitch(){ bool c[4]; //Holds the value of each pin //Read the value of each pin: c[0] = !digitalRead(pinChannelA); c[1] = !digitalRead(pinChannelB); c[2] = !digitalRead(pinChannelC); c[3] = !digitalRead(pinChannelD); //Set the channel based on each pin: channel = (c[0]*1) + (c[1]*2) + (c[2]*4) + (c[3]*8); } void setup() { Serial.begin(31250);//Set the baud rate for MIDI //Serial.begin(4800);//Set the baud rate for MIDI setupVelocities(); pinMode(pinLED,OUTPUT); pinMode(pinChannelA,INPUT_PULLUP); pinMode(pinChannelB,INPUT_PULLUP); pinMode(pinChannelC,INPUT_PULLUP); pinMode(pinChannelD,INPUT_PULLUP); channel = EEPROM.read(9); //Read channel - Will be from 0 - 16 if(channel == 0) {setChannelFromSwitch();} //Set channel to 0 - 15 else {channel--;} //Get channel from 1 - 16 to 0 - 15 omniMode = EEPROM.read(124); } void loop() { if (Serial.available())//If there is midi data available { serialBuffer = Serial.read(); //Put the contents of the serial buffer into a variable if (serialBuffer >= 128) //Messages greater than or equal to 128 are new messages, //Decide what kind of message it is and set the messageType: { if(serialBuffer >= B11111000) {/*If it's real time, do nothing.*/} else if(serialBuffer >= B11110000) //If it's system common { MIDIMessages = static_cast(serialBuffer);//Set the midi message type //channelReceived = 17 ; } else //If it is a channel message { MIDIMessages = static_cast(serialBuffer & B11110000);//Get the message type channelReceived = (serialBuffer & B00001111);//Get the channel } lastSerialBuffer = 128; } else if(channel == channelReceived || omniMode)//If data on the right channel { //Serial.print(omniMode ? "omniMode on" : "omniMode off"); switch (MIDIMessages)//Do something based on what kind of data it is { case MIDIMessage::noteOn: //If it is a noteon //If the serialBuffer has been set last cycle: if (lastSerialBuffer != 128) { //If it knows what note to play, the serial buffer has velocity information if (serialBuffer > 0)//If it not a noteoff { //int trigTime = serialBuffer * 10 ; //Calculate the trigger time - Old way int trigTime = velocities[serialBuffer]; //Calculate the trigger time Serial.print(trigTime); //flashLED(); for(const auto &instrument : instrumentList) { if (lastSerialBuffer == instrument->getNoteNum()){ instrument->trigger(trigTime); } } } else //if it is a noteon with velocity zero - ie a noteoff { for(const auto &instrument : instrumentList)//This does not off for all instruments - does this matter? { if (lastSerialBuffer == instrument->getNoteNum()){ instrument->noteOff(); } } } lastSerialBuffer = 128 ; //The note has been played so set it ready to receive the next note } else {lastSerialBuffer = serialBuffer;} //This sets the CC No. or the note No. return; case MIDIMessage::noteOff: //If it is a noteoff //When set to 128, it is ready to be replaced by the actual note //digitalWrite(13, HIGH); if (lastSerialBuffer != 128) { for(const auto &instrument : instrumentList)//This does note off for all instruemnts - does this matter? { if (lastSerialBuffer == instrument->getNoteNum()){ instrument->noteOff(); } } lastSerialBuffer = 128 ; //The note has been played so set it ready to receive the next note } else {lastSerialBuffer = serialBuffer;} //This sets the CC No. or the note No. return; case MIDIMessage::CC: //If the MIDI message is a CC - if (lastSerialBuffer != 128) { if(lastSerialBuffer == static_cast(CCs::changeChannel))//Channel { if (serialBuffer == 0) { setChannelFromSwitch(); saveToEEPROM(9, 0); } else if (serialBuffer >= 1 && serialBuffer <= 16) { int channelToSave = serialBuffer; channel = serialBuffer - 1 ; saveToEEPROM(9, channelToSave); } } else if(lastSerialBuffer == CCs::allNotesOff)//allNotesOff { if (serialBuffer == 0){ endTrigs(); } } else if(lastSerialBuffer == CCs::omniOff)//omniOff { if (serialBuffer == 0){ omniMode = false; saveToEEPROM(124,0); } } else if(lastSerialBuffer == CCs::omniOn)//omniOn { if (serialBuffer == 0){ omniMode = true; saveToEEPROM(124,1); } } else{//If it is another CC: check if it is to change mapping: for(const auto &instrument : instrumentList)//Notes { if (lastSerialBuffer == instrument->getCCNum()){ instrument->setNoteNum(serialBuffer); break; } } } lastSerialBuffer = 128; //Sentinel } else {lastSerialBuffer = serialBuffer;} //This sets the CC No. or the note No.} return; default: lastSerialBuffer = 128; return; } }//End if data on right channel }//end Serial.available() checkEndTrigs(); checkLED(); }