微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Arduino - 测量一键按下和释放之间的时间间隔 - 向 MIDI 键盘添加力度

如何解决Arduino - 测量一键按下和释放之间的时间间隔 - 向 MIDI 键盘添加力度

我希望你做得很好,我正在尝试制作一个 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,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,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,1);
                main_midi.sendNoteOff(key + note_offset,1);
                //update array
                key_activated[key] = 0;
              }
            }
            digitalWrite(output_main[key],HIGH); //turn on output main key
          }
        }

Circuit Diagram of the Keyboard

解决方法

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

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

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

,

这里有一个想法,假设如果键盘手按住一个键,触针将保持 LOW 并且会有 3 个有趣的状态变化

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

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

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

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,1);
            break;

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

        case Event::ev_nothing:
        default:
            break;
        }

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。