使用Python的MIDO库写入MIDI和弦时遇到时序异常问题
解决MIDO写入和弦时音符结束时间异常的问题
兄弟,你的问题核心是没搞懂MIDI消息里time参数的相对时间机制——MIDO里的time指的是当前消息和上一条消息之间的时间差,不是绝对的时间戳!
你现在的代码里,给每个note_off都设置了相同的tick值,这就导致:
- 第一个音符的关闭消息会在和弦启动后T ticks触发(T是你计算的和弦时长)
- 第二个音符的关闭消息要等第一个关闭消息发完再等T ticks,也就是总共2T ticks才关闭
- 第三个音符则要3T ticks才关闭
结果就是音符结束时间依次延后,但你说的是“依次提前”?可能是chordTimes的含义理解错了(比如把结束时间当成了时长),但不管怎样,相对时间的用法肯定是错的。
正确的做法是让所有和弦音符同时关闭,只需要给第一个note_off设置总时长,剩下的note_off都设为0(意思是紧跟前一条消息发送)。下面是修改后的代码:
for i in range(len(chords)): # 之前从1开始会漏掉第一个和弦,改成从0开始 # 缓存和弦结果,避免重复调用函数 chord_notes, chord_symbol = chordMidiNotes(chords[i], extraBass=False) # 所有音符同时触发,time设为0 for note in chord_notes: track.append(Message('note_on', note=note, velocity=100, time=0)) # 计算和弦持续时长的tick数 # 这里分两种情况处理chordTimes: if i < len(chordTimes) - 1: # 如果chordTimes是每个和弦的开始时间戳,持续时长=下一个和弦开始时间 - 当前和弦开始时间 duration_sec = chordTimes[i+1] - chordTimes[i] else: # 最后一个和弦没有下一个时间点,给个默认时长(比如2秒) duration_sec = 2.0 duration_ticks = time_in_ticks(duration_sec, mo) # 发送关闭消息:第一个用总时长,其余紧跟发送 for idx, note in enumerate(chord_notes): if idx == 0: track.append(Message('note_off', note=note, velocity=127, time=duration_ticks)) else: track.append(Message('note_off', note=note, velocity=127, time=0)) mo.save("songWithChords.mid")
另外补充几个小细节:
- 之前的循环从
range(1, len(chords))开始,会直接跳过第一个和弦,改成range(len(chords))才对 - 把
chordMidiNotes的结果一次性存下来,避免重复调用两次,效率更高 - 如果
chordTimes本身就是每个和弦的持续秒数,那直接用duration_sec = chordTimes[i]就行,不用计算差值
这样修改后,所有和弦的音符都会同时开始、同时结束,就不会出现时序异常的问题了。
内容的提问来源于stack exchange,提问作者chikitin




