找回密码
 快速注册

QQ登录

只需一步,快速开始

查看: 15840|回复: 2

[电脑与MIDI音乐] MIDI文件格式分析

[复制链接]

1万

积分

27

听众

7415

金贝

中国原创歌词基地中级

积分
15296
sina 发表于 2016-10-31 20:06:11 | 显示全部楼层 |阅读模式
MIDI文件除了有常用的以.mid为扩展名的格式以外,还有一种以.rmi为扩展名的。这种格式与.mid不同的是它在.mid格式的前面增加了一个文件头,后面的部分几乎和.mid的一样。即.rmi=RMI格式文件头+.mid文件。
4 T6 s' R* q: f$ j  J, f5 k
) G( Y8 R% m8 |) p; U6 c; n这个文件头可以描述成以下这种形式:
, l- b" K" ]0 K4 j( W) l
4 z4 l+ J* ]) J52 49 46 46 LL LL LL LL 52 4D 49 44 64 61 47 61 SS SS SS SS
/ h7 G7 d& n/ m8 ^3 ^6 W) R0 d+ Y0 H
和.mid文件格式类似,这个文件头的前四个字节是“RIFF”,接下来的是一个四个字节的整数,它表示从最后一个LL起到文件结束的字节数。假设这个数是100,则这四个字节就是“64 00 00 00”。然后紧接着的八个字节又是文字标识,不过这次是“RMIDdata”,最后的四个字节SS,表示MIDI文件的字节数,如果MIDI文件长度为139,则这四个字节就是“8B 00 00 00”。文件头后就是和MIDI文件一样的内容了,这些内容的长度就是SS的值。! m; v8 C) Z% o0 e; m" L) |

+ h* A3 M; M/ o2 e3 d) |7 Y6 P6 X该文档是前两编的补充,主要讲述以下内容:
7 n; w; q1 K2 V4 M2 [# I
2 _0 ]# p5 [- t# P. h+ s5 C音符十六进制的计算
& B1 i+ q3 M' y+ H关于乐器选择; ^! w# Z& ]  D8 |/ ~7 M
RPN和NRPN
: G4 ~. f% N  l在MIDI中,中央C是C5,最低音是C0,最高音是G5,要计算任何一个音符对应的十六进制,可以使用这个公式:
$ `6 m5 Y3 ~: W2 A0 P4 A3 b( I' n( k$ B. `4 B) D
假设音符是NO,表示第O八度的音名N,比如G2中,N为G,O为2,则它的十进制为O*12+N,N的值为了简便起见,用下表给出:" U  E! f* W# W$ N; ^
2 U4 N+ \& A" E9 M" N  v
; e, d6 d1 ?/ Y

* V) L% F1 A* h3 w" i. d0 ^这样G2的十进制值为2*12+7=31,十六进制为1F。7 l' t. y4 Z, b5 ~2 _- P
! }, I; _9 n; w% d! G7 r" Z
若知道音符的十六进制,也可以很容易求出音符,比如64(16)=100(10),5 }, ?/ Z; v; j  [' I, B5 Y1 O

7 o  k: ~4 \2 J) G% U1 m而100 div 12=8,100 mod 12=4,对应音符为E8。写成公式就是:
5 p; k' ~+ I# K9 B
( i, R! y- L" r4 mN=B mod 12;O=B div 12;(设B为表示音符的字节的十进制数)
, n4 ?3 g8 x7 A* _1 R4 g; L1 J3 W9 _3 E& g# ]2 ]1 w
乐器是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。这样就完成了标准的乐器选择。! ?# x1 q& Q! U* }& Y
( @0 }% E! `2 ?' R$ c
事实上,它是由三个事件共同完成的,如果某次选择乐器和上次的乐器有共同的参数,可以不必重复使用相关的操作。4 t" @( @0 k; p0 Z: S: r: \6 z
" k7 V+ {4 g6 I. N6 ?+ T! s5 x; @8 ~
在本例中,您可能发现了一点,当连续使用同类操作时可以不必每次指定操作种类,比如这里的连续再次使用控制器,所以第二个控制器并没有使用B0作为标志,而是直接使用控制器号码和它的参数。这一点和音符是一样的。其实,如果连续使用Patch Change事件(我是说如果),则也可不必每次都写Cx,打开了一次就可以了;不过就Patch Change事件而言,连续地更换乐器的结果是仅最后一个有效而已。
. S# v7 [- q$ L! y% h3 t
* X3 g, G2 H. E/ u, Y在前面的文档中并没有提及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。6 u5 C) Q! d- J8 q" d2 R
3 F5 W# d+ W: s! Y4 B6 F
要书写二进制(十六进制)文件,应该准备好一些工具,比如我自己用的是VC++,因为学习MIDI格式无非是想写它的软件,既然VC++可以编辑二进制文件,就将就着用吧。其次,应该找个可以编辑和播放MIDI文件的软件,比如Cakewalk,这样就可以开始了。- b: Z% o' z% E- T, j
% V7 o0 z" n& ^. S
首先书写文件头“4d 54 68 64 00 00 00 06”,我们直接写同步多音轨的格式,先写1个音轨,并以120为一个音符的基本时间。这样,随后的字节是:“00 01 00 01 00 78”。; _4 }- O- N3 D  s( R1 l7 T' @: s

* l" E8 O+ t# M: Z& ]  q8 e现在,如果用Cakewalk打开会失败,因为我们指定的音轨数为1,但是并没有书写任何音轨,如果改成“00 01 00 00 00 78”再打开,就不会出问题了。所以,今后如果更改了音轨数,千万不要忘记向“上头”汇报。
( W1 i! n* a6 h0 k
  F+ n' l6 }; `+ ]9 G5 u把轨道数改回01,继续我们的实验。先写音轨的头信息:“4D 54 72 6B”(MTrk),因为我们还不能确定后面有多少字节,所以先把它假设成“00 00 00 00”,以后再回来改。
# ~% r" u1 x0 I0 R* f1 J% h. ^$ v6 {/ c2 z, M
我们先尝试设置歌曲的速度和节拍等基本信息。假设一个四分音符的时间是半秒,即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文件末尾。
3 ~& r7 t" T0 `9 a8 [
9 e+ x9 T- b# w" l" m这时先不要急着用Cakewalk验证,因为我们还没有向“上级”报告,没错,把前面表示字节数的“00 00 00 00”改成“00 00 00 07”,如果用VC++作为二进制文件的编辑器,选择了事件后,可以在状态栏看到选择的字节长。保存后,再用Cakewalk打开,就可以看见速度是120。
  m/ n. A& ]9 O8 h3 y7 |
% p( ]; f  ?7 M! g% v1 _- G. ]我们再来设置节拍和调号,因为一般用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就是在对其进行重新验证,这一点,我们以后再讨论。
% c3 m8 U6 x; S' j
6 T4 C7 r% g8 U* m8 i- C 20067925814128.jpg
音频应用店铺 音乐发布 中国原创歌词网
回复

使用道具 举报

1万

积分

27

听众

7415

金贝

中国原创歌词基地中级

积分
15296
 楼主| sina 发表于 2016-10-31 20:06:25 | 显示全部楼层
用同样的方法,您可以很容易地设置歌曲的标题和版权,这作为一个练习,在这里就不多写了。我们现在学习写一个含有音符的轨道。首先您应该知道要做哪些事:1、写新音轨的信息头;2、向上级汇报多了一个音轨。接下来,我们开始写入一个简单的音符。' W: ^( i1 X2 N" K' U

, C- a$ S+ |# J3 G7 \" ]% z假设向第一拍写一个中音A,这里可能要先说明一下,音符是从C0开始一起向上数的,数到中央C(C5)是十六进制的3C,则中音A应该为45,在附件中有详细的计算方法。我们知道在音乐中一个音符通常有三个属性:音高、力度和时值。可是我们在事件表中并没有看见有什么可以直接设置音符时值的标志。不错,事实上,音符的时值是由按下的时间和松开的时间决定的。我们假设要写入一个八分音符。它的Tick数是四分音符的一半,即60,十六进制表示成3C。我们先来看看与音符有关的标志。5 \4 K" }5 `) L! l3 h4 j! s( B
* O  s7 |7 @: Q1 ]7 Y& k& t" m+ ~
在事件表中,9x是用来打开一个音符,我们这里假设使用第7个通道(注意到MIDI有16个通道(Channel),而第10个被默认地用作打击乐,所以,我们在这个阶段(没有学习Sysx之前),先不要使用第10个通道),则9x中的x是6;再看它的参数,一个是音符,这里我们写入45,第二个是力度,我们用70,因为是一开始就触发的,所以前面的时间差还是00。这样我们就在第5个通道以力度112按下了一个中音A。对应的字节描述是“00 96 45 70”。它的时值不用想都知道一定是0,这取决于什么时候把它松开。
" M0 N0 e) [% R, ?2 w3 F
* V( D7 R8 L7 a6 u; }' t特别地,如果一个音符的力度为0,则MIDI认为用户想松开这个键,因为9x已经打开了通道,所以我们直接写入一个带00力度的同一音符就可以决定这个音符的时值了。根据前面的分析,这个时间差应该是3C,所以我们在写入3C后写上音符45和它的力度00,即“3C 45 00”。统计好字节数并向这一轨的头信息中更新,然后保存到磁盘,用Cakewalk打开并进入事件列表窗口便可以验证了。
# j4 [7 t2 x  K7 j9 b: i) K1 B9 H5 R& J7 L& i: o
在这个基础上,我们再尝试在A的后面增加一个四分音符中音#G。因为96已经打开了通道,我们没有必要每次都使用9x,只要输入事件信息即可。对于中音#G,它的十六进制是44,相对刚才输入00力度的A来说时间差为00,因此可以表示成“00 44 64”,这里我们已经假设力度为100;然后是松开它,因为是四分音符,所以时间差是78H,别忘记力度是00,它的字节应表示成“78 44 00”,做好后面的工作,然后验证看对不对。! n- h9 t+ t. S

+ L1 ]) S' R2 \  }: r8 w+ {" z; ]+ v我们再做个稍微复杂一点的实验:在原来的基础上,在同一轨的第一拍加上一个附点四分中音D。这里就不能再使用追加的方法了,因为前面的事件已经过了3个八分音符的时间,无论再加上什么,都只会发生在后面,所以我们要在前面插入一些字节。
: B" ~: K! R. {* R  U, X4 A- X6 k) k0 G
9x已经打开了通道,我们直接在9x按下的音符后加上一个音符事件“00 3E 64”,这里的00显然是个时间差,3E是中音D,64是力度,也就是说,在按下中音A的同时按下了中音D。我们又按下一个键了,要在什么时候,在哪里松开才能保证输入的是个附点八分音符呢?首先,它的时值是3个八分音符,即180,这里还有一点要注意,180是个大于128的数,它的动态字节就应该表示成“81 34”,在哪里输入才好呢?如果你觉得在按下D后输入,或者在任何什么地方输入这个时间差,然后再写上“3E 00”可以表示松开的话就完全误解了时间差的概念。其实,我们只要简单地在松开#G的时候松开D就可以了,所以应该在末尾补上“00 3E 00”。统计好字节数后到Cakewalk中去验证验证吧。这里附有我们目前写下的Midi文件样本。6 U) B$ W6 Z" K) j

5 V/ d) ]' `- j- Y$ A$ t# B: B% P4 S到目前为此,我们应该可以输入任何形式的音符了,不过MIDI除了音符以外,还可以包括各种控制器和系统码,它们的地位不亚于音符,我们现在马上学习如何使用控制器。7 Z" d5 q+ P% c- M- j$ P
0 _3 T5 C1 P3 H6 i: s) O8 T/ f1 f, o
控制器比音符要简单多了,我们尝试在#G前加入相位控制(Pan),它的十进制代码是10,十六进制是0A,我们将参数设置成111,即十六进制的6F。首先查得控制器是Bx,这里的x和上面一样,也是6。接下来写入控制器号0A,然后是参数6F,别忘了前面的时间差是00。所以这段字节是“00 B6 0A 6F”,它应放在松开#G的事件之前,与按下#G同时。不过,一旦使用了非音符,而后面还有音符事件时,则必须重新通知打开音符,这说起来复杂,做起来还是比较容易的,我们只要稍微改写下一个音符事件即可:原本是“时间差+音符+力度”,我们加入一个打开音符的标志,成为“时间差+9x+音符+力度”即可。校验过头信息后,去Cakewalk中进行更进一步的检验便知它的可行。
( [8 c9 V$ e- @2 \: V5 M$ l
' c& _: D4 g, @3 G- B其实,时间差为00的控制事件如果出现的时间也是00,则在Cakewalk中会尽可能地把它们放在轨道信息中,而不在事件列表中重复。我们可以利用这一点给音轨设置初始乐器和音量,这就作为一个练习,在此就不再说明了。2 E4 F4 H" i. i$ s" @: f5 J+ B' b5 E/ P
) B% o/ D% f/ j) A6 f' `6 v8 K& Q
至于其他的诸如触后键等与控制器类似的格式在此就不多说了。在这里有必要提醒的是滑音。滑音的乐理范围是-8192~8191,但是在使用时参数是个正数,比如要设置成0,则应该是0-(-8192)=8192,它才是参数。8192的7位双字节表示成“8192 mod 128=00H;8192 div 128=40H”。如果时间差是00,则应表示成“00 E6 00 40”
* F6 K2 c# ~! |- v- [' b0 C% Z) {2 t$ G- w% J
最后我们看看系统码。系统码的构成本来是“F0 厂家ID 设备号码 格式代码 传送命令 具体参数 F7”,而在文件中,则不以开头的“F0”为系统码,而字节数也仅记录剩余的系统码,比如XG的复位码是“F0 43 10 4C 00 00 7E 00 F7”,则在文件中应写成“00 F0 08 43 10 4C 00 00 7E 00 F7”,其中第一个00是时间差,F0是系统码标志,08是后面的字节数。有一点要注意的是,几个系统码不可以写在一起,比如“00 F0 0D 43 10 4C 00 00 7E 00 F7 F0 AA BB CC F7”或“00 F0 0C 43 10 4C 00 00 7E 00 F7 AA BB CC F7”都是不好的写法。如果存在以上系统码集,可以分成两个事件:“00 F0 08 43 10 4C 00 00 7E 00 F7 00 F0 04 AA BB CC F7”
2 H( a) y6 ^+ N. k- L7 r$ N7 `4 E% R0 Z: a
当然系统码可以写在任何音轨,不过一般我们会考虑把歌曲播放前发送的系统码写在全局音轨中,并把时间差设成00。
6 R! g# g5 F  Y% c
( m9 c, o5 q9 G2 J# X作为一个参考,这里再附上一个MIDI样本。$ `2 L2 n6 H( N4 H2 Y

( }% L8 w7 [( `' h+ A2 F虽然我们只讨论了同步多音轨的格式,其实对于其他两种,比如较常见的单音轨格式,所有的事件只写在一个音轨中,即只要存在一个“MTrk”就足够了。而相对地,用于记录音轨数的两个字节也永远为“00 01”,连续事件如果出现的通道不同,也必须重新指定通道(8x~Ex)。在此不详细讨论了。
音频应用店铺 音乐发布 中国原创歌词网
回复 支持 反对

使用道具 举报

1万

积分

27

听众

7415

金贝

中国原创歌词基地中级

积分
15296
 楼主| sina 发表于 2016-10-31 20:06:52 | 显示全部楼层
该文档是前两编的补充,主要讲述以下内容:
0 {9 ~! y+ \. @* o; M& c1 n6 S* n; G6 I8 }! x8 M2 ]2 _
音符十六进制的计算3 B& j# W4 u8 |, F! C% v+ _; \
关于乐器选择
0 d/ U* a7 j  FRPN和NRPN
) o- \& Z6 O$ Y% {. @在MIDI中,中央C是C5,最低音是C0,最高音是G5,要计算任何一个音符对应的十六进制,可以使用这个公式:  G2 k# H& g4 l3 r9 a) g( a1 s
- W: S+ P; T2 h- L
假设音符是NO,表示第O八度的音名N,比如G2中,N为G,O为2,则它的十进制为O*12+N,N的值为了简便起见,用下表给出:; t* n  |' c8 B1 _3 f6 v, j; [

; z4 _' K# t( Z- x8 C) P4 H# f& F) `, z: Z% i" \3 P5 n( t7 a
* Q1 S! h1 m0 d; S9 G* s+ O
这样G2的十进制值为2*12+7=31,十六进制为1F。- t4 c- p5 p) Y' ^8 X

5 A6 l4 C6 u& q0 X: v. Q若知道音符的十六进制,也可以很容易求出音符,比如64(16)=100(10),8 W* P2 r: i& x2 L# K4 _

. i7 g& m" a% b而100 div 12=8,100 mod 12=4,对应音符为E8。写成公式就是:+ o$ e0 E3 i( s& M! p( \6 K
2 ~: W& j) R- X+ g  w3 m$ N
N=B mod 12;O=B div 12;(设B为表示音符的字节的十进制数)
/ e) U0 o: H, v- T7 c6 [1 d  Z+ c- ~7 g+ i# x1 [
乐器是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。这样就完成了标准的乐器选择。  i2 \  I% @# T( V, V. C
. t0 ^8 B: Z' W( k4 Q  S
事实上,它是由三个事件共同完成的,如果某次选择乐器和上次的乐器有共同的参数,可以不必重复使用相关的操作。$ u: W% S4 F, y' z1 g
6 b$ ?" w/ y2 C6 M; O
在本例中,您可能发现了一点,当连续使用同类操作时可以不必每次指定操作种类,比如这里的连续再次使用控制器,所以第二个控制器并没有使用B0作为标志,而是直接使用控制器号码和它的参数。这一点和音符是一样的。其实,如果连续使用Patch Change事件(我是说如果),则也可不必每次都写Cx,打开了一次就可以了;不过就Patch Change事件而言,连续地更换乐器的结果是仅最后一个有效而已。
& x$ x( w, G6 T2 I, x( ^( T3 r* M6 j7 P' n3 v6 j& \  ~2 S; S7 z( ?6 l
在前面的文档中并没有提及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.
音频应用店铺 音乐发布 中国原创歌词网
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 快速注册

本版积分规则

小黑屋|手机版|音频应用|歌词网|投稿|中国原创歌词基地 ( 鄂ICP备13005321号-1 )

GMT+8, 2026-7-2 00:31 , Processed in 0.033070 second(s), 8 queries , Redis On.

Powered by Audio app

中国原创歌词网微信: 924177110

快速回复 返回顶部 返回列表