如何用Python的Pretty MIDI读取MIDI文件中的休止符?
解决PrettyMIDI读取MIDI文件时忽略休止符的问题
PrettyMIDI默认仅存储实际发声的音符,休止符的时长隐含在时间轴的空白间隔中,需要手动计算或通过采样识别。以下是两种实用解决方案:
方法一:通过音符时间差计算休止符
直接基于音符的起始/结束时间,计算相邻音符间的空白时长,还原休止符信息:
示例代码
import pretty_midi # 加载目标MIDI文件 midi_data = pretty_midi.PrettyMIDI('your_midi_file.mid') # 提取目标轨道的音符(多轨道可遍历处理) target_instrument = midi_data.instruments[0] notes = target_instrument.notes # 按音符起始时间排序,确保时间顺序正确 notes.sort(key=lambda note: note.start) # 整理包含休止符的事件序列 event_sequence = [] previous_note_end = 0.0 for note in notes: # 计算当前音符与上一个音符之间的休止时长 rest_duration = note.start - previous_note_end # 过滤浮点误差导致的微小间隔(可根据需求调整阈值) if rest_duration > 0.001: event_sequence.append( {"type": "rest", "duration": round(rest_duration, 2)} ) # 添加音符事件,包含音高、时长信息 event_sequence.append( { "type": "note", "pitch": note.pitch, "duration": round(note.end - note.start, 2) } ) # 更新上一个音符的结束时间 previous_note_end = note.end # 输出结果 for event in event_sequence: if event["type"] == "rest": print(f"休止符 | 时长: {event['duration']} 秒") else: print(f"音符 | 音高: {event['pitch']} | 时长: {event['duration']} 秒")
说明
- 基于原始音符事件,精度完全匹配MIDI文件的时间标记,适合需要精确时长数据的场景。
- 多轨道场景下,可分别处理每个轨道的音符序列,或合并所有轨道时间轴计算全局休止符。
方法二:通过钢琴卷帘(Piano Roll)识别休止符
将MIDI转换为时间步采样的矩阵,遍历每个时间步判断是否有发声音符,从而识别休止符:
示例代码
import pretty_midi import numpy as np # 加载MIDI文件并提取目标轨道 midi_data = pretty_midi.PrettyMIDI('your_midi_file.mid') target_instrument = midi_data.instruments[0] # 生成钢琴卷帘矩阵,fs为采样率(每秒采样次数) fs = 100 # 可根据精度需求调整 piano_roll = target_instrument.get_piano_roll(fs=fs) # 生成对应的时间轴 time_steps = np.arange(piano_roll.shape[1]) / fs # 遍历时间步识别音符与休止符 current_state = "rest" segment_start = time_steps[0] for idx in range(piano_roll.shape[1]): # 判断当前时间步是否有音符发声 has_active_note = np.any(piano_roll[:, idx] > 0) if has_active_note and current_state == "rest": # 休止符结束,计算时长 rest_duration = round(time_steps[idx] - segment_start, 2) if rest_duration > 0.01: # 过滤微小间隔 print(f"休止符 | 时长: {rest_duration} 秒") current_state = "note" segment_start = time_steps[idx] elif not has_active_note and current_state == "note": # 音符结束,计算时长 note_duration = round(time_steps[idx] - segment_start, 2) print(f"音符段 | 时长: {note_duration} 秒") current_state = "rest" segment_start = time_steps[idx] # 处理最后一段未结束的状态 if current_state == "rest": final_rest_duration = round(time_steps[-1] - segment_start, 2) if final_rest_duration > 0.01: print(f"休止符 | 时长: {final_rest_duration} 秒") else: final_note_duration = round(time_steps[-1] - segment_start, 2) print(f"音符段 | 时长: {final_note_duration} 秒")
说明
- 适合可视化或需要按固定时间步处理的场景,精度受采样率
fs影响,采样率越高精度越高,但计算量也会增加。
内容的提问来源于stack exchange,提问作者Megan
相关产品推荐
相关产品推荐

