You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用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")

另外补充几个小细节:

  1. 之前的循环从range(1, len(chords))开始,会直接跳过第一个和弦,改成range(len(chords))才对
  2. chordMidiNotes的结果一次性存下来,避免重复调用两次,效率更高
  3. 如果chordTimes本身就是每个和弦的持续秒数,那直接用duration_sec = chordTimes[i]就行,不用计算差值

这样修改后,所有和弦的音符都会同时开始、同时结束,就不会出现时序异常的问题了。

内容的提问来源于stack exchange,提问作者chikitin

火山引擎 最新活动