Standard MIDI File(SMF)是存储MIDI事件时间序列的标准格式,非音频数据,包含track、delta time、status byte和data bytes;需正确处理VLQ编码、running status及0xFF元事件,常见类型为Type 0(单轨)和Type 1(多轨)。

什么是Standard MIDI File(SMF)格式
标准MIDI文件(.mid)不是音频数据,而是事件时间序列,包含track、delta time、status byte和data bytes。直接用二进制读取会踩坑:误把0xFF元事件当音符、忽略running status导致解析错位、没处理variable-length quantity(VLQ)导致时间戳全乱。
用libmidi++或RtMidi读取前先确认文件类型
很多“MIDI文件”其实是Type 0(单轨)或Type 1(多轨),Type 2极少用且不被多数库支持。用xxd -l 8 file.mid检查头4字节是否为"MThd",再读第8–9字节(big-endian)判断format字段值:0、1或2。libmidi++默认只支持Type 0/1;RtMidi根本不解析文件,只收发实时MIDI消息——别拿它去读.mid文件。
- 推荐轻量方案:
tiny_midi(单头文件,支持Type 0/1,能提取note_on/note_off事件) - 若需精确时序和控制器信息,用
libsmf(C++11,提供Smf::getTrackCount()、Smf::getEventCount(track)等接口) - 别手写VLQ解码:每个delta time是变长整数,高位bit=1表示继续读下个byte,例如
0x81 0x00实际是0x0001(不是0x8100)
从MIDI事件中提取音符起止时间
MIDI里没有“音符”对象,只有note_on(velocity > 0)和note_off(或note_on with velocity = 0)。同一通道上相同note_number的note_on必须匹配最近未关闭的note_off,否则出现悬停音(stuck note)。关键点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 按
delta time累加得到绝对时间(单位:ticks),再根据division(文件头第10–11字节)转为秒:若division > 0x8000,则是frames-per-second模式;否则是ticks-per-quarter-note,需结合set_tempo元事件计算BPM -
note_on事件的velocity为0时,等效note_off,必须统一处理 - 多轨文件中,不同
track可能有相同时间戳的音符,需合并排序后处理
// tiny_midi示例:获取第一个音符的pitch和duration
for (auto& ev : track.events) {
if (ev.type == midi::EVENT_NOTE_ON && ev.velocity > 0) {
int pitch = ev.data[0];
// 找下一个同channel同pitch的note_off或velocity=0的note_on
}
}
常见解析错误与绕过方式
真实MIDI文件常含非标准扩展:厂商专用元事件(0xFF 0x7F)、未终止的note_on、嵌套sysex块。硬解析易崩溃:
立即学习“C++免费学习笔记(深入)”;
- 遇到未知
status byte(如0xF8、0xF9)直接跳过,别假设它是音符 - 元事件
0xFF后跟0x51(set_tempo)必须记录,否则BPM算错;0xFF 0x54(SMPTE offset)可忽略 - 某些DAW导出的MIDI含大量
0xFF 0x2F 0x00(track end),但位置不在末尾——以track length字段为准,别依赖该标记 - 音高
note_number范围是0–127,但0(C-1)和127(G9)极少使用,过滤掉pitch 108能避开测试数据噪音
真正麻烦的是节奏量化误差和running status状态丢失——这需要在逐字节解析时严格维护上一个status byte,而不是靠event type字段猜。

















