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

如何解决MIDI文件单轨多音符同时播放问题?含mido技术问询

处理MIDI单轨多音符问题的解决方案

A. 自动拆分复音到独立轨道的工具

  • 专业MIDI编辑软件:Cakewalk、FL Studio、Ableton Live这类工具都自带「拆分复音到轨道」功能,导入MIDI后选中目标轨道,通过对应菜单选项就能自动将同时发声的音符分配到独立轨道。
  • 脚本/轻量工具:可以用midicsv把MIDI转成CSV格式,写简单脚本按时间分组音符后再转回MIDI;Python的pretty_midi库也能直接实现该逻辑,通过其Instrument类可拆分复音到多个实例。

B. 用mido库处理该问题

完全可以实现,核心思路是手动跟踪音符的触发/结束时间,将重叠或同时发声的音符分配到不同轨道,步骤如下:

  1. 读取原MIDI文件,遍历目标轨道的所有消息,累加time属性得到每个消息的绝对时间。
  2. 维护字典记录当前活跃音符(键为音符编号,值为起始绝对时间、所属轨道等信息)。
  3. 遇到note_on消息时,若存在未结束的活跃音符(或与上一个note_on绝对时间相同),则将新音符分配到新轨道。
  4. 最后将拆分后的消息写入新MIDI文件,每个轨道对应一组无重叠的音符。

简单实现代码片段:

import mido
from collections import defaultdict

def split_polyphonic_track(mid, track_idx):
    original_track = mid.tracks[track_idx]
    new_tracks = [mido.MidiTrack() for _ in range(8)]  # 预设8个轨道覆盖多数场景
    current_track = 0
    active_notes = defaultdict(list)  # 记录音符对应的轨道和起始时间
    absolute_time = 0

    for msg in original_track:
        absolute_time += msg.time
        if msg.type == 'note_on' and msg.velocity > 0:
            # 检查是否有重叠或同时触发的活跃音符
            has_overlap = any(start <= absolute_time for _, start in active_notes.values())
            if has_overlap:
                current_track = (current_track + 1) % len(new_tracks)
            active_notes[msg.note].append((current_track, absolute_time))
            # 写入消息到对应轨道(先存绝对时间,后续转相对时间)
            new_tracks[current_track].append(msg.copy(time=absolute_time))
        elif msg.type in ('note_off',) or (msg.type == 'note_on' and msg.velocity == 0):
            if msg.note in active_notes:
                track_num, start_time = active_notes[msg.note].pop()
                new_msg = msg.copy(time=absolute_time - start_time)
                new_tracks[track_num].append(new_msg)
    
    # 将轨道内的绝对时间转为相对时间
    for track in new_tracks:
        last_time = 0
        for msg in track:
            msg.time -= last_time
            last_time += msg.time
    
    # 替换原轨道并保留非空新轨道
    mid.tracks.pop(track_idx)
    mid.tracks.extend([t for t in new_tracks if t])
    return mid

# 使用示例
mid = mido.MidiFile('input.mid')
mid = split_polyphonic_track(mid, 0)
mid.save('output.mid')

C. 用mido Message的time属性识别同时/重叠音符

mido的time是相对时间,即当前消息与上一条消息的时间间隔,要识别同时/重叠音符,需先计算每个消息的绝对时间:

  1. 初始化absolute_time = 0,遍历轨道消息时,每次执行absolute_time += msg.time,得到当前消息的实际触发时间。
  2. 同时触发的音符:多个note_on消息的absolute_time完全一致(通常表现为前一条消息的time为0)。
  3. 重叠(延音)的音符:新note_on的absolute_time小于某一活跃音符的结束时间(即该音符尚未触发note_off)。

识别逻辑示例:

import mido

def detect_overlapping_notes(track):
    absolute_time = 0
    active_notes = {}  # 键:音符编号,值:起始绝对时间
    overlapping_records = []

    for msg in track:
        absolute_time += msg.time
        if msg.type == 'note_on' and msg.velocity > 0:
            # 检查当前活跃的未结束音符,记录重叠关系
            for note_num, start_time in active_notes.items():
                overlapping_records.append((note_num, msg.note, start_time, absolute_time))
            active_notes[msg.note] = absolute_time
        elif msg.type in ('note_off',) or (msg.type == 'note_on' and msg.velocity == 0):
            if msg.note in active_notes:
                del active_notes[msg.note]
    return overlapping_records

# 使用示例
mid = mido.MidiFile('input.mid')
overlaps = detect_overlapping_notes(mid.tracks[0])
print("重叠音符记录:", overlaps)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:15:36