我一开始根本没想搞什么“声音的颜色”,这个念头纯粹是那天在收拾老旧的硬盘,翻出来一个我十年前用Processing写的小程序,那个程序只能跟着电脑里的音乐跳几个方块。我看着觉得没劲,就寻思着能不能搞点更直观,更“情绪化”的东西。
冲动与开工:先把麦克风接上
我的第一步特别直接,就是决定把声音实时抓取进来。我之前在搞一些嵌入式项目时,用过Python处理音频,所以这回我直接掏出了Python那一套,主要是因为它装好就能跑,不用费劲去搭什么复杂的环境。我当时的想法很简单:先把麦克风的数据流给捞出来,这才是万事开头第一步。
我翻找了半天,找到一个叫PyAudio的库,之前用它来录音还挺顺手。我设置了一个非常小的缓冲区,这样才能保证声音是实时的,不会有那种延迟感。但问题马上就来了,我第一次跑起来,程序就卡住了。电脑的系统老是跟我抢麦克风的权限,搞得我来回折腾了好几遍,才终于让它老老实实地开始接收我的声音。
我对着麦克风“喂”了一声,屏幕上当然什么都没有。下一步就是要把那些密密麻麻的数字,变成能看懂的东西。
核心操作:把声音拆开,找到颜色
声音进来后,就是一堆振动数据,要怎么把振动变成色彩?我坐下来琢磨了好几个小时,决定用两个维度来对应色彩的两个关键要素:
- 声音的“大小”(音量):这个我决定用来控制颜色的“亮度”。声音越大,颜色就越亮,越刺眼。
- 声音的“高低”(频率):这个最关键,我决定用来对应颜色的“色相”(Hue)。低沉的低频对应暖色(红、橙),高昂的高频对应冷色(蓝、紫)。
为了实现这个“高低”的区分,我必须把麦克风抓到的声音切成一小块一小块,然后用一种算法去算出每一小块里面“低音有多少、高音有多少”。这个过程听起来很复杂,但就是套用现成的工具。我找出了之前收藏的一个音频处理代码片段,直接往里头一扔,它就能把振动数据拆解开,吐给我一堆“频率能量”的数据。
这一步是实践过程中最耗时间的。因为数据太不稳定了!我轻轻一咳嗽,频率数据就乱跳,导致我屏幕上的颜色像得了癫痫一样闪烁。我试了三种不同的平滑算法,一开始用平均值,效果很差,颜色过渡太慢,像慢动作。后来我换成了指数平滑,也就是让新的数据对结果的影响稍微大一点,但也不能完全盖过老数据。这才稍微稳定下来,看起来不再那么“神经质”。
搭建可视化:把色卡画出来
数据稳住了,接下来就是把颜色画出来。
我用了一个非常简陋的图形库,因为我不想把精力浪费在复杂的界面设计上。我直接画了一个全屏的大方块。我的目标就是:这个大方块的颜色,就是我声音的颜色。
我写了一段映射代码,专门负责把频率数据(比如0到10000赫兹)转化成色相环上的一个角度(比如0度到360度)。把音量数据(比如0到1.0)转化成亮度的百分比。我对着麦克风低吼了一声“——”,屏幕立刻爆出了一个深沉的红色。然后我换成高音区的尖叫,屏幕刷的一下变成了亮眼的青色。
但我在测试低语的时候,发现了一个大问题:音量太小,亮度也太低,屏幕几乎就是黑的。这不符合我的“游戏”设定。我立刻修改了亮度映射的曲线,让微小的声音也能产生足够的可视化效果,这样即便是我安静地哼着小曲,也能看到柔和的色彩在流淌。
实现:打包成“游戏”
当看到我正常的说话声音能流畅地在屏幕上变化出各种柔和的色彩时,我心里那叫一个舒服。我给这个东西起名叫“声音的颜色”,虽然它不是严格意义上的游戏,但我决定把它做成一个可以即时体验的小程序。
我花了两天时间,把它所有的依赖项都打了一个包,这样别人下载回去,不用装什么复杂的Python环境,双击就能跑起来,直接接入麦克风。我甚至还偷偷加了一个功能:如果你超过五秒不说话,程序会把屏幕颜色慢慢淡化成一种柔和的灰色,像是在等待新的声音输入一样。
这个实践过程,让我意识到,很多时候我们把技术想得太复杂了,只要抓住核心逻辑:抓数据,拆数据,重新映射数据,就能实现很多看起来很炫酷的东西。每当我疲惫时,我就会打开这个“色彩_游戏”,对着麦克风随便说点什么,看着我的声音在屏幕上绽放出独有的色彩,感觉真是特别奇妙。