Midi note on note off commands swapped?

Hi there,

I have a code I largely copied from someone else's project. But after I used it to play midi notes, I found out that note off commands were given before note on commands.
So I switched the numbers indicating these commands (128 and 144) and now the code works.
However I have added a pedal switch for holding notes. When it's pressed the 'note off' commands should be ignored. I haven't had success so far, and I wonder if it has something to do with these swapped numbers.
Here is the code without any addition for the pedal, my question is: can anyone see why the numbers need to be swapped for this code to function properly?


//This code has a quirk: it only works if the midi commands for note on and note of (144 and 128) are swapped.


#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

void sendMidiMessage(int cmd, int channel, int note, int velocity);
void scanColumn(int colNum);

const int NUM_ROWS = 7;
const int NUM_COLS = 7;

const int NOTE_ON_CMD = 128; // swapped with note off
const int NOTE_OFF_CMD = 144; // 
const int NOTE_VELOCITY = 127;

// Define an array of MIDI note numbers 
int NoteArray []= {38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,64, 65, 66, 67, 68,69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,};

boolean keyPressed[NUM_ROWS][NUM_COLS];
uint8_t keyToMidiMap[NUM_ROWS][NUM_COLS];

// 74HC595 pins
const int dataPin = 28;
const int latchPin = 26;
const int clockPin = 24;

// Row pins
const int rowPins[NUM_ROWS] = {32,34,36,38,40,42,44};

// MIDI channel (use 0 for Channel 1)
const int MIDI_CHANNEL = 0;

// MIDI baud rate
const int SERIAL_RATE = 31250; // needs to be 31250 when going through midi port, can be 9600 for hairless

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 5; // Adjust this delay as needed

void setup() {
  
  int note; 

   for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      keyPressed[rowCtr][colCtr] = false;
      keyToMidiMap[rowCtr][colCtr] = note;
      note++;
    }
  }

  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  for (int i = 0; i < NUM_ROWS; i++) {
    pinMode(rowPins[i], INPUT_PULLUP); // Use internal pull-ups
  }

  Serial.begin(SERIAL_RATE);
}

void loop() {
 
    // ---------------------------------------------------midi notes
  
  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    scanColumn(colCtr);
    int rowValue[NUM_ROWS];

    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
          keyPressed[rowCtr][colCtr] = true;
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false;
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      }
    }
  }
}

void sendMidiMessage(int cmd, int channel, int note, int velocity) {
  Serial.write(cmd | channel);
  Serial.write(note);
  Serial.write(velocity);
}

void scanColumn(int colNum) {
  digitalWrite(latchPin, LOW);

  if (0 <= colNum && colNum <= 6) {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << colNum);
  } else {
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << (colNum - 7));
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  }
  digitalWrite(latchPin, HIGH);
}


I'm reading your code between other things , and I saw right away your use of an uninitialised variable.

  int note = 0;  // or whatever you want note numbers to start with

If things have been working just fine, you've been lucky. Luck runs out, every time. :expressionless:

If you haven't done, go to the IDE preferences and crank up all verbosity and warnings and stuff. Then heed the red ink - I am fairly sure you'd spill some here along the lines of "use of uninitialised blah blah blah".

More later.

Gotta ask, though, if chatGPT had anything to do with what you ar showing us now.

a7

OK, I'll keep reading, but you could add some printing to your logic… it seems possible, not seeing a schematic around, that your entire switch handling is upside down.

So rather than just change the command number, you'd have to swap the guts of the if/else statement.

But do this first and see if there are any surprises:

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
          keyPressed[rowCtr][colCtr] = true;

Serial.print(rowCtr); Serial.print(", "); Serial.print(colCtr); Serial.println(" went pressed!");

          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false;

Serial.print(rowCtr); Serial.print(", "); Serial.print(colCtr); Serial.println(" went got released.");

          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      }

HTH, L8R

a7

Hi Alto,
In the original code which I will post below, the initial note number is the starting note of a number of consecutive notes in a chromatic scale.
But I wanted to be able to switch between octaves, and found a (not very elegant) solution: create three note arrays and switch between them. The code I posted here is the stripped down version, but with the problem preserved.
And yes, GPT created the array solution.
As I found the initial note number did nothing in my code, I removed the note number after the note variable.
For reference, below the code I 'stole' for my project. It was clearly written by someone that does understand what they are doing:

//this code I got from Github. It was posted there accompanying this Youtube video:
//https://www.youtube.com/watch?v=TeOvE9mf0BA

// midi channels
const int CHANNEL1 = 0;
const int CHANNEL2 = 1;

// pitch bend
const int PITCHBEND_CMD = 224;
const int PB_LSB = 0;
int PB_VALUE = 64;
bool PBisOn = false;

// control change 
const int CONTROLCHANGE1_CMD = 176;
const int CC_LSB = 1;
int CC_VALUE = 64;
bool CCisOn = false;

// notes
const int NOTE_ON_CMD = 144;
const int NOTE_OFF_CMD = 128;
const int NOTE_VELOCITY = 127;

// drums
int DRUM_VELOCITY =  0;
int DRUM_NOTE[] = {31,32,33,34,35,36}; 
const int DRUM_TOL = 19;
bool drumOn[] = {false,false,false,false,false,false};

// joystick analog pins
const int X_pin = 0;
const int Y_pin = 1; 

// drum pins
const int drumPin[] = {2,3,4,5,6,7};

// keyboard matrix
const int NUM_ROWS = 8;
const int NUM_COLS = 7;

// Row input pins
const int row1Pin = 5;
const int row2Pin = 6;
const int row3Pin = 7;
const int row4Pin = 8;
const int row5Pin = 9;
const int row6Pin = 10;
const int row7Pin = 11;
const int row8Pin = 12;

// 74HC595 pins
const int dataPin = 4;
const int latchPin = 3;
const int clockPin = 2;

boolean keyPressed[NUM_ROWS][NUM_COLS];
uint8_t keyToMidiMap[NUM_ROWS][NUM_COLS];

// bitmasks for scanning columns
int bits[] =
{ 
  B10000000,
  B01000000,
  B00100000,
  B00010000,
  B00001000,
  B00000100,
  B00000010,
  B00000001
};

// MIDI baud rate
const int SERIAL_RATE = 31250; // needs to be 31250 when going through midi port, can be 9600 for hairless

void setup()
{
  
  int note = 28; 
  
  for(int colCtr = 0; colCtr < NUM_COLS; ++colCtr)
  {
    for(int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr)
    {
      keyPressed[rowCtr][colCtr] = false;
      keyToMidiMap[rowCtr][colCtr] = note;
      note++;
    }
  }

  // setup pins output/input mode
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  pinMode(row1Pin, INPUT);
  pinMode(row2Pin, INPUT);
  pinMode(row3Pin, INPUT);
  pinMode(row4Pin, INPUT);
  pinMode(row5Pin, INPUT);
  pinMode(row6Pin, INPUT);
  pinMode(row7Pin, INPUT);
  pinMode(row8Pin, INPUT);

  Serial.begin(SERIAL_RATE);
}

void loop()
{
  
  // keyboard
  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr)
  {
    //scan next column
    scanColumn(colCtr);

    //get row values at this column
    int rowValue[NUM_ROWS];
    rowValue[0] = digitalRead(row1Pin);
    rowValue[1] = digitalRead(row2Pin);
    rowValue[2] = digitalRead(row3Pin);
    rowValue[3] = digitalRead(row4Pin);
    rowValue[4] = digitalRead(row5Pin);
    rowValue[5] = digitalRead(row6Pin);
    rowValue[6] = digitalRead(row7Pin);
    rowValue[7] = digitalRead(row8Pin);

    // process keys pressed
    for(int rowCtr=0; rowCtr<NUM_ROWS; ++rowCtr)
    {
      if(rowValue[rowCtr] != 0 && !keyPressed[rowCtr][colCtr])
      {
        keyPressed[rowCtr][colCtr] = true;
        sendMidiMessage(NOTE_ON_CMD, CHANNEL1, keyToMidiMap[rowCtr][colCtr], NOTE_VELOCITY);
      }
    }

    // process keys released
    for(int rowCtr=0; rowCtr<NUM_ROWS; ++rowCtr)
    {
      if(rowValue[rowCtr] == 0 && keyPressed[rowCtr][colCtr])
      {
        keyPressed[rowCtr][colCtr] = false;
        sendMidiMessage(NOTE_OFF_CMD, CHANNEL1, keyToMidiMap[rowCtr][colCtr], NOTE_VELOCITY);
      }
    }
  }

  // joystick X-axis - pitch bend
  PB_VALUE = (analogRead(X_pin) / 1024.0) * 127;
  if (PB_VALUE < 53 || PB_VALUE > 56) {
    sendMidiMessage(PITCHBEND_CMD,CHANNEL1,PB_LSB,PB_VALUE);
    PBisOn=true;
  }
  else if (PBisOn==true && PB_VALUE > 53 && PB_VALUE < 56) {
    sendMidiMessage(PITCHBEND_CMD,CHANNEL1,PB_LSB,PB_VALUE);
    PBisOn=false;
  }

  // joystick Y-axis - control change
  CC_VALUE = (analogRead(Y_pin) / 1024.0) * 127;
  if (CC_VALUE < 53 || CC_VALUE > 56) {
    sendMidiMessage(CONTROLCHANGE1_CMD,CHANNEL1,CC_LSB,CC_VALUE);
    CCisOn=true;
  }
  else if ( CCisOn == true && CC_VALUE > 53 && CC_VALUE < 56) {
    sendMidiMessage(CONTROLCHANGE1_CMD,CHANNEL1,CC_LSB,CC_VALUE);
    CCisOn=false;
  }

  // drums
  for(int drumNo = 0; drumNo < 6; ++drumNo){
    DRUM_VELOCITY = (analogRead(drumPin[drumNo]) / 1023.0) * 127;
    if( drumOn[drumNo]==false && DRUM_VELOCITY > DRUM_TOL ){
      sendMidiMessage(NOTE_ON_CMD,CHANNEL2,DRUM_NOTE[drumNo],DRUM_VELOCITY);
      drumOn[drumNo]=true;
    }
    else if ( drumOn[drumNo]==true && DRUM_VELOCITY <= DRUM_TOL ) {
      sendMidiMessage(NOTE_OFF_CMD,CHANNEL2,DRUM_NOTE[drumNo],DRUM_VELOCITY);
      drumOn[drumNo]=false;
    }
  }
}

// functions

void sendMidiMessage(int cmd, int channel, int lsb, int msb) {
  Serial.write(cmd + channel); // send command plus the channel number
  Serial.write(lsb); // least significant bit 
  Serial.write(msb); // most significant bit
}

void scanColumn(int colNum)
{
  digitalWrite(latchPin, LOW);

  if(0 <= colNum && colNum <= 7)
  {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000); //right sr
    shiftOut(dataPin, clockPin, MSBFIRST, bits[colNum]); //left sr
  }
  else
  {
    shiftOut(dataPin, clockPin, MSBFIRST, bits[colNum-8]); //right sr
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000); //left sr
  }
  digitalWrite(latchPin, HIGH);
}

Hi A7,

here's the output the serial monitor gave. The row and column numbers are OK, but what about the seemingly random characters at the beginning of the lines?

IDK about the random characters. Please post or say where you have posted the code that produced the output we see.

Which output seems to show that the logic is upside down, and you are recognizing the events exactly backwards.

So now we need to see your schematic, as it is hard to say why. My theory is that the keypad scanning is where it is being turned upside down.

If you based all this on a few websites, please also provide links to those.

a7

Those are the MIDI messages you're sending.

OK, making the dangerous assumption that the github code is correct, and making what I could of that drawing referred to as a schematic, I cannot say why you are not experiencing joy.x

I'll look closer later and perhaps make some suggestions about targeted testing. Perhaps you should carefully confirm that you have wired it accordian to that drawing.

Overlooking some obvious ways to cut down the code, and the handling built in for larger matrices, there is at heart a standard matrix keyboard scanner that should do exactly like what it should.

Just now looking for some kinda clue, I see the differences that maybe you said cgphatGPT helped with. So far it is sometimes odd, but looks plausible. Your original posted code.

Imma suggest also that you go back to code to which you, or chatGPT, did the least damage adter having found it. Ideally the code would run unaltered. You may need to tweak one or another thing, but the less "creative" you get the better.

L8R

a7

Hi A7, here's the setup I tested the code on. I haven't drawn the complete keyboard matrix, but in the schematic you can see the seven rows and columns clearly indicated.

OK, so they're the note on and note off commands. Makes sense

I'm pretty sure the code I got from Github is OK.
What I'm seeing is that the code GPT generated has done away with the bitmask that is in the original code.
I have no idea what the function of that is, or if it is related to the swapping of note on and note off commands.
It would seem that somewhere a HIGH and LOW status were swapped in the code.

It has not. it just did something clever in its stead, viz:

1 << colNum

generates the flying 1 bitmask.


Stop for now trying to get chatGPT, or your, improvements or alterations functioning.

Say whether or not the more original (ideally unedited) GitHub code functions. If you already said, please say again.

Or why you can no longer use that code. I assume you got the real code to work first.

That would motivate looking at the chatGPT code; if you have other problems, it is premature to do that.

a7

The original code appears to function. Next cycle I will look at the first posted code again, beat me to it and figure out how it is different to the github code.

Watch for tricks, you'll need to understand both sketches to see why yours doesn't work.

If you haven't a hardware issue.

L8R

a7

Someone over-optimised the code.

Three loops were combined into one. Unfortunately later parts of two loops depended upon the first having run to completion.

That and one other major error I will not tell you are all that was wrong with the loop() function.

Probably:

void loop() {
 
    // ---------------------------------------------------midi notes
  
  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    scanColumn(colCtr);
    int rowValue[NUM_ROWS];

//... read all input lines
    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr)
      rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);

    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      if (rowValue[rowCtr] == HIGH) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
          keyPressed[rowCtr][colCtr] = true;
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false;
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      }
    }
  }
}

I think you should be able to drop that in and see.

a7

Forget post #14. @alto777 - you masked the real problem with a red herring. You obvsly had too much of one thing and not enough of another when you cooked up that bogus theory.

There was nothing wrong with combining the three loops. Here is the loop() from #1 above with the only thing wrong with it fixed:

void loop() {
 
    // ---------------------------------------------------midi notes
  
  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    scanColumn(colCtr);
    int rowValue[NUM_ROWS];

    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
          keyPressed[rowCtr][colCtr] = true;
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false;
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      }
    }
  }
}

Im surprised it wasn't the first thing almost anyone should have noticed. Things that are upside down come up alla time. Fixing them is usually a simple matter and in good code can mean a small change in one place.

@wannabuildamidikeyboard please drop this one in and see if it works.

a7

A7, thanks for trying to solve the puzzle. However, the latest version of the code you provided is identical to the one I posted.
For the record: the original code works fine for producing midi notes, but I had to swap note on and note off command numbers to make it work.
As I'm trying to add a pedal function with code that should suspend any note off commands, I thought it a good idea to have this quirk sorted out first.

I tried to add a pedal function to the code I posted here, with (to me) an incomprehensible result:
Only 'note off' commands are given, regardless of the switchstate of the PedalPin.
So I have two questions: 1 why is the state of the pedalswitch ignored? 2 why do I get only 'note off' commands instead of 'note on'? nothing changed in the 'note on' part of the matrix reading.


//This code has a quirk: it only works if the midi commands for note on and note of (144 and 128) are swapped.


#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

void sendMidiMessage(int cmd, int channel, int note, int velocity);
void scanColumn(int colNum);

const int NUM_ROWS = 7;
const int NUM_COLS = 7;

const int NOTE_ON_CMD = 128; // swapped with note off
const int NOTE_OFF_CMD = 144; // 
const int NOTE_VELOCITY = 127;

// Define an array of MIDI note numbers 
int NoteArray []= {38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,64, 65, 66, 67, 68,69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,};

boolean keyPressed[NUM_ROWS][NUM_COLS];
uint8_t keyToMidiMap[NUM_ROWS][NUM_COLS];

// 74HC595 pins
const int dataPin = 28;
const int latchPin = 26;
const int clockPin = 24;

// Row pins
const int rowPins[NUM_ROWS] = {32,34,36,38,40,42,44};

//pedal pin
const int PedalPin =3;
int PedalState = HIGH;

// MIDI channel (use 0 for Channel 1)
const int MIDI_CHANNEL = 0;

// MIDI baud rate
const int SERIAL_RATE = 31250; // needs to be 31250 when going through midi port, can be 9600 for hairless

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 5; // Adjust this delay as needed

void setup() {
  
  int note; 

   for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      keyPressed[rowCtr][colCtr] = false;
      keyToMidiMap[rowCtr][colCtr] = note;
      note++;
    }
  }

  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  pinMode (PedalPin, INPUT_PULLUP);

  for (int i = 0; i < NUM_ROWS; i++) {
    pinMode(rowPins[i], INPUT_PULLUP); // Use internal pull-ups
  }

  Serial.begin(SERIAL_RATE);
}

void loop() {
 
    // ---------------------------------------------------midi notes
  
int SwitchState = digitalRead (PedalPin);

  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    scanColumn(colCtr);
    int rowValue[NUM_ROWS];

    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
          keyPressed[rowCtr][colCtr] = true;
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          if (keyPressed[rowCtr][colCtr] = false && SwitchState == HIGH) {
          //Serial.println(SwitchState);
          //delay (600); //Serial monitor shows that switch is functioning properly
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
        }
      }
    }
  }
}

void sendMidiMessage(int cmd, int channel, int note, int velocity) {
  Serial.write(cmd | channel);
  Serial.write(note);
  Serial.write(velocity);
}

void scanColumn(int colNum) {
  digitalWrite(latchPin, LOW);

  if (0 <= colNum && colNum <= 6) {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << colNum);
  } else {
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << (colNum - 7));
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  }
  digitalWrite(latchPin, HIGH);
}


Hi A7, I found the answer to my own questions: the thinking mistake I made was treating the code bits that are about 'note on' and 'note off' as if they were doing what the code lines seemed to do.
But as the midi command numbers for note on and note off were switched, the code lines containing NOTE_OFF_COMMAND were actually about switching notes on.
So I put the If statement in the other part of the code, and for better legibility I switched back the midi command numbers 128 and 144, and also switched the note on and note of lines in the code.
I can't claim I understand exactly how the code works, but the intended goal is achieved.

However, I still don't get how and where in the process the note on and note off commands were somehow swapped; it would still be a good thing to find out as I feel I can't post this code anywhere for others to use as it must contain some mistake.

for reference, here is the complete code that I now use with the keyboard.

//This code is working with the 49 note midi controller. The heart of the code, reading the keyboard matrix using a shift register was taken from the Youtube tutorial 
//https://www.youtube.com/watch?v=TeOvE9mf0BA&t=1s . Parts of code were added using ChatGPT. Somewhere in the proces the 'note on' and 'note off' commands were switched

//in addition to reading a 49 note keyboard, this code contains parts for dealing with:
//ten pots associated with undefined midi CC messages for assigning through midi learn
//two buttons for choosing between three note arrays. Here these arrays are one octave up and down, but they can be changed according to liking
// a joystick for pitch bend and modulation
// a pedal for sustaining notes. pedal is working though notes can't be retriggered while pedal is pushed down
// the pitch bend is not functioning on PC but works properly on Ipad

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

void sendMidiMessage(int cmd, int channel, int note, int velocity);
void scanColumn(int colNum);
void sendPotMessage(int cmd, int channel, int lsb, int msb);

// MIDI channel (use 0 for Channel 1)
const int MIDI_CHANNEL = 0;
const int MIDI_CHANNEL1 = 1;
const int MIDI_CHANNEL2 = 2;

// MIDI baud rate
const int SERIAL_RATE = 31250; // needs to be 31250 when going through midi port, can be 9600 for hairless


//-------------------------------pedal---------------------------------------------
const int PedalPin = 3;
int SwitchState = 0;

//-------------------------------joystick -------------------------------------------

//pitch bend
const int PITCHBEND_CMD = 224;
const int PB_LSB = 0;
int PB_VALUE = 64;
bool PBisOn = false;

 //control change 
const int CONTROLCHANGE1_CMD = 176;
const int CC_LSB = 1;
int CC_VALUE = 64;
bool CCisOn = false;

// joystick analog pins
const int X_pin = A14;
const int Y_pin = A15;

//--------------------------midi notes and keyboard matrix --------------------------------------

const int NUM_ROWS = 7;
const int NUM_COLS = 7;

const int NOTE_ON_CMD = 144; // in the original code these two numbers were the other way round, with 128 for note on. somehow I had to swap these
const int NOTE_OFF_CMD = 128;  
const int NOTE_VELOCITY = 127;

// Define an array of MIDI note numbers 
int noteArray1[] = {26, 27,28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,64, 65, 66, 67, 68,69, 70, 71, 72, 73, 74, };
int noteArray2 []= {38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,64, 65, 66, 67, 68,69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,};
int noteArray3 [] = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,64, 65, 66, 67, 68,69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98};

int* activeArray = noteArray2; // Start with the second array

boolean keyPressed[NUM_ROWS][NUM_COLS];
uint8_t keyToMidiMap[NUM_ROWS][NUM_COLS];

// 74HC595 pins
const int dataPin = 28;
const int latchPin = 26;
const int clockPin = 24;

// Row pins
const int rowPins[NUM_ROWS] = {32,34, 36, 38, 40, 42, 44};

//---------------------------------------------------- octave switches -------------------------------------------------

const int switchOnePin = 8;
const int switchTwoPin = 9;
const int ledRedPin = 10;
const int ledGreenPin  = 11;

// Variables to track octave switching buttons state
int switchOneState = HIGH; // Assuming the button is LOW when pressed
int lastSwitchOneState = HIGH;
int switchTwoState = HIGH;
int lastSwitchTwoState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 10; // Adjust this delay as needed

//-----------------------------------------Pots-----------------------------------------

const int potPin1 = A0;
const int potPin2 = A1;
const int potPin3 = A2;
const int potPin4 = A3;
const int potPin5 = A4;
const int potPin6 = A5;
const int potPin7 = A8;
const int potPin8 = A9;
const int potPin9 = A10;
const int potPin10 = A11;
const int numPots = 10;

int potPins [numPots] = {potPin1, potPin2, potPin3, potPin4, potPin5, potPin6, potPin7, potPin8, potPin9, potPin10};
int prevPotValues [numPots] = {0};

const int ccNumberPot1 = 7; // 7 is volume
const int ccNumberPot2 = 13;
const int ccNumberPot3 = 14;
const int ccNumberPot4 = 15;
const int ccNumberPot5 = 20;
const int ccNumberPot6 = 21;
const int ccNumberPot7 = 102;
const int ccNumberPot8 = 103;
const int ccNumberPot9 = 104;
const int ccNumberPot10 = 105;

// Define threshold values for each potentiometer
const int threshold = 10; // Adjust this threshold as needed

// Variables to store the previous potentiometer values
int prevPotValue1 = 0;
int prevPotValue2 = 0;
int prevPotValue3 = 0;
int prevPotValue4 = 0;
int prevPotValue5 = 0;
int prevPotValue6 = 0;
int prevPotValue7 = 0;
int prevPotValue8 = 0;
int prevPotValue9 = 0;
int prevPotValue10 = 0;
//-----------------------------------------einde pots----------------------------
//////////////////////////////setup////////////////////////////////////setup/////////////////////////////////////////////////////////////////////

void setup() {
  
  //----------------------------buttons and LEDS-------------------------------------------------------------------

  pinMode(switchOnePin, INPUT_PULLUP); // Assuming the button is connected to ground when pressed
  pinMode(switchTwoPin, INPUT_PULLUP);
  pinMode(ledGreenPin, OUTPUT);
  pinMode(ledRedPin, OUTPUT);

  //-----------------------------pedal-------------------------------------------------------------

  pinMode(PedalPin, INPUT_PULLUP);

    //--------------------------setup joystick input-------------------------------------

  pinMode (X_pin, INPUT); // Joystick X-axis pin
  pinMode (Y_pin , INPUT); //

  //-------------------------------pots setup------------------------------------------

  for (int i = 0; i < numPots; i++) {
    pinMode(potPins[i], INPUT);
  }

  // Initialize previous potentiometer values
  for (int i = 0; i < numPots; i++) {
    prevPotValues[i] = analogRead(potPins[i]);
  }


  //-------------------------------keyboard matrix setup ----------------------------------------

  int note = 0; 

   for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      keyPressed[rowCtr][colCtr] = false;
      keyToMidiMap[rowCtr][colCtr] = note;
      note++;
    }
  }

  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  for (int i = 0; i < NUM_ROWS; i++) {
    pinMode(rowPins[i], INPUT_PULLUP); // Use internal pull-ups
  }

  Serial.begin(SERIAL_RATE);
}
//////////////////////////////////////////////////// loop ////////////  loop ////////////////  loop  ////////////////////////////////////////////////////////

void loop() {

//----------------------------loop pots--------------------------------------------
// code generated by GPT

  for (int i = 0; i < numPots; i++) {
    int potValue = analogRead(potPins[i]);

    // Check if the potentiometer value has changed and exceeds the threshold
    if (abs(potValue - prevPotValues[i]) > threshold) {
      int mappedValue = map(potValue, 0, 1023, 0, 127);
      int ccNumber = 100 + i; // Adjust this as needed
      MIDI.sendControlChange(ccNumber, mappedValue, MIDI_CHANNEL1);
      prevPotValues[i] = potValue;
    }
  }

//-----------------------------------------------------------switch octaves ---------------------------------------------------------
// code generated by GPT

    // Read the state of the switches with debounce
  int switchOneReading = digitalRead(switchOnePin);
  int switchTwoReading = digitalRead(switchTwoPin);

  // Check if switchOne has been pressed and released
  if (switchOneReading != lastSwitchOneState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (switchOneReading != switchOneState) {
      switchOneState = switchOneReading;

      if (switchOneState == LOW) { // Button is pressed
        // Check the current activeArray and update accordingly
        if (activeArray == noteArray2) {
          activeArray = noteArray1;
        } else if (activeArray == noteArray3) {
          activeArray = noteArray2;
        }
      }
    }
  }

  // Check if switchTwo has been pressed and released
  if (switchTwoReading != lastSwitchTwoState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (switchTwoReading != switchTwoState) {
      switchTwoState = switchTwoReading;

      if (switchTwoState == LOW) { // Button is pressed
        // Check the current activeArray and update accordingly
        if (activeArray == noteArray2) {
          activeArray = noteArray3;
        } else if (activeArray == noteArray1) {
          activeArray = noteArray2;
        }
      }
    }
  }

  // Update the state of switches for the next iteration
  lastSwitchOneState = switchOneReading;
  lastSwitchTwoState = switchTwoReading;

  //-------------------------------------------------leds------------------------------------------
    if (activeArray == noteArray2) {
    digitalWrite (ledRedPin, LOW);
    digitalWrite (ledGreenPin, LOW);
  }
  if (activeArray == noteArray1) {
    digitalWrite (ledRedPin, HIGH);
    digitalWrite (ledGreenPin, LOW);
  }
      if (activeArray == noteArray3) {
    digitalWrite (ledRedPin, LOW);
    digitalWrite (ledGreenPin, HIGH);
      }

    // --------------------------------------joystick -----------------------------------------
 
 {
  // joystick X-axis - pitch bend
  int newPB_VALUE = map(analogRead(X_pin), 0, 1023, 0, 127); //this line works with Ipad, not with pc
  //int newPB_VALUE = map(analogRead(X_pin), -8192, 8192, 0, 127); this line contains proper pitchbend values but is not working
  newPB_VALUE = 127 - newPB_VALUE;  // Reverse the direction because joystick is mounted upside down

  if (abs(newPB_VALUE - PB_VALUE) > 2) {
    sendPotMessage(PITCHBEND_CMD, MIDI_CHANNEL2, PB_LSB, newPB_VALUE);
    PB_VALUE = newPB_VALUE;
  }

  // joystick Y-axis - control change
  int newCC_VALUE = map(analogRead(Y_pin), 0, 1023, 0, 127);
  newCC_VALUE = 127 - newCC_VALUE;  // Reverse the direction because joystick is mounted upside down

  if (abs(newCC_VALUE - CC_VALUE) > 2) {
    sendPotMessage(CONTROLCHANGE1_CMD, MIDI_CHANNEL2, CC_LSB, newCC_VALUE);
    CC_VALUE = newCC_VALUE;
  }
}
  
   
    // ---------------------------------------------------midi notes --------------------------------------------------
  
     
SwitchState = digitalRead (PedalPin);

  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    scanColumn(colCtr);
    int rowValue[NUM_ROWS];

    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          // Button pressed
         if ( keyPressed[rowCtr][colCtr] = true  && SwitchState == HIGH){
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL1, activeArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY); // in the original code note off here was note on
         }
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false ;
          //Serial.println(SwitchState);
          //delay (600); //Serial monitor shows that switch is functioning properly
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL1, activeArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);// in the original code note on here was note off
        }
        
      }
    }
  }
}

void sendMidiMessage(int cmd, int channel, int note, int velocity) {
  Serial.write(cmd | channel);
  Serial.write(note);
  Serial.write(velocity);
}

//-------------------------------------------------- functions -----------------------------------------------------------

void scanColumn(int colNum) {
  digitalWrite(latchPin, LOW);

  if (0 <= colNum && colNum <= 6) {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << colNum);
  } else {
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << (colNum - 7));
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  }
  digitalWrite(latchPin, HIGH);

}
void sendPotMessage(int cmd, int channel, int lsb, int msb) {
  Serial.write(cmd + channel); // send command plus the channel number
  Serial.write(lsb); // least significant bit 
  Serial.write(msb); // most significant bit
}

Sry, I must say I got confused with all the versions. In some version of chatGPT's code, you wrote

//... 
      if(rowValue[rowCtr] != 0 && !keyPressed[rowCtr][colCtr])
      {
        keyPressed[rowCtr][colCtr] = true;
        sendMidiMessage(NOTE_ON_CMD, CHANNEL1, keyToMidiMap[rowCtr][colCtr], NOTE_VELOCITY);
      }

My fixed version read

      if (rowValue[rowCtr] == LOW) {
        if (!keyPressed[rowCtr][colCtr]) {
          keyPressed[rowCtr][colCtr] = true;
          sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL, NoteArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      }

where you see I check if the row can pull the currently scanned column LOW, not that it is HIGH ( != 0).

One was nested if statements, the other used a logical and && operator. Both compute the same result, with the difference being checking LOW (0) instead of HIGH (not 0, != 0 that is).

When the line can be lulled LOW and we didn't think the key was being pressed, we mark it as pressed.

My correction #15 to my correction #14 (!) was the unfortunately just original, cut and pasted from the tests I was doing at a moment when I had it broken. Changing HIGH to LOW in that makes it work and removes any mystery about why it was backwards.

a7