Arduino - Measuring the time interval between one button press and release - Add Velocity to MIDI Keyboard

855 Views Asked by At

I hope you are doing all really well, I am trying to make a MIDI Piano Keyboard, Pretty basic I press a key the MIDI signal is send and the sounds comes.

But I want to add the velocity to the my keys, there are one contacts per key (the keyboard I am using is Fatar Keyboard).

I need to calculate the time interval between the first contact and then the second contact ( Circuit Diagram is attached below).

  • All the keys are in set as input pull_up
  • when a key is pressed it goes low … Of course

Mentioned Below is the function where I am reading the keys. what do I need to do to get the following situation done

[they are 49 keys which are arranged in to two diode matrices. There are actually 98 switches in the matrix. The reason for this is that there are two switches under each key. When a key is pressed, one of the switches closes slightly before the other one. By measuring the time of flight between the switches we can derive the velocity]


Situation 1

  • Key is pressed
  • Start Time
  • Time for how long its pressed
  • Key Released

Code

        void read_keys() {
    
     for (uint8_t key = 0; key < 49; key ++) {
        digitalWrite(output_main[key], LOW); //turn off output main key
        
            if (digitalRead(input_pullup[key]) == LOW) {
              //check main input key is presses
              //check with key_activated array
              firstcontactdownmills = millis();
              Serial.println(key);
              Velocity = map(currentmills - firstcontactdownmills, 0, 256, 127, 0);
              if (key_activated[key] == 0) {
    
                //send midi on command
                my_midi.sendNoteOn(key + note_offset, Velocity, 1);
                main_midi.sendNoteOn(key + note_offset, Velocity, 1);
                //update array
                key_activated[key] = 1;
              }
            }
        
            else { //if key released
              //check with key_activated array
              if (key_activated[key] == 1) {
        
                //send midi off command
                my_midi.sendNoteOff(key + note_offset, 0, 1);
                main_midi.sendNoteOff(key + note_offset, 0, 1);
                //update array
                key_activated[key] = 0;
              }
            }
            digitalWrite(output_main[key], HIGH); //turn on output main key
          }
        }

Circuit Diagram of the Keyboard

2

There are 2 best solutions below

1
On

You can add a state variable to your keys, that keeps track of where your key is. You can then start the timer at the transition from not_pressed to half_pressed. You then evaluate the velocity at a transition from half_pressed to full_pressed.

You should also add a timeout that resets it back to unpressed in case a key press is missed.

But I am not sure if your loop will be fast enough after adding this kind of logic.

32
On

Here's an idea that assumes that if a keyboard player is holding down a key, the contact pin will stay LOW and that there will be 3 interesting state changes

  • First HIGH->LOW : First contact - record the current time using millis()
  • Second HIGH->LOW : Second contact - calculate velocity and send key on.
  • Third HIGH->LOW : Release contact - send key off

Since it doesn't seem possible to actually know if it's contact1 or contact2 that causes the pin to go LOW, it's very sensitive. If you start the program while holding down a key, that will cause the program to think it's the first contact - and everything after that will be messed up.

First, you need something to convert the time between two contact events into velocity. Here's a linear conversion funciton. You'll need to find the proper constants for min and max for your keyboard by measuring.

unsigned char calc_velocity(unsigned ms) {
    static constexpr unsigned min = 2;   // the fastest time you've measured
    static constexpr unsigned max = 80;  // the slowest time you've measured
    static constexpr unsigned mul = 127000 / (max - min);

    if(ms < min) return 127; // harder than "possible", log and recalibrate
    if(ms > max) return 0;   // softer than "possible", log and recalibrate

    return (127000 - ((ms - min) * mul)) / 1000; // 0(min vel) - 127(max vel)
}

Then, you could create a class to keep track of the state for one key alone. It makes it easier than to have a lot of separate arrays.

// an enum for keyboard events
enum class Event { ev_nothing, ev_key_on, ev_key_off };

struct Key {
    unsigned long first_contact{};
    int contact_count = 0;
    unsigned char velocity{};
    bool contact_state = false;

    // set contact state and return an Event to act upon
    Event set(bool contact) { // false = no contact, true = contact

        // if the state has not changed, do nothing
        if(contact == contact_state) return Event::ev_nothing;

        contact_state = contact;                        // set the new state

        // only care about when state changes to having contact
        if(contact_state) {
               
            // count HIGH->LOW transitions
            contact_count = (contact_count + 1) % 3;

            // 1 = first contact
            // 2 = second contact (key on)
            // 0 = release contact (key off)

            switch(contact_count) {
            case 2:                                     // second contact
                velocity = calc_velocity(millis() - first_contact);
                return Event::ev_key_on;
            case 0: return Event::ev_key_off;           // release contact
            case 1: first_contact = millis();           // first contact
            }
        }
        return Event::ev_nothing;
    }
};

Then define these globally:

constexpr std::uint8_t kNumberOfKeys = 49;
Key keys[kNumberOfKeys];

With that, your read_keys() function could look this this:

void read_keys() {    
    for (uint8_t key = 0; key < kNumberOfKeys; ++key) {
        digitalWrite(output_main[key], LOW); //turn off output main key

        // Do a digitalRead() and call the set() function for that key which will
        // return an Event.

        switch(keys[key].set( digitalRead(input_pullup[key]) == LOW )) {
        case Event::ev_key_on:
            my_midi.sendNoteOn(key + note_offset, keys[key].velocity, 1);
            main_midi.sendNoteOn(key + note_offset, keys[key].velocity, 1);
            break;

        case Event::ev_key_off:
            my_midi.sendNoteOff(key + note_offset, 0, 1);
            main_midi.sendNoteOff(key + note_offset, 0, 1);
            break;

        case Event::ev_nothing:
        default:
            break;
        }

        digitalWrite(output_main[key], HIGH); //turn on output main key
    }
}

This could be made so that each Key object would know which key number it actually is and so that it could read its own pin etc. It could also return an Event object which starts by turning off output main key and turns it back on when it's destroyed - but I think this leaves it more open since I don't know much about why the output main key should be turned off and on.

Disclaimer: I've ofcourse not been able to test this - so see it as building blocks.