如何解决Mido生成带tempo变更的MIDI文件时间偏移问题?
问题分析
你的代码核心问题在于tick计算逻辑错误:你用当前要设置的新tempo来计算从起始到当前位置的总tick数,但MIDI的tick是基于当前生效的tempo累计的——每个时间段的tick时长由该时段内的tempo决定,不能用后续的tempo来计算前面的时间。这导致后续所有tempo事件的delta time被错误放大/缩小,出现延迟和间隔异常。
修正方案
正确的做法是:基于每个时间段内生效的tempo,计算相邻事件的时间差对应的delta ticks。具体步骤:
- 将采样位置转换为绝对秒数;
- 维护上一个事件的时间点和当前生效的tempo;
- 对每个tempo变更,计算与上一个事件的时间差,用当前生效的tempo将时间差转为delta ticks;
- 添加tempo事件并更新状态。
修正后的代码:
import mido from mido import MidiFile, MidiTrack, MetaMessage def create_tempo_map_midi(positions, tempos, sample_rate=44100, output_midi_file='tempo_map.mid'): midi = MidiFile() track = MidiTrack() midi.tracks.append(track) ticks_per_beat = midi.ticks_per_beat # 直接使用MidiFile默认PPQN,也可手动指定如480 # 将采样位置转换为绝对秒数 times_seconds = [pos / sample_rate for pos in positions] # 初始tempo:用第一个tempo,同时处理第一个事件的时间为0 current_tempo = 60_000_000 / tempos[0] # 转为微秒每拍格式 prev_time_sec = times_seconds[0] # 添加第一个tempo事件(时间为0) track.append(MetaMessage('set_tempo', tempo=int(current_tempo), time=0)) # 从第二个事件开始遍历处理 for idx in range(1, len(times_seconds)): curr_time_sec = times_seconds[idx] target_tempo_bpm = tempos[idx] target_tempo_micro = 60_000_000 / target_tempo_bpm # 计算当前事件与上一个事件的时间差(秒) time_diff_sec = curr_time_sec - prev_time_sec # 用当前生效的tempo,将时间差转为delta ticks delta_ticks = mido.second2tick(time_diff_sec, ticks_per_beat, current_tempo) delta_ticks = max(0, int(round(delta_ticks))) # 取整并确保非负 # 添加tempo变更事件 track.append(MetaMessage('set_tempo', tempo=int(target_tempo_micro), time=delta_ticks)) # 更新状态变量 current_tempo = target_tempo_micro prev_time_sec = curr_time_sec midi.save(output_midi_file) if __name__ == "__main__": tempo_values = [98.0, 98.0, 101.5467, 103.3155, 105.0865, 106.8571, 108.6168, 110.3756, 112.1227, 113.8698, 115.6076, 117.3423, 119.0782, 120.8079, 122.5382, 124.2676, 126.0, 156.0, 156.0, 152.5883, 149.1766, 145.7649, 142.3532, 138.9415, 135.5298, 132.1181, 128.7064, 125.2947, 121.883, 118.4713, 115.0596, 111.6479, 108.2362, 104.8245, 98.0] pos_values = [0, 1404000, 1417500, 1430528, 1443333, 1455922, 1468303, 1480483, 1492469, 1504268, 1515886, 1527329, 1538603, 1549713, 1560664, 1571460, 1592242, 1592745, 8309514, 8317994, 8326664, 8335532, 8344608, 8353901, 8363422, 8373183, 8383196, 8393475, 8404034, 8414888, 8426055, 8437553, 8449402, 8461625, 8474246] create_tempo_map_midi(pos_values, tempo_values)
关键说明
- 用
mido.second2tick替代手动计算,避免公式错误,该函数会自动根据给定的tempo(微秒/拍)和PPQN计算对应的tick数; - 维护
current_tempo变量,确保每个时间段的tick计算使用该时段生效的tempo; - 对delta ticks做取整处理,因为MIDI的tick必须是整数;
- 单独处理第一个tempo事件,确保初始时间基准正确。
内容的提问来源于stack exchange,提问作者NickShredScott
相关产品推荐
相关产品推荐

