LunarVox 工程链接: LunarVox - Coding
LunarVox (Lunar - 月亮的, Vox - 声音) 是控制台演示工具包的一个组件,用于合成声音或者编曲。这个名字的灵感取自 SunVox, 也是一个编曲软件。
所谓「控制台演示工具包」,就是指在尽可能不依赖 X Window / Wayland 的情况下做音乐、 PPT 或者视频。后期当然可能会用到 Framebuffer, 毕竟有很多内容还是需要图形展示的。
所谓「证过的定理都是平凡的,写过的程序都是垃圾的」,这个程序也不例外。尽管中间还是磨了很多弯路,刚写成时感觉走上了人生巅峰,现在回头看一下功能还是十分单一有限而没有优化的。
或许这只是因为当初对这个项目的定位是模块化音乐制作软件,当模块加载那一块写好的时候就失去了继续开发的动力——毕竟用户编写模块嘛,我作为开发者就只需要写一些示例模块就可以了。至于用户玩出什么花样,玩出花样了会不会贡献给社区,甚至会不会形成社区,这些我是无法预见也无法控制的。
那么这个工程的主要痛点在哪呢?
很容易想到,怎么才能播放声音。借助 Java, 播放声音还是很好解决的(LineOut.kt
中实现了 PCM 采样播放),毕竟已经给封装好了接口,直接调用就可以了。PCM 的格式也是需要关心的,在这个项目里我选择了 8 位有符号单声道格式 (-128 ~ 127 代表不同的扬声器电平),这里如果我不找个借口说是「为了实现 8-bit 的复古感」就只剩下「这么做比较简单」这一个理由了。
接下来就是一个需要稍微动一下脑筋才会想到的内容——音频撕裂。我们知道画面显示的时候有帧率,也有垂直同步来避免画面撕裂。然而音频怎么做呢?有一个最直白的方法就是生成多少就立刻拷进音频缓冲区,这样如果缓冲区满就会阻塞音频生成线程,也不需要自己实现等待了。然而,如果音频缓冲区是环形缓冲区呢?新写入的内容会覆盖旧内容,这样写入就一直不会阻塞,但写入头就会超过读取头,造成撕裂。最后决定模拟图形绘制的方法,用两个缓冲区,一次生成 1s 的采样,更换缓冲区,再生成下 1s 的采样,等待读取线程,循环往复,这样可以确保读取线程不会读到脏数据或者被覆写。
然后再想一下就是溢出。当我们处理波形的时候,按照惯性我们会使用 \([-1, 1]\) 的实数,再 127 * FLOOR
得到 PCM 采样数据。现在还没有什么大问题,然而在叠加多个模块产生的波形的时候就会出溢出问题。更坑的是,如果你选择溢出时直接截断,后面叠加的波形很有可能(在不溢出的情况下)和之前叠加好的波形抵消,从而又落回 byte
范围内。这个问题,最后还是用一个很直接的方法解决了——使用 int[]
进行处理,到最后要输出的时候在输出成 byte[]
.
最后,基本没有什么特别需要费心的问题了。可能有一点——软件是用十二平均律计算频率的。如果用户想用弦乐的话,十二平均律会让弦乐听起来稍微有那么一点点走音,而且会产生频率不完全成比例而产生的哇哇声。这个……我们就在之后的发布再修正吧。
基本就是这样。
Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.