MIDI文件格式分析
(理论篇)
MIDI文件属于二进制文件,这种文件一般都有如下基本结构: 文件头+数据描述
文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会出现文件头这种东西。
而数据描述部份是主体,我们现在来一起分析它的结构:
在每个Midi文件的开头都有如下内容,它们的十六进制代码为:“4d 54 68 64 00 00 00 06 ff ff nn nn dd dd”。
前四个是ASCII字符“MThd”是用来鉴别是否Midi文件,而随后的四个字节是指明文件头描述部分的字节数,它总是6,所以一定是“00 00 00 06”,以下是剩余部分的含义:
ff ff | 指定Midi的格式 | 00 00 | 单音轨 |
00 01 | 多音轨,且同步。这是最常见的 | ||
00 02 | 多音轨,但不同步 | ||
nn nn | 指定轨道数 | 实际音轨数加上一个全局的音轨 | |
dd dd | 指定基本时间 | 一般为120(00 78),即一个四分音符的tick数,tick是MIDI中的最小时间单位 |
以上就是MIDI文件头了,后面的所有内容都是真正做事的,我们先来看看它的构成。
MIDI的数据是由若干个格式相同的子数据构成的,这些子数据在多音轨的格式中记录了一个轨道的所有信息。多加一个音轨,就简单地把数据追加在前一音轨的后面就可以了,不过不要忘记更改文件头中的nn nn(轨道数)。
先看全局音轨。全局音轨包括歌曲的附加信息(比如标题和版权)、歌曲速度和系统码(Sysx)等内容。
不管是全局音轨还是含有音符的音轨,都以“4D 54 72 6B”开头,它其实是ASCII字符“MTrk”,其后跟着一个4个字节的整数,它标志了该轨道的字节数,这不包括前面的4个字节和本身的4个字节。这一点,我们可以在后面的例子中去理解。
接着就是记录数据的地方了,每一个数据有着相同的结构:时间差+事件。
所谓时间差,指的是前一个事件到该事件的时间数,它的单位是tick(MIDI的最小时间单位)。它的构成比较特殊,这里要用二进制来说明。
一个字节有8位,如果仅使用7位,它可以表示0~127这128个数,而剩下的一位,则用来作为标志。如果要表示的数在以上范围,则这个标志为0,这时,一个7位的字节可以表示0~127tick。如果要表示的数超出了这个范围(比如240),则把标志设置成1,然后记录下高7位,剩下的留给下一个字节,在该例中240可以分解成128*1+112,这里的1就是第一个字节要记录的,加上标志位,应该为10000001,即十六进制的81;而112是下一个字节记录的,它的十六进制为70:所以要表示240这个时间,要写成81 70。同理,如果要表示65535tick,则可以先计算出65535=1282*3+1281*127+1280*127,然后得出结果:83 FF 7F。由此,我们反过来也可以知道如何确定时间差:只要标志位为0,则表示结束读取时间差。比如82 C0 03表示1282*2+1281*64+1280*3=40963,如果基本时间为120,则有341:043个四分音符。
以这种方式记录整数的字节称为动态字节,它根据记录的整数改变自身的长度,这在后面还要用到,所以必须熟练计算。
看完了这么麻烦的东西,我们再来看个更麻烦的东西:事件。在这些标准的解释后面,我们会通过一些例子来进一步掌握这些内容。
事件大体上可以分为音符、控制器和系统信息这几个种类。对于这些事件,都有统一的表达结构:种类+参数。
对于一个音符,由于它的有效范围是0~127,所以直接用00~7F作为“种类”,可以认为是个音符,比如3C表示中央C。而一个音符的最重要的参数是力度(也叫速度:velocity)。比如,3C 64 表示一个力度为十进制100的中央C音符。
因为一个字节有8位,所以剩余的一位如果置1,再联合其他的7位,则可以表示各种信息。我们暂且无视一个音轨到底是全局的还是用于记录音符的。它们归根结底都是用来记录各种事件的,只不过有些应出现在全局音轨比较合乎逻辑而已。既然这样,我们就可以从下面的表来看事件:
下表中,x表示音轨0~F,比如81表示松开第二轨的音符。
种类 | 参数(十六进制) | |
字节 | 含义 | |
8x | 松开音符 | 音符(00~7F):松开的音符 |
力度:00~7F | ||
9x | 按下音符 | 音符(00~7F):按下的音符 |
力度:00~7F | ||
Ax | 触后音符 (Key After Touch) | 音符:00~7F |
力度:00~7F | ||
Bx | 控制器 | 控制器号码:00~7F |
控制器参数:00~7F | ||
Cx | 改变乐器 | 乐器号码:00~7F |
Dx | 触后通道 | 值:00~7F |
Ex | 滑音 | 音高(Pitch)低位:Pitch mod 128 |
音高高位:Pitch div 128 | ||
F0 | 系统码 | 系统码字节数:动态字节 |
系统码:不含开头的F0,但包括结尾的F7 | ||
FF | 其他格式 | 程式种类:00~FF |
数据占用的字节数:动态字节 | ||
数据:个数由上一参数确定 | ||
00~7F | 上次激活格式的参数(8x、9x、Ax、Bx、Cx、Dx、Ex) |
下表详细地列出了FF的详细情况,对于字节数由数据决定的情况,表中以“--”表示。
种类 | 字节数 | 数据 | |
字节 | 含义 | ||
00 | 设置轨道音序 | 02 | 音序号 00 00~ FF FF |
01 | 歌曲备注 | -- | 文本信息 |
音轨文本 | 文本信息 | ||
02 | 歌曲版权 | -- | 版权信息 |
03 | 歌曲标题 | -- | 歌曲标题:用于全局音轨,第一次使用表示主标题,第二次表示副标题 |
音轨名称 | -- | 音轨名 | |
04 | 乐器名称 | -- | 音轨文本(同01/2) |
05 | 歌词 | -- | 歌词 |
06 | 标记 | -- | 用文本标记(Marker) |
07 | 开始点 | -- | 用文本记录开始点(同01/2) |
2F | 音轨结束标志 | 00 | 无 |
51 | 速度 | 03 | 3字节整数,1个四分音符的微秒数 |
58 | 节拍 | 04 | 分子 |
分母:00(1),01(2),02(4),03(8)等 | |||
节拍器时钟 | |||
一个四分音符包含的三十二分音符的个数 | |||
59 | 调号 | 02 | 升降号数:-7~-1(降号),0(C),1~7(升号) |
大小调:0(大调),1(小调) | |||
7F | 音序特定信息 | -- | 音序特定信息 |
这些就是MIDI结构的全部内容,我们现在通过一个实例来分析。
MIDI文件格式分析
(RMI篇)
MIDI文件除了有常用的以.mid为扩展名的格式以外,还有一种以.rmi为扩展名的。这种格式与.mid不同的是它在.mid格式的前面增加了一个文件头,后面的部分几乎和.mid的一样。即.rmi=RMI格式文件头+.mid文件。
这个文件头可以描述成以下这种形式:
52 49 46 46 LL LL LL LL 52 4D 49 44 64 61 47 61 SS SS SS SS
和.mid文件格式类似,这个文件头的前四个字节是“RIFF”,接下来的是一个四个字节的整数,它表示从最后一个LL起到文件结束的字节数。假设这个数是100,则这四个字节就是“64 00 00 00”。然后紧接着的八个字节又是文字标识,不过这次是“RMIDdata”,最后的四个字节SS,表示MIDI文件的字节数,如果MIDI文件长度为139,则这四个字节就是“8B 00 00 00”。文件头后就是和MIDI文件一样的内容了,这些内容的长度就是SS的值。