EvoGrain:用 L-system 和颗粒合成做算法声音设计
Nathan Ho 做了 EvoGrain,一个离线的 Rust 程序:它随机生成 L-system,把规则解释成海龟命令,再把得到的颗粒集合合成音频,只留下适应度函数喜欢的那些。文章从 50 条规则的随机文法一路讲到闭环合成,把每个阶段都走了一遍。
中文
复制


几年前我在这里写过一篇关于 L-system 与颗粒合成的文章。当时我就不太满意,如今再看甚至觉得难堪。这篇文章是想纠正那次的不堪,做法是一个野心大得多的项目:用 L-system 和颗粒合成来生成音频。下面我会完整、逐步地讲清这个叫 EvoGrain 的系统是如何搭建的,以及我在构建过程中经历了什么。上面可以听到一段输出示例,文章后面还有更多例子。
背景
这里要谈几个启发我的来源。
Iamus

Iamus1由马拉加大学的一群研究人员在 2010 年前后打造,是算法作曲旧时代里比较有说服力的项目之一(那时算法艺术还没因为一堆莫名其妙的理由突然闯进公众视野)。创作者声称,乐谱的生成没有任何人工干预,Iamus 是“第一台发展出自身原创风格的计算机”。这种风格属于后调性现代古典音乐的范畴。
Iamus 的宣传文案把我觉得项目里最有意思的部分埋没了:Iamus 不用人类已有的作品来训练,完全基于规则。既然如此,不妨听听 Hello World(2011),这是它第一部被公开首演的作品。评价这段录音时必须考虑一个显而易见的事实——演奏者是真人音乐家,他们不可避免地加入了自己的诠释。从作曲角度看,它也许有点漫无目的,但对于一个没有语料库支撑的产物来说,已经相当令人印象深刻。Iamus 的网站据说曾提供大量生成乐谱的免费下载,但我现在不确定去哪儿找了。
我自然好奇它是怎么运作的。Iamus 的源代码和详细技术说明从未公开;这个项目与 Melomics Spain SL 公司的一款商业产品绑定,而这家公司似乎已经不存在了。2 关于该项目方法最详细的描述,见于 Vico 等人在《Handbook of Artificial Intelligence for Music》一书中撰写的一章;1 同一团队还有几篇较短的论文也值得一看。3 4 假设这些论文相当准确地反映了项目的实际情况,其要点是:Iamus 使用 Lindenmayer 系统(L-system)和进化算法。L-system 生成一个有限的符号串,这些符号再通过“音高升高一个半音”“输出一个音符”“切换乐器”之类的指令序列编码成一份乐谱。L-system 是确定性的,恰好编码一份乐谱。
进化算法把 L-system 的规则当作基因型(遗传物质),把生成的乐谱当作表现型(生物体的外在性状),乐谱再交由适应度函数评估,并配有常规的选择、交叉和变异算子。这套进化系统显然还支持人工引导的进化。5
我从关于 Iamus 的论文里得到的最有用的启发是间接编码。在这里,基因型和表现型之间的关系非常复杂,由 L-system 中各个符号的含义以及它们如何生成乐谱决定。这些决策对能否得到音乐上有趣的结果至关重要,或许比适应度函数和进化计算那部分还重要。
其他音乐 L-system
关于用 L-system 做算法作曲,资料很多。面向普通读者的一本好书是 Tom Johnson 的 Self-Similar Melodies,6书里用的 L-system 非常简单,追求的是让听众能清楚听出结构,也就是所谓的“过程音乐”。Mason 和 Saffle 的论文7用了类似的方法,但还展示了生成的旋律如何模仿其他作曲家旋律的统计特征。Jones 走的是另一条路,8用 L-system 来划分时间和音高空间。DuBois 的博士论文9提到了一些更早的例子,并描述了他自己的系统,其中包含与演奏者的实时交互。
L-system 与声音合成的结合相对少有人探索,我找到的最好的资料是 Stelios Manousakis 关于 Musical L-Systems 的学位论文。10他研究的一大部分是用 L-system 直接生成波形,这和我这里做的略有不同;不过他也把 L-system 和颗粒合成结合起来,那部分看起来和我在这个项目里做的相当接近。如果我没理解错,一个区别在于他的系统是为实时设计的,而我接下来要描述的系统是离线的,试图利用非实时这一媒介的优势。
EvoGrain
本节介绍我自己写的 L-system 颗粒合成器,暂定名为 EvoGrain。EvoGrain 是一个离线程序,可合成几秒到 30 秒长的音频缓冲区。它的产物并非一首完整的音乐作品,而是供采样和变形使用的原材料。当然,如果结果本身听起来就有意思,那算是额外收获。
在我看来,EvoGrain 介于合成器与算法作曲系统之间。(算法声音设计系统的已有工作可参见 11。)它还力求做到真正自主:暴露给用户的参数极少,大部分行为都是随机的。反复运行 EvoGrain,应当能生成多种多样的结果。
EvoGrain 被设计成一个完全基于规则的系统。它不使用外部训练数据,也不刻意模仿已有的声音或音乐风格。
EvoGrain 的架构如下:
- L-system 随机化器产生 L-system 的规则和一个初始海龟状态。这个 L-system 随机化器的概率分布与行为会显著影响输出。
- 运行 L-system,生成海龟命令序列。
- 海龟解释命令序列,产生一组颗粒。每个颗粒是一组键值数据,包含起始时间、时长、音高和合成参数。
- 用一些临时性的全局方法对颗粒集合进行“调整”,以提升其音乐性。
- 将颗粒集合合成为音频文件,并绘制图形以便可视化。
- 对颗粒集合的信息做统计分析,得出一个适应度值。
- 针对不同的 L-system 反复执行整个流程,再用一个基本的优化过程挑出其中最好的结果。
数据流图如下:

下面逐节说明每个步骤。从最底层往上讲最容易理解。
合成:从颗粒集合到音频
EvoGrain 内置一个基础的颗粒合成引擎。DSP 和合成部分我刻意做得简单、常规,因为这个项目想让人关注的是颗粒的摆放位置和参数。
合成器的任务是把一个颗粒集合(即一组颗粒)生成为立体声音频文件。一个集合通常包含约 20,000 个颗粒。每个颗粒由以下键值数据合成:
- 颗粒的起始时间。
- 时长。
- 振幅。
- 立体声声像。
- 距离:混响发送量。
- 振幅包络:矩形、衰减、反向或 Hann 窗。
- 音高。
- 合成引擎:“Basic”用于基础波形,“PM”用于 2-op 正弦波相位调制。
- 音色参数:
- Basic 引擎:波形可选正弦波或三角波。我最初还给 Basic 引擎加了方波和锯齿波,后来觉得效果不太理想,就去掉了。
- PM 引擎:index 和 ratio。
- 相邻起始间隔(IOI):与下一个颗粒起始时间之间的标称距离。(合成器实际上并不使用它,但把它当作颗粒参数处理比较方便。)
振荡器没有做抗混叠,无所谓,听起来没问题。
加了一些振幅补偿:音高超过 1000 Hz 基频时音量会逐渐衰减,以缓解刺耳的高频。时长较长时振幅也会略微降低。
“距离”参数的主要作用是把颗粒送入混响。我实现了一个立体声到立体声的 16x16 FDN 混响,混合矩阵的思路来自 Menzer 和 Faller 的论文12,并使用了能量守恒的 LFO 调制全通滤波器。13 这个混响算不上出色,我也没花多少时间调它,但够用。要不要加混响,我一度很犹豫;实现之后发现,加了混响结果更容易听。考虑到我戴着耳机、开发过程中要试听几十个这样的结果,这一点相当重要。EvoGrain 输出一个混音后的立体声文件,同时也会分别输出 dry、wet(混响前)和 wet(混响后)文件,方便用户自行混音。
振幅(dB)、音高(MIDI 音符编号)、时值、IOI 和 PM 比率这几个颗粒参数,编码时用的都是参数的对数而非实际值。颗粒参数到合成参数也可以采用其他非线性映射,例如把音高参数量化到某个音阶上。
调整:改善颗粒集合
“调整”是我对生成之后编辑颗粒集合这一操作的叫法。调整不是 L-system 流水线的标准环节,但能提升结果的音乐性。目前我采用的调整步骤如下:
- 将颗粒的整个时间跨度从头到尾缩放,使其落在目标时长范围内。
- 把所有颗粒的时值一起缩放,使其中位数为 20 毫秒(具体数值可由用户调整)。
- 如果大量颗粒相互重叠,就略微降低它们的振幅,以免动态范围过大,有点像压缩器。
还可以做更复杂的调整,比如校正声像使其居中等等。我建议让所有调整步骤都保持确定性。
解释:从命令序列到颗粒集合
随手搜一下 L-system,就能看到用它生成的各种漂亮分形。从字符串重写系统到图形输出,中间这一步靠的是 turtle graphics。教科书里讲这个模型时,会给你一只位于 2D 空间的海龟,它的状态包括位置、朝向,可能还有其他东西,然后你向它发送“前进 1 个单位”“转 30 度”“把颜色设为绿色”之类的命令。解释一串命令会改变状态,最终画出图形。
EvoGrain 的做法本质上不是图形化的,但和 turtle graphics 相当接近。EvoGrain 的海龟由以下部分组成:
- “海龟状态”,包含一个颗粒的各项参数:起始时间、IOI、时值、声像、振幅包络等。
- 一个存放海龟状态的 FIFO 栈,初始为空。
海龟的命令如下:
Grain(g):用海龟当前状态下的全部参数生成一个颗粒,然后把起始时间向前推进一个 IOI。MoveParam(param, delta)(m):按相对量移动某个数值参数。例如MoveParam(Pitch, +2)把音高升高两个半音。下一节会修改这条命令。SetParam(param, value)(s):把某个数值参数设为绝对值。FlipPan(p):把声像参数左右镜像。SetEnvelope(type)(e):设置振幅包络。SetSynthEngine(engine)(y):设置合成引擎。SetBasicWaveform(waveform)(w):设置 Basic 引擎的波形。Push([):把所有参数(包括时间)压入栈(FIFO)。Pop(]):弹出栈顶条目,把当前海龟的参数设为该条目的参数。PushSequence(<):与“Push”相同。PopSequence(>):弹出栈顶条目,把当前海龟的参数设为该条目的参数,但不改变当前海龟的时间参数。
显然,Grain、MoveParam 和 SetParam 是这里最重要的三条。连续解释 10 条 Grain 命令,会得到 10 个完全相同、在时间上均匀分布的颗粒。如果在中间插入带小幅增量的 MoveParam 命令,各个颗粒就会彼此不同,形成平滑的音型。此外还有绝对(SetParam)与相对(MoveParam)参数变化之间的平衡:如果以 MoveParam 为主,参数就是逐步移动的;如果以 SetParam 为主,颗粒的跳变会更多(在 L-system 的语境下,往往跳到一致而具体的值上)。这三类命令的相对出现频率具有音乐上的意义,改动它们的效果很容易听出来。
四条“括号命令”Push/Pop/PushSequence/PopSequence 对制造重复结构很重要(尽管 L-system 本身也能制造重复)。关键在于,Push 和 Pop 让海龟能够向后穿越时间,从而制造复调。
命令可以随意到什么程度都行。比如我加上 FlipPan,只是因为它在 L 和 R 声道之间来回弹跳听起来很舒服。设计得好的命令集会让系统更倾向于产生好结果。你也许还能想到更多海龟命令——我有过想法但没试过的包括:设置速度,用它乘以后续 MoveParam 命令的增量,从而放慢、加快或镜像海龟的运动;SetParam 的一个变体,移动到当前值与新值的平均处,而不是硬跳;用栈的当前大小影响颗粒参数;启用或禁用音高按音阶量化的命令;以及一条在时间上创建标记、让附近的颗粒被修改(吸引或排斥它们等)的命令。
处理边界
MoveParam 有一个大问题:没有任何逻辑能阻止海龟走到某个离谱的参数值上,然后一路飙到无穷远。多用一些 SetParam 和 Push/Pop 命令能缓解这个问题,但不足以杜绝。一个朴素的做法是把越界的参数裁剪回边界内,可这往往会让海龟黏在边界上,尤其对音高来说效果很差。另一种做法是在两个边界之间镜像海龟,但这像是敷衍了事,而且行为不具音乐性,常常导致海龟在两个边界之间来回折返。你也可以放任参数越界,在更高层面处理——把出现这类问题的 L-system 丢弃或施加惩罚。
但我最喜欢的方案来自 Manousakis,他描述了“阈值”算子。思路是把相对参数设置的逻辑做得更复杂。我们给 MoveParam 加一个新字段:
MoveParam(param, delta, reset):将参数按相对量delta移动。如果结果值越界,就把参数重置为绝对值reset。
“重置”参数应当选在始终处于边界内的值。这虽然是个临时办法,但想想爵士即兴演奏者会怎么做就说得通了——如果你一路向上冲到乐器的最高音区,跳回正常音域是很自然的事,尤其当极端音区会带来疲劳时。
可即便如此,海龟还是会一路跑到边界的极值处,我仍然觉得这不具音乐性。为了解决这个问题,我用了模糊逻辑。每个参数都分配一个梯形函数 f(x):完全在边界内时为 1,完全越界时为 0,两者之间线性过渡:
MoveParam(param, delta, reset):将参数按相对量 delta 移动。设 x 为结果值。把参数设为 x f(x) + reset (1 - f(x))。
于是当 f(x) = 0(在边界内)时,参数正常移动;当 f(x) = 1(越界)时,我们重置;其余情况则在两种行为之间线性插值。我喜欢这个做法,因为它让每个参数上颗粒的边缘分布随着逼近边界而逐渐收窄。
“边界拥抱”是算法音乐中一个众所周知的问题。Brown 等人关于算法旋律生成的论文14讨论了这个问题,并在旋律生成中使用“弹性带”,也就是每加一个新音符,就把音高与音域中心做平均。这条“弹性带”也为上面的模糊逻辑方法提供了灵感。记住,用你的海龟设定边界并不自私,你只是在练习自我关照。
扩展:从 L-system 到命令序列
许多不错的资料都介绍过 L-system,比如 Prusinkiewicz 1986 年的论文。15 我在这里再过一遍 L-system,只是为了厘清术语。一个 L-system 由一个 符号 字母表构成,其中的符号要么是待重写的 变量,要么是重写系统不去触碰的 终结符。在这里,终结符正是交给海龟的命令。L-system 还有一个被指定为 公理 的变量,以及一组 规则,使每个变量都恰好映射到一个符号串(后继)。运行 L-system 时,从一个只含公理的字符串开始,按规则重写变量,逐代产生新的字符串。我在这里讨论的 L-system 是确定性的、上下文无关的(“DOL-system”)。
许多 L-system 会不断生成越来越长的字符串,没有尽头,但这里我们需要一个有限的结果。一种做法是只迭代有限代。我最后用的是一种受 Iamus 论文启发的技术:给每条规则分配一个 预算,即一个正整数,规定这条规则可以被调用多少次。一条规则用满这么多次之后,它对应的变量就被替换为空串。
许多 L-system 的例子都使用有限字母表,而这个不是,因为命令允许 SetParam(Pitch, +60.234) 这样的参数。不过,命令是终结符,规则对它们一无所知。
到目前为止描述的所有步骤——扩展、解释、调整和合成——都是确定性的,L-system 明确地编码了它生成的音频文件。关于随机 L-system 的文献相当多,但在这种情境下确定性是最好的。能够调整这些步骤、观察改动如何影响结果,而中间不掺入随机性这个混淆因素,这样很好。
随机化:生成 L-system
我已经把从 L-system 到最终音频文件的整个流程倒着走了一遍。现在轮到流程的第一步:生成 L-system 本身。这一步非常开放,我相信你可以在我的做法基础上找到改进和扩展的空间。
L-system 最大的优势在于能建模层次化的输出。很多音乐在某种程度上都是层次化的——整首作品分成段落,段落分成乐句,乐句分成音型,音型分成音符。虽然我并不打算生成整首作品,但在我看来,EvoGrain 的输出最有吸引力的地方,正是它带有清晰的类分形结构,就像一首完整的作品可能具备的那样。16
很多 L-system 的例子都是手写的,只有几条规则和几个符号。这里的 L-system 规模更大:50 条规则,每条规则最多包含数百个符号。这里的 50 个变量——即只存在于 L-system 中、用于被替换成其他符号的符号——只用数字表示,标记为 #0、#1、#2……其中 #0 是公理。这些符号分为两组:#0–#19 是宏变量,被视为构成颗粒集合中最大结构的变量;#20–#49 是微变量,产生可能只有几个颗粒的小音型。我觉得 20–30 这个分界比较合适,但宏变量和微变量的数量都可以调整。
随机化器按以下逻辑生成 L-system 规则:
- 对于宏变量 #n,其后继是一个约含 15 个符号的字符串,其中每个符号:
- 有 90% 的概率是从 #(i + 1) 到 #(i + 4)(含两端)中随机选取的宏变量。
- 有 10% 的概率是一条随机海龟命令。
- 对于微变量,其后继是一个含 50–500 个符号的字符串,其中每个符号:
- 有 95% 的概率是一条随机海龟命令。
- 有 5% 的概率是一个微变量。
因此 #0 是整首作品,#1 是最高层的结构,#2 是下一层,依此类推。但它们并非完美嵌套,因为 #0 可以跳过层级直接调用 #4。靠后的宏变量可以展开为微变量,而微变量之间可以无限地相互引用。
后继符号的数量会影响生成命令序列的长度,并最终决定结果中的颗粒数,因此把它调对相当重要。
要生成一条随机的海龟命令,我们直接从帽子里抽。概率经过挑选,让 MoveParam 和 Grain 最常见,SetParam 相对少见,其他命令更罕见。括号命令 [ ] < > 在所有其他命令之后插入,插入方式保证它们总能配对。做法是随机选一组插入点,再生成一个随机的 Dyck word。17 我不知道这有多大必要,但要是它们配不上对,我会很难受。
SetParam 和 MoveParam 命令带有数值字段,随机化器必须为它们提供值。为了设定这些值,我为每个合成参数手动定义了两个概率分布:一个绝对分布,以及每个颗粒参数各自的增量分布。比如音高,绝对分布是 MIDI 音符编号 30 到 120 的均匀分布,增量分布是标准差为 3.0 的正态分布。随机化器创建一个 SetParam(Pitch, <value>) 时,值从绝对分布中采样;而 MoveParam(Pitch, <delta>, <reset>) 中的增量从增量分布中采样(重置值则取自绝对分布)。大多数增量分布关于 0 对称,但振幅(以 dB 计)的分布是偏斜的,使振幅更可能逐颗粒减小而非增大——这听起来不错,因为它隐约模仿了衰减的回声。这些分布只有 L-system 随机化器知道,用来产生这些命令;海龟并不对它们采样,因为命令序列的解释是确定性的。
举个例子,下面是 EvoGrain 随机生成的一个 L-system 的规则,它出现在本文开头,在下面也会作为颗粒集合 #6 出现。每条命令用一个字母表示,所以为简洁起见,我不展示 SetParam(s)、MoveParam(m)等的参数。直接看这些规则对理解颗粒集合帮助不大,只能说明随机化器的行为。
#0 [budget 5] -> #1<#1#2<#4#2>m#1>#4#4#1#4[#4]#1
#1 [budget 6] -> #5<g[#5]#2g<#3>#3>
#2 [budget 9] -> [#3#3[#4]#5[m]#4<#3<#4[g]#3>#3>#3]
#3 [budget 11] -> m<#6#7>#7<#6>#6#7#4#4<#6#6[#6#7]#4#7>#6
#4 [budget 10] -> #7#7[#7#7]#6<#6#5<#5[#5m]#8#7[#7]#6>#7[#7]#6#8>#8#8
#5 [budget 13] -> [#6[#6<m>#8[#6]#7[#9#7]#8]#7]
#6 [budget 14] -> #10<#10<#8#8>#10#7#8#8<g#8gg#9>#8#9>
#7 [budget 12] -> <#9#9<#10m[#9]#9#9#9>#9[#11]#9#10>#8#8#9
#8 [budget 9] -> [#10[#12[m]#9]#10#9#11]#11#10#10[#12]#11<#12>#10
#9 [budget 10] -> #11#10<#11#10#10#12#10#11#13#10#12#13#12m>#11#13m#12g#10
#10 [budget 8] -> <#14#14[#13]#11<#13>#13>#14#14<#11[#12]#13>#13[#13#12[m]#12]
#11 [budget 10] -> #15#15#13#15#12[#15#15#12]#13#15#12#14#15#14[#14#12<#12>#12]
#12 [budget 9] -> #15#15[#15]#15<#15>#16<#14#13>
#13 [budget 13] -> <#16>#16#16[#16<#16[m]g>#17]
#14 [budget 8] -> <#16#15<#16>#18>#16[#17]#15
#15 [budget 11] -> <#18m<#18>#18[#17[#18[#16<#17#17>#16]#17]#16]#16>
#16 [budget 8] -> [#18#19#18<#17#17#19>#18]#19#17m#17#17#19#19#17#18[#17]#19#19#19
#17 [budget 17] -> #20<g#19#18#21>#21[#18]
#18 [budget 15] -> #19g#20<#22>#20#22[m#20#20#20]#20#20
#19 [budget 17] -> #20<#20#23#22#22#21#21#20#21#23[#22#23[#23]#20#20]#23#21#20>
#20 [budget 11] -> #21gmmmggmmggmmmmmmm#36gm#22mgggmmgm#25#26mm#31<#35[#30mg]m#23#33gmgmmmmmmgmmmmmmmm#34mgmmmmmm<mm[mmmmgmmmgmgm]mgmmgmm#21mmmggmmmmmgmggmm#32gmmgmgmmgmgggmmgmmmm[g#43mmgmmmmgmmgmmmgmm#46mggggmm#20#27gmmggmgmmggmwymmmmmm]mmmgmmmmmmmmgmm>wgmmmmmmmmmm#41m#31mmmmggggmggmy#37>mmmmmmmmgmmgmmgmgmmmggg#35gmgm
#21 [budget 17] -> mggmmgmmmgmmmmm<mmgmmmmgmgmmmmgmmmmgmmg<mmmmmmmmmmmgmmmmm[gm]gmgmgmmmmmgmmm#36mmmmggmm>mgmmmmggmmmmmgmmmm#30m>gmmmgmmmmmgmgmmmmmmmmgmgmgmgmgmmmmmmmmmmmmgmmmmgmmmmmgm#24mg#24mmmmmmmmg#40mmmmgmmmgmmmmmmgm#42g#43#47mmmmgmmy#22wggmgmgggmmmgmgmmmmgmmmmmgmgmmgmmgwmmmmmmmmgmmgmmmggmmgmmmgmmmmgmgg
#22 [budget 13] -> m[mmgmggmmgmmmmmmmmmgmmmmgmmggs]mgmmmgm[mmgmg#37mmgggmmmmgmmmmmmmmmmmmmgmmmmmmmgmmmmggemmmggmgm#42mmsggmmmgmmmggggmmmpmmgg#22gmmmm[mmmmmmm]#34gmmmgmgmmmggggmg#31m]mmmgmmmmmmmmmmgm#28mmmgmmggmggmmmmggmmmmmm
#23 [budget 8] -> mmmgmm#42ggggmmmmmgmmmsmmmmmmmgggmmmmmmm#31#20ggmmg<mmmgmmgmmmmggmm#27mmpmmm#32mmgmggmmm#37mmmggggmmgmgmmgmmmmmgg#41mmmmmggmgm#46gmgmgmmmmggmgmgsmmmmgmmgmgmmmmmmmmp#26mgmmmmmgggmmgm#22mgmmmmgmmgmmmmgmgmggggmg<gmgmmggmmmmgmm>#36mmggmmmmmgmmmmmmmmgmmmmmmmmmgmggmmmgmmmggmggm>mmggmmmgmmmgmggm#43gmmmmmmmmmgg#43mggmgmmm#39mggmmmmmmgmmmmmmmmmgmmmmgmmgmgmm
#24 [budget 9] -> mmmm<mmmggmmmgmmggmgmm#37mgmgmmmm[mmgmmmmggmmgm]gmmmmmmmmmmmg[mgmmmmmmygmmgmmmmmgmmmg#31m#32ggmmm#47m<gm>m#35gmggmmm#40gmgpmmmmmgmgmmm#34mmmmmm#37#42sm#36mmmgggmmgmgm#47m[ggmmmmmm#26mggmmmmgmmm]mmg[mgm#47mmmmmmm#40mmggmmmm#31gmgmgmmmmmmmg#29gmmmmmmgmmmgmmg#45mmm#44#36mgmmmmm<mmm#38<mgggmgmmgmmmm#25mmmmmggmmmmmmmmmmm#44mmggmg#33ggmmm#46gmggmg>mm#28#45gmmgmgmggmmmmmmgmggmgmmmm#27mgmmmmmmm#30mm#37gmgmmmmmgmmmgmgggm>mgmmmmmmmmmmgmmggggymmgmgmmgmmmmmmggmgmmmmmmmggmmmmmmmmmmgmmm]ggm#48mmmmgmgmmmmmmsmymmmeggmmgmmmmmmmmggmgmmmmgmmmmmm]mmmm>gm#33megmmgmmmmmmmmmgmmmgmmmmgmmmmmmm
#25 [budget 14] -> gmmmmmmggmmmgm<gmggmmmmmgmgmmmmmmmmmm#22mmmmmgmmgmmmmmg#34>mmgmgygmmgmegmmmmg<mm<m#25mmmmgg>m<mmm>g>mgmm[mgmm]gmgmmm[mgmm]mm<mgmggmgmmmm>gymmggm
#26 [budget 13] -> ggmmmmmgmgggmmmm<mgmgmggmmmmmgmmmmmmmmmgmm#33mggg[mmgmmm#37mmgmmgmmgg#25]#44gmm#40<mmmm#34gmmmggmggmmmgm#41mgmmgmmgmmmgmmmmmgg>mgmmmm>gmgmggmgmmgggmgw<gmmgmmgg>mmmgmgwgmmggm[mmgsmmgmgm]mmmm#38gmmmmmmmgm#27mmggmmmmmmmmgmmmgmgmggmg#44mgmmmmmmmmmpmggmmmgmmmmmmmmg#47mgmmmm#28mmmmmgymmmgmmmgmgmgmmmmmmmmmmgmmmmygmmmmgmmmggmgmm#43gg
#27 [budget 7] -> ggmm[mggmmmmgm#27ggmgmmmmmmm]#28mgmmmmmmmmmmmmmmmmmgmmmg<gmmmgmmggmmmmggmmggmmm#26#36mmggmmmp#37mmmmmm#31ggmmmmmmmmwmggmgggmmmmgmmgmmmgmmmggmmg#24mmmmmmmmmgm#20m#38gmmg#25#22mmmmgm>mmmmmmmgmmmmmmgmmgmgmgggmg#38#44mgmmgmmmmmgmm#44mmmmmmmmmgmgmmmgm#47mmmgmm[gmmmmg#47mmmgggggmmmgmm[mmmmmpmmgmgmmmmmgmgmmmgggmm#27m]mmmmmmggmgwgg#42mm#31ggmmmgmmgm]gmmmmggmmmmmmmgggmg#24ggmmm
#28 [budget 11] -> mmmmgmmgmmmmgmmg#41mmmmgmmmmmmmmmmmgmgmmgmgmmmmmmgmmmmmmmmgmmgmggmgmgmgmgemmmmgpmmmggmgggmg#39<mmmgggymmmgmgggmmmg#40mmmgmmgsmmgmggmmgmmgmmmmmgm#47mgmmmmgmmm<gmggmgm[mmmmmgmgg#44mmg]m>mgmmmggmgym>mmgggmgmmmm#42ggmgmmg<mmmmggmggmmyggmmggmmmmmggmmmmmmggggmmggggggmmgmmggmmmgmmgmgsggmmmmgmmmmgmm#32m#20gmmmgmmmgm<gmgmmmym#48mmggg>mmgmmmgmmmmmmmmmmmmgmmmmggmmmmmmmmmmm#32mmgmgmmmmg#31mgmmmmmmggggmggggmmgmmmmgmmgggggmmmgmmmm>mgmmmmmgmmmmmmggmmmgmggmmmmmmmmm#30g#29gmm#31
#29 [budget 14] -> <mmmmmmmmmmmmmg[mm<mmmgmgmmgmmmgm>m]ygmmmmwgmmmmmgmmmmgmmmmmmmmmgmmmmmmmg#31ggmggmgggmg#41<mmmmmmmmmggmggwgmmg#20mggmmgmmmgmmggemgmggmgmmmmmmmgmmgmmm#39ggmmgmgmgmmm#20mgmmgy#49mmm#20gmmgmmmmgmmmgggmmmgm#22mmmmgmmggmggmmgmmgmgm#40gmgm#21gmm>mmmmmgmmmmgmmymggmmggggg>gmg#29gymmygmmmm#27#37m#44m#33mmmgmm
#30 [budget 7] -> mmmm<mmmmmgggm[mmmmggmgmgmmmmgmmm]gmmmmmgmmmmgmggmmmgmmmgmmgmmmgmmm>g#40gmmmmsgmmm#49mmmmm#21gmmmmemmmmmgmmmmgmmmgmgmgmggmmmm#37#45mmm#25mmmgmm#28wmmmggg#29#36ggm#20mm
#31 [budget 9] -> mggmgmggmm[mmm#32mmgmmgmgg#39ggmmmmmm#22mmm#21gmggmmgm#21mmgmggm#38ggmmmg#27mmmm#25gmgmmggmmmmgm#39ggmmmmmmmmmmmmg#35mmmwgmmmmmmmmmmsmmgm#24mmmmmgmmggmmm#49gmmmgmmmggmmgmmgmmmygmgm#48m#41gmggggwmmmgggmmmgmmmgmmmmmgm#46mgmgmmmmmmmmmmmgmmmmmmmmmmmmmmmsmmgm#27mmg[mgmggggmgwgmmmggmg#22mggmgmmmmgmmmmmmggmm#23gmgmmmmmgmmmmmgg]mmmmmgmm#24mg]#23gmgmmggmmmmm#48
#32 [budget 13] -> mmmmgggg#39mmmmmmmgmgmmmmgmggmgmmmmmgmm#25g#48mmgmmm[mmmmgggggggmmm#34gmm#34mm#28mmmmmmgmmmmgmm#31mmmmmmmg#35mmgmmm#46mmmmmmgmmmmgmmmm#42gmmmmmmggmmmmgggmmmm]ymgmmmwggmmmmmm#35mmemmmsmmmm#23mgmmmmm[gmmgmmgmm#27mmm#44mgmmmg#44#44mmgmgmmmmgmgg#28mmmmgmgmmmmmgmmgmmgmgmgmgmmmgmgmmgmmmmg]gmgmgmmmm<#47mgmmgmmmgmmmmmmmm#35mmmmmmgm#26mgmmgmgmmgmgmmmmgmmgmgmmmmmm#36mmgmgmmgmmmmgmmym#23gwgmmmmgmmmggm#40mmmmgmmgm#40mmmmmgmmmgmgmmgmgmmmgmmgmmmmmmgmg#42mgmmmmggmmmmm#25mmmmmmgmm>mmmmmmmmmmmmgggmgmmggmm#28gmmgmg
#33 [budget 5] -> gmggmggmmm[m#42g<mgmgmmgmgmmmm#27m>ggm#22mmmgggggmmgmggmggm#27#42mmmmmggmggmmgmmg#29mmggmmmmgmgm#47mmmmmmmmmmmmgmg#22gmm#21mmmgmggmmmggmmmmgmmmmgp#28#33gmgmmwmgmmmmggmgmgmgmmmmgmmmggmmmmmmmmm#24mmpmmgmgmgmmmmmggmmgmgmmgmmmmgmgmmgmggmgmgwmmmmmmmmmmmmm]mm#33mgmmmmmmmggmgmmmgemg#33gmmmmmmgmmgmmmmgg
#34 [budget 10] -> mggg#24gmmm#28mmggmmgmgmmmmgmmgmggmmmgm#33ggmmggmmmmmmmgm#39mmmmgmmmggmmmmg#49mmg<mgsmmmmggmmmmmmmmmgmmmgmmmmmm>mmg#43gmmm#49mm<mmmgmmmggmmmmmgmmegmmgg#36mgmmmm>mmmgmmm<g>mmmmgm#46m[ggmmgggmggmm#49mmmmmmmmmmmmgmm#22gmggmmmgmmmgmmmmmmmm#31ggmmggmmmgmmmmmmg#26mmgmgmggmmgmmmmmmmmm[mm]mmmmg]msmggmmwm[ge]m#40mgmmmggmgmmmgmmmmmmmgmmmgm
#35 [budget 13] -> gmm[gm<mgmgmm#28gmmmm[mgm]mmgm#46>mmmgmgmgmg#36mgm#48m#24gggmm<mmmmmggm[mmg[mmg]gm]m#41>m]gm[gmmm#22mmmmmmmmm]mmygmm#44mmmgmggmmmm
#36 [budget 13] -> mmmgmmm[gmm]gmmm#42mmmm[ggggmm<gmmmg>gp#26mmggg<m>mm]mmg[g#42<mg>mmmm]mwg
#37 [budget 11] -> ggmmgmg#38g#43gmgm#26mmmmgmmmmgm#29#28ymmmmmgggmmmggggmmmmgw#20mgmmm[m[mgmgm#24mmmmggggmmmgmmmmmmgmmmgmm[g#49mmmmgmggmmmg#21]mmmgmmmmmmmggmmmmmmmgpmgmgmmmgmpgmmm[gs#30mmmm]gmmgm]mmgmm#33mmm[#35mmmmmm]gmgme]mmmmmmgggggmmm#45gmmm
#38 [budget 9] -> mmmgg#26mps#46mmmmsgggmgg#45gm[gm[mmgmmmggmmmgmmmggmgm#35mmwgmm#34mm[mmmgmmmmm#33mmmmmm#25m]mmgmgmmmggm<gggm<gg>mmmmmmgmmmgmmggmmggg[gmgmmgmmmmmgmm<mgg>mmgmgmgmggm]mmmmmmgm<mgmmgm>mgmmggm#32mgmmmmmmmm>mgmgmmgmmgmmmmmmmmmmmmmg#32mmmmgg<mmmgmmmem>mm<mmgmmmgmm>mmg#41gg#40mmmmmmmmmmmggmmmm#28mmgmm#29m#32g]m]mmmmgg
#39 [budget 10] -> mmgmmmgmgmgmmmmmmgmmmmmmmmmmgmmggm#33gmmg[ggmmmmmmmgmmgmmmmgmmggmm#21<m#30#32mgmmmgmmmmgmgmgggm<#29#35mmgmmmgmmmmgm#41mmmmggggmmmmggmmgmgmgmmggmgggmmmmmggmmmmmmmmmmmmgmm>mgmmgggmmgmmwmmmmg#24mmmmmmmgmgmggmmmmmgmmmmmmmgp#45ggmmmggmmmmgmm#40gmgmmmmmmm#21mgggmmmgm#47mmgmmgmmgm#25mmmmmmmmggmmmmmmmgmggmmggmmmmmmmymgggmmmgmggmmmm>mmmmmgmmmmmmggmmmmggmmmmgmgggmg]mmmggmmggmgmm
#40 [budget 15] -> #21mmm<gmggmgm[ggggmmgmg#28mgmgmm]wgmmmmmgggmmmmgm#23g>mmmgmmm#42#23mmmsmgmmmgggmmmmmmgm#42mmm<m#48ggmmggmgmgmmgm>m#38mm<mmsmmgmmmg<mgmgmmmmggmmgmm>mmg#24gmmm#33ggmg#31m>mmmgmggmmmmm<mmmmgmmmgmgmmmgmmmmm>mmggmmmmgmgmmm#32ggg#23mmmgmgmmgmgmmmmmgmmmggmggmmmmgmmmmmggmgmmgmg#32mmgmmmggmmmmg
#41 [budget 18] -> mwpmmm#45gmgg[mmg#35m[mmgms#23mmmmm<mggggmgmgmmmmmmgmmmmmmmmmggmmmgmggmmmmgmmemmmggmmmg>mmmgmmmmmmm#43gmmmmmgmmm#27mgmmmmmmmm#37mmmmmgm#41mmmmmmmmmmm#48mmmmmgm#22mmgmgmmmgmggmmmmg#21mgmmmmmgmmmmgmm#20mgmmmmmmmmmm#44mmgm]gg#29gmmmmgmgmm[mmmmgmgmmgmmmmmmmmmwmm<gmmmmmmggmmmmgmmmmgmmgmmmw>mmmgg]mmgmmgmmmgmmmgmmmm<egmmymmmm#23mmgmgmmmggmmmwgmmmmmmmgmmm#23ggmgmwgmmmmmggmm[mmmmm]msg#32mgm>mmmmg#22mgmgmmmmpwg#45gmg]mmm#37mgggggmmmgg#41mmgmmm#31mgg
#42 [budget 9] -> mmmmmmmm#44mggmgmmmgmmmmggmmgmm#35[mgmmmm#22mmgmmw#36gmmmmmmmmgmm#34gmmggmmmgmmmmg<ggmmmggmg#27mmgmpmgmmmm#36mmggmym#34gmmmmgmgmgmmmmmm[mmgmmwgmmmgmgmgmmmgmmmgmmm#31mmmggmmgggmmmmmmggmmmmmmgggmmmmmgm]mgmmmmmmgg#45mmgmmgmmmgmmmmgmm#20mgmmggmmmmm[mmgggmmgmmgmmgmmmmgmmmmmmm#33gm[gmmmm#30ggmggmgmmggmgpmg#43mmmmmgmmmmgmmmgmmmmymmm#44gmmg#47mgmmm#28#40mmmmmmmgmgmmmgmmmmmmgmmmgmggmmgm]mm#36mgmgmgmgmmg#49mggmmmmmgmgggmmm]gm#34mgmgmmm>m#29mmmmm]mmmggmmmmmmgmgmmgmmmm#20mmmgggmgmggymgmmgmggmm#21gmgmmmmmmmmmmmm
#43 [budget 8] -> #21m[gmmmgmgmpgmmmgemmmgmm[m#33g#35mggmmmmm]sgmmmgggmmmgmmgggmgggmmmmmmgmmmmmmmmggmmmmmmgmgggmm#35mggmmmmgmm#39gmg#21wmmmmgmgmmmmmmpgmmg#33mmmmgmg[mmmggmmgmgmmmmmgmgmmmmmmmmmgmg#38mmmm#22mmmmmgmmmgmmmmmgmgwmmmmmm<mmmmmmpmmmmggmemmmmmwmgmmmmmgmmgmmmmm#48mmmmmgmmmmmmmgmm#49mmgmmgmmmgmm#26mmmm[sm#44gmmmmmggmggmmm]mm#29mmg<mmmmgmmgmmggmg#22mmmmgmmggmmmmgmmmmgmmmmmmmgmmmgmwmgmmmggmmmmmmm#33#45gmm<mmmm#43mgmmmmmmm#25mmmmmmg#37mmmmmmmmmmmggggmgmmm#48mmmm#20mmmm#38mm#42mgmm>mg>mgmmmgmmmmmmgmggmm#40>gggmmgmyg#36mmgmmmmmpmmmmmmmmgmgmggmmggmgmmmmgpgmm#47mmmm]#30mm]gmmmgggmmgmmgmgmmmggmmgm#36ggmmgmmmmmmmmmgmmgmmmggmm
#44 [budget 18] -> gmgm[mmmmmgmmgmmmmmmg#33gmmmgmgmmmgmggg#34mggmmmgmmgmgmgmmmm#39mmmmgmmgmmmmmmmmmmmmmggmmmmmmmmmmmmmmmmmgmmmm<ggmmmgmmmmmmmmgggmgmm<mmgmggmmgmmgmggmmggmmmmgmgmmg#43mmgmgm>mmgmg#26mmmmgmmmmmmmmmmgm>mmmmpmgmmmgmggmmmggm#24#22mmmmggmmmgmmmmmmgmgmmmmggmgmmgmgmgg<mmmgmmpgmmmmmmmggmmmgmmmmgmmgmmmmmggmmmg>mmmggmmmsgmmmgmmmmmmgmgmmmmygmmmgmmmmmmg#35g#20mmmgmmmmggmmgmgmmwmmmmmmmmmmgmmmm#33mmmmmmgmmmmgmgmm#34mmmmmm]mmmmmggpmggmmsmmmmgmmgg#43m#39mmggmmg#37m#35mgmmmmgmmggmgm#28mgmggmmmgmgmgggmmgmgmgm
#45 [budget 10] -> g[gm#24gggg#21gmmmmmggggmmmg#21m#49<gmmmg#43mmgmg<mmmmgmgmmmmmmm#42gggmmm#49m>gmmmmmgmmmggggggmmgmmmmmmmg#49mmggm[mmgm]gm#46gmmggm#32mm>g]mmegmsg[gmgmmgmmmggmmmgggmgmmgm]mmg#28mmmggggmgmmmmgmgmmm[gmgmmm]mmmm#48mgggmgmmgmmmggmmmmmgyg
#46 [budget 12] -> mmmmmm#36mmmmmmgmgmmmmgg#39mmmgmmmmmmmmmmmg[gmgmmmg]mggmgggmmmggmmwmmmgggmggmmmmmmg#32gmmgmmgm<mmmgmmmm>pggmgmmmm#42g#42gmmmggggmmmmmmgmmmgmmggmggm#25m<ggmmmmmmgmmmmmm>gmmmmmmmgmgmgmmmm#20ggmggmgm#21mmgggmgggmmmmmmm#42gmmmmgmgmgmmmmg#30m
#47 [budget 10] -> mgmmmmggg#39gmgmmmmmmgmmgmmggmmmmmmmmgmmmggggmmgmgmmmgmgmmmggm[gmgmgmggm#47#30mmmggmggmmmmmmmmgm]mmmmmm#39ggmm#40mgggmmmmgmgmmwmggmmmm#30m<mmmmmmmggmg#24mm#49mmmmmgmmmmmgemgmmmgggmgmgmgmmmg>#40ggmmm#44mmsmgwmmsmm#22mggmmmmmggm#31mmmmmmmgmgggggmmggmmmmmmmgmmmgmmmgmmgmmmmgmmmmgmgmggmgmy#39gmmmmmmggmmmmmgmmmmmmgmmmgmmg#38mmmmmmgmm#31mmmgmm
#48 [budget 11] -> mmmmmg#35gmgmmmmgmgmmmmsggmmmm#28gmgmmmmmmmmmm#32gmmggmgmmmg#43gmmgggmmmmmgmgggmmgmmgmym#20mgmmgmmgmmgm[ggmmmmmmmmmmm#21mggmmmmmmmmmmgmgm#29mmggmmgmgmmgmmgmmmmmmmmmggmgmmmmmmmmgggg#32m#22gmmmmmmmmwgmggpmmggmmggmmmgmmmmmmmgggmgmm<mgmmmmmgmmggmmmmmmmgmmmmmmgmmmgmmmgggmmmmmmmmmmmmgg#32mm>gmmmggmgmmmgmmgmmmmgmmmgmmmggmm#46mmgmgmm#41mmmgmmmgmmmgmsmmmmmgmggmm#24mgmmmmgmmgmmmmmmgmgmmmmmmmm#28mgmmm#38gmgmgmmmmmymgmmmmgm<m#31g>#36mmgmmmmmmmmgmgmmgmmgmmggmmmmmggmmmggmmgmgmmmmmmmgymmmmmmggmmmymmmmgmmmmmmmg]gmmggmmmmmmmmmmmmmmmmmm#23mm#26gmmgmmmmgg
#49 [budget 7] -> mmmmggm[mmmmmmmgmgmmmmmmmm#28mgmmggmgmmmmggmmm#44mmgmmgmmmmgggggmmgmmmpmmmgmmmm<m#21mgmmgggmmmmmmgmmmgmm[mgmgm]mmmgmgmmgmmmgmm#43mmmm#46gmmggg#23mgmegg#21m#28m#23mgm>mmmg#49mmmgmgmmmmmgmm#30gmmggmmmgmm[m]mmm#34mgggmg#49g#41gmmgm#43mgm]mmgmmmm#35mmmmmgmmggmm
评估:适应度函数
除了直接跑一遍,很难从 L-system 预测最终会得到什么样的颗粒集合。因此,这个集合完全可能是一堆 basura:颗粒太少,或者太多。适应度函数就是在这里引入的,它通过评估给定颗粒集合的好坏来闭合整个回路。
计算适应度时,我先算一些描述性统计量:
- 每秒平均颗粒数(总颗粒数 / 时长秒数)
- 音高的中位数和第 90 百分位数
- 中位“重叠系数”(每个颗粒的时值 / IOI)
针对每个特征,我定义一个梯形函数,把我更喜欢的取值打更高的分。比如我希望音高中位数在 C4(中央 C)附近,但不超过 F#6,于是定义一个梯形函数:在 60 处从 0 起步,到 70 升到 1,保持 1 直到 80,再在 90 处降到 0。我取颗粒的实际音高中位数代入这个梯形函数,得到一个 0 到 1 的分数。其他统计量也照此打分,最后做加权求和,得到最终的适应度指标。
设计这个适应度函数时,我优先考虑的是容易实现、且有助于避开我不喜欢的那些输出的指标。目前这个适应度函数只用来剔除明显的垃圾,还谈不上任何审美层面的评估。更精细的指标是可行的,可以借助计算美学方法,找出主观上更好的颗粒集合。具体该用哪些指标,我自己也还没想清楚。无论如何,对于想往这个方向探索的人,我建议做多目标优化:一个适应度值负责识别并丢弃明显糟糕的输出,另一个负责选美。
适应度函数只需要一个颗粒集合。你也可以先合成音频,再计算振幅、频谱质心等,然后对其做时间序列分析。大约两年前我确实在这个项目的一次失败尝试中这么做过,但发现适应度函数很难设计、调参也不直观,而且合成与分析这两步让迭代慢了很多。尽管如此,把合成算法放进反馈回路所带来的审美潜力,还是很诱人。
搜索:寻找适应度最高的 L-system
我给自己的软件起了个暂定名“EvoGrain”,灵感主要来自进化计算项目 Iamus,但最终并没有用遗传算法。《算法设计手册》明确劝读者不要用 GA 做优化,理由是增加的复杂度并不划算。这只是一家之言,但我认真对待。况且 EvoGrain 的目标未必是找到最优解,而只是产出一批听感不错的多样结果。这意味着在这里用最基础的优化算法就够了,实践中看来的确如此。
最终采用的搜索算法是:先生成 100 个随机 L-system 组成的池子,评估各自的适应度,挑出适应度最高的那个。然后做一轮简单的随机搜索,重复 10 次:随机选一条 L-system 规则,重新随机化,检查新 L-system 的适应度是否高于旧的,高就替换。由于适应度函数是各梯形适应度之和,存在一个可达的最大适应度,达到即可提前退出。(我有个直觉:梯形适应度函数是评估计算艺术的好构件——艺术决策里往往是一段令人满意的取值区间,而非某个唯一最优值。)
从 EvoGrain 早期那个被放弃的原型里,我得到的教训是:不要在适应度函数或进化那一侧过度设计。这些部分并不是真正出彩的地方,这个项目的大部分精力都花在了随机化器、海龟指令集和合成器上。
实现与结果
EvoGrain 是一个离线的命令行 Rust 程序,约 2,000 行代码。没有用任何专门的 DSP 库,只用 hound crate 输出 WAV。输出文件包括:
- L-system,序列化为 JSON,是其余所有文件的完整配方。
- 四个立体声 WAV 文件:干声、混响输入、混响输出,以及干声与混响混合后的输出。
- 一份文本形式的“成绩单”,显示 grains/sec、适应度等人类可读的统计量,便于调参和调试。
- 一张散点图(用
plotterscrate 绘制),横轴为时间、纵轴为音高,颜色表示合成引擎。
颗粒集合是不可预测的。有时它们会生成带有重复结构、听得见也看得见的图案,有时则生成团块和云状物。有时一堆颗粒会在特定的纵向时刻聚成一团。无论哪种情况,总归存在某种形状。它不是一段在任意位置开始和结束的无限纹理;全局结构始终存在,而且它会预判音频的结束(更准确地说,是它决定音频文件何时结束)。
我认为 EvoGrain 实现了我为它设定的所有目标,唯独“产出多样化的输出”这一条尚有争议。我听过大量 EvoGrain 的输出,这让我多少成了个行家,在我听来结果差异相当明显。但要是有人说这输出听起来就是含混不清的哔哔嘟嘟声,我也不会怪他们。评判权交给你。
示例
以下是 EvoGrain 输出的十个未经筛选的示例,全部使用相同的默认参数。横轴为时间,纵轴为音高,粉色代表 Basic 合成引擎,蓝色代表 PM 引擎。
整个系统从反复随机化、搜索到生成音频输出,只需几秒钟,因此这些示例加起来用时不到一分钟。
颗粒集合 0

颗粒集合 1

颗粒集合 2

颗粒集合 3

颗粒集合 4

颗粒集合 5

颗粒集合 6

颗粒集合 7

颗粒集合 8

颗粒集合 9

结语
EvoGrain 是一个研究项目。重点在于展示这个构想能做到什么,而不是向你推销一件新的声音设计秘密武器。我把源代码公开了,想用就可以用。源代码。
我大约花了半天时间就写出了 EvoGrain 系统的快速原型,出来的声音还算像样;再花一个周末,声音就变得不错了。这是一份邀请,请你亲手试一试。内部有大量参数需要调,大大小小的决策也不少,每一种实现都会产出不同的音色。
关于后续方向,有几点想法:
- 这个系统能否在输出音乐性不减的前提下被简化?所谓“简化”,是指更容易用语言描述,或者更容易用代码实现。
- 任何合成引擎都可以用来生成颗粒。对采样缓冲区做扫描式播放,效果肯定很好。EvoGrain 里我刻意避开了这种做法,因为它太讨巧了——用缓冲区颗粒化做出好听的声音非常容易,而我想尽力在没有采样的条件下也拿到好结果。逐颗粒空间化的可能性还可以继续扩展;哪怕只有立体声输出,用 Haas 效应做声像或做双耳虚拟化也有很大发挥空间。
- 更多交互。也许可以让用户直接编辑 L-system。或者给定一个 L-system,对规则做小幅改动生成若干随机变异,让用户挑最喜欢的一个。
- 逆向建模。有一篇论文18描述了以 SVG 图像为输入,反推出一个能生成该图像近似结果的 L-system——说实话这在我看来简直是黑魔法。这个系统甚至允许用户修改图形的规则来编辑图像(!)。这套思路用在音乐或音频上到底行不行?只能试了才知道……
- 引入遗传算法。倒不一定是因为它优化适应度函数的效果最好,而是因为它好玩、够酷。既然要做遗传算法,何不做到底,实现一个完整的人工生命模拟器,带上核苷酸碱基、密码子、阅读框和基因组学?19
脚注
2 根据 Wayback Machine 的记录,melomics.com 最后一次正常运行是在 2018 年初。如今这个网站已经贴满了赌场广告。品牌转型转得挺有意思。
16 当然,分形与音乐之间的类比高度依赖于音乐风格和文化。顺便提醒一句:分形不一定具有自相似性。分形并不需要在每个细节层级上都看起来一样。
包含上述十七条引用来源的完整参考文献列表见原文。