|  | 
 
| MIDI文件除了有常用的以.mid为扩展名的格式以外,还有一种以.rmi为扩展名的。这种格式与.mid不同的是它在.mid格式的前面增加了一个文件头,后面的部分几乎和.mid的一样。即.rmi=RMI格式文件头+.mid文件。 % ?( k" l* z) A; k& a6 G3 C2 O# e3 d" u# B8 ^
 这个文件头可以描述成以下这种形式:
 / E/ O% [3 N, `' b1 q" i# A3 ~0 x, F2 T- r' m
 52 49 46 46 LL LL LL LL 52 4D 49 44 64 61 47 61 SS SS SS SS: }( ~7 B! q: R) h2 L7 x+ q
 
 5 y" ^9 Q' ?- a+ I" }3 |和.mid文件格式类似,这个文件头的前四个字节是“RIFF”,接下来的是一个四个字节的整数,它表示从最后一个LL起到文件结束的字节数。假设这个数是100,则这四个字节就是“64 00 00 00”。然后紧接着的八个字节又是文字标识,不过这次是“RMIDdata”,最后的四个字节SS,表示MIDI文件的字节数,如果MIDI文件长度为139,则这四个字节就是“8B 00 00 00”。文件头后就是和MIDI文件一样的内容了,这些内容的长度就是SS的值。
 1 l' _( M3 P2 N, ^8 @) b, B$ ]! j! d4 Z$ I% r+ q
 该文档是前两编的补充,主要讲述以下内容:1 @: O3 [) Q" b: _" W  i( [
 
 9 ?" s/ x; j0 a音符十六进制的计算
 * E# q( Y2 Q) |0 m2 A( }关于乐器选择
 5 J  @% o3 u6 }3 I2 U6 ORPN和NRPN! z, M! s' h6 _
 在MIDI中,中央C是C5,最低音是C0,最高音是G5,要计算任何一个音符对应的十六进制,可以使用这个公式:1 {+ D  V, C8 ~6 M
 ( U$ _6 H$ T1 K
 假设音符是NO,表示第O八度的音名N,比如G2中,N为G,O为2,则它的十进制为O*12+N,N的值为了简便起见,用下表给出:, H. L7 B! I& T- K
 3 K' T: A' l, S0 ^7 J4 R
 
 : V! B, x3 f2 ~1 r2 N9 m8 x0 e! L- I6 t( E
 这样G2的十进制值为2*12+7=31,十六进制为1F。
 3 t* B/ f/ r' z2 L* Z: c+ `# [# f
 + p3 c1 V9 n' P7 {9 t1 b; B若知道音符的十六进制,也可以很容易求出音符,比如64(16)=100(10),
 , J" k+ V; i% S/ P+ |) x- ^" J& ?" Z7 y4 [( T0 l
 而100 div 12=8,100 mod 12=4,对应音符为E8。写成公式就是:0 }  f  @0 C7 l3 G" l6 [0 v4 e, K2 n
 , f# I, r0 r$ l
 N=B mod 12;O=B div 12;(设B为表示音符的字节的十进制数)
 ( r/ z. o5 i) A5 n4 Y- P
 " Q) Y. l$ C3 x5 Y5 ^( J乐器是MIDI中比较重要的因素,要选择所有的乐器不仅仅只是使用Cx号标志就能完成的,还必须结合BankSelect(乐队选择),而BankSelect其实是由0号控制器和32号控制器完成的,它们的十六进制代码分别是00和20.比如要选择出XG标准中的Slow Violin,它在第08H个乐队中的第28H号乐器中,所以它的完整代码应为“00 B0 00 00 00 20 08 00 C0 28”。我们来分析它的构成:这里我们假设时间差为00,所有信息都发给通道00,所以第一个00是时间差,B0是打开控制器的标志,并指定发送到通道00,接下来的00 00,是由控制器号00和控制器参数00构成的,它事实上是表示0号控制器的参数为0,即BankSelect-MSB的参数为0,然后是下一个事件,它是“00 20 08”,即时间差是00,使用20H号控制器,参数为08H,即BankSelect-LSB的参数是08H,这样就指定了Bank(乐队)。再下来就是“00 C0 28”,就是所谓的Patch Change事件了,它的时间差为00,参数是28H。这样就完成了标准的乐器选择。# N$ l" \' O5 [& y* H
 2 O- c; H* W. q8 M  G5 A0 \
 事实上,它是由三个事件共同完成的,如果某次选择乐器和上次的乐器有共同的参数,可以不必重复使用相关的操作。
 # u) @0 J8 t% c  f; }; m: E( e6 q9 Y, Y8 `) A- P+ w, D
 在本例中,您可能发现了一点,当连续使用同类操作时可以不必每次指定操作种类,比如这里的连续再次使用控制器,所以第二个控制器并没有使用B0作为标志,而是直接使用控制器号码和它的参数。这一点和音符是一样的。其实,如果连续使用Patch Change事件(我是说如果),则也可不必每次都写Cx,打开了一次就可以了;不过就Patch Change事件而言,连续地更换乐器的结果是仅最后一个有效而已。
 3 G: I- ^9 r1 L8 v. w( i/ H0 `* u5 g, [# |4 z3 f5 N: @
 在前面的文档中并没有提及RPN和NRPN,其实它们是由四个连续的控制器来实现的。我们假设要使用RPN事件的Coarse Turning,它的参数假设是4096,则它的字节是“00 B0 65 00 00 64 02 00 06 20 00 26 00”,我们来分析这段字节:首先我们先看看RPN是由哪四个控制器组成的——首先设置RPN-MSB和RPN-LSB,分别对应的控制器是65H和64H,Coarse Turning的RPN码是2,所以MSB为0,LSB为2;然后是设置Data Entry MSB和Data Entry LSB,对应的控制器是06H和26H,而4096 div 128=32,4096 mod 128=0,对应的十六进制数分别是20H和00H。因此就构成了上面的字节。而NRPN和RPN原理是一样的,只不过不用RPN-MSB和RPN-LSB,而改用NRPN-MSB和NRPN-LSB而已,它们对应的十六进制数分别为63H和62H。
 , Z% ~+ z- g' Y" e& _/ r: v/ ~0 B5 u% K$ O
 要书写二进制(十六进制)文件,应该准备好一些工具,比如我自己用的是VC++,因为学习MIDI格式无非是想写它的软件,既然VC++可以编辑二进制文件,就将就着用吧。其次,应该找个可以编辑和播放MIDI文件的软件,比如Cakewalk,这样就可以开始了。7 ]. E7 a8 c, d. e/ b1 H0 Q) V# ^
 
 $ I4 W) k% Q* G8 Z首先书写文件头“4d 54 68 64 00 00 00 06”,我们直接写同步多音轨的格式,先写1个音轨,并以120为一个音符的基本时间。这样,随后的字节是:“00 01 00 01 00 78”。# C# ]8 F, y& G6 D! M% ?
 
 4 F! I9 `! T$ w/ z现在,如果用Cakewalk打开会失败,因为我们指定的音轨数为1,但是并没有书写任何音轨,如果改成“00 01 00 00 00 78”再打开,就不会出问题了。所以,今后如果更改了音轨数,千万不要忘记向“上头”汇报。
 2 i- B9 [  Y4 A& T! y" {6 q
 4 M# L7 C0 |/ G$ q把轨道数改回01,继续我们的实验。先写音轨的头信息:“4D 54 72 6B”(MTrk),因为我们还不能确定后面有多少字节,所以先把它假设成“00 00 00 00”,以后再回来改。
 0 O7 y% @  F/ Z) T% p2 [. e5 A1 J& x1 W! G( F3 ]
 我们先尝试设置歌曲的速度和节拍等基本信息。假设一个四分音符的时间是半秒,即0.5*106微秒。它的十六进制数是07A120,再看事件表,设置速度是51,但是在其前面必须是FF,然后它须要3个字节作为参数,因此字节数为03,参数为“07 A1 20”,也就是“FF 51 03 07 A1 20”。这是事件部分,不要忘记在其之前有个参数——时间差。这是一开始就应该设置的参数,因此时间差为00。所以,完整的事件应该是“00 FF 51 03 07 A1 20”,我们把这一段追加在Midi文件末尾。
 $ n3 S3 r2 c6 m4 f" ~2 r; m& @+ o$ b# j3 D1 V' ?! m6 L
 这时先不要急着用Cakewalk验证,因为我们还没有向“上级”报告,没错,把前面表示字节数的“00 00 00 00”改成“00 00 00 07”,如果用VC++作为二进制文件的编辑器,选择了事件后,可以在状态栏看到选择的字节长。保存后,再用Cakewalk打开,就可以看见速度是120。& D9 F* }/ F- _1 h  x1 R! B9 w
 ( g8 S, ~& r6 c9 ?% `  G  p
 我们再来设置节拍和调号,因为一般用Cakewalk新建一个Midi会默认地设置成4/4,C大调,我们就改设成6/8,A大调。查阅事件表知道,58和59是分别用来设置节拍和调号的。虽然设置节拍的参数很多,但在现在的系统中,后两个参数是被忽略的,而且Cakewalk还会对其进行修正。因此,我们只要设置好实际有用的就可以了。分子是6,分母是8,所以第一个参数是06,第二个参数是03(23=8)。最后,补上前面的时间差和后面的两个被忽略的参数,它应该是“00 FF 58 04 06 03 00 00”;再看调号,A调有3个升号,因此可以这样的事件可以表示为“00 FF 59 02 03 00”。事实上,大小调是个被忽略的参数。我们统计一下至今为止事件的字节数,然后更改前面的参数,即把“00 00 00 07”改成“00 00 00 15”。保存后用Cakewalk打开,再进入五线谱窗口,就可以马上验证了。细心的你可能已经发现,进入五线谱窗口前和平常有些延迟,这是因为我们并没有设置好那些可以忽略的字节,而Cakewalk就是在对其进行重新验证,这一点,我们以后再讨论。
 : n. C7 R+ a/ U% Q& v, Q& Y9 _% u1 I) ~5 V& [, B2 t! s* K
 
   | 
 |