udanish50/midipy: A Python package for MIDI data processing, analysis, and parsing.
MIDI文件格式
概述
- MIDI文件由文件头和若干个音轨(track)组成,音轨不一定为音频数据,也可能保存乐曲的meta信息
文件头Header chuck
- 文件头固定为14个字节
| 项目 | 长度 | 内容 |
|---|---|---|
| MIDI标识 | 4 | “MThd” |
| 文件头长度header_len | 4 | [0x00,0x00,0x00,0x06],其后共6个字节 |
| 格式mid_format | 2 | 0,1,或2 |
| Track数量 | 2 | |
| division | 2 | delta timing的单位 |
- 如果格式mid_format为1时,文件将有两个音轨,第一个只保存meta数据,第二个为音频音轨
音轨Track chuck
- 音轨以”MTrk”开头
| 项目 | 长度 | 内容 |
|---|---|---|
| 音轨标识 | 4 | “MTrk” |
| 音轨长度track_len | 4 | |
| 音轨内容 | 由音轨长度决定 |
- 音轨长度:大端模式,例如使用python的
int.from_bytes(trk[4:8], byteorder='big')转化 - 一个音轨由一到多个音轨event组成,共分为MIDI、Meta、SysEx三种不同event,event包含delta-time(用可变长度格式表示)标识时间的偏移和具体event内容
- 不同类型event用其第一个字节标识,MIDI为0x80
0xEF,SysEx为0xF00xF7,Meta为0xFF - 构成形式为
<MTrk> + <track_len> + <delta-time + event_1> +<delta-time + event_2> + ...
可变长度格式的转化(如delta-time、数据长度)
- delta-time可能为1~4字节长,字节的第一个比特如果为1,表明后面是否还有delta-time数据。转化方法代码实例为:
## decide the variable length value
def get_len(var):
## input: 4 bytes
## return: 1) the number of bytes of the variable length, 2) the actual length it indicates
real_len = 0
var_len = 1
continuation = False
for i in range(4):
real_len = (real_len<<7)|(var[i]&0x7f)
if var[i]&0x80==0:
var_len = i+1
break
return real_len, var_len- 测试用例:
| 数据 | 有效位 | delta-time数据 | 实际值 |
|---|---|---|---|
[0x00, 0xf0, 0x30, 0x30] | 1 | 0x00 | 0x0 |
[0x40, 0x80, 0xf0, 0x30] | 1 | 0x40 | 0x40 |
[0x7f, 0xff, 0x50, 0x30] | 1 | 0x7F | 0x7F |
[0x81, 0x00, 0xe0, 0x30] | 2 | 0x81 0x00 | 0x80 |
[0xc0, 0x00, 0xf5, 0x30] | 2 | 0xC0 0x00 | 0x2000 |
[0xff, 0x7f, 0xf0, 0x30] | 2 | 0xFF 0x7F | 0x3FFF |
[0x81, 0x80, 0x00, 0xf0] | 3 | 0x81 0x80 0x00 | 0x4000 |
[0xc0, 0x80, 0x00, 0xf0] | 3 | 0xC0 0x80 0x00 | 0x100000 |
[0xff, 0xff, 0x7f, 0xd0] | 3 | 0xFF 0xFF 0x7F | 0x1FFFFF |
[0x81, 0x80, 0x80, 0x00] | 4 | 0x81 0x80 0x80 0x00 | 0x200000 |
[0xc0, 0x80, 0x80, 0x00] | 4 | 0xC0 0x80 0x80 0x00 | 0x8000000 |
[0xff, 0xff, 0xff, 0x7f] | 5 | 0xFF 0xFF 0xFF 0x7F | 0xFFFFFFF |
t = [[0x00, 0xf0, 0x30, 0x30], # 00
[0x40, 0x80, 0xf0, 0x30], # 40
[0x7f, 0xff, 0x50, 0x30], # 7F
[0x81, 0x00, 0xe0, 0x30], # 81 00
[0xc0, 0x00, 0xf5, 0x30], # c0 00
[0xff, 0x7f, 0xf0, 0x30], # ff 7f
[0x81, 0x80, 0x00, 0xf0], # 81 80 00
[0xc0, 0x80, 0x00, 0xf0], # c0 80 00
[0xff, 0xff, 0x7f, 0xd0], # ff ff 7f
[0x81, 0x80, 0x80, 0x00], # 81 80 80 00
[0xc0, 0x80, 0x80, 0x00], # c0 80 80 00
[0xff, 0xff, 0xff, 0x7f]] # ff ff ff 7fMeta event
- Meta event以0xFF开头
| 项目 | 长度 | 内容 |
|---|---|---|
| Meta event标识 | 1 | 0xFF |
| Meta类型标识 | 1 | 如0x03等 |
| event 长度 | 1~4 | 参考可变长度格式 |
| event内容 | 由event长度决定 |
- 测试用例
0x00, 0xFF, 0x51, 0x03, 0x0C, 0xB7, 0x35, 0x00, 0xFF, 0x2F, 0x00包含两个Meta event,0x00, 0xFF, 0x51, 0x03, 0x0C, 0xB7, 0x35,和0x00, 0xFF, 0x2F, 0x00两者delta-time均为0.
Meta类型
| 值 | 类型 | 值 | 类型 | 值 | 类型 | 值 | 类型 |
|---|---|---|---|---|---|---|---|
| 0x00 | Sequence number | 0x04 | 0x20 | 0x58 | |||
| 0x01 | 0x05 | 0x2F | End of track | 0x59 | |||
| 0x02 | 0x06 | 0x51 | Tempo setting | 0x7F | |||
| 0x03 | Sequence or track name | 0x07 | 0x54 |
Tempo计算
- 首先由Meta内容长度提取出Tempo数据
- 其为大端显示,转化方式如
tempo = int.from_bytes(data, byteorder='big') - BPM为
int(60*(10**6)/tempo)) - 测试用例:
0x00, 0xFF, 0x51, 0x03, 0x0C, 0xB7, 0x350x51为Tempo类型,长度为0x03,内容为0x0C, 0xB7, 0x35,计算出的tempo为833333,单位为微秒 换算为BPM为72