如何解决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 事件之间的时间转换为速度。这是一个线性转换函数。您需要通过测量找到适合您键盘的 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,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 举报,一经查实,本站将立刻删除。