// Buttons are connected to the Digital pins. // The button must have a common on the ground, a Normally Closed ( N.C. ) pin, and a Normally Open ( N.O. ) pin. // When button is not pressed , ground is on the N.C. pin // When button starts to be pressed , N.C. and N.O. are NOT connected to the ground, timer is started // When button finishes to be pressed, ground is on the N.O. pin, timer is stopped, and velocity for note-on is calculated // An additional button N.C. has a shift feature // Serial port is used to send midi msgs note-on et note-off to the Expresseur. // Speed of the serial port must be 115200 // to setup pianissimo ( pp ) and fortissime ( ff ), press first button on startup, then press like a pp, then like a ff #include // eeprom is used to store settings ( ppDt and ffDt values ) #define MIDI_CHANNEL 1 // midi channel [0..15] #define offsetPitch 30 // offset of the pitch sent on Midi #define shiftPitch 24 // shift of pitch when shift button is on // #define MIDI_ASCII 1 // to send midi msgs in ASCII format ( for debug ) struct tButton { boolean press , start ; // status of the button word maskNC , maskNO ; // mask to extract the bits for N.C and N.O switches long t ; // t when the button starts to be pressed long dt ; // duration between the beginning and the end of the press int pitch ; // pitch sent on note-on and note-off } ; #define nbButton 4 // number of buttons tButton button[nbButton] ; // storage of the all buttons properties int nrButton ; // generic button counter tButton *pButton ; // generic pointer to the current button word buttons ; // value of pins connected to the buttons long lSppDt ; // long value of Sppt, for comparison purpose int SffDt , SmfDt , SmpDt , SppDt ; // calibration of dt for ff, mf, mp, pp const int SppVelo = 1 ; // velocity for pianissimo (dt = SppDt) const int SmpVelo = 40; // velocity for mesopiano (dt = SmpDt) const int SmfVelo = 76; // velocity for mesoforte (dt = SmfDt) const int SffVelo = 127; // velocity for fortissimo (dt = SffDt) const int SmpRatio = 3 ; // ratio of dt between ff=>mp ( e.g. 2 means that ff=>mp are on 1/2 of dt range ) const int SmfRatio = 6 ; // ratio of dt between ff=>mf. Must be greater than SmpRatio.( e.g. 4 means that ff=>mf are on 1/4 of dt range ) int v , idt ; // generic value of velocity boolean outsideTimer ; // to flag a timer in progress, and avoid midi msgs to be sent during such critical phase int sSetup ; // calibration on startup : 2=calibrate pp , 1=calibrate ff , other=no calibration int nrByte ; // used for EEprom index // MIDI tools /////////////////// byte SmidiMsg[3] ; void sendMidiMsg() { // send the three MIDI bytes on serial port #ifdef MIDI_ASCII // ASCII mode with one line per message, starting with character equal, and the three bytes in ascii-hexa format Serial.print("="); if ( SmidiMsg[0] < 16 ) Serial.print("0"); Serial.print(SmidiMsg[0],HEX); if ( SmidiMsg[1] < 16 ) Serial.print("0"); Serial.print(SmidiMsg[1],HEX); if ( SmidiMsg[2] < 16 ) Serial.print("0"); Serial.println(SmidiMsg[2],HEX); #else // standard 8-bits MIDI message Serial.write(SmidiMsg,3); #endif } void writeInt(int l , int *nrByte ) { // write int value in EEPROM int k = l ; int h ; byte b ; for(int i = 0 ; i < 2 ; i ++ ) { h = (k & 0xFF00) >> 8 ; b = byte( h ) ; EEPROM.write((*nrByte) ++ , b ); k <<= 8 ; } } int readInt(int *nrByte ) { // read int value in EEPROM byte b ; int h , l ; l = 0 ; for(int i = 0 ; i < 2 ; i ++ ) { l <<= 8 ; b = EEPROM.read((*nrByte) ++); h = b ; l |= h ; } return l ; } void calculatempmf() { SmpDt = SffDt + ( SppDt - SffDt ) / SmpRatio ; SmfDt = SffDt + ( SppDt - SffDt ) / SmfRatio ; lSppDt = SppDt ; } void sendNoteOn(int p , long dt) { switch ( sSetup ) { case 2 : // setup dt for pianissimo lSppDt = dt ; SppDt = (int)dt ; sSetup = 1 ; return ; case 1 : // setup dt for fortissimo SffDt = (int)dt ; calculatempmf() ; // caclculation of intermediate values for the curve sSetup = 0 ; nrByte = 0 ; writeInt(SppDt, &nrByte) ; // storage in eeprom for next boot writeInt(SffDt, &nrByte) ; return ; default : break ; } if ( dt > lSppDt ) v = SppVelo ; // long really to long ... else { idt = (int)dt ; if ( idt > SmpDt ) v = constrain(map(idt,SppDt,SmpDt,SppVelo,SmpVelo),SppVelo,SmpVelo) ; // linear between pp and mp else { if ( idt > SmfDt ) v = constrain(map(idt,SmpDt,SmfDt,SmpVelo,SmfVelo),SmpVelo,SmfVelo) ; // linear between mp and mf else v = constrain(map(idt,SmfDt,SffDt,SmfVelo,SffVelo),SmfVelo,SffVelo) ; // linear between mf and ff } } SmidiMsg[0] = 0x90 | MIDI_CHANNEL ; // note-on SmidiMsg[1] = byte(p) ; // pitch SmidiMsg[2] = byte(v) ; // velocity sendMidiMsg() ; } void sendNoteOff(int p ) { if ( sSetup > 0 ) return ; SmidiMsg[0] = 0x80 | MIDI_CHANNEL ; // note-off SmidiMsg[1] = byte(p) ; // pitch SmidiMsg[2] = 0 ; // velocity sendMidiMsg() ; } void setup() { Serial.begin(115200); //delay(1000); //Serial.println("test"); // read prvious pp and ff settings nrByte = 0 ; SppDt = readInt(&nrByte) ; SffDt = readInt(&nrByte) ; calculatempmf() ; // setup buttons pinMode(10,INPUT_PULLUP); // N.C. bit 2 on port B button[0].maskNC = 1<<(2+8) ; pinMode(9,INPUT_PULLUP); // N.O. bit 1 on port B button[0].maskNO = 1<<(1+8) ; pinMode(11,INPUT_PULLUP); // N.C. bit 3 on port B button[1].maskNC = 1<<(3+8) ; pinMode(12,INPUT_PULLUP); // N.O. bit 4 on port B button[1].maskNO = 1<<(4+8) ; pinMode(4,INPUT_PULLUP); // N.C. bit 4 on port D button[2].maskNC = 1<<4 ; pinMode(3,INPUT_PULLUP); // N.O. bit 3 on port D button[2].maskNO = 1<<3 ; pinMode(6,INPUT_PULLUP); // N.C. bit 6 on port D button[3].maskNC = 1<<6 ; pinMode(5,INPUT_PULLUP); // N.O. bit 5 on port D button[3].maskNO = 1<<5 ; // pinMode(8,INPUT_PULLUP); // N.C. bit 0 on port B // button[4].maskNC = 1<<(0+8) ; // pinMode(13,INPUT_PULLUP); // N.O. bit 5 on port B // button[4].maskNO = 1<<(5+8) ; //pinMode(A0,INPUT_PULLUP); // Shift connected on A0 for(nrButton = 0 , pButton = button ; nrButton < nbButton ; nrButton ++ , pButton ++ ) { pButton->press = true ; pButton->start = true ; pButton->dt = -1 ; } sSetup = 0 ; if ( digitalRead(10) == HIGH) // enter in setup for pp & ff, if button is pressed on boot { while ( digitalRead(10) == HIGH ) { sSetup = 2 ; delay(1000); } } } void loop() { buttons = word(PINB,PIND) ; // read pins from register PINB and PIND outsideTimer = true ; for(nrButton = 0 , pButton = button ; nrButton < nbButton ; nrButton ++ , pButton ++ ) { if ( buttons & (pButton->maskNC) ) // pin N.C. opened : pressed { if ( pButton->press ) // wait for end of press { if ( pButton->start ) // start tr { pButton->t = micros() ; // risk if pressed during ms when overload of tr ( each 72 hours ) pButton->start = false ; // tr started } if (! ( buttons & (pButton->maskNO) ) ) // pin N.O. closed : end of press { pButton->press = false ; // wait for end of unpress pButton->dt = micros() - pButton->t ; } else outsideTimer = false ; } } else // pin N.C. closed : unpressed { if (! pButton->press ) // end of unpress { pButton->press = true ; // wait for new press pButton->dt = 0 ; } pButton->start = true ; // wait to start tr } } if ( outsideTimer ) // if no timer is ticking, a chance to trigger long procedure to send midi msgs { for(nrButton = 0 , pButton = button ; nrButton < nbButton ; nrButton ++ , pButton ++ ) { switch ( pButton->dt ) { case -1 : // nothing to do break ; case 0 : // note-off is pending sendNoteOff(pButton->pitch ) ; pButton->dt = -1 ; break ; default : // note-on is pending pButton->pitch = offsetPitch + nrButton + ((/*digitalRead(A0) == HIGH*/false )?shiftPitch:0 ) ; sendNoteOn (pButton->pitch , pButton->dt) ; pButton->dt = -1 ; break ; } } } }