最近学了一点即兴伴奏,想训练自己分辨和弦,编配和弦的能力,但是手头没有钢琴去尝试,只有光遇游戏里的十五键钢琴(惨奥)。

无意之中发现了这么个项目 https://github.com/Rainbow-Dreamer/musicpy/

顿时来了灵感,这么熟练python了,为什么不用python来写音乐呢

背景

该项目作者的思想是用代码来写音乐,达到一定的抽象程度,短短几十几百个字节能够表达出一个midi文件轨道记录的几千个音符。

我所了解到的即兴伴奏的思想就是左手基本上就是几个套路(如隔三空二四二之类的),而用midi文件来表达这些的话,却要把每个音符全都无脑的记录下来。

其实呢,只需要主旋律,和这个小节对应的和弦,再加上伴奏的织体,就足以表达了。

那么我可不可以开发这样一个项目呢,能够随意改动和弦和织体,然后不断的试听和感受不同和弦不同织体产生的效果,达到很好的训练目的(而这一需求,要是用一些专业软件不断的调整音轨里面的高度来实现,将是灾难性的麻烦),我希望我改个和弦只需要改一个字母!!

程序逻辑设计

最终决定,用文本格式表达简谱的内容,包含主旋律、和弦、织体等信息,自己定义其格式,然后编写一个简谱解释器,能够播放出来或者转换成音轨记录到 midi 文件里面。

初步的规则如下

  1. 用 1~7 表达简谱中的音符,0 代表休止符,和简谱一致
  2. 在一个音符后面,用 ` 代表高音,用 . 代表低音,可以叠加
  3. 在一个音符后面,用 _ 代表八分音符(可叠加成十六分音符等),用 - 代表延音,用 ^ 表示连音线,用 * 表示附点
  4. 在一个音符后面,用 b 代表降音,用 # 代表升音
  5. 用中括号 [] 括起来的音符表示同时发音
  6. 用括号 () 括起来的音符,可以同时进行 `._b#的运算,降低表达长度
  7. 在小括号后面加 $ 表示括号内全部连音,比如 (222)$ 表示一个四分音符内三连音
  8. 用 | 表示小节线,每一行可以放任意多小节,换行表示两个小节叠加成不同的声部,用单独的一减号 - 表示另起一个小节,而不是叠加声部。
  9. 能够按特定的格式提前定义若干个织体格式,然后将织体当成一个函数,参数是和弦,程序自动计算出织体加和弦对应的若干个音符,进而得到整个伴奏。

织体究竟该怎么定义,这是个头大的问题,因为实际应用中,可以很灵活应用,比如我可以隔三空二四,可以隔三空二四二,还可以减法法则,从后往前减去若干个音符。

最终决定用替换法,比如 C 大调和弦 C E G,我用 1 表示 C,2 表示 E,3表示 5,4表示高音C,5表示高音E,以此类推。那么我隔三空二就可以表示为 1343,隔三空二四二是 13435343,隔三永远是 13,那么我可以把13用一个字母代替,比如a,把43和53分别用 b和c表示,那隔三空二四二就表示为 abcb,隔三空二二二就可以是 abbb,哈哈哈哈哈哈哈。

精心设计之后,决定织体定义格式如下

1
2
3
4
5
6
texture <织体名>: {
@ = <默认的织体标识> # 可选,<默认的织体标识> 可以是下面等号左边的任意一个值,表示默认值。
<织体标识1> = <织体内容1>
<织体标识2> = <织体内容2>
...
}

织体标识就相当于上面讲到的 a b c ,织体内容就是上面讲到的 13435343。

实际解析时,会先将若干个织体标识组转换为织体内容,然后织体内容最终会被转换成具体的音符。

下面举例

1
2
3
4
5
6
7
texture s:{
@ = 1
1 = (13434343).._
a = (13).._
b = (43).._
c = (53).._
}

这样一定义,再定义如何调用:

1
织体名(和弦[,织体标识组][,和弦转位][,减法法则])

国际惯例,中括号是可选参数

那么, s(C,abcb) 替换里面的标识就等价于 (13)…_(43)…_(53)…_ 进而等价于 (15)…_(1`5)…_(3`5)…_(1`5)…_

也就是说 s(C,abcb) 就表示 C 和弦的隔三空二四二,只需要改一个字母把 C 改成 F,就成了四级和弦的隔三空二四二!

下面介绍各个参数

  1. 织体标识组省略:省略的情况下,默认是 @ 指向的织体。
  2. 和弦转位:用 +1 或 +2 等等表示,+1 表示第一转位,即 s(C,+1) 最终会被解析成(31`)…_(`3`1)…_(3`1`)…_(3`1`)…_
  3. 减法法则:用 -1 或 -3 等等表示,表示减去多少个音符,即 s(C,-1) 最终会被解析成 (15)…_(1`5)…_(1`5)…_(1`0)…_

三个参数位置可以随意调整和缺省,因为解析的时候,前面带有+就是和弦转位,-就是减法法则,不带就是织体标识,参数之间用逗号隔开。

此外还有一些细节,在示例中说到,详见 summer.txt

程序诞生

经过几天的调试努力,最终诞生,取名为 simp-score,用法参数如下:

1
simp-score <src> [dest]

表示将 src 文件里记录的简谱,解析并输出到 dest 的 midi 文件中。

还有一些可选参数,详见 --help

我将菊次郎的夏天作为示例乐谱 summer.txt,最终生成 summer.mid

注意我调用了 mingus 库的内容,需要 pip install mingus 安装这个库,才能正常运行。

后序可能的改进

如果后序改进,可能会添加自定义和弦的功能,预设了大多和弦在程序开头,或许万一有别的特殊需要呢。