钢琴作品中有很多以华丽绵密的伴奏声部为特色。李斯特的很多作品中,右手常在高音区铺出一整片珍珠般的音流用作装饰而非旋律。肖邦夜曲 Op.9 No.1 中间段落的结尾,变换了和声音色的反复段落,制造出幽谷回声般的空间感。德彪西《水中倒影》和《欢乐岛》用绵密的琶音把键盘变成了水面。而里姆斯基-科萨科夫的《野蜂飞舞》则让钢琴模拟出昆虫振翅的嗡鸣。
这些作曲家都在做同一件事:把钢琴当作通用音色画布。他们并不满足于“钢琴只弹钢琴该弹的东西”。
这让我想到一个更极端的问题。如果不考虑演奏可行性——不管手指够不够、踏板忙不忙得过来——纯粹从声音合成的角度看,一架固定音色的三角钢琴,作为通用声音拟合器极限能做到什么程度?
我的核心发现可以概括为三点。
-
时间轴上的密集表示,是语音可辨识度的关键。
把输入语音切成每 20 ms 一个的短音符,在 88 个琴键上联合优化激活强度,语音识别器(Whisper)居然能从钢琴渲染出的“叮叮咚咚”里读出中文诗。五段诗词的平均字错误率(CER)从早期稀疏路线的 100% 降到了 68%。这说明钢琴声音里确实可以保留足够的语音线索。
-
密集表示用在器乐上会变成灾难。
当成千上万个短音符砸向一首流行歌时,听感不是音乐,而是白噪声般的密集敲击。给音符赋予自适应时值——低音铺长音、中高音用中等长度——器乐的音乐性立即回归。《画心》的音符数从 2706 个骤降到 473 个,频谱误差反而基本维持。
-
长时值会牺牲语音细节,并带来“风铃感”杂音。
当音符变长,歌词的可识别度随之下降;同时,过多的高音谐波叠加产生一种类似风铃的泛音杂响。最终采用分音区时间网格:低音区继续用长音支撑结构,高音区回到 10 ms 级别的短音刻画快速变化,在两者之间取得平衡。
下面按类别展示一些拟合结果。所有 MIDI 都由同一套固定音色(FluidSynth Grand Piano, bank 0 / program 0)渲染,可以直接点击试听。
语音
铃声与音效
电子游戏
器乐与歌曲
影视与动漫
Demo
生成你的拟声MIDI: 🤗 Speaking MIDI
五线谱转写
开头两段语音MIDI对应的五线谱。由于曲谱记谱缺少MIDI的逐音符力度,播放效果略差。
拟合算法
项目源代码:k-l-lambda/speaking-midi
问题定义
给定一段目标音频 $y$,我们想找到一组 MIDI 事件 $\mathcal N$,让固定钢琴音色 $\mathcal R_\theta$ 的渲染结果尽可能逼近 $y$:
$$ \mathcal N^*=\arg\min_{\mathcal N\in\mathcal C} D\bigl(y,\mathcal R_\theta(\mathcal N)\bigr). $$
这里的约束 $\mathcal C$ 包括音高范围、力度范围,以及同音高按键不能重叠。它同时涉及离散变量、连续时间和未知音符数量,是一个逆合成问题。
声音表示的边界
我们不能直接在波形上叠加单音模板来评估,因为功率谱有交叉项:
$$ \left|\sum_i X_i\right|^2 =\sum_i|X_i|^2+2\operatorname{Re}\sum_{i<j}X_i\overline{X_j}. $$
模板相加忽略了相位干涉,真实音源还有力度层和释放尾音。因此,优化目标只能建立在近似频谱上,最终质量必须以真实 MIDI 渲染为准。
分音区时间网格
最终采用的方案把时间轴和音高轴一起切分:
| 音区 | MIDI 音高 | 起音步长 | 可选按键时长 |
|---|---|---|---|
| 低音 | 21–59 | 40 ms | 80 / 240 / 640 ms |
| 中音 | 60–83 | 20 ms | 40 / 80 / 240 ms |
| 高音 | 84–108 | 10 ms | 10 / 20 / 40 ms |
所有音区共享同一套 2048 点 STFT 分析,但分别建立激活网格,再共同解释同一份 Mel 功率谱目标。优化变量是各网格上的非负激活 $A^{®}_{pdn}$:
$$ \hat M_{bt}=\sum_r\sum_{(p,d)\in r}\sum_n A^{(r)}_{pdn}D^{(r)}_{pd,b,t+4-s_rn}. $$
直观地说,低音用少量长事件托住持续结构,高音用密集短事件捕捉快速变化。优化器把它们当作一个联合拼图来解。
从连续激活到合法 MIDI
激活矩阵不能直接播放。实现时先对每个音高–时值组合做能量标定,把激活转换成整数力度;然后按得分排序,剔除同音高重叠事件,得到固定支持集 $Z$;最后只在 $Z$ 上重新优化 200 步,让剩余音符补偿被删去的能量。
这个“先筛选后重拟合”的步骤不能省略,否则删去冲突事件后,整体频谱平衡会被打破。
损失函数与高频控制
基础目标是对数功率谱的平方误差:
$$ L_{\mathrm{dense}}= \frac1{BT}\sum_{b,t} \left[\log\left(1+\frac{\hat M_{bt}}{\varepsilon}\right) -\log\left(1+\frac{M_{bt}}{\varepsilon}\right)\right]^2. $$
为了防止高音区产生过多“风铃”杂响,还可以启用保守型目标:对超过原音频的能量施加额外惩罚,尤其是高频带。不过实验表明,过强的高音成本会牺牲语音中的快速细节,因此它更像一个可选的音色调节旋钮,而非万能默认。
实验能告诉我们什么
- 语音:密集或分音区表示都能让 ASR 识别出内容,说明钢琴声里确实可以编码可辨识的语音线索。但这不等于人耳一定能听懂——识别器有语言先验的加持。
- 器乐:《画心》的音符数从密集短音符的 2706 个降到多时值的 473 个,对数谱 MAE 从 0.284 微降至 0.271。更少的长事件就足以保留音乐轮廓。
- 分音区:《半岛铁盒》前 12 秒的 MAE 从 0.543 降到 0.493,代价是音符数从 1327 暴涨到 5023。细节是用密度换来的。
这些数字提醒我们:没有免费的午餐。想保留语音的颗粒度,就要承受密集敲击;想让器乐流畅,就要放弃一部分高频细节。钢琴作为拟合器,它的边界不在物理键盘,而在我们愿意接受的听觉妥协里。