0

我希望你做得很好,我正在尝试制作一个 MIDI 钢琴键盘,非常基本,我按下一个键,发送 MIDI 信号并发出声音。

但是我想将速度添加到我的键上,每个键有一个联系人(我使用的键盘是 Fatar 键盘)。

我需要计算第一次接触和第二次接触之间的时间间隔(下面附有电路图)。

  • 所有键都设置为输入 pull_up
  • 当一个键被按下时,它会变低……当然

下面提到的是我正在读取键的功能。我需要做什么才能完成以下情况

[它们是 49 个按键,排列成两个二极管矩阵。矩阵中实际上有 98 个开关。这样做的原因是每个键下都有两个开关。当一个键被按下时,其中一个开关在另一个开关之前稍微闭合。通过测量开关之间的飞行时间,我们可以推导出速度]


情况一

  • 键被按下
  • 开始时间
  • 按下多长时间的时间
  • 密钥释放

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

键盘电路图

4

2 回答 2

0

您可以为您的密钥添加一个状态变量,以跟踪您的密钥的位置。然后,您可以在从 not_pressed 转换到 half_pressed 时启动计时器。然后,您评估从 half_pressed 到 full_pressed 过渡的速度。

您还应该添加一个超时,将其重置为未按下,以防错过按键。

但我不确定添加这种逻辑后你的循环是否足够快。

于 2021-04-19T20:44:34.973 回答
0

这是一个假设,如果键盘演奏者按住一个键,接触针将保持LOW不变,并且会有 3 个有趣的状态变化

  • First HIGH->LOW : 第一次接触 - 使用记录当前时间millis()
  • Second HIGH->LOW : 第二次接触 - 计算速度并发送键。
  • 第三个 HIGH->LOW :释放触点 - 发送键关闭

由于实际上似乎不可能知道是contact1还是contact2导致引脚消失LOW,因此它非常敏感。如果您在按住某个键的同时启动程序,这将导致程序认为这是第一次接触 - 之后的一切都会搞砸。

首先,您需要将两个接触事件之间的时间转换为速度。这是一个线性转换函数。您需要通过测量为您的键盘找到合适的min常数max

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)
}

然后,您可以创建一个类来单独跟踪一个键的状态。它比拥有许多单独的数组更容易。

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

然后全局定义这些:

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

有了它,你的read_keys()函数可能看起来像这样:

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

可以这样做,以便每个Key对象都知道它实际上是哪个键号,以便它可以读取自己的引脚等。它还可以返回一个Event对象,该对象从关闭输出主键开始,并在它被破坏时重新打开 -但我认为这让它更加开放,因为我不太了解为什么应该关闭和打开输出主键。

免责声明:我当然无法对此进行测试 - 所以将其视为构建块。

于 2021-04-20T17:28:15.353 回答