kita / notes

约 2 分钟

BPM 被砍成一半,整首歌慢了一倍

转谱流程里有个参数看着不起眼,但错一点全盘皆错:BPM。

因为 MusicXML 里音符的时长是相对值(quarterLength),实际持续多少秒要算:

秒数 = quarterLength × 60 / BPM

BPM 写错了,所有音符的实际时长就跟着错。喂进 CeVIO 之后表现是:唱出来和伴奏对不上,整首歌要么赶要么拖。

先估,再"校正"

估 BPM 的路子是用 librosa 做 beat tracking。但它对分离后的人声效果不好——人声的节拍点太弱,鼓都被分走了。

所以改成对整首歌做检测,再做一次八度校正把结果拉进 70~180 这个合理区间。

到这一步拿到的还只是"参考值"。我又写了一个 refine_bpm():生成一批候选(原值、±0.5、×2、×1.5、×2÷3 等),然后对每个候选算时值整齐度——把音符时长按这个 BPM 量化,看量化误差有多小。整齐度最高的那个胜出。

思路没问题。问题是评分函数有个盲点。

盲点:BPM 和它的一半,一样整齐

把 BPM 砍一半,所有音符时长正好翻倍——原本是二分音符的变成全音符,四分的变成二分。量化误差完全一样。

也就是说,143.55 和 71.78 这两个候选,用整齐度打分是并列第一。

我的代码在并列时没做任何约束,直接取了它先遍历到的那个。于是:

143.55  →  71.78

整首歌慢了一倍。

这个 bug 很隐蔽:算法每一步都对,输出却错了。整齐度确实是有效指标,只是它对这个特定的变换不敏感。

补一条约束

修法是在并列时加一条偏好:优先贴近音频先验值(也就是 librosa 从原曲直接估出来的那个数)。

因为音频先验虽然不准,但它至少是从真实音频里来的;倍率变换是纯数学操作,没有新信息。两者整齐度打平时,相信有信息来源的那个。

改完之后 143.55 保住了。

唯一需要人填的参数

到现在为止,我在这条链路上做的所有自动估算都可以跑通,但 BPM 是唯一一个我不敢保证的。

原因很实在:音乐本身就有"同一段旋律,记成 70 BPM 的四分音符还是 140 BPM 的八分音符"这种模糊性——记谱层面上两种都对,取决于编曲者怎么想。算法没法替你判断。

所以面板上我把 BPM 做成了橙色必填建议项,配一张说明卡:知道就手填,自动值只是参考。

这是我在这类"自动流程"里学到的:不是每个环节都该自动。 那些在语义上就存在多解的环节,硬要自动就等于在猜,而且猜错了自己还不知道——就像这个 143.55 变成 71.78,输出看着完全正常,只有听的时候才发现整首歌拖了。

← 回到全部笔记