如何解决MIDI文件单轨多音符同时播放问题?含mido技术问询
处理MIDI单轨多音符问题的解决方案
A. 自动拆分复音到独立轨道的工具
- 专业MIDI编辑软件:Cakewalk、FL Studio、Ableton Live这类工具都自带「拆分复音到轨道」功能,导入MIDI后选中目标轨道,通过对应菜单选项就能自动将同时发声的音符分配到独立轨道。
- 脚本/轻量工具:可以用
midicsv把MIDI转成CSV格式,写简单脚本按时间分组音符后再转回MIDI;Python的pretty_midi库也能直接实现该逻辑,通过其Instrument类可拆分复音到多个实例。
B. 用mido库处理该问题
完全可以实现,核心思路是手动跟踪音符的触发/结束时间,将重叠或同时发声的音符分配到不同轨道,步骤如下:
- 读取原MIDI文件,遍历目标轨道的所有消息,累加
time属性得到每个消息的绝对时间。 - 维护字典记录当前活跃音符(键为音符编号,值为起始绝对时间、所属轨道等信息)。
- 遇到
note_on消息时,若存在未结束的活跃音符(或与上一个note_on绝对时间相同),则将新音符分配到新轨道。 - 最后将拆分后的消息写入新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是相对时间,即当前消息与上一条消息的时间间隔,要识别同时/重叠音符,需先计算每个消息的绝对时间:
- 初始化
absolute_time = 0,遍历轨道消息时,每次执行absolute_time += msg.time,得到当前消息的实际触发时间。 - 同时触发的音符:多个
note_on消息的absolute_time完全一致(通常表现为前一条消息的time为0)。 - 重叠(延音)的音符:新
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
相关产品推荐
相关产品推荐

