基于Python mido库获取MIDI文件音符起止时间的相关问题
问题1解答
你对这两条消息的时间理解是错误的:
- mido中消息的
time字段是delta time,代表当前消息与上一条消息的时间间隔,单位为ticks(MIDI时钟滴答数),不是毫秒。 - 你给出的两条消息的实际含义是:上一条事件执行完成后,间隔1个tick触发64号音符按下;再间隔119个tick触发64号音符释放(velocity为0的
note_on消息是MIDI标准中约定的音符停止事件,等价于note_off)。 - 该音符的起始时间是当前这条
note_on之前所有消息的time累加得到的总ticks值,结束时间是起始时间 + 119 ticks。
更优的获取音符起止时间的方案:
不要将MidiFile对象转成字符串导出到文本再解析,直接遍历mido读取到的轨道对象即可,还可以直接调用库内置方法转换为实际秒数,示例代码如下:
from mido import MidiFile, tick2second mid = MidiFile('./midi/piano/WA_Mozart_Marche_Turque_Turkish_March_fingered.mid', clip=True) ticks_per_beat = mid.ticks_per_beat current_tempo = 500000 # MIDI默认初始速度是500000微秒/四分音符,即120BPM current_time = 0 # 累计总ticks active_notes = {} # 存储已按下未释放的音符,key为note值,value为起始ticks note_list = [] # 存储最终的音符信息:[note编号, 起始时间秒, 结束时间秒, 力度] for track in mid.tracks: for msg in track: # 先累加当前delta time到总时间 current_time += msg.time # 处理速度变化元消息 if msg.type == 'set_tempo': current_tempo = msg.tempo # 处理音符按下事件 elif msg.type == 'note_on' and msg.velocity > 0: active_notes[msg.note] = current_time # 处理音符释放事件 elif (msg.type == 'note_off') or (msg.type == 'note_on' and msg.velocity == 0): if msg.note in active_notes: start_tick = active_notes.pop(msg.note) # 转成实际秒数 start_sec = tick2second(start_tick, ticks_per_beat, current_tempo) end_sec = tick2second(current_time, ticks_per_beat, current_tempo) note_list.append([msg.note, start_sec, end_sec, msg.velocity])
这个方案可以直接得到每个音符对应的实际秒级起止时间,避免手动解析字符串出错。
问题2解答
有两个核心原因:
- 你看到的
time是单条消息和上一条消息的间隔,不是从曲目开头算起的总时间,所有消息的time累加得到的总ticks数才对应整个曲目的时间长度。 - ticks是MIDI的相对时间单位,需要结合MIDI文件的
ticks_per_beat(每个四分音符对应的ticks数,你提供的文件里对应值为24)和实时速度(set_tempo消息定义的微秒每四分音符)才能转换为实际秒数。单条消息的间隔ticks数值本身就不会很大,累加后转换得到的总时长就和你在编辑器里看到的3分钟。
内容的提问来源于stack exchange,提问作者Michaelo
相关产品推荐
相关产品推荐

