我已经很多年没有做过任何 MIDI 编程了,但是你的基本想法非常合理(没有双关语)。
MIDI 是一个“事件”(或“消息”)流,其中最基本的两个是“音符开”和“音符关”,它们带有音符编号(0 = C 中间 C 以下五个八度,到 127 = G 比 G 高出中间 C 五个八度,以半音为单位)。这些事件在对速度敏感(“触敏”)的键盘上带有一个“速度”数字,其力度(您猜对了)在 0 到 127 之间。
在力度、和弦和踏板之间,我认为您可以为钢琴键盘设计一个相当不错的“打字”界面。尤其是和弦可能是一种非常强大的技术——正如我在 cmets 中提到的,这就是为什么普通速记员可以使用stenotype machine 来跟上人们连续几个小时的谈话,而即使是顶级打字员也不会无法通过普通打字机式键盘长时间使用。与机器速记一样,您需要一本关于和弦含义和和弦序列的“字典”。 (你能告诉我以前在机器速记的软件方面工作吗?)
要做到这一点,基本的部分是:
- 接收 MIDI 输入。不要尝试自己做,使用图书馆。 编辑:显然,Java Sound API supports MIDI,包括从 MIDI 控制器接收事件。凉爽的。 This page 也可能有用。
- 将该数据转换为您要发送的击键,例如通过我上面提到的字典。
- 将击键输出到计算机。
要最广泛地与软件兼容,您必须将其编写为键盘设备驱动程序。这是操作系统的插件,用作键盘事件的来源,与底层硬件(在您的情况下为钢琴键盘)对话。对于 Windows 和 Linux,您可能会想要使用 C。
但是,由于您只是生成击键(而不是试图拦截它们,我多年前就尝试这样做),因此您可以使用操作系统具有的任何功能来发送人工击键。 Windows 有一个用于执行此操作的界面(可能有几个,我想到的是SendInput,但我知道有一些“日志”界面可以做类似的事情),我相信其他操作系统也可以。这对你的目的来说可能已经足够了——这就是我要开始的地方,因为设备驱动程序路由会很尴尬,而且你可能不得不使用与 Java 不同的语言。 (我是 Java 的忠实粉丝,但操作系统用来与设备驱动程序对话的接口往往更容易通过 C 和类似语言使用。)
更新:更多关于和弦到击键的“字典”:
基本上,字典是我们使用最长前缀匹配搜索的trie(感谢@Adam)。详情:
在机器速记中,速记员通过同时按下速记机上的多个键,然后将它们全部释放来书写。他们称之为键盘的“敲击”;这就像在钢琴上弹奏和弦。笔画经常(但不总是)对应于口语的音节。像音节一样,有时一个笔画(和弦)本身就具有全部意义,有时它仅具有与以下笔画结合的意义。 (想想“好”与“好”,然后是“再见”)。尽管他们会受到所就读学校的严重影响,但每个速记员都会有自己的“词典”,了解他们用什么笔画来表示什么,这是一本他们将在工作生涯中不断磨练的词典。字典将包含速记部分(简称“速记”)为一笔长或多笔长的条目。通常,会有多个条目具有相同的起始笔划,它们的长度和后续笔划不同。例如(我不会在这里使用真正的速记,只是占位符),可能有这些条目:
A = 阿尔法
A/B = 字母
A/B/C = 字母
A/C = 空调
B = 蜜蜂
B/C = 因为
C = 海
D = 狗
D/D = 迪迪
(这些字母不是音符,只是抽象标记。)
请注意A 开始多个条目,还请注意,如何翻译C 笔划取决于您之前是否见过A、B,或者您是重新开始。
另请注意(尽管上面的小示例中没有显示),可能有多种方式可以“播放”同一个单词或短语,而不仅仅是一种。速记员这样做是为了更容易根据手的位置从前面的单词流到下一个单词。那里有一个明显的音乐类比,您可以使用它来使您的打字流程更类似于演奏音乐,以防止这对您的钢琴演奏产生负面影响,并最大限度地提高这实际上有助于 RSI 的可能性。
在将 steno 翻译成标准文本时,我们再次使用“最长前缀匹配”搜索:翻译算法从曾经书写的第一个笔画开始,并查找以该笔画开头的条目。如果只有一个条目,而且只有一个笔划,那么我们可以可靠地说“那是要使用的条目”,输出相应的文本,然后从下一个笔划重新开始。但更有可能的是,该笔画开始了多个不同长度的条目。所以我们看下一个笔画,看看是否有以这两个笔画开头的条目按顺序排列;以此类推,直到我们找到匹配项。
所以有了上面的字典,假设我们看到了这个序列:
A C B B C A B C A B D
我们是这样翻译的:
-
A 是三个不同长度条目的开始;看下一笔:C
-
A/C 只匹配一个条目;输出“空调”并从下一个笔划重新开始:B
-
B 开始两个条目;看下一笔:B
-
B/B 不启动任何东西;取上一个最长的匹配 (B) 并输出 ("bee")
- 输出
B = "bee",我们的缓冲区中仍有B 笔划。它开始两个条目,所以看下一个笔划:C
-
B/C 匹配一个条目;输出“因为”并从下一个笔划重新开始: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”。
- (上面未显示)您可能有无法翻译的笔画序列,因为它们与字典中的任何内容都不匹配。 (Steno 人使用名词“未翻译”——例如,在我们的字典中,笔画
E 将是“未翻译”。)
- (上面未显示)一些速记理论允许同一组笔画在更广泛的背景下具有多个含义。 Steno 人称这些为“冲突”。你可能想在你的项目中禁止它们,事实上,当速记员过去手动翻译速记时,冲突很好,因为他们会知道句子中的哪个位置是正确的选择,但随着增加在机器翻译中,无冲突速记理论的出现是为了避免必须仔细检查生成的翻译文本并“修复”冲突。
- 实时翻译(您会这样做)意味着如果您收到部分匹配,您会希望在等待下一个和弦时保持它 - 但可能只到超时,此时你会尽可能地翻译缓冲区中的内容。 (或者您可能不想超时;这是您的决定。)
- 最好有一个写着“忽略上一个笔画”的笔画
- 最好有一个写着“完全清除缓冲区而不输出任何内容”的笔划