过去两年,一位开发者一头扎进Game Boy Advance和rp2040的音频合成世界里,不断打磨自己的振荡器代码。目标很纯粹:在性能抠到极点的硬件上,用最少的指令跑出波形。

他给整件事定下的总原则是“全用整数,不管浮点”。即使手边有FPU,整数在溢出回绕上的简洁性依然让振荡器实现轻松许多。所有运算都基于32位值,这刚好是GBA的原生寄存器宽度,也是他实践多年得出的甜区。

打开网易新闻 查看精彩图片

选择32位相位还有一个听感上的隐性好处:它能把人耳可听范围内所有音高的精度都罩住。相位位数一降,低频段的音准就会开始走样——很多demoscene创作者能通过技巧绕过去,但作曲时就得时刻记着这个限制。一旦你上了32位,这件事完全不用操心,再往64位走收益就极微了。

所有振荡器的起点,都来自一个锯齿波。这个锯齿波只有两个要素:当前相位,以及每个采样点加到相位上的步长。步长由频率、采样率和32位最大值决定:把目标频率乘以0x100000000再除以采样率,就得到每一步的累加值。每生成一个采样,相位加上步长,然后把相位值直接输出为int32——一个单调上升的锯齿波就出来了。

走到汇编层面,过程更干脆:循环里就三条指令,加相位、存相位、减计数。每完成一次加法,只要相位溢出了0xFFFFFFFF,就代表一个完整振荡周期过去了。如果步长刚好等于0x80000000,波形恰好每个两个采样振荡一次,正好卡在采样率一半的奈奎斯特频率上。

步长一旦超过0x80000000,有趣的变化就发生了:输出波形并不会“更快”,而是开始反向滚动,原本的上升锯齿会翻转为下降锯齿。放在数学上解释,这是数字溢出回绕带来的自然折叠效应,但在低算力合成里,它变成了一个几乎零成本的波形变体。

不过,这些棱角分明的波形都会带出一个问题:混叠。尖锐的跳变在频谱里制造出听感不太舒服的额外频率。业界修复混叠的方法之一是PolyBLEP(多项式带限阶跃函数),在转换点做一小段修正,就能大幅减少杂音。

然而,开发者在这段代码里完全没去碰PolyBLEP。原因非常直白:他要的是“不计一切代价的快”。在GBA这类极度受限的设备上,接受混叠、继续前行是必须做下的决定。想用便宜方案获得干净声音时,他更倾向于用相位调制在正弦波之间绕行——这条路虽然没这些基本波形便宜,但也不会太远。

八年前构建第一个合成器时,他就发现,极简架构的音频引擎本身就能驱动出人意料的表达力。把一个上升锯齿的加法循环跑在汇编里,再把反向下折的波形直接当作变种使用,就足以在游戏中搭出厚实的声音基底。

这样一来,振荡器设计回归到一个很本真的张力上:你是要更干净的频响,还是要更轻更快的执行周期?在这枚32位相位循环里,他的答案毫无含糊——先把速度推满,音色瑕疵留到其他手段里再去补。

他还在筹备一份后续分享,专门讲讲相位调制如何用极低开销合成出干净的声音。至于那个“快速的PolyBLEP版本”,也许有一天会做,但目前并不在待办清单里。眼下,这套用32位整数堆出来的振荡器,已经能让一块掌机芯片跑出足够强烈的节奏。