Keyboard (Piano) Scanner with Sensitivity and Sustain Arduino Mega 2560

Hello, through the steps indicated by Grumpy_Mike, I managed to make my old CASIO CTK 485 come back to life as a controller. The step by step I followed is here.

After this feat, a friend became interested in the project, as he also had a defective keyboard.

We followed the same steps as my CASIO post and were able to map the key matrix. The difference was that we came across a combination, where 12 + 11 (23) wires generate two 6x11 arrays, where there are two possibilities of connections to activate the same key. There is also a discounted ribbon below the keyboard.

I believe this is related to the sensitivity of the keys.

At this point the doubts started, analyzing the code used in my CASIO I don't know how to introduce this second matrix to activate the sensitivity of the keys.

Another question that arose was how to add a sustain pedal to the code.

I am currently using this code with the Arduino mega 2560, but with 6 wires, there is no touch sensitivity and no sustain.

The full code is here.

#include <MIDI.h>
#include <Keypad.h>

const byte ROWS = 6; // 6 Linhas
const byte COLS = 11; // 11 Colunas
 
char keys[ROWS][COLS] = {
{36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96},
{37, 43, 49, 55, 61, 67, 73, 79, 85, 91, 97},
{38, 44, 50, 56, 62, 68, 74, 80, 86, 92, 98},
{39, 45, 51, 57, 63, 69, 75, 81, 87, 93, 99},
{40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 100},
{41, 47, 53, 59, 65, 71, 77, 83, 89, 95, 101}
};
 
byte rowPins[ROWS] = {22, 26, 30, 34, 38, 42}; //conectar-se às pinagens de linha do teclado
byte colPins[COLS] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10}; //conectar-se às pinagens das colunas do teclado

Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

byte pressed = 32;
byte chanel = 0; // Canal MIDI para usar

void setup() {
 Serial.begin(115200); // definir isso o mesmo que no Hairless
}


void loop() {
 // Fills kpd.key[49] array with up-to 10 active keys.
 // Returns true if there are ANY active keys.
 if (kpd.getKeys())
 {
   for (int i = 0; i < LIST_MAX; i++) // Scan the whole key list.
   {
     if ( kpd.key[i].stateChanged )   // Only find keys that have changed state.
     {
       pressed = kpd.key[i].kchar + 12;
       switch (kpd.key[i].kstate) {  // Report active key state : IDLE, PRESSED, HOLD, or RELEASED
         case PRESSED:
           sendMIDI(chanel | 0x90, pressed, 100);
           break;
          case RELEASED:
           sendMIDI(chanel | 0x80, pressed, 64);
           break;
       }
     }
   }
 }
}  // End loop

void sendMIDI(byte type, byte note, byte velocity){
  Serial.write(type);
  Serial.write(note & 0x7F);
  Serial.write(velocity);
}

I appreciate anyone who can help.

1 Like

The difference was that we came across a combination, where 12 + 11 (23) wires generate two 6x11 arrays, where there are two possibilities of connections to activate the same key.

Ok the way these work is that one matrix triggers first as the key is pressed followed by the second key. There is only a few mS between these but the time between the two keys gives you a measure of the speed it is pushed down.

I don’t know which way round these are so as a test just set up both matrix lines to address the same key. Then look for this key press and store the millis timer in a variable. Then look at the output of the second matrix and when you see this become pressed store the millis timer in a second variable. Then subtract the two variables and print the result. If you always get a value of 0 or 1 then you are looking at the wrong one first, so swap these over in your test.

Then try to have that first one scanning and when it sees a key press, transfer the matrix scanning states to the second matrix and wait for the key to go down. And print the time interval. Play about and see the range of numbers you get. Then use the map function to convert this range of numbers into the range 20 to 127.

Use the result of that mapping to set the velocity in the MIDI note on message.

Hello Grumpy, I'm sorry for my lack of knowledge, but I don't understand much about programming.

Then look for this key press and store the millis timer in a variable. Then look at the output of the second matrix and when you see this become pressed store the millis timer in a second variable. Then subtract the two variables and print the result.

I was able to interpret what you advised me, but I don't know how to do it. Let's score if I'm on the way, you want me to find out what the time difference is between the first key press and the second, right?

My problem is that I don't know how to write code for this.

Searching for older information within the forum I found this code:

  if  (rowValue <= 8) {
            NOTE_VELOCITY = 70;
            Serial.Println(NOTE_VELOCITY);
}
  else {
            NOTE_VELOCITY = 126;
             Serial.Println(NOTE_VELOCITY); 
};

Is there a way to include it in my code so that something along these lines would occur:

If line 6 or earlier is signed, would the volume be 70, if not (in the case of line 7 onwards) the volume would be 126 or 127?

If it works, how do I change the code to fit what I'm using.

Sorry but that code you posted has nothing to do with what you have to write.
I can not write it for you because only you have the hardware.
If you can’t code at all then I am afraid this is probably the end of your journey with this project, until you learn to code.

Grumpy_Mike Another project that I followed and I even changed the code for my situation was Moura's keyboardscanner, which is specifically used for sensitive keyboards.
I tried to help there, but I didn't get a return.

I changed the code to use according to the double matrix, but I don't get a response when I type on my keyboard.

I understand my limitation in relation to programming and I am studying to solve this.

Moura Code:

/*
Moura's Keyboard Scanner: turn you broken (or unused) keyboard in a MIDI controller
Copyright (C) 2017 Daniel Moura <oxe@oxesoft.com>

This code is originally hosted at https://github.com/oxesoft/keyboardscanner

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "arduino2.h" // install the library from https://github.com/FryDay/DIO2

#define KEYS_NUMBER 61

#define KEY_OFF               0
#define KEY_START             1
#define KEY_ON                2
#define KEY_RELEASED          3
#define KEY_SUSTAINED         4
#define KEY_SUSTAINED_RESTART 5

#define MIN_TIME_MS   3
#define MAX_TIME_MS   50
#define MAX_TIME_MS_N (MAX_TIME_MS - MIN_TIME_MS)

#define PEDAL_PIN     21

//find out the pins using a multimeter, starting from the first key
byte output_pins[] = {
    22,
    24,
    25,
    26,
    27,
    28,
    29,
    30,
    31,
    32,
    33
};
byte input_pins[] = {
    34, 35,
    36, 37,
    38, 39,
    40, 41,
    42, 43,
    44, 45
};

//cheap keyboards often has the black keys softer or harder than the white ones
//uncomment the next line to allow a soft correction
//#define BLACK_KEYS_CORRECTION

#ifdef BLACK_KEYS_CORRECTION
#define MULTIPLIER 192 // 127 is the central value (corresponding to 1.0)
byte black_keys[] = {
    0,1,0,1,0,0,1,0,1,0,1,0,
    0,1,0,1,0,0,1,0,1,0,1,0,
    0,1,0,1,0,0,1,0,1,0,1,0,
    0,1,0,1,0,0,1,0,1,0,1,0,
    0,1,0,1,0,0,1,0,1,0,1,0,
    0
};
#endif

//uncomment the next line to inspect the number of scans per seconds
//#define DEBUG_SCANS_PER_SECOND

/*
426 cyles per second (2,35ms per cycle) using standard digitalWrite/digitalRead
896 cyles per second (1,11ms per cycle) using DIO2 digitalWrite2/digitalRead2
*/

//uncoment the next line to get text midi message at output
//#define DEBUG_MIDI_MESSAGE

byte          keys_state[KEYS_NUMBER];
unsigned long keys_time[KEYS_NUMBER];
boolean       signals[sizeof(input_pins) * sizeof(output_pins)];
boolean       pedal_enabled;

void setup() {
    Serial.begin(115200);
    pinMode(13, OUTPUT);
    digitalWrite(13, LOW);
    int i;
    for (i = 0; i < KEYS_NUMBER; i++)
    {
        keys_state[i] = KEY_OFF;
        keys_time[i] = 0;
    }
    for (byte pin = 0; pin < sizeof(output_pins); pin++)
    {
        pinMode(output_pins[pin], OUTPUT);
    }
    for (byte pin = 0; pin < sizeof(input_pins); pin++)
    {
        pinMode(input_pins[pin], INPUT);
    }
    pinMode(PEDAL_PIN, INPUT);
    pedal_enabled = digitalRead(PEDAL_PIN) != HIGH;
}

void send_midi_event(byte status_byte, byte key_index, unsigned long time)
{
    unsigned long t = time;
#ifdef BLACK_KEYS_CORRECTION
    if (black_keys[key_index])
    {
        t = (t * MULTIPLIER) >> 7;
    }
#endif
    if (t > MAX_TIME_MS)
        t = MAX_TIME_MS;
    if (t < MIN_TIME_MS)
        t = MIN_TIME_MS;
    t -= MIN_TIME_MS;
    unsigned long velocity = 127 - (t * 127 / MAX_TIME_MS_N);
    byte vel = (((velocity * velocity) >> 7) * velocity) >> 7;
    byte key = 36 + key_index;
#ifdef DEBUG_MIDI_MESSAGE
    char out[32];
    sprintf(out, "%02X %02X %03d %d", status_byte, key, vel, time);
    Serial.println(out);
#else
    Serial.write(status_byte);
    Serial.write(key);
    Serial.write(vel);
#endif
}

void loop() {
#ifdef DEBUG_SCANS_PER_SECOND
    static unsigned long cycles = 0;
    static unsigned long start = 0;
    static unsigned long current = 0;
    cycles++;
    current = millis();
    if (current - start >= 1000)
    {
        Serial.println(cycles);
        cycles = 0;
        start = current;
    }
#endif
    byte pedal = LOW;
    if (pedal_enabled)
    {
        pedal = digitalRead2(PEDAL_PIN);
    }
   
    boolean *s = signals;
    for (byte section_index = 0; section_index < sizeof(input_pins); section_index += 2)
    {
        for (byte o = 0; o < sizeof(output_pins); o++)
        {
            byte output_pin = output_pins[o];
            for (byte i = 0; i < 2; i++)
            {
                byte input_pin = input_pins[section_index + i];
                digitalWrite2(output_pin, LOW);
                *(s++) = !digitalRead2(input_pin);
                digitalWrite2(output_pin, HIGH);
            }
        }
    }

    byte          *state  = keys_state;
    unsigned long *ktime  = keys_time;
    boolean       *signal = signals;
    for (byte key = 0; key < KEYS_NUMBER; key++)
    {
        for (byte state_index = 0; state_index < 2; state_index++)
        {
            switch (*state)
            {
            case KEY_OFF:
                if (state_index == 0 && *signal)
                {
                    *state = KEY_START;
                    *ktime = millis();
                }
                break;
            case KEY_START:
                if (state_index == 0 && !*signal)
                {
                    *state = KEY_OFF;
                    break;
                }
                if (state_index == 1 && *signal)
                {
                    *state = KEY_ON;
                    send_midi_event(0x90, key, millis() - *ktime);
                }
                break;
            case KEY_ON:
                if (state_index == 1 && !*signal)
                {
                    *state = KEY_RELEASED;
                    *ktime = millis();
                }
                break;
            case KEY_RELEASED:
                if (state_index == 0 && !*signal)
                {
                    if (pedal)
                    {
                        *state = KEY_SUSTAINED;
                        break;
                    }
                    *state = KEY_OFF;
                    send_midi_event(0x80, key, millis() - *ktime);
                }
                break;
            case KEY_SUSTAINED:
                if (!pedal)
                {
                    *state = KEY_OFF;
                    send_midi_event(0x80, key, MAX_TIME_MS);
                }
                if (state_index == 0 && *signal)
                {
                    *state = KEY_SUSTAINED_RESTART;
                    *ktime = millis();
                }
                break;
            case KEY_SUSTAINED_RESTART:
                if (state_index == 0 && !*signal)
                {
                    *state = KEY_SUSTAINED;
                    digitalWrite(13, HIGH);
                    break;
                }
                if (state_index == 1 && *signal)
                {
                    *state = KEY_ON;
                    send_midi_event(0x80, key, MAX_TIME_MS);
                    send_midi_event(0x90, key, millis() - *ktime);
                }
                break;
            }
            signal++;
        }
        state++;
        ktime++;
    }
}