You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Mido生成带tempo变更的MIDI文件时间偏移问题?

问题分析

你的代码核心问题在于tick计算逻辑错误:你用当前要设置的新tempo来计算从起始到当前位置的总tick数,但MIDI的tick是基于当前生效的tempo累计的——每个时间段的tick时长由该时段内的tempo决定,不能用后续的tempo来计算前面的时间。这导致后续所有tempo事件的delta time被错误放大/缩小,出现延迟和间隔异常。

修正方案

正确的做法是:基于每个时间段内生效的tempo,计算相邻事件的时间差对应的delta ticks。具体步骤:

  1. 将采样位置转换为绝对秒数;
  2. 维护上一个事件的时间点和当前生效的tempo;
  3. 对每个tempo变更,计算与上一个事件的时间差,用当前生效的tempo将时间差转为delta ticks;
  4. 添加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:18:17