MIDI foot keyboard - strange behaviour using latch mode

Hello everyone,

I transformed an old bass foot pedal keyboard from a old organ into a MIDI keyboard to be able to send MIDI notes to synth and other organ with 2 mode: Momentary and Latch mode. I have some strange behaviour with the latch mode.

I made a mix from these two links to code my program:
_ Arduino MIDI foot pedal
_ MIDI Foot pedal from ElectroniqueAmateur (sorry, it's in French)

Since the keyboard has 13 foot pedals, I used only 1 ATmega to realize the MIDI emitter with the following features:
_ 1 potentiometer for the volume of the MIDI note sent.
_ 2 buttons to be able to octave up or down the MIDI note sent
_ 1 button to choose the mode: momentary or latch mode.
All the buttons have schmitt latches to avoid bouncing issues.

About the latch mode: I tried to program it to follow this algorithm:
_ when Latch is ON, when you hit the C foot pedal, a C noteOn is sent. When you release the C foot pedal, the C note "hold" until
_ If you hit C foot pedal again. In this case, a C noteOff is sent (here a noteOn with 0 velocity).
_ If you hit any other foot pedal except the C foot pedal, the current "holding" note is turned off and a [new note] noteOn is sent.

The momentary mode is ok and works great but when in Latch mode, I encounter sometimes a stange behaviour: when I hit another note when a previous one was hold, the note is still on and I cannot turn it off even if I hit back the corresponding foot pedal. I have to turn the arduino off by removing the power...

Is there a problem in the way I coded the program? Or Do I have forget something to take into consideration for my approach that is a lot of If...Else statements?

Here is the code:

// ces pins de l'Arduino sont reliées aux 4 boutons-poussoir
// (apres passage par un cd40106, pour éviter les rebonds)
#define pinOctaveUp  17
#define pinOctaveDown  18 
#define pinHold 16

int octave = 0;            //valeur initiale par défaut de l'octave
int octaveDownReady = 1;   //vrai quand le bouton  n'est pas enfoncé
int octaveUpReady = 1;     // vrai quand le bouton n'est pas enfoncé

int holdReady = 1;      // utile pour le hold mode
int holdNew;
int holdingNote = 0;    
int holdingNotelast = 0;
boolean boolHold = 0;


boolean C = 1;
boolean CS = 1;
boolean D = 1;
boolean DS = 1;
boolean E = 1;
boolean F = 1;
boolean FS = 1;
boolean G = 1;
boolean GS = 1;
boolean A = 1;
boolean AS = 1;
boolean B = 1;
boolean C2 = 1;

boolean Clast = 1;
boolean CSlast = 1;
boolean Dlast = 1;
boolean DSlast = 1;
boolean Elast = 1;
boolean Flast = 1;
boolean FSlast = 1;
boolean Glast = 1;
boolean GSlast = 1;
boolean Alast = 1;
boolean ASlast = 1;
boolean Blast = 1;
boolean C2last = 1;

int vol = 0;

void setup()  {
  Serial.begin(31250);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, INPUT);
  pinMode(13, INPUT);
  pinMode(14, INPUT);
}

void loop()  {

  // faut-il changer d'octave?

  if (digitalRead(pinOctaveDown))  // bouton octave down enfoncé
  {
    if ((octaveDownReady) && (octave > -2))
    {
      octaveDownReady = 0;
      octave = octave - 1;
    }
  }
  else  // bouton octave down n'est pas enfoncé
  {
    octaveDownReady = 1;
  }

  if (digitalRead(pinOctaveUp))  // bouton octave up enfoncé
  {
    if ((octaveUpReady) && (octave < 2))
    {
      octaveUpReady = 0;    
      octave = octave + 1;
    }      
  }
  else  // bouton octave up n'est pas enfoncé
  {
    octaveUpReady = 1;
  }
  
  //changer de mode ? hold ou released
  holdNew = digitalRead(pinHold);
   if (holdReady == 0 && holdNew == 1)  // bouton octave down enfoncé
  {
    if (boolHold == 0)
    {
      //holdReady = 0;
      boolHold = 1;
    }
    else
    {
      boolHold = 0;
    }
   } 
  holdReady = holdNew;
  
  vol = analogRead(A5);
  vol = map(vol, 0, 1023, 0, 127);
  
  C = digitalRead(2);
  D = digitalRead(3);// passe de pin4 à pin3
  E = digitalRead(4);// passe de pin6 à pin4
  FS = digitalRead(5);// passe de pin8 à pin5
  GS = digitalRead(6);// passe de pin10 à pin6
  AS = digitalRead(7);// passe de pin12 à pin7
  C2 = digitalRead(8);// passe de pin14 à pin8
  
  CS = digitalRead(13);// passe de pin3 à pin13
  DS = digitalRead(12);// passe de pin5 à pin12
  F = digitalRead(11);// passe de pin7 à pin11
  G = digitalRead(10);// passe de pin9 à pin10
  A = digitalRead(9);// passe de pin11 à pin9
  B = digitalRead(14);// passe de pin13 à pin14

  if(boolHold){
   if(C != Clast)  {
     if(C == 0) { 
    holdingNote=36+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }    
   if(CS != CSlast)  {
     if(CS == 0) { 
    holdingNote=37+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }
   if(D != Dlast)  {
        if(D == 0) { 
        holdingNote=38+12*octave;          
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
            noteOn(0x90, holdingNote, 0x00);
            holdingNote = 0;
          }
    }
   }
   if(DS != DSlast)  {
     if(DS == 0) { 
    holdingNote=39+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    } 
   if(E != Elast)  {
     if(E == 0) { 
    holdingNote=40+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }   
   if(F != Flast)  {
     if(F == 0) { 
    holdingNote=41+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }     
   if(FS != FSlast)  {
     if(FS == 0) { 
    holdingNote=42+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }
   if(G != Glast)  {
     if(G == 0) { 
    holdingNote=43+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }  
   if(GS != GSlast)  {
     if(GS == 0) { 
    holdingNote=44+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }     
   if(A != Alast)  {
     if(A == 0) { 
    holdingNote=45+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }
   if(AS != ASlast)  {
     if(AS == 0) { 
    holdingNote=46+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    } 
   if(B != Blast)  {
     if(B == 0) { 
    holdingNote=47+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    } 
   if(C2 != C2last)  {
     if(C2 == 0) { 
    holdingNote=48+12*octave;       
          if(holdingNotelast != holdingNote){
          noteOn(0x90, holdingNotelast, 0x00);
          noteOn(0x90, holdingNote, vol);
          }
          else {
          noteOn(0x90, holdingNote, 0x00);
          holdingNote = 0;
          }
     }
    }     
    holdingNotelast = holdingNote;
  }
  else 
  {  
    noteOn(0x90, holdingNote, 0x00);
    holdingNote = 0;
  if(C != Clast)  {
    if(C == 0)  {
      noteOn(0x90, 36+12*octave, vol);
    }
    else  {
      noteOn(0x90, 36+12*octave, 0x00);
    }
  }
  
  if(CS != CSlast)  {
    if(CS == 0)  {
      noteOn(0x90, 37+12*octave, vol);
    }
    else  {
      noteOn(0x90, 37+12*octave, 0x00);
    }
  }
  if(D != Dlast)  {
    if(D == 0)  {
      noteOn(0x90, 38+12*octave, vol);
    }
    else  {
      noteOn(0x90, 38+12*octave, 0x00);
    }
  }
  if(DS != DSlast)  {
    if(DS == 0)  {
      noteOn(0x90, 39+12*octave, vol);
    }
    else  {
      noteOn(0x90,39+12*octave, 0x00);
    }
  }
  if(E != Elast)  {
    if(E == 0)  {
      noteOn(0x90, 40+12*octave, vol);
    }
    else  {
      noteOn(0x90, 40+12*octave, 0x00);
    }
  }
  if(F != Flast)  {
    if(F == 0)  {
      noteOn(0x90, 41+12*octave, vol);
    }
    else  {
      noteOn(0x90, 41+12*octave, 0x00);
    }
  }
  if(FS != FSlast)  {
    if(FS == 0)  {
      noteOn(0x90, 42+12*octave, vol);
    }
    else  {
      noteOn(0x90, 42+12*octave, 0x00);
    }
  }
  if(G != Glast)  {
    if(G == 0)  {
      noteOn(0x90, 43+12*octave, vol);
    }
    else  {
      noteOn(0x90, 43+12*octave, 0x00);
    }
  }
  if(GS != GSlast)  {
    if(GS == 0)  {
      noteOn(0x90, 44+12*octave, vol);
    }
    else  {
      noteOn(0x90, 44+12*octave, 0x00);
    }
  }
  if(A != Alast)  {
    if(A == 0)  {
      noteOn(0x90, 45+12*octave, vol);
    }
    else  {
      noteOn(0x90, 45+12*octave, 0x00);
    }
  }
  if(AS != ASlast)  {
    if(AS == 0)  {
      noteOn(0x90, 46+12*octave, vol);
    }
    else  {
      noteOn(0x90, 46+12*octave, 0x00);
    }
  }
  if(B != Blast)  {
    if(B == 0)  {
      noteOn(0x90, 47+12*octave, vol);
    }
    else  {
      noteOn(0x90, 47+12*octave, 0x00);
    }
  }
  if(C2 != C2last)  {
    if(C2 == 0)  {
      noteOn(0x90, 48+12*octave, vol);
    }
    else  {
      noteOn(0x90, 48+12*octave, 0x00);
    }
  }
 }
  
  Clast = C;
  CSlast = CS;
  Dlast = D;
  DSlast = DS;
  Elast = E;
  Flast = F;
  FSlast = FS;
  Glast = G;
  GSlast = GS;
  Alast = A;
  ASlast = AS;
  Blast = B;
  C2last = C2;
  
  delay(50);
}

 void noteOn(byte cmd, byte data1, byte data2) {
   Serial.write(cmd);
   Serial.write(data1);
   Serial.write(data2);   
   delay(10);
 }

Thank you by advance for your consideration about my code issues!

I can't examine closely the code until I get to the lab.

Your code looks plausible, but is at a beginner's level. Nothing wrong with that, no one was born knowing anything about this stuff. But trying to find what the thing does (or does not) do gets harder and harder when it is all expressed so literally.

I recommend that you take a break from getting this project finished and learn about array variables, for loops and functions.

Those key and basic features of, well, just about any programming language would make this sketch much smaller. Easier to understand and therefor easier to fix, or modify for enhancements when you get to thinking about what else it could do for you.

Sooner or later; now looks like a good perfect time.

Take those terms to google or your favorite source for learning up on this. Time spent will return to you in multiples.

HTH

a7

Thank you alto777 for your reply!

I began to read some articles from blogs about matrix and array for MIDI Keyboard and I begin to understand the way you talk. It's really interesting and a powerful way of coding!
I read this post from baldengineer where it's pointed out this method is very memory inefficient and a more efficient method would be using bitwise operators to keep track of each key as a bit. I'll have a proper look for this and the way I could achieve my goal with the octaves buttons. Maybe changing my approach and have finally a go for matrixes and arrays.
So, here was "just" programming considerations.

But related to the behaviour of my current keyboard and code and when I was looking for matrixes and arrays courses/tutorials, I saw the aspect of diodes to prevent "ghosting" effect in keyboard matrix (the way it's physically connected). So now, it's more hardware considerations.

I forget to mention that the keyboard I hijacked (the foot pedal keyboard of a yamaha electone b35n) was set to be monophonic. I suppose I would be able to make it polyphonic in the future but for now I would like to know why this strange behaviour appears sometimes. The pedal keyboard will be used for live performances on stage, so I need it to be very reliable.
Here is the schematic:
yamaha electone b35_pkboard

I followed this article to connect my keyboard. So I unsoldered all the diodes from the 13 keys and replaced them with wires. I used a 10kOhm for each keys before the pins of the Atmega. And I used the ribbon cable to connect to my Atmega board

May this strange behaviour comes from these removed diodes that prevent "ghost" effect ? Is this strange behaviour can come from a hardware issue or is it only related to my software?
(whatever it's hard or soft issue, I will have a look to code a more readable program but I would like to know where I should focus for resolving this strange behaviour first).

Th ghost effect that the diodes solves is for a different way of handling multiple buttons or other contacts, and refers to problems that are due to pressing more than one key at a time.

That is not what you are seeing.

With the multiplexor, you will not need special effort to recognize and react to multiple keys going up or down independently.

I'm in transit looking through the tiny window - more accurate not looking. I will check later on the progress - seems it is a simple hardware issue like a mis-wired or unwired input or noise on the signal lines or something.

a7

Allright, understood, I'll have a look to the hardware part looking for something wrong.

I managed to code in a better way using "for" loops, arrays and functions thanks to your boosting reply :wink: Thank you

here is my new code (only verified in tinkercad, not yet physically)

// ces pins de l'Arduino sont reliées aux 3 boutons-poussoir
// (apres passage par un cd40106, pour éviter les rebonds)
#define pinOctaveUp  17
#define pinOctaveDown  18 
#define pinHold 16

int octave = 0;            //valeur initiale par défaut de l'octave
int octaveDownReady = 1;   //vrai quand le bouton  n'est pas enfoncé
int octaveUpReady = 1;     // vrai quand le bouton n'est pas enfoncé

int holdReady = 1;			// utile pour le hold mode
int holdNew;
int holdingNote = 0; 		
int holdingNotelast = 0;
boolean boolHold = 0;

const byte notePitches[13] = {36,37,38,39,40,41,42,43,44,45,46,47,48};

int vol = 0;
int vals[13],valsavt[13];

void setup()  {
  Serial.begin(31250);
  
  for(int i=0; i<13; i++)  //état normal des touches NON enfoncées
  {
      pinMode(i+2, INPUT);
      valsavt[i]=1;
  }
}

void loop()  {
  
  OctaveMgmt();
  choosingMode();
    
  vol = analogRead(A5);
  vol = map(vol, 0, 1023, 0, 127);
  
  //lecture des pinState
  vals[0] = digitalRead(2);		// note C2	(36)
  vals[1] = digitalRead(13);	// note CS2	(37)
  vals[2] = digitalRead(3);		// note D2	(38)
  vals[3] = digitalRead(12);	// note DS2	(39)
  vals[4] = digitalRead(4);		// note E2	(40)
  vals[5] = digitalRead(11);	// note F2	(41)
  vals[6] = digitalRead(5);		// note FS2	(42)
  vals[7] = digitalRead(10);	// note G2	(43)
  vals[8] = digitalRead(6);		// note GS2	(44)
  vals[9] = digitalRead(9);		// note A2	(45)
  vals[10] = digitalRead(7);	// note AS2	(46)
  vals[11] = digitalRead(14);	// note B2	(47)
  vals[12] = digitalRead(8);	// note C3	(48)
  
  //momentary or latch mode to send MIDI note coressponding to previous keys states
  if(boolHold) playNoteLatch();
  else playNoteMomentary();
  
  delay(50);
}

void choosingMode()
{
    //changer de mode ? hold ou released
  holdNew = digitalRead(pinHold);
   if (holdReady == 0 && holdNew == 1)  // bouton octave down enfoncé
  {
    if (boolHold == 0)
    {
      //holdReady = 0;
      boolHold = 1;
    }
  	else
  	{
    	boolHold = 0;
  	}
   } 
  holdReady = holdNew;
}

void OctaveMgmt()
{
    //OctaveDown Button
  if (digitalRead(pinOctaveDown))  
  {if ((octaveDownReady) && (octave > -2))  //button pressed
    {
      octaveDownReady = 0;
      octave = octave - 1;
    }
  }
  else octaveDownReady = 1; //button not pressed
  
  //OctaveUp Button
  if (digitalRead(pinOctaveUp))
  {if ((octaveUpReady) && (octave < 2)) //button pressed
    {
      octaveUpReady = 0;    
      octave = octave + 1;
    }      
  }
  else octaveUpReady = 1; //button not pressed
}

void playNoteMomentary()
{    
  for(int i=0; i<13; i++)
  {
    if(vals[i] != valsavt[i])  //les pinstate ont-ils changé ?
  	{
      if(vals[i] == 0) // which keys is pressed and which note to send
      {
        noteOn(0x90, notePitches[i]+12*octave, vol);
      }
      else
      {
        noteOn(0x90, notePitches[i]+12*octave, 0x00);
      }
      valsavt[i] = vals[i];      
  	}
  }
}

void playNoteLatch()
{  
  for(int i=0; i<13; i++)
  {
    if(vals[i] != valsavt[i])//les pinstate ont-ils changé ?
  	{
      if(vals[i] == 0) // which keys is pressed and which note to send
      {
        holdingNote = notePitches[i]+12*octave;
        
        if(holdingNotelast != holdingNote)
        {
       		noteOn(0x90, holdingNotelast, 0x00);
        	noteOn(0x90, holdingNote, vol);
        }
        else 
        {
       		noteOn(0x90, holdingNote, 0x00);
       		holdingNote = 0;
        }
        holdingNotelast = holdingNote;
      }
    }
    valsavt[i] = vals[i];   	
  }
}

void noteOn(byte cmd, byte data1, byte data2) {
   //Serial.write(cmd);
   //Serial.write(data1);
   //Serial.write(data2);
   
   //Serial.println(cmd);
   Serial.println(data1);
   Serial.println(data2);
   Serial.println();
   
   delay(10);
 }

Nice. Looking forward to a close fly-over about it later.

Now one more step

  //lecture des pinState
  vals[0] = digitalRead(2);		// note C2	(36)
  vals[1] = digitalRead(13);	// note CS2	(37)
  vals[2] = digitalRead(3);		// note D2	(38)
  vals[3] = digitalRead(12);	// note DS2	(39)
  vals[4] = digitalRead(4);		// note E2	(40)
  vals[5] = digitalRead(11);	// note F2	(41)
  vals[6] = digitalRead(5);		// note FS2	(42)
  vals[7] = digitalRead(10);	// note G2	(43)
  vals[8] = digitalRead(6);		// note GS2	(44)
  vals[9] = digitalRead(9);		// note A2	(45)
  vals[10] = digitalRead(7);	// note AS2	(46)
  vals[11] = digitalRead(14);	// note B2	(47)
  vals[12] = digitalRead(8);	// note C3	(48)

If you had an array like this

const byte pinNumbers[13] = {
// C2 CS2 D2... 
   2, 13, 3,  12,  4,  11,  5,  10,  6,  9,  7,  14,  8
};

then you could

  for (byte me = 0; me < 13; me++)
    vals[me] = digitalRead(pinNumber[me]);

Untested. Close, I bet, or fine.

It's kinda addictive after a while. :expressionless:

You can get further than I'd care to admit using bluffing and pattern recognition…

a7

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.