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

在指定MetaMessage时间添加MIDI和弦出现位置与时长异常求助

解决MIDI和弦添加的位置、时长与内容问题

看起来你在给Type 0 MIDI文件添加和弦时遇到了几个典型的MIDI时间处理和数据映射问题,我来帮你逐个拆解并修复:

1. 和弦时长变为预期3倍的核心原因:Ticks Per Beat不匹配

MIDI的时间计算完全依赖每四分音符的Tick数(TPB),你创建输出文件时没有指定TPB,mido会用默认值(通常是480),而你的原文件avm.mid的TPB很可能是160(480/160=3,正好对应时长放大3倍的问题)。

修复方法:创建输出文件时直接继承原文件的TPB:

mo = MidiFile(type=1, ticks_per_beat=mid.ticks_per_beat)

2. 和弦位置错误与时长计算错误:混淆了绝对时间和相对时间

MIDI轨道中的time参数是相对时间(从上一个事件到当前事件的Tick间隔),但你的chordTimes应该是标记点的绝对时间戳(从文件开头到标记点的总Tick数)。当前代码里的note_off时间用了chordTimes[i+1],这会让和弦时长等于下一个标记的绝对时间,而不是两个标记之间的间隔,直接导致位置和时长都错了。

修复后的循环逻辑:

for i in range(len(chords)):
    chordInfo = chordMidiNotes(chords[i], extraBass=False)
    chordNotes = chordInfo[0]
    
    # 计算当前和弦的起始相对时间
    start_time = chordTimes[0] if i == 0 else (chordTimes[i] - chordTimes[i-1])
    
    # 批量添加和弦开启事件(所有音符同时发声)
    for idx, note_val in enumerate(chordNotes):
        track.append(Message(
            'note_on',
            note=note_val,
            velocity=100,
            time=start_time if idx == 0 else 0
        ))
    
    # 计算和弦持续时长:当前标记到下一个标记的间隔,最后一个和弦默认1拍
    if i < len(chordTimes) - 1:
        duration = chordTimes[i+1] - chordTimes[i]
    else:
        duration = mid.ticks_per_beat  # 用原文件的1拍时长作为默认值
    
    # 批量添加和弦关闭事件(所有音符同时结束)
    for idx, note_val in enumerate(chordNotes):
        track.append(Message(
            'note_off',
            note=note_val,
            velocity=127,
            time=duration if idx == 0 else 0
        ))

3. 和弦内容不正确:排查chordMidiNotes函数

这个问题100%出在你自定义的chordMidiNotes函数上,常见的坑包括:

  • 和弦音符的八度错误(比如生成了C3而不是常用的C4);
  • 和弦类型映射错误(比如把Cmaj生成成了Cmin);
  • 返回的音符列表格式不对(比如包含非整数的MIDI音符值)。

调试建议:
在调用函数后立刻打印结果,确认输出的MIDI音符是否符合预期:

chordNotes = chordInfo[0]
print(f"Chord {chords[i]} notes: {chordNotes}")

比如C大调和弦的正确MIDI音符应该是[60, 64, 67](对应C4、E4、G4),如果输出不是这个,就需要修正函数的映射逻辑。

4. Type 0转Type 1的额外注意事项

Type 0文件的所有事件都在一个轨道,Type 1是多轨道,要确保新轨道的时间基准和原轨道完全同步:

  • 不要修改原轨道的任何内容,直接添加到输出文件即可(你当前这部分代码是对的);
  • 新轨道的所有相对时间必须基于原文件的TPB计算,这也是为什么我们要继承原文件的TPB。

完整修正后的代码示例

from mido import MidiFile, MidiTrack, Message

# 先确保你的chordMidiNotes函数返回正确的MIDI音符列表,这里给个示例实现
def chordMidiNotes(chord_name, extraBass=False):
    chord_map = {
        'Cmaj': [60, 64, 67],
        'Dmin': [62, 65, 69],
        'Gmaj': [67, 71, 74],
        # 根据你的需求添加更多和弦映射
    }
    # 如果需要额外低音,就在列表开头加根音的低八度
    if extraBass and chord_name in chord_map:
        return (chord_map[chord_name][:1] + chord_map[chord_name],)
    return (chord_map.get(chord_name, []),)

fname = "avm.mid"
mid = MidiFile(fname)  # 读取原Type 0 MIDI文件
# 假设你已经正确获取了标记点的绝对Tick时间到chordTimes
chordTimes = [m.time for m in mid.tracks[0] if m.is_meta and m.type == 'marker']
chords = ['Cmaj', 'Dmin', 'Gmaj']  # 替换成你的目标和弦列表

# 创建输出文件,继承原文件的TPB
mo = MidiFile(type=1, ticks_per_beat=mid.ticks_per_beat)
chord_track = MidiTrack()

for i in range(len(chords)):
    chordInfo = chordMidiNotes(chords[i], extraBass=False)
    chordNotes = chordInfo[0]
    
    if not chordNotes:
        print(f"警告:和弦{chords[i]}没有对应的MIDI音符")
        continue
    
    # 计算当前和弦的起始相对时间
    start_time = chordTimes[i] if i == 0 else (chordTimes[i] - chordTimes[i-1])
    
    # 添加和弦开启事件
    for idx, note_val in enumerate(chordNotes):
        chord_track.append(Message(
            'note_on',
            note=note_val,
            velocity=100,
            time=start_time if idx == 0 else 0
        ))
    
    # 计算和弦持续时长
    if i < len(chordTimes) - 1:
        duration = chordTimes[i+1] - chordTimes[i]
    else:
        # 最后一个和弦默认持续1拍
        duration = mid.ticks_per_beat
    
    # 添加和弦关闭事件
    for idx, note_val in enumerate(chordNotes):
        chord_track.append(Message(
            'note_off',
            note=note_val,
            velocity=127,
            time=duration if idx == 0 else 0
        ))

# 添加原轨道和新和弦轨道到输出文件
mo.tracks.append(mid.tracks[0])
mo.tracks.append(chord_track)
mo.save("songWithChords.mid")

额外调试技巧

  • 用MIDI编辑工具(比如MidiEditor)打开原文件和输出文件,对比标记点的Tick数和和弦事件的时间;
  • 打印原文件的TPB确认:print(mid.ticks_per_beat),确保输出文件的TPB和它一致;
  • 验证chordTimes中的值是否是正确的绝对Tick数,而不是秒或其他时间单位。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 10:52:57