pretty_midi中的时间单位

问题描述

今天在读自动伴奏项目代码时遇到了一个问题。项目中有一段代码的意图是利用 pretty_midi 来读取 MIDI 文件中所有音符的信息(音高、开始时间、结束时间)。根据代码可以推测出,pretty_midi 各个方法的参数、返回值都是用”秒“作为时间的单位的。于是,我合理推测 MIDI 就是用”秒“来作时间单位的。顺手测试一下这个结论,我用 pretty_midi 生成了一个只包含从 0 秒持续到 3 秒的音符的 MIDI 文件,放进 FL Studio 中,如图:

将 FL 的时间显示改成 M:S:CS 模式,奇怪的事情出现了——显示的时间并不是 3 秒:

注意到速度设置的是 130 BPM,尝试调整到 120 BPM,发现显示的时间变成了预期中的 3 秒:

我感到很困惑,如果 MIDI 中时间的单位是”秒“,那改变 FL 中的速度应该不会影响音符的持续时间才对。既然音符的持续时间会随着速度改变,MIDI 中的时间单位应该是”节拍”才对。如果 MIDI 中时间的单位是“节拍”,传给 pretty_midi 的参数中的 start=0end=3 的单位会是“节拍”吗?我突然紧张起来,感觉自己排查出了代码中的一个重大 Bug。

考证过程

最权威的资料莫过于官方文档了。在查阅了 pretty_midi官方文档后,我发现 pretty_midi 中时间的单位的确是“秒”(也就是说,代码是没有问题的,白高兴了):

但 FL 中的奇怪现象该如何解释呢?我又搜索了 MIDI 文件相关的信息,从 Mido 的文档中可以看到,MIDI 文件中时间的单位确实是“节拍”:

这么看来,唯一的可能性,就是 pretty_midi 的代码中存在从“秒”到“节拍”的转换。这也就意味着,需要提供一个速度。

又是在 Mido 的文档中,可以看到 MIDI 文件的默认速度是 120:

pretty_midi 的文档中可以看到,pretty_midi 也遵循了这个规定:

而且,pretty_midi 是会将速度信息写进 MIDI 的。我们可以通过以下代码获取 MIDI 文件的速度信息:

midi_file = pretty_midi.PrettyMIDI('/path/to/midi')
midi_file.get_tempo_changes()[1][0]

总结

总结一下,pretty_midi 中时间的单位是“秒”,而 MIDI 文件的时间单位是“节拍”,中间存在单位转换;在进行单位转换时,默认的速度是 120 BPM。在 FL 中,FL 是以设定的速度来播放 MIDI 的,而不是根据 MIDI 中的速度信息来播放。