Need help shift register for midi note input

Hi guys and gals, I am programming far beyond my understanding through copying and adapting existing code.
But I'm stuck, and any help is greatly appreciated!
I got a code for reading a 7x8 keyboard matrix which was read using a shift register. That code worked fine although one column did not generate notes.
Now I'm using the same code but slightly modified to accomodate a 4x4 keyboard matrix.
I find that turning bitmask lines on and off in the code makes the difference between midi notes being played or not. However, I can get only three rows to play, and this moment only two. My hardware setup:

Arduino Uno
button matrix of 16 buttons, 4 rows, 4 columns, with diodes galore. This matrix is tested with a led and functions properly.
row pins of the button matrix are 9,10,11,12 the wires leading from the rows are also grounded though 10k Ohm resistors
shift register used 74HC595
the columns of the button matrix are attached to the shift register pins 2, 3, 4 and 15
5 volt and ground are connected to the correct pins of the shift register
a midi port is connected to the Arduino. This is the code:



#include <MIDI.h>

// Keyboard matrix
const int NUM_ROWS = 4;
const int NUM_COLS = 4;

// Row input pins
const int rowPins[NUM_ROWS] = {9,10,11,12};

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

// bitmasks for scanning columns
const byte bits[] = {
  //B10000000,//this line turned off two columns give midi notes
 //B01000000,//this line turned off three columns give midi notes
B00100000, //als deze aan staat transponeren alle note 4 halve omhoog
  B00010000,//column one sound
  B00001000,//column two sound
  B00000100,//column three sound
  B00000010,
  B00000001,
};

int prevKeyStates[NUM_ROWS][NUM_COLS];

MIDI_CREATE_DEFAULT_INSTANCE();

void setup() {
  // Set pin modes
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  for (int i = 0; i < NUM_ROWS; ++i) {
    pinMode(rowPins[i], INPUT_PULLUP);
  }

  Serial.begin(9600);
  MIDI.begin(1); // MIDI channel 1
}

void loop() {
  // Scan columns
  for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
    // Activate column
    scanColumn(colCtr);

    // Read row values
    for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
      int keyState = digitalRead(rowPins[rowCtr]);

      // Check if key is pressed and was not pressed before
      if (keyState == HIGH && prevKeyStates[rowCtr][colCtr] == LOW) {
        // Calculate key number
        int keyNum = colCtr * NUM_ROWS + rowCtr + 1;

        // Calculate MIDI note number
        int noteNum = keyNum + 50;

        // Send MIDI note on command
        MIDI.sendNoteOn(noteNum, 127, 1);
      }
      // Check if key was released and was pressed before
      else if (keyState == LOW && prevKeyStates[rowCtr][colCtr] == HIGH) {
        // Calculate key number
        int keyNum = colCtr * NUM_ROWS + rowCtr + 1;

        // Calculate MIDI note number
        int noteNum = keyNum + 50;

        // Send MIDI note off command
        MIDI.sendNoteOff(noteNum, 0, 1);
      }

      prevKeyStates[rowCtr][colCtr] = keyState;
    }
  }
}

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

  if (0 <= colNum && colNum <= 4) {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000); // Right shift register
    shiftOut(dataPin, clockPin, MSBFIRST, bits[colNum]); // Left shift register
  } else {
    shiftOut(dataPin, clockPin, MSBFIRST, bits[colNum - 4]); // Right shift register
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000); // Left shift register
  }

  digitalWrite(latchPin, HIGH);
}

There seems to be a mismatch between your bits[] sent out and the shift register pins used..

You are setting the 74HC595 pins as follows:
colNum - pin
0 - 5
1 - 4
2 - 3
3 - 2

Please review and comment

Thank you for answering Mancera. I have experimented with using various shift register pins for output, to check if the silent column had something to do with the pins. To clarify, here is the schematics. I may have switched shift register pins again.
How should I read the bits sent out?

To clarify: I don't understand your comment. Why would the combination of pins used and column numbers be a problem? Notes might be played in the wrong order, but shouldn't I hear something?
Am I wrong in thinking I could use any of the output pins of the shift register?
This question comes from someone who, despite seeing multiple videos on shift registers, has no clue what the bitmask's function is.

Very strange use of 74HC595 register and pointless in my opinion. Are those triangles on the diagram buffers? Or diodes? If they are diodes, the symbol is wrong.

I've used this setup for a 7x8 matrix and it worked just fine. Just what is so strange about the use of this particular shift register? And yes, the triangles are diodes.

I wondered too. If they are diodes, it is not clear that they are at all in the correct place to do what diodes are meant to do in a scanned keyboard circuit.

See the below 4x4 scanned matrix circuit. Each switch has a diode in series with it, together they form the path between a row and a column.

Is your wiring wrong or your schematic?

Note too the way to draw a diode so everyone doesn't wonder what the little triangles are.

a7

Because in this case you have 4x4 keys and for them you need 8 pins of the microcontroller for direct reading. You use many more elements with a more complex program to use only 1 pin less!? (According to the scheme you use 7). In my opinion, you are unnecessarily complicating your work, but if this is your goal, then you are doing well. This is not sarcasm. There are many people for whom it is important to do something in their own way, even if according to the others it is unnecessary and complicated.

thanks for this!

Hi Flashko, this is just a test setup; my goal is a 49 key keyboard, hence the shift register.
However, I have to revisit the original code to see if I messed up, so until further notice it might be best to close this topic.

It seems I actually wired the diodes the wrong way. That would explain why I still had a ghost note problem (at some point three of the four columns were returning midi notes as they were intended to).

Hm, okay. Have you considered using a PS2 keyboard? If I remember correctly there is a suitable library for Arduino. Or maybe use some sort of IR remote control? For example, an IR remote control that is connected with a cable to the Arduino. So you will only need 1 pin from the microcontroller.

Hi Flashko, I have a specific keyboard layout in mind which I already use on an acoustic piano: Janko keyboard layout
And I am happy to announce that I got my setup working. There was a number of mistakes I made: leaving out essential lines of code from the original, soldering mistakes, a bad 5 volt connection.
Next step is a four or five octave keyboard, the step after that is making it velocity sensitive.

But I could use more help: I'm able to set the start note of the keypad, which plays a chromatic scale. What I would really like to be able to do is create an array with custom notes, or better still, multiple arrays that can be selected by designated buttons or key combinations on the keypad. In case you would like to give a suggestion, here is the functioning code.

//original code taken from Youtube 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 = 4;
const int NUM_COLS = 4;

// Row input pins
const int row1Pin = 9;
const int row2Pin = 10;
const int row3Pin = 11;
const int row4Pin = 12;
//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 = 48; // was 28. determines lowest 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++;
    }
  }

  // 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 <= 4)//was 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 [colNum-*] hiermee experimenteren.value was 8
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000); //left sr
  }
  digitalWrite(latchPin, HIGH);
}

Imma assume you mean "dedicated", which might work better than chords (not music chords, but certain multiple keys pressed at the same time), that's up to you.

Either would be easy. For the moment, you can delay implementing the ability to select in the fly, and just change map pins by tweaking the code so you can prove you are picking up the right array.

Having made the choice, do you just mean that now a key, say C3 physical, should play instead something that is in a table indexed by note number, like you could make a keyboard that played backwards by octave so playing a physical C scale would sound like

C2 B2 A2 G2 F2 E2 D2 C3 B3 A3 G3 F3 E3 D3 C4 and so forth?

if I remember how notes are named at all.

a7

Why use 74HC595 registers? They are better suited for more outputs and you need more inputs. In your case for reading many keys on a keyboard it is more appropriate to use 74HC165 registers. Thus, with only 3 or pins of the microcontroller, you can read many such registers, as to each key you will need to add only 1 resistor to prevent the input of the register from floating. In my opinion, the program will also be simpler. You won't even have a problem no matter how many keys are pressed at the same time.

I'm not clear, but it seems you are suggesting that @wannabuildamidikeyboard jertison the idea of using a matrix arrangement for the key switches.

Which will certainly work, and work better in some ways, but would take a crap-ton of wiring.

a7

Hi 777, thanks for reacting. Your interpretation of what I am trying to achieve is correct.
Meanwhile I've succeeded in obtaining a code with a note array in it.
I have spent something like two hours with ChatGPT trying to pry a working code out of it. It came up with a code which has an odd bug: note on and note off commands are switched.
When a key is pressed, first a note off command is given, then a note on command.
Even after generating ten versions of the code GPT couldn't figure out the problem.
I managed to solve it by swapping the midi message numbers for note on and note off in the first part of the code, which will look odd to anyone inspecting the code I suppose.

#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

const int NUM_ROWS = 4;
const int NUM_COLS = 4;

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 16 MIDI note numbers (60-75)
int midiNotes[] = {50, 53,  55,  56,  57,  60,  62,  65,  62,  65,  67,  68,  69,  72,  74,  77  };

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

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

// Row pins
const int rowPins[NUM_ROWS] = {9, 10, 11, 12};

// 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

void setup() {
  int note = 60; // Starting note (adjust as needed)

  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() {
  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, midiNotes[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);
        }
      } else {
        if (keyPressed[rowCtr][colCtr]) {
          // Button released
          keyPressed[rowCtr][colCtr] = false;
          sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL, midiNotes[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 <= 3) {
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << colNum);
  } else {
    shiftOut(dataPin, clockPin, MSBFIRST, 1 << (colNum - 4));
    shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
  }
  digitalWrite(latchPin, HIGH);
}

So… you good, or is there an issue you are working or even strukkling with?

I see a two dimensional array keyToMidiMap going unused in the latest code, and a one dimensional array midiNotes which you use like a 2D array through some simple expressions.

Is it your plan to have multiple maps of either kind, so you can redefine what switch sends which note on the fly, like choosing "normal", "reversed", "totally crazy" on the fly?

Here beginning as needing only 4x4 or 16 elements, but in the end, each map would have 79, an entry for each key?

Is there any rhyme or reason to these additional maps that might make some sense and not be best done with a map? A reverse keyboard, for example, woukd only need a simple linear expression something like

    int noteToPlay = 88 - realNoteNumber;

You would for full maps on,y need to increase by one the dimensions of the map array, so you could use an array of arrays. Three dimensional in the case of having multiple two dimensional mappings.

a7

HI 777,
At the moment I am happy with my progress.
And yes, the idea is to have multiple arrays. An 'array of arrays' would be beyond my grasp right now I'm afraid.
(The unused array was in the original code which GPT used as a template. I'll try and understand what's going on there.)
I'm thinking of a button or maybe a potmeter to choose a root note. The arrays should then add increments to that note; a major scale would be 0,2,2,1,2,2,2,1, a minor 0,2,1,2,2,1,2,2 .
The scale in the code is a minor blues scale.
I'm working on a prototype with 49 keys, so four octaves. If I successfully complete that project I would want to try one extra octave and maybe velocity sensitivity.
I guess I'd better close this thread as it is turning into another subject.
Thanks again, and I'll be back here if I run into problems.

1 Like