C++ Arduino-测量按下和释放一个按钮之间的时间间隔-将速度添加到MIDI键盘
我希望你们都做得很好,我正在尝试制作一个MIDI钢琴键盘,非常基本,我按下一个键,MIDI信号发出,声音响起 但是我想给我的键增加速度,每个键有一个触点(我使用的是法塔键盘) 我需要计算第一个触点和第二个触点之间的时间间隔(电路图附在下面)。C++ Arduino-测量按下和释放一个按钮之间的时间间隔-将速度添加到MIDI键盘,c++,arduino,midi,piano,C++,Arduino,Midi,Piano,我希望你们都做得很好,我正在尝试制作一个MIDI钢琴键盘,非常基本,我按下一个键,MIDI信号发出,声音响起 但是我想给我的键增加速度,每个键有一个触点(我使用的是法塔键盘) 我需要计算第一个触点和第二个触点之间的时间间隔(电路图附在下面)。 所有按键都设置为输入上拉 当按下一个键时,它会变低…当然 下面提到的是我读取按键的功能。我需要做什么才能完成以下情况 [它们是49个键,排列在两个二极管矩阵中。矩阵中实际上有98个开关。原因是每个键下有两个开关。按下一个键时,其中一个开关比另一个稍早关
- 所有按键都设置为输入上拉
- 当按下一个键时,它会变低…当然
情景1
- 按键已按下
- 开始时间
- 时间紧迫多久
- 密钥释放
code
无效读取密钥(){
对于(uint8_t key=0;key<49;key++){
digitalWrite(输出主键,低);//关闭输出主键
if(数字读取(输入\上拉[键])==低){
//检查主输入键是否按下
//用钥匙检查激活的阵列
firstcontactdownmills=millis();
Serial.println(key);
速度=地图(currentmills-firstcontactdownmills,0,256,127,0);
如果(按键激活[按键]==0){
//按命令发送midi
我的midi.sendNoteOn(键+音符偏移量,速度,1);
主midi.sendNoteOn(键+音符偏移,速度,1);
//更新数组
按键激活[按键]=1;
}
}
否则{//如果密钥已释放
//用钥匙检查激活的阵列
如果(按键激活[按键]==1){
//发送midi关闭命令
我的midi.sendNoteOff(键+音符偏移量,0,1);
主midi.sendNoteOff(键+音符偏移量,0,1);
//更新数组
键被激活[键]=0;
}
}
digitalWrite(输出主键,高);//打开输出主键
}
}
您可以将状态变量添加到关键点,以跟踪关键点的位置。然后,您可以在从未按到半按的转换过程中启动计时器。然后计算从半压到全压过渡时的速度 您还应该添加一个超时,以便在错过按键时将其重置回未按下状态
但我不确定添加这种逻辑后,您的循环是否足够快。这里有一个想法,假设键盘播放器按下一个键,触针将保持
低位,并且将有3个有趣的状态变化
- 第一次高->低:第一次接触-使用
millis()记录当前时间
- 第二次高->低:第二次接触-计算速度并发送钥匙
- 第三高->低:释放触点-发送键关闭
因为它似乎不可能真正知道是触点1还是触点2导致pin变低,所以它非常敏感。如果你按下一个键启动程序,会让程序认为这是第一次接触,之后的一切都会一团糟
首先,需要将两次接触事件之间的时间转换为速度。这是一个线性转换函数。您需要通过测量找到键盘的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)
}
然后全局定义这些:
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
}
}
void read_keys(){
对于(uint8_t key=0;key
这可以使每个键
对象知道它实际上是哪个键号,这样它就可以读取自己的pin码等。它还可以返回一个事件
对象,该事件从关闭输出主键开始,当它被破坏时再打开它-但我认为这会使它更开放,因为我不太了解它为什么输出主键应该关闭和打开
免责声明:我当然无法对此进行测试,所以请将其视为构建块。情况2:您可能应该放弃按键。如果用户按下的按键刚好足以关闭第一个联系人,但不足以关闭联系人2,则这是一个非常软的按键-用户可以在该位置按住按键很长一段时间,直到松开为止。甚至不要为此发送NodeOn
。只关心联系1和联系2之间的时间,这将为您提供所需的信息,以便使用v发送备注
constexpr std::uint8_t kNumberOfKeys = 49;
Key keys[kNumberOfKeys];
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
}
}