Wednesday, December 4, 2013

How to build 64 keys MIDI keyboard

Hi folks.... Been a long time since I've promised to write a post on how to build 64 keys midi keyboard. In fact this post will be on what needs to be done to extend the original post where we built midi keyboard from 32 key piano toy. In this case, of course, you will require a toy with more keys... I have not seen much of the toy pianos with 64 keys (have you?) but I guess you could build a matrix from scratch yourself using push buttons (that is fun!)



You will need to slightly modify Schematics and Code to to increase number of keys.

On schematics you will have to replicate same 32 keys scan matrix and as already discussed you can organize it in several ways 8*8 or 16*4 scan matrix. Lets see how it is achieved in 8*8 rows/columns case

Assuming you already have a scan matrix with 64 keys or were able to extend 32 keys scan matrix to 64 keys you will have now 8 rows and 8 columns from original project 8*4. Now you need to do same extension on the Arduino side by adding 4 digital inputs to support extra 4 rows displayed as YELLOW group on the schematics. Hope this part does make sense. If it does not I suggest to read links that were posted on original blog post to understand how scan matrix works - that should make it clearer.

Below is modified code to handle the extra 4 rows. I've added comments to highlight the extra code added.

After modifying original sketch it should allow all 64 keys to be played at the same time (not that you would do it).

You may notice there is a lot repetition in the code. It was left intentionally that way to highlight parts that needs to be repeated. And in fact it could be made simpler by organizing rows and groupValue(s) in array. You can check the Velocity midi keyboard example as it makes use of array for rows and groupValue(s).

One of the steps you will need to do in the sketch is to Map keys to MIDI notes. You can use following resrouces:

Midi notes map (NOTE to NUMBER):

Generally good resource on MIDI:


Below is a sketch modification required to implement 8*8 matrix




Above we have discussed 8*8 martix to midi implementation.

I have not seen 16*4 matrix but if you got one, modifications will be more complex. We will not discuss details but roughly explain whats needed in this case and hopefully you will go for 8*8.

You will increase number of  columns (extra 8) and will have to add one more 74HC595 to implement 8 more columns. Number of rows will remain 4. Code modifications required will be following. You will need to add values to bits array will to increase scan loop to 16 (8 more scans for new 8 columns). Disadvantages of 16*4 matrix is it will be about twice slower than 8*8 as it will require 8 more serial scans (while in 8*8 it requires only 4 more). Schematic is also more complex in 16*4 implementation as it will require extra 74HC595 unless you use a better serial to parallel chip that has 16 outputs instead of 8. And overall from 16*4 to 8*8 you will economy couple of I/Os only - not much difference.

In fact 8*8 matrix could be implemented without 74HC595 if you ok to use all I/Os on the Arduino but make scan matrix faster... I might put a short post on it. That would make sense in case of Arduino Mega where you have plenty of I/Os and simplicity of schematics and implementation speed is what you want to achieve...

Hope that makes things a bit clearer... :)

11 comments:

  1. hi! need some info, i have a 48 keys keyboard, how can i program that? i assume that i have to erase two block of the text taking the 60 value (C4) as my center? or i need a totally new code? (in not good programer) thanks!

    ReplyDelete
    Replies
    1. Hi, if you use above code it should just work. You will have same code but schematics will not utilize 2 input pins. So 2 rows: 2*8=16 keys will be reduced (which is 64-16=48 what you need).

      But if you still want to remove redundant code then just remove code for 2 rows. You do not need following:

      int groupValue7 = digitalRead(row7);
      int groupValue8 = digitalRead(row8);

      and anything related in

      if (groupValue7 != 0 && !keyPressed[col + 48]) {
      keyPressed[col + 48] = true;
      noteOn(0x91, keyToMidiMap[col + 48], noteVelocity);
      }

      if (groupValue8 != 0 && !keyPressed[col + 56]) {
      keyPressed[col + 56] = true;
      noteOn(0x91, keyToMidiMap[col + 56], noteVelocity);
      }

      and

      if (groupValue7 == 0 && keyPressed[col + 48]) {
      keyPressed[col + 48] = false;
      noteOn(0x91, keyToMidiMap[col + 48], 0);
      }

      if (groupValue8 == 0 && keyPressed[col + 56]) {
      keyPressed[col + 56] = false;
      noteOn(0x91, keyToMidiMap[col + 56], 0);
      }



      You just need to play with it (quite a basic code - easy to understand)

      By the way you could take a small course from Coursera? has number of courses on programming. I found it useful.

      Delete
    2. thanks men!, i will report my results!

      Delete
  2. Hello, thanks for the project, it's what i've been looking for ages.
    I got a little problem/question, the PCB for my keys are a little weird.
    It has 17 wires, the keys are divided in 10 groups with 6 keys each and a group with one key alone, what are the steps so i can wire and code it correctly?

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Podrias subir una foto del circuito armado? Gracias

    ReplyDelete
  5. Hi Jen, can you show how is the modification for 8x8 keymatrix , but without 74HC595 and using Arduino Mega?

    ReplyDelete
  6. Hi Jean ,

    Thanks for posting this sketch , it's a great job.
    I'm trying to make a MIDI controller, and use your schedule and already managed to add 4 analog controls with CONTROL CHANGE for each. I am now trying to add the nRF24L01 , but it is very complicated because it already can transmit the analog controls but do not know how to transmit the digital controls (MATRIX ) .
    Have you ever tried to do this program ? or you can help me with this schedule ?

    the codes are in the following dialogue.


    I'm using a ARDUINO MEGA 2560 in the transmitter and the receiver ARDUINO UNO . I changed the DIGITAL pins MEGA 2560 to 22 ... 29 .
    I added the libraries of nRF24L01 .
    I called the pins 48, 50 , 51, 52 and 53 for nRF24L01 .

    Please , I hope you can help me .
    I had several unsuccessful attempts .

    Thank you so much!!!

    Mauricio Salgado

    ReplyDelete
  7. CODE TRANSMITTER:

    #include
    #include "nRF24L01.h"
    #include "RF24.h"

    RF24 radio(48,53);

    const uint64_t pipe = 0xE8E8F0F0E1LL;

    // Pin Definitions
    // Rows are connected to
    const int row1 = 22;
    const int row2 = 23;
    const int row3 = 24;
    const int row4 = 25;
    const int row5 = 26;
    const int row6 = 27;
    const int row7 = 28;
    const int row8 = 29;

    int val[4];
    int lastVal[4];


    // The 74HC595 uses a serial communication
    // link which has three pins
    const int clock = 8;
    const int latch = 9;
    const int data = 10;


    uint8_t keyToMidiMap[64];

    boolean keyPressed[64];

    int noteVelocity = 127;


    // use prepared bit vectors instead of shifting bit left everytime
    int bits[] = { B00000001, B00000010, B00000100, B00001000, B00010000, B00100000, B01000000, B10000000 };


    // 74HC595 shift to next column
    void scanColumn(int value) {
    digitalWrite(latch, LOW); //Pulls the chips latch low
    shiftOut(data, clock, MSBFIRST, value); //Shifts out the 8 bits to the shift register
    digitalWrite(latch, HIGH); //Pulls the latch high displaying the data
    }

    void setup() {

    // Map scan matrix buttons/keys to actual Midi note number. Lowest num 41 corresponds to F MIDI note.
    keyToMidiMap[0] = 48;
    keyToMidiMap[1] = 41;
    keyToMidiMap[2] = 42;
    keyToMidiMap[3] = 43;
    keyToMidiMap[4] = 44;
    keyToMidiMap[5] = 45;
    keyToMidiMap[6] = 46;
    keyToMidiMap[7] = 47;

    keyToMidiMap[8] = 56;
    keyToMidiMap[1 + 8] = 49;
    keyToMidiMap[2 + 8] = 50;
    keyToMidiMap[3 + 8] = 51;
    keyToMidiMap[4 + 8] = 52;
    keyToMidiMap[5 + 8] = 53;
    keyToMidiMap[6 + 8] = 54;
    keyToMidiMap[7 + 8] = 55;

    keyToMidiMap[16] = 64;
    keyToMidiMap[1 + 16] = 57;
    keyToMidiMap[2 + 16] = 58;
    keyToMidiMap[3 + 16] = 59;
    keyToMidiMap[4 + 16] = 60;
    keyToMidiMap[5 + 16] = 61;
    keyToMidiMap[6 + 16] = 62;
    keyToMidiMap[7 + 16] = 63;

    keyToMidiMap[24] = 72;
    keyToMidiMap[1 + 24] = 65;
    keyToMidiMap[2 + 24] = 66;
    keyToMidiMap[3 + 24] = 67;
    keyToMidiMap[4 + 24] = 68;
    keyToMidiMap[5 + 24] = 69;
    keyToMidiMap[6 + 24] = 70;
    keyToMidiMap[7 + 24] = 71;

    keyToMidiMap[32] = 80;
    keyToMidiMap[1 + 32] = 73;
    keyToMidiMap[2 + 32] = 74;
    keyToMidiMap[3 + 32] = 75;
    keyToMidiMap[4 + 32] = 76;
    keyToMidiMap[5 + 32] = 77;
    keyToMidiMap[6 + 32] = 78;
    keyToMidiMap[7 + 32] = 79;

    keyToMidiMap[40] = 88;
    keyToMidiMap[1 + 40] = 81;
    keyToMidiMap[2 + 40] = 82;
    keyToMidiMap[3 + 40] = 83;
    keyToMidiMap[4 + 40] = 84;
    keyToMidiMap[5 + 40] = 85;
    keyToMidiMap[6 + 40] = 86;
    keyToMidiMap[7 + 40] = 87;

    keyToMidiMap[48] = 96;
    keyToMidiMap[1 + 48] = 89;
    keyToMidiMap[2 + 48] = 90;
    keyToMidiMap[3 + 48] = 91;
    keyToMidiMap[4 + 48] = 92;
    keyToMidiMap[5 + 48] = 93;
    keyToMidiMap[6 + 48] = 94;
    keyToMidiMap[7 + 48] = 95;

    keyToMidiMap[56] = 104;
    keyToMidiMap[1 + 56] = 97;
    keyToMidiMap[2 + 56] = 98;
    keyToMidiMap[3 + 56] = 99;
    keyToMidiMap[4 + 56] = 100;
    keyToMidiMap[5 + 56] = 101;
    keyToMidiMap[6 + 56] = 102;
    keyToMidiMap[7 + 56] = 103;

    // setup pins output/input mode
    pinMode(data, OUTPUT);
    pinMode(clock, OUTPUT);
    pinMode(latch, OUTPUT);

    pinMode(row1, INPUT);
    pinMode(row2, INPUT);
    pinMode(row3, INPUT);
    pinMode(row4, INPUT);
    pinMode(row5, INPUT);
    pinMode(row6, INPUT);
    pinMode(row7, INPUT);
    pinMode(row8, INPUT);

    Serial.begin(57600);

    radio.begin();
    radio.openWritingPipe(pipe);

    delay(1000);
    }

    ReplyDelete
  8. Continued...


    void loop() {

    radio.write(val,sizeof(val));
    radio.write(lastVal,sizeof(lastVal));


    val[0] = analogRead(A0)/8;
    if (val[0] != lastVal[0])
    {
    MIDImessage(176,11,val[0]);}
    lastVal[0] = val[0];

    val[1] = analogRead(A1)/8;
    if (val[1] != lastVal[1])
    {
    MIDImessage(176,12,val[1]);}
    lastVal[1] = val[1];

    val[2] = analogRead(A0)/8;
    if (val[2] != lastVal[2])
    {
    MIDImessage(176,21,val[2]);}
    lastVal[2] = val[2];

    val[3] = analogRead(A1)/8;
    if (val[3] != lastVal[3])
    {
    MIDImessage(176,22,val[3]);}
    lastVal[3] = val[3];


    for (int col = 0; col < 8; col++) {

    // shift scan matrix to following column
    scanColumn(bits[col]);

    // check if any keys were pressed - rows will have HIGH output in this case corresponding
    int groupValue1 = digitalRead(row1);
    int groupValue2 = digitalRead(row2);
    int groupValue3 = digitalRead(row3);
    int groupValue4 = digitalRead(row4);
    int groupValue5 = digitalRead(row5);
    int groupValue6 = digitalRead(row6);
    int groupValue7 = digitalRead(row7);
    int groupValue8 = digitalRead(row8);

    // process if any combination of keys pressed
    if (groupValue1 != 0 || groupValue2 != 0 || groupValue3 != 0 || groupValue4 != 0 || groupValue5 != 0 || groupValue6 != 0 || groupValue7 != 0 || groupValue8 != 0) {

    if (groupValue1 != 0 && !keyPressed[col]) {
    keyPressed[col] = true;
    noteOn(0x90, keyToMidiMap[col], noteVelocity);
    }

    if (groupValue2 != 0 && !keyPressed[col + 8]) {
    keyPressed[col + 8] = true;
    noteOn(0x90, keyToMidiMap[col + 8], noteVelocity);
    }

    if (groupValue3 != 0 && !keyPressed[col + 16]) {
    keyPressed[col + 16] = true;
    noteOn(0x90, keyToMidiMap[col + 16], noteVelocity);
    }

    if (groupValue4 != 0 && !keyPressed[col + 24]) {
    keyPressed[col + 24] = true;
    noteOn(0x90, keyToMidiMap[col + 24], noteVelocity);
    }

    if (groupValue5 != 0 && !keyPressed[col + 32]) {
    keyPressed[col + 32] = true;
    noteOn(0x90, keyToMidiMap[col + 32], noteVelocity);
    }

    if (groupValue6 != 0 && !keyPressed[col + 40]) {
    keyPressed[col + 40] = true;
    noteOn(0x90, keyToMidiMap[col + 40], noteVelocity);
    }

    if (groupValue7 != 0 && !keyPressed[col + 48]) {
    keyPressed[col + 48] = true;
    noteOn(0x90, keyToMidiMap[col + 48], noteVelocity);
    }

    if (groupValue8 != 0 && !keyPressed[col + 56]) {
    keyPressed[col + 56] = true;
    noteOn(0x90, keyToMidiMap[col + 56], noteVelocity);
    }

    }

    // process if any combination of keys released
    if (groupValue1 == 0 && keyPressed[col]) {
    keyPressed[col] = false;
    noteOn(0x90, keyToMidiMap[col], 0);
    }

    if (groupValue2 == 0 && keyPressed[col + 8]) {
    keyPressed[col + 8] = false;
    noteOn(0x90, keyToMidiMap[col + 8], 0);
    }

    if (groupValue3 == 0 && keyPressed[col + 16]) {
    keyPressed[col + 16] = false;
    noteOn(0x90, keyToMidiMap[col + 16], 0);
    }

    if (groupValue4 == 0 && keyPressed[col + 24]) {
    keyPressed[col + 24] = false;
    noteOn(0x90, keyToMidiMap[col + 24], 0);
    }

    if (groupValue5 == 0 && keyPressed[col + 32]) {
    keyPressed[col + 32] = false;
    noteOn(0x90, keyToMidiMap[col + 32], 0);
    }

    if (groupValue6 == 0 && keyPressed[col + 40]) {
    keyPressed[col + 40] = false;
    noteOn(0x90, keyToMidiMap[col + 40], 0);
    }

    if (groupValue7 == 0 && keyPressed[col + 48]) {
    keyPressed[col + 48] = false;
    noteOn(0x90, keyToMidiMap[col + 48], 0);
    }

    if (groupValue8 == 0 && keyPressed[col + 56]) {
    keyPressed[col + 56] = false;
    noteOn(0x90, keyToMidiMap[col + 56], 0);
    }
    }
    }
    void MIDImessage(byte command, byte data1, byte data2) //pass values out through standard Midi Command
    {
    Serial.write(command);
    Serial.write(data1);
    Serial.write(data2);

    }

    void noteOn(int cmd, int pitch, int velocity) {
    Serial.write(cmd);
    Serial.write(pitch);
    Serial.write(velocity);
    }

    ReplyDelete
  9. CODE RECEIVER:

    #include
    #include "nRF24L01.h"
    #include "RF24.h"

    RF24 radio(9,10);
    const uint64_t pipe = 0xE8E8F0F0E1LL;


    int val[4];
    int lastVal[4];


    void setup()
    {

    Serial.begin(57600);
    radio.begin();
    radio.openReadingPipe(1,pipe);
    radio.startListening();
    }

    void loop()
    {
    if ( radio.available() )
    {
    radio.read(val,sizeof(val));
    radio.read(lastVal,sizeof(lastVal));


    if (val[0] != lastVal[0])
    {
    MIDImessage(176,11,val[0]);}
    lastVal[0] = val[0];


    if (val[1] != lastVal[1])
    {
    MIDImessage(176,12,val[1]);}
    lastVal[1] = val[1];


    if (val[2] != lastVal[2])
    {
    MIDImessage(176,21,val[2]);}
    lastVal[2] = val[2];


    if (val[3] != lastVal[3])
    {
    MIDImessage(176,22,val[3]);}
    lastVal[3] = val[3];
    }
    }
    void MIDImessage(byte command, byte data1, byte data2) //pass values out through standard Midi Command
    {
    Serial.write(command);
    Serial.write(data1);
    Serial.write(data2);

    }

    void noteOn(int cmd, int pitch, int velocity) {
    Serial.write(cmd);
    Serial.write(pitch);
    Serial.write(velocity);
    }

    ReplyDelete