NPR 上 Dan Tepfer 的钢琴世界 启发我探索自己的增强型钢琴演奏宇宙。我是否可以编写一个程序,实时学习以我的风格在我的演奏的音乐间隙中即兴创作?
以下文章几乎是对 PIanoAI 制作过程的逐一记录。 如果您只想试用 PIanoAI,请转到 GitHub 获取说明和下载。 否则,请留在这里了解我的许多编程失误。
灵感
在观看了 NPR 上 Dan Tepfer 的视频 后,我截取了他的电脑的定格画面,并了解到他使用 Processing 软件 连接到他的(非常精美的)MIDI 键盘。
我很容易地用我自己的(不精美的)MIDI 键盘组装了 类似的东西。
正如视频中所解释的那样,Dan 的增强型钢琴演奏基本上允许您以指定的模式镜像或回声某些音符。 他的许多模式似乎都与歌曲有关。 经过考虑,我决定我对一些不同的东西感兴趣:我想要一个钢琴伴奏,它可以实时学习模仿我的风格并在任何歌曲中我的演奏的空白处即兴演奏。
我不是第一个制作钢琴 AI 的人。 谷歌制作了 A.I. Duet,这是一个很棒的程序。 但是我想看看我是否可以制作一个专门针对我自己的风格调整的 AI。
进入人工智能之旅
我的设备没什么特别的。 我是从一个放弃学习弹钢琴的人那里二手买来的。 几乎任何 MIDI 键盘和 MIDI 适配器都可以。
我通常需要至少做两次才能做对每件事。 我还需要清楚地画出一切才能完全理解我在做什么。 因此,我编写程序的过程基本上如下
- 在纸上画出来。
- 用 Python 编写程序。
- 重新开始。
- 再次在纸上画出来。
- 用 Go 编写程序。

12 页纸中的前两页用于弄清楚我在做什么
每次我在纸上画出我的想法时,大约需要三张纸才能让我的想法真正开始成形。
用 Python 编程钢琴 AI
一旦我了解了我在做什么,我就在 一组 Python 脚本中实现了所有内容。 这些脚本建立在 Pygame 之上,它对 MIDI 有很好的支持。 这个想法非常简单 —— 有两个线程:节拍器和监听器。 监听器简单地记录主机演奏的音符。 节拍器会一直滴答作响并播放队列中的任何音符,或者如果没有音符在队列中,则要求 AI 发送一些新音符。
我使其具有一定的可插拔性,因为您可以对 AI 进行变体,因此可以轻松地配备不同的钢琴增强功能。 有一种算法可以简单地回声,一种用于演奏和弦结构中的音符(在它确定和弦之后),还有一种用于从 马尔可夫链生成钢琴跑动。 这是我使用此算法的后一个版本的影片(当我的右手远离键盘时,AI 开始演奏,直到我再次演奏)
我的第一个钢琴演奏 AI
关于这一点,我有一些不喜欢的地方。 首先,它不好。 充其量,钢琴 AI 伴奏听起来像一个小孩努力模仿我的演奏(我认为这有几个原因,基本上涉及不考虑力度数据和过渡时间)。
其次,这些 Python 脚本无法在 Raspberry Pi 上运行; 该视频是在我使用 Windows 的情况下拍摄的。 我不知道为什么。 我在 Python 3.4 上遇到了麻烦,所以我升级到了 3.6。 使用 Python 3.6,我仍然遇到了奇怪的问题。 pygame.fastevent.post 可以工作,但是 pygame.fastevent.get 不能工作。 我对此感到沮丧并找到了替代方案。
替代方案是用 Go 编写。 Go 明显比 Python 快,这非常有用,因为这是一个低延迟应用程序。 我的耳朵可以分辨出 20 毫秒的差异,所以我希望将处理时间保持在最低限度。 我找到了 Go MIDI 库,因此移植非常可行。
用 Go 编程钢琴 AI
我决定简化一下。 我不会制作许多具有不同算法的模块,而是专注于我最感兴趣的一个:一个可以实时学习以在我的演奏的空白处即兴演奏的程序。 我拿出更多的纸张并开始了。
第 1 天
大多数代码与我之前的 Python 脚本相同。 在用 Go 编写时,我发现生成线程比在 Python 中容易得多。 线程遍布这个程序。 有用于监听 MIDI 的线程、用于播放音符的线程以及用于跟踪音符的线程。 我很想在我的线程中使用全新的 Go 1.9 sync.Map,但我意识到我可以利用地图的地图,这超出了 sync.Map 的复杂性。 尽管如此,我只是制作了一个类似于我编写的另一个同步地图存储(schollz/jsonstore)的地图的地图。
我试图使一切都变得优雅(双关语),因此我将组件(MIDI、音乐、AI)实现为具有自己功能的自己的对象。 到目前为止,MIDI 侦听效果很好,并且似乎响应迅速。 我还实现了播放功能,这些功能也可以工作。 这很容易做到。
第 2 天
我首先将所有代码重构到文件夹中,因为我想为每个对象保留 New 函数。 这些对象已经固化; 有一个 AI 用于学习和生成乐句,一个 Music 对象用于钢琴音符模型,一个 Piano 对象用于与 MIDI 通信,以及一个 Player 对象用于将所有内容整合在一起。
我花了很多时间用笔和纸弄清楚 AI 应该如何工作。 我意识到用钢琴音符制作马尔可夫链的方法不止一种。 钢琴音符有四个基本属性:音高、力度、持续时间和延迟(到下一个音符的时间)。 钢琴音符的基本马尔可夫链将包含四个不同的马尔可夫链,每个属性一个。 它的图示如下

这是钢琴属性的基本马尔可夫链。
在此,下一个音符 (P2) 的下一个音高由上一个音符 (P1) 的音高决定。 力度 (V1/V2)、持续时间 (D1/D2) 和延迟 (L1/L2) 也是如此。 实际的马尔可夫链只是枚举每个属性值的相对出现频率,并使用随机选择器来选择一个; 但是,钢琴属性不一定是独立的。 有时,音高和力度之间或力度和音符的持续时间之间存在关系。 为了解决这个问题,我允许不同的耦合。 您可以将属性耦合到任何其他属性的当前值或上一个值。 目前我只允许两个耦合,因为这已经足够复杂了。 但从理论上讲,您可以将下一个音高的值耦合到上一个音高、力度、持续时间和延迟。
一旦我在理论上弄清楚了一切,我就开始实现 AI。 AI 只是一个马尔可夫链,因此它确定一个音符属性的相对频率表,并具有一个从累积概率和随机数计算它们的函数。 在晚上结束时,它奏效了! 嗯,有点... 这是我演奏一段乐句并获得 AI 钢琴跑动的傻视频
使用基本马尔可夫链进行钢琴伴奏的示例。
看来我明天还有更多改进要做。
第 3 天
我找不到任何可以改进的地方。 但也许我昨天尝试的耦合并不好。 我最感兴趣的耦合可以这样可视化

这是一个钢琴属性的马尔可夫链示例,具有很多耦合。
在这个耦合中,上一个音高决定了下一个音高。 下一个力度由上一个力度和当前音高决定。 当前音高也决定了持续时间。 当前持续时间决定了当前延迟。 这需要在正确的顺序(音高、持续时间、力度、延迟)中进行评估,这由用户决定,因为我不想在树遍历中编程。
好吧,我尝试了这个,听起来很糟糕。 我对结果感到非常失望。 考虑到我可能需要尝试不同的机器学习,我决定公开我的 repo; 也许有人会找到它并接管我,因为我不确定我是否会继续研究它。
第 4 天
经过一番思考,我决定尝试一下神经网络(提交 b5931ff6),但结果很糟糕,至少可以说。 我也尝试了几种变体:将音符作为对输入,无论是单独输入每个属性,还是将所有属性作为一个向量输入。 这听起来不太好; 时间安排完全不对,音符也到处都是。
我还尝试了一个神经网络,我将整个键盘的布局(提交 20948dfb)发送进去,然后是它应该过渡到的键盘布局。 这听起来很复杂,因为我认为它确实如此,而且似乎也没有用。
我注意到神经网络的最大问题是很难获得随机性。 我尝试引入一个随机列作为随机向量,但它会产生太多虚假音符。 一旦 AI 钢琴即兴演奏开始,它似乎就会陷入局部最小值,并且不会探索太多超出范围的东西。 要使神经网络工作,我必须像 Google 那样,深入学习什么是“旋律”,什么是“和弦”,以及什么是“钢琴”。 唉。
第五天
我放弃了。
我确实放弃了。 但是,当我在森林里跑步时,我不禁重新思考整个项目。
人工智能到底是什么? 它应该以我的智力水平来演奏吗? 当我演奏时,我的智力水平是多少? 当我思考自己的智力时,我意识到:我不是很聪明!
是的,我是一个简单的钢琴演奏者。 我只是简单地演奏和弦中的音阶中的音符。 我会在心情好的时候加入一些小乐段。 实际上,我越想越觉得,我的钢琴即兴演奏就像将我重复使用、复制或拼接的乐段连接起来。 因此,钢琴人工智能应该完全做到这一点。

这里有一些更多的笔记来收集我的想法,但这次是在一个小页面上。
我把这个想法写在必要的纸上,然后回家编程。 基本上,这个版本的 AI 是另一个马尔可夫方案,但高于一阶(即,记住的不仅仅是最后一个音符)。 并且马尔可夫转换应该连接更大的音符片段,这些片段已知是乐段(即,基于我的演奏历史)。 我为此实现了一个新的 AI(提交 bc96f512)并试用了一下。
第六天
搞什么鬼! 今天有人把我的 Piano AI 放在 Product Hunt 上了。 哎呀,我几乎完成了——但没有完全完成——所以我希望今天没有人尝试它。

我喜欢在 ProductHunt 上,但我希望我先完成。
带着全新的心态,我发现了很多容易解决的问题
- 一个地图上的音符在错误的节拍上的错误(我仍然不知道这是怎么发生的,但我现在会检查它);
- 一个防止 AI 在即兴创作时进行即兴创作的错误;
- 修复并发访问地图的错误; 以及
- 一个防止在学习时进行即兴创作的错误。
我添加了命令行标志,所以它准备好了——而且它实际上有效! 这里有我教了大约 30 秒然后进行即兴演奏的视频
我收到了很多关于这个项目的很棒的反馈。 这个 Hacker News 讨论 很有启发性; 这里有一些值得注意的例子
- 这里有一篇关于音乐创作的 很棒的论文,作者是 Peter Langston,它有一个“riffology”算法,似乎与我的算法相似。
- Dan Tepfer 似乎使用 SuperCollider,而不是 Processing,进行程序音乐创作。
- 这里有一些 很棒的序列预测器,可以用来改进它。
如果您有更多想法或问题,请告诉我。 Tweet @yakczar。
本文最初发表于 Raspberry Pi AI,并经许可转载。
2 条评论