udanish50/midipy: A Python package for MIDI data processing, analysis, and parsing.

MIDI文件格式

概述

  • MIDI文件由文件头和若干个音轨(track)组成,音轨不一定为音频数据,也可能保存乐曲的meta信息

文件头Header chuck

  • 文件头固定为14个字节
项目长度内容
MIDI标识4“MThd”
文件头长度header_len4[0x00,0x00,0x00,0x06],其后共6个字节
格式mid_format20,1,或2
Track数量2
division2delta timing的单位
  • 如果格式mid_format为1时,文件将有两个音轨,第一个只保存meta数据,第二个为音频音轨

音轨Track chuck

  • 音轨以”MTrk”开头
项目长度内容
音轨标识4“MTrk”
音轨长度track_len4
音轨内容由音轨长度决定
  • 音轨长度:大端模式,例如使用python的int.from_bytes(trk[4:8], byteorder='big')转化
  • 一个音轨由一到多个音轨event组成,共分为MIDI、Meta、SysEx三种不同event,event包含delta-time(用可变长度格式表示)标识时间的偏移和具体event内容
  • 不同类型event用其第一个字节标识,MIDI为0x800xEF,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]10x000x0
[0x40, 0x80, 0xf0, 0x30]10x400x40
[0x7f, 0xff, 0x50, 0x30]10x7F0x7F
[0x81, 0x00, 0xe0, 0x30]20x81 0x000x80
[0xc0, 0x00, 0xf5, 0x30]20xC0 0x000x2000
[0xff, 0x7f, 0xf0, 0x30]20xFF 0x7F0x3FFF
[0x81, 0x80, 0x00, 0xf0]30x81 0x80 0x000x4000
[0xc0, 0x80, 0x00, 0xf0]30xC0 0x80 0x000x100000
[0xff, 0xff, 0x7f, 0xd0]30xFF 0xFF 0x7F0x1FFFFF
[0x81, 0x80, 0x80, 0x00]40x81 0x80 0x80 0x000x200000
[0xc0, 0x80, 0x80, 0x00]40xC0 0x80 0x80 0x000x8000000
[0xff, 0xff, 0xff, 0x7f]50xFF 0xFF 0xFF 0x7F0xFFFFFFF
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 7f

Meta event

  • Meta event以0xFF开头
项目长度内容
Meta event标识10xFF
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类型

类型类型类型类型
0x00Sequence number0x040x200x58
0x010x050x2FEnd of track0x59
0x020x060x51Tempo setting0x7F
0x03Sequence or track name0x070x54

Tempo计算

  • 首先由Meta内容长度提取出Tempo数据
  • 其为大端显示,转化方式如tempo = int.from_bytes(data, byteorder='big')
  • BPM为int(60*(10**6)/tempo))
  • 测试用例:0x00, 0xFF, 0x51, 0x03, 0x0C, 0xB7, 0x35 0x51为Tempo类型,长度为0x03,内容为0x0C, 0xB7, 0x35,计算出的tempo为833333,单位为微秒 换算为BPM为72