我已经很多年没有做过任何 MIDI 编程了,但是你的基本想法非常合理(没有双关语)。
MIDI 是“事件”(或“消息”)流,其中两个最基本的是“音符打开”和“音符关闭”,它们带有音符编号(0 = C 比中音 C 低五个八度,到 127 = G 比中音 C 之上的 G 高五个八度,以半音表示)。这些事件在对力度敏感(“触摸敏感”)的键盘上带有一个“力度”数字,力度(您猜对了)在 0 到 127 之间。
在力度、和弦和踏板之间,我认为你可以为钢琴键盘设计出一个相当好的“打字”界面。和弦尤其可能是一种非常强大的技术——正如我在评论中提到的,这就是为什么普通速记员可以使用速记机 http://en.wikipedia.org/wiki/Stenotype为了跟上人们连续几个小时的交谈,而即使是顶级打字员也无法通过普通打字机式键盘进行任何长时间的交谈。与机器速记一样,您需要一本关于和弦含义和和弦序列的“字典”。 (你能看出我曾经在机器速记软件方面工作吗?)
为此,基本部分是:
- 接收 MIDI 输入。不要尝试自己执行此操作,请使用库。Edit:显然,Java Sound API支持MIDI http://download.oracle.com/javase/6/docs/api/javax/sound/midi/package-summary.html,包括接收来自 MIDI 控制器的事件。凉爽的。这一页 http://www.jsresources.org/faq_midi.html可能也有用。
- 将该数据转换为您想要发送的击键,例如通过我上面提到的字典。
- 将击键输出到计算机。
为了与软件最广泛地兼容,您必须将其编写为键盘设备驱动程序。这是操作系统的插件,用作键盘事件的源,与底层硬件(在您的例子中为钢琴键盘)对话。对于 Windows 和 Linux,您可能会想要使用 C 语言。
然而,既然你只是生成击键(不是试图拦截它们,这是我几年前尝试做的),您也许可以使用操作系统具有的任何功能来发送人工击键。 Windows 有一个接口可以做到这一点(可能有几个,我正在考虑的是SendInput http://msdn.microsoft.com/en-us/library/ms646310%28v=vs.85%29.aspx但我知道有一些“日记”界面可以做类似的事情),而且我确信其他操作系统也可以。这可能足以满足您的目的 - 这就是我要开始的地方,因为设备驱动程序路线将会很尴尬,并且您可能必须使用与 Java 不同的语言。 (我是 Java 的忠实粉丝,但操作系统用来与设备驱动程序通信的接口往往更容易通过 C 等语言使用。)
Update:更多关于和弦到击键的“字典”:
基本上,字典是一个trie http://en.wikipedia.org/wiki/Trie(谢谢,@Adam)我们使用最长前缀匹配进行搜索。细节:
在机器速记中,速记员通过同时按下速记机上的多个键,然后全部释放来进行书写。他们称之为键盘的“敲击”;这就像在钢琴上弹奏和弦一样。笔画经常(但并非总是)对应于口语的音节。就像音节一样,有时一个笔画(和弦)本身就有意义,有时它只与后面的笔画组合在一起才有意义。 (想想“好”与“好”后跟“再见”)。尽管他们会受到所就读学校的严重影响,但每个速记员都会有自己的“字典”,记录他们用什么笔画表示什么,这是他们在工作过程中不断磨练的字典。字典中会有速记部分(简称“steno”)为一笔长或多笔长的条目。通常,会有多个具有相同起始笔画的条目,这些条目通过它们的长度和后续笔画来区分。例如(我不会在这里使用真正的速记,只是占位符),可能有这些条目:
A = alpha
A/B = alphabet
A/B/C = alphabetic
A/C = air conditioning
B = bee
B/C = because
C = sea
D = dog
D/D = Dee Dee
(这些字母并不是音符,只是抽象标记。)
注意A
开始多个条目,还要注意您如何翻译C
中风取决于您以前是否见过A
, a B
,或者你正在重新开始。
另请注意(尽管上面的小示例中未显示),“播放”同一单词或短语可能有多种方式,而不仅仅是一种。速记员这样做是为了根据手的位置更容易从前一个单词流到下一个单词。这与音乐有一个明显的类比,您可以使用它来使您的打字流程更类似于演奏音乐,以便防止这种情况对您的钢琴演奏产生负面影响,并最大限度地提高实际上有助于 RSI 的可能性。
将速记翻译成标准文本时,我们再次使用“最长前缀匹配”搜索:翻译算法从所写的第一个笔画开始,并查找以该笔画开头的条目。如果只有一个条目,并且只有一笔长,那么我们可以可靠地说“这就是要使用的条目”,输出相应的文本,然后从下一笔开始。但更有可能的是,该笔划开始了不同长度的多个条目。因此,我们查看下一个笔画,看看是否有按顺序以这两个笔画开头的条目;依此类推,直到我们找到匹配项。
因此,通过上面的字典,假设我们看到了这个序列:
A C B B C A B C A B D
我们的翻译方式如下:
-
A
是三个不同长度条目的开始;看下一个笔画:C
-
A/C
仅匹配一项;输出“空调”并从下一个行程开始:B
-
B
开始两个条目;看下一个笔画:B
-
B/B
不启动任何东西;取最长的上一场比赛(B
)并输出(“蜜蜂”)
- 有输出
B
=“蜜蜂”,我们还有一个B
我们的缓冲区中的行程。它开始了两个条目,所以看看下一个笔画:C
-
B/C
匹配一个条目;输出“because”并从下一个笔画开始:A
-
A
开始三个条目;看下一笔:B
-
A/B
开始两个条目;看下一笔:C
-
A/B/C
只匹配一个条目;输出“字母”并从下一个笔画开始:A
-
A
开始三个条目;看下一个笔画:B
-
A/B
开始两个条目;看下一个笔画:D
-
A/B/D
不匹配任何内容,因此取最长的前一个匹配项(A/B
)并用它来输出“字母表”。这给我们留下了D
仍在缓冲区中。
-
D
开始两个条目,所以我们通常会查看下一个笔画 - 但我们已经处理了所有笔画,所以单独考虑它。孤立地看,它翻译为“狗”,所以输出它。
以上需要注意的几个方面:
- 您有已读过但尚未翻译的笔画缓冲区。
- 您总是希望尽可能地将最多的笔划与单个条目进行匹配。
A/B
应该翻译为“alphabet”,而不是“alpha”和“bee”。
- (上面未显示)您很可能有无法翻译的笔划序列,因为它们与字典中的任何内容都不匹配。 (速记人使用名词“untranslate”——例如,在我们的字典中,笔划
E
将是一个“未翻译”。)
- (上面未显示)一些速记理论允许基于更广泛的上下文,同一组笔划可以表示多个事物。速记人称这些为“冲突”。你可能想在你的项目中禁止它们,事实上,当速记员过去手动翻译速记时,冲突是很好的,因为他们知道句子中的哪个位置是正确的选择,但随着在机器翻译中,无冲突速记理论的出现是为了避免必须仔细检查最终的翻译文本并“修复”冲突。
- 实时翻译(您正在做的)意味着,如果您收到部分匹配,您将希望在等待下一个和弦时保留它 - 但可能只到超时,此时您会尽可能翻译缓冲区中的内容。 (或者也许您不想要超时;这是您的决定。)
- 可能最好的中风是“忽略之前的中风”
- 可能最好有一个笔划表示“完全清除缓冲区而不输出任何内容”