mid文件的数据结构
提示
1.要想彻底弄懂本文的内容,必须具备基础的乐理知识。
2.关键词:音轨、通道、MIDI事件、MIDI命令、时间差、瞬时。
一、概述
MIDI是英语 Music Instrument Digital Interface的缩写,中译为"数字化乐器接口",也就是说它的真正涵义是一个供不同设备进行信号传输的接口。
mid文件和常见的mp3文件的本质是完全不同的。mp3文件是把声音的模拟信号经过取样→量化处理→压缩处理,转变成与声音波形对应的数字信号,播放时,这些数字信号转化为音频流。MIDI文件不是直接记录乐器的发音,而是记录演奏乐器的各种信息或指令,如使用哪一种乐器,什么时候按某个键,力度怎么样,还有颤音、滑音、发音的方位(在左边还是右边)等等一系列复杂的信息都是可以用数字来表示的,播放时,这些数字信号转化为控制信息流,控制信息流通过播放软件转换为声音。因此MIDI文件通常比声音文件小得多。mid文件只记录演奏乐器的信息,不能记录歌手的声音。
mid文件不但可以播放,甚至可以把MIDI音乐转变成看的见的乐谱并打印出来,这就可以用于音乐教学,尤其是识谱。
mid文件基本上由两大部分组成:文件头和音轨块。文件头用来描述文件格式、音轨数量等。音轨存储音符集和其它信息的数据,音符集、乐谱、文本等等,都可以分配一个音轨。
二.文件头
文件头的结构:4D 54 68 64 00 00 00 06 ff ff nn nn dd dd
前4个字节是"MThd"的ASCII码。
接着的4个字节是后面3个参数部分的长度,它≡6。注意:在MIDI文件中,有关长度、大小的数值,都是采用高位在前的方式。
参数一:ff ff,其值表示文件格式,有3种格式:
=0:单音轨
=1:同步多音轨
=2:异步多音轨
单轨,很显然就只有一个轨道。
同步多音轨表明这些音轨都在同一时间开始。这种格式的第1个音轨是全局音轨,专用于设置全局性的参数,所以这种文件至少有2个音轨。
异步多音轨表明这些音轨可以不同时开始。
参数二:nn nn,是音轨数量。
参数三:dd dd,如果第1字节最高位=0,那么表示每秒=多少个瞬时(tick),默认=120,即16进制的0078。瞬时是MIDI文件中的最小时间单位,1瞬时=多少微秒呢?1瞬时=每秒微秒数÷每秒瞬时数,默认1瞬时=1000000÷120=8333微秒。很多文章说这个参数表示每个四分音符=多少个瞬时,这是错误的,笔者用反证法来说明:如果那些文章说得对,那么这个参数值越大音乐节奏越慢,而事实上,这个值越大音乐节奏越快。
如果参数三第1字节最高位=1,那么这个参数表示的意义就不同了,这时候,参数的0—7位(也就是参数的第2字节)表示每个SMTPE帧=多少瞬时。8—14位表示每秒=多少个SMTPE帧。这个值越大音乐节奏越快。注:SMTPE帧是个什么东东,笔者找不到这方面的解释。
三、音轨块
文件头之后的文件部分是音轨块,音轨块是若干个音轨的集合。每个音轨由一个音轨头和若干个MIDI事件组成。
对于同步多音轨的MIDI文件来说,一般在文件头之后的第一个音轨是全局音轨,对其它音轨都起作用的参数在这里设置,例如标题和版权、歌曲速度和系统信息码等。全局音轨后面才是各种乐器或声部的音轨。通常都是一种乐器或一个声部占用一个音轨。这种音轨又称为一个通道,通道可以放在任意音轨(例如通道4,可以放在第1音轨或者第4音轨或者第16音轨)。MIDI可以同时演奏16种乐器,也就是可以同时开通16个通道(能同时开通的音轨就不止16个了),第10通道是专为打击乐设定的。每个通道内的乐器都可以同时发几个音,组成和弦。当然,用户也可以自行增加音轨,这只要简单地把制作好的音轨数据追加在最后一个音轨的后面就可以了,不过不要忘记更改文件头中的音轨数。
对于单音轨的MIDI文件来说,一个音轨可以包含2个以上的通道。
MIDI文件可以表现出128种乐器音色和72种打击乐音色,这些音色都是以编号的形式在文件中出现的。音色名称及对应编号见表7、表8、表9。
1.音轨头的结构:4D 54 72 6B xx xx xx xx
前4个字节是"MTrk"的ASCII码,接着的4个字节是音轨的长度(不包括音轨头本身8字节)。
2.MIDI事件的结构:
在音轨头之下是MIDI事件的集合。事件可分为音符事件、元事件、控制器事件和系统信息事件几大类。这些事件中,元事件、控制器事件和系统事件一般排列在音轨的前面,例如给音轨设置初始乐器和音量,这些事件通常是时间差=00的控制事件。而音符事件就排列在音轨后面了。
MIDI事件由时间差和MIDI命令组成。一个MIDI事件中一般只含有一个MIDI命令。
时间差(delta-time,有的文章称为时间片)表示相邻两个MIDI事件的间隔时间,时间差的单位是瞬时。时间差的计算有点复杂,详见后文“四、时间差的计算”
音符事件的MIDI命令由命令码和参数组成。命令码是一个最高位为1的字节,但是命令码只占用了这个字节的高4位,低4位是将被执行命令的通道号,所以也可以说,音符事件的MIDI命令是由命令码、通道号和参数三部分组成。
音符事件的格式是:时间差+命令码+参数。命令码及参数见表1。
表1 音符事件的命令码及参数
-----------------------------------------------------------------------------------
命令码 参数 描述
-----------------------------------------------------------------------------------
8x nn vv 关闭声音设备,nn=音名编号,vv=音符力度
9x nn vv 打开声音设备,参数同上
Ax nn vv 对已产生的声音连续演奏,nn=音名编号,vv=新力度
Bx cc vv 某个控制器的控制值发生变化时发出,cc=控制器编号,vv=控制值
Cx pp音色变化信息,音色被改变时发出,pp=新的音色号。注意,只有一个参数
Dx cc通道力度信息,通道的压力发生变化时发出,cc=新力度。注意,只有一个参数
Ex tt bb 滑音信息,该信息由一个二进制数的14位描述,tt=14位数的高7位,bb=低7位
-----------------------------------------------------------------------------------
说明:
①命令码中的x表示通道号。例如:90表示0通道开放声音。
②所有参数都只有一个字节,其值均≤7F(但Ex命令是把2字节作为一个14位二进制数)。
③在9x命令之后,就可以写入音符了。MIDI规定,若连续向同一通道上发送多个音符,只需要1个命令码,但是时间差必不可少。例如:0091 63 40 3C 6540,表示通道1连续演奏2个音符63和65,3C是音符65的时间差。当力度=0时,表示停止声音,但不关闭设备,如:0091 63 40 3C 63 00,最后3个字节表示在3C个瞬时后,停止63音符,没关设备,还可以在后面接续音符代码。
④Bx命令是向MIDI控制器发出指令。共有128个MIDI控制器。控制器名称及对应的编号见表10。
⑤Dx命令:当有一些对力度敏感的键盘不支持上面提到的复音被触发后,可以通过发送这个信息,用以表示当前所有被按下的键中力度最大的单个键的力度信息,
⑥如果使用了非音符命令(如Bx),而后面还有音符事件时,则必须重新使用9x命令。
⑦Ex命令每滑一个半音,对应值为128,比如要滑八度音阶(共12个半音),其值=12×128。滑音又分上滑音、下滑音,滑音的乐理范围是-8192-8191,上滑音值为正,下滑音值为负,但是用参数表示却都是正数。参数计算方法是用滑音值减去(-8192),然后用mod(求余数)计算出14位二进制的高7位,用div(求商的整数)计算出14位二进制的低7位。当参数的2个字节组成的二进制数的最高位=1,是上滑音,最高位=0,是下滑音。比如要设置滑音为中值0,则应该是0-(-8192)=8192,8192才是参数。8192的14位双字节表示成8192 mod 128=00H;8192 div 128=40H。如果通道号=6,那么代码为 00 E6 0040。由于滑音控制器的原因,滑音的范围实际上最多滑一个全音,也就是2个半音。滑音需要3个控制器:⑴滑音时间,5号控制器,数值=0—7F,数值越大滑音时间越长;⑵滑音开关,45号控制器,数值=0或7F,0为关,7F为开;⑶滑音的起始音,54号控制器,数值=音符编号,最终变化音的力度值决定滑音的持续音量。
3.元事件的结构
MIDI事件根据性质可分为通道信息和系统信息两类。通道信息用于控制器的声音产生与设定,表2所列的命令就属于通道信息。下面讲的元事件却是属于系统信息类别。对事件的不同划分在于它们的控制指令不同。
元事件(meta-event)是一种有点特殊的MIDI事件,它的特殊表现在:
①普通MIDI事件的命令码都是1个字节,而元事件的命令码是2个字节,第1字节是标识符FF,第2字节是命令码。
②普通MIDI事件的命令码中包含通道号,而元事件的命令码中没有通道号。正因为没有通道号,所以全局性的命令只能放在全局音轨(单音轨除外),而针对某个音轨的命令才能放在该音轨。
③普通MIDI事件中命令的参数长度是一个字节,且最高位均为0。而元事件命令参数长度可以大于1字节,且参数的最高位可以为1。
④由于元事件命令参数可以多于1字节,所以,元事件命令后面紧接跟着一个长度参数(长度参数本身是一个字节),指明后面参数的总长度(字节数)。
元事件用于设置歌曲的速度和节拍等基本信息。元事件的格式也是由时间差和命令码、参数三部分构成,常用的命令码及参数见表2。
表2 元事件常用的命令码及参数
-------------------------------------------------------------------------------------
命令码 参数 描述
-------------------------------------------------------------------------------------
00 02 ssss ssss=音轨序号
01 nn tt... 注释文本,nn=文本长度,tt=文本字符
02 nn tt... 版权信息,只用于第1个音轨中,参数同注释文本
03 nn tt... 歌曲标题或者音轨名,参数同注释信息
04 nn tt... 所在音轨使用的乐器名称,参数同注释信息
05 nn tt... 歌词,参数同注释信息
06 nn tt... 标记,参数同注释信息
07 nn tt... 提示,参数同注释文本
20 01 cc 通道前缀,cc=通道号(0—F)
21 未知
2F 00 音轨结束命令,必须用在每个音轨的结尾
51 03 tt tt tt 节拍速度,单位:微秒,它表示一拍=多少微秒
54 hh mm ss fr SMTPE偏移量,描述音轨开始时的SMTPE时间,参数:时/分/秒/SMTPE帧
58 04 nn dd cc bb 节拍设定
59 02 sf mi 音调设定
7F xx dd... 音序器描述,xx=被发送的字节数,dd=八位二进制数据
-------------------------------------------------------------------------------------
说明:
①00命令只能产生在第一个音轨的非零时刻之前。此命令在同步多音轨文件中,必须产生在全局音轨。此命令在异步多音轨文件中,用于识别每个音轨,如果忽略,这个序列号就用音轨出现的次序表示。
②06命令标记有意义的点(如:“诗篇1”),通常用于单独音轨或同步多音轨的第1个音轨。
③07命令用来提示场景中发生的事情,如:“幕布升起”、“退出”、“台左”等等。
④20命令关联紧跟的元事件和系统专用事件的MIDI通道,直到下一个包含通道信息的MIDI事件。
⑤51命令也意味着歌曲节奏的改变。节拍速度缺省值=07A120=0.5秒,相当于每分钟120拍。每分钟节拍数=1分钟的微秒数÷节拍速度。
⑥54命令必须产生在非零元事件之前,且在第一个事件之前。在同步多音轨的MIDI文件中,必须放在全局音轨中设置。
⑦58命令中,nn=分子(每小节多少拍)。dd=分母(几分音符为一拍),例如:0=全音符,1=二分音符,2=四分音符,3=八分音符……。cc=MIDI时钟拍包含的瞬时数目,通常=24(10进制的32)。bb==一个四分音符包含多少个MIDI时钟拍,标准数为8。
⑧59命令中,sf=升(降)调记号,其最高位=0为升调,=1为降调,低7位表示升(降)几个半音。sf=0为基准C大调,例如:在五线谱上A大调有3个升号,所以sf=03,而F大调有1个降号,所以sf=81。mi=大(小)调记号,mi=00为大调,mi=01为小调。关于简谱与五线谱的调号对应详见表5、表6。
⑨7F命令是系统高级事件,表示制造商音序器统一化的描述。数据必须以制造商ID打头。
4.系统消息事件的结构
系统消息应用于整个系统而不是特定通道,不含有通道码。
系统消息事件也是一种特殊的MIDI事件,它控制整个系统的消息,通常用于控制midi键盘。常用消息码见表3。
表3 常用系统消息码
------------------------------------------------------------
消息码 参数 描述
------------------------------------------------------------
F0 ii xx...ii=厂商标识号ID,00-7F,xx...=任意字节厂商信息
F1 未定义
F2 cc dd 歌曲位道指针,cc=位道低字节,dd=位道高字节
F3 cc 歌曲选择,cc=歌曲号
F4、F5 未定义
F6 旋律请求
F7 系统消息的结束标记
F8 用于同步整个系统的计时器
F9 未定义
FA 启动当前的序列
FB 从停止的地方继续一个序列
FC 停止一个序列
FD 未定义
FE 活动检测
FF 系统复位
-------------------------------------------------------
说明:
①F0为专用消息,F1-F7为公共消息,F8-FF为实时消息
②F0命令还有一种用法,其格式与元事件的格式相同:时间差+命令码+参数,例如XG的复位码是:00 F0 08 43 10 4C00 00 7E 00 F7,其中第一个00是时间差,F0是系统码,08是参数长度,F7是结束标记。
③几个F0命令不能写在一个事件中,如果存在F0命令集,可以分成几个事件。例如:00 F0 0D 43 10 4C 00 00 7E00 F7 F0 AA BB CC F7
内含2个F0命令,可以分成2个事件:00 F0 08 43 10 4C 00 00 7E 00 F700 F0 04 AA BBCC F7"
④F0码可以写在任何音轨,不过一般会把歌曲播放前发送的系统码写在全局音轨中,并把时间差设为0。
⑤笔者在一篇文章看到,F7命令也可以带2个参数:数据长度和数据。例一:停止一个序列用:F7 01 FC,继续一个序列用:F7 01FB,其中的01就是表示F7命令发送的数据为1个字节,而FC、FB这两个命令就是被发送的数据。例二:向一个外部设备发送一个“停止”命令,间隔48个瞬时后再发送“继续”命令:00F7 01 FC 30 F7 01FB。这种形式与例一的性质相同,不过在F7命令之前加上了时间差。最前的00是第一个F7命令的时间差,30是第二个F7命令的时间差,表示相隔48瞬时。
5.音名编号
在一条MIDI命令的后面,可以跟着另一条MIDI命令,也可以跟着音乐的音符。在MIDI文件中,每个音符都是用音名的对应编号来表示的,例如:中央C的编号=3C。八度音阶与音名相对应的编号见表4。
表4 八度音阶与音名相对应的编号
-----------------------------------------------------
音阶| CC# DD# EF F#G G#A A# B
-----------------------------------------------------
C0 | 0001 02 0304 05 0607 08 090A 0B
C1 | 0C0D 0E 0F10 11 1213 14 1516 17
C2 | 1819 1A 1B1C 1D 1E1F 20 2122 23
C3 | 2425 26 2728 29 2A2B 2C 2D2E 2F
C4 | 3031 32 3334 35 3637 38 393A 3B
C5 | 3C3D 3E 3F40 41 4243 44 4546 47
C6 | 4849 4A 4B4C 4D 4E4F 50 5152 53
C7 | 5455 56 5758 59 5A5B 5C 5D5E 5F
C8 | 6061 62 6364 65 6667 68 696A 6B
C9 | 6C6D 6E 6F70 71 7273 74 7576 77
C10 | 78 797A 7B 7C7D 7E 7F
-----------------------------------------------------
说明:
①C5在乐理中称为"中央C"。
②五线谱中的升号(#)及降号(b)与调号的对应关系见表5
③普通钢琴的最低音是15号音,最高音是6C号音。
表5 升、降号与调号的对应关系
--------------------------------------
#数 0 1 2 3 4 5 6 7
调号 C G D A E B #F #C
b数 0 1 2 3 4 5 6 7
调号 C F bB bE bA bD bG bC
--------------------------------------
说明:例如:2个升号是D大调(简谱记为:1=D),2个降号是降B大调(简谱记为:1=bB)。
6.简谱的唱名与音名编号
为了方便不懂五线谱的初学者,笔者特地把简谱各大调的唱名与音名的编号一一对应列表(三个八度,应付一般的歌曲应该够了),为在解析MIDI文件或自己编写MIDI代码时提供参考,见表6。
表6 简谱大调唱名与音名的编号对应表
----------------------------------------------------------------------
音域 ┌───低音───┐ ┌───中音───┐┌───高音───┐
唱名 1 2 34 5 67 1 23 4 56 7 12 3 45 6 7
----------------------------------------------------------------------
C调 30 32 34 35 37 39 3B 3C 3E 40 41 43 45 47 48 4A 4C 4D 4F 5153
D调 32 34 36 37 39 3B 3D 3E 40 42 43 45 47 49 4A 4C 4E 4F 51 5355
E调 34 36 38 39 3B 3D 3F 40 42 44 45 47 49 4B 4C 4E 50 51 53 5557
F调 35 37 39 3A 3C 3E 40 41 43 45 46 48 4A 4C 4D 4F 51 52 54 5658
G调 37 39 3B 3C 3E 40 42 43 45 47 48 4A 4C 4E 4F 51 53 54 56 585A
A调 39 3B 3D 3E 40 42 44 45 47 49 4A 4C 4E 50 51 53 55 56 58 5A5C
B调 2F 31 33 34 36 38 3A 3B 3D 3F 40 42 44 46 47 49 4B 4C 4E 5052
----------------------------------------------------------------------
说明:例如简谱1=D,就看D调那一行的编号,1=A,就看A调那一行的编号。
表7 128种乐器音色名称及对应编号
------------------------------------------------------------------------------------------
编号 音色名 编号 音色名 编号 音色名 编号 音色名 编号 音色名
------------------------------------------------------------------------------------------
00 原声钢琴 01 明亮大钢琴 02 电声钢琴 03 酒吧钢琴 04 电钢琴1
05 电钢琴2 06 拨弦古钢琴 07 击弦古钢琴 08 钢片琴 09 钟琴
0A 八音琴 0B 颤音琴 0C 马林巴 0D 木琴 0E 管钟
0F 大扬琴 10 拉杆风琴 11 节奏风琴 12 摇滚风琴 13 教堂风琴
14 簧风琴 15 手风琴 16 口琴 17 探戈手风琴 18 尼龙弦木吉它
19 金属弦木吉它 1A 爵士电吉它 1B 滑音电吉它 1C 闷音电吉它 1D 过载
1E 失真 1F 合唱 20 声学贝司 21 指弹贝司 22 拨片电贝司
23 无品贝司 24 击弦贝司1 25 击弦贝司2 26 合成贝司1 27 合成贝司2
28 小提琴 29 中提琴 2A 大提琴 2B 倍大提琴 2C 弦乐震音
2D 弦乐拨奏 2E 管弦乐竖琴 2F 定音鼓 30 弦乐合奏1 31 弦乐合奏2
32 合成弦乐1 33 合成弦乐2 34 合唱“啊” 35 人声“噢” 36 合成人声合唱
37 管弦乐强奏 38 小号 39 长号 3A 大号 3B 弱音小号
3C 法国号 3D 铜管合奏 3E 合成铜管1 3F 合成铜管2 40 高音萨克斯
41 次中音萨克斯 42 中音萨克斯 43 低音萨克斯 44 双簧管 45 英国管
46 大管 47 黑管 48 短笛 49 长笛 4A 竖笛
4B 排箫 4C 吹瓶声 4D 日本笛 4E 口哨声 4F 埙
50 合成方波 51 合成锯齿波 52 合成蒸汽风琴 53 合成夜莺 54 合成精灵
55 合成人声 56 合成平行五度 57 合成铜管领奏 58 新世纪音色垫 59 温暖音色垫
5A 复音音色垫 5B 合成合唱音色垫5C 弓弦音色垫 5D 金属音音色垫 5E 光环音色垫
5F 吹风音色垫 60 雨声 61 音轨 62 水晶 63 大气
64 明亮 65 诡异 66 回声 67 科幻 68 西塔尔(印度)
69 班卓琴(美洲)6A 三昧线(日本)6B 十三弦筝(日本)6C 卡林巴(非洲)6D 风笛(苏格兰)
6E 里拉提琴 6F 沙奈管 70 碰铃 71 摇摆舞铃 72 钢鼓
73 木鱼 74 太科鼓 75 旋律嗵鼓 76 合成鼓 77 反转钹
78 吉它指触杂音 79 呼吸声 7A 海浪声 7B 鸟鸣声 7C 电话铃声
7D 直升飞机声 7E 鼓掌声 7F 枪声
------------------------------------------------------------------------------------------
说明:
①有些电脑的MIDI音色可能与本表有差异,例如有的厂商用中国民族乐器的音色替代表中的某些音色。
表8 72种打击乐音色名称及对应编号(10通道专用)
------------------------------------------------------------------------------------------
编号 音色名 编号 音色名 编号 音色名 编号 音色名 编号 音色名
------------------------------------------------------------------------------------------
11 人声 one 12 人声 Two 13 人声 Three 14 人声 four 15 人声 five
16 MC-505信号音1 17 MC-505信号音218 大乐队小军鼓 19 小军鼓滚奏 1A 响指2
1B 激光枪声 1C 合成拍音 1D 高音刷音 1E 低音刷音 1F 鼓槌
20 敲方板 21 节拍器 22 节拍器重音 23 低音大鼓 24 高音大鼓
25 鼓边 26 小鼓 27 拍手声 28 电子小鼓 29 低音落地嗵鼓
2A 合音踩镲 2B 高音落地嗵鼓 2C 踩镲 2D 低音嗵鼓 2E 开音踩镲
2F 中低音嗵鼓 30 中高音嗵鼓 31 低音镲1 32 高音嗵鼓 33 点钹1
34 中国钹 35 敲钟声 36 铃鼓 37 侧击钹 38 颈铃
39 单面钹2 3A 回响梆子 3B 敲钹2 3C 高音圆鼓 3D 低音圆鼓
3E 弱音手鼓 3F 空心手鼓 40 低音手鼓 41 高阶定音鼓 42 低阶定音鼓
43 高音碰铃 44 低音碰铃 45 沙锤 46 响葫芦 47 短哨
48 长哨 49 短音刮板 4A 长音刮板 4B 击棒 4C 高音响板
4D 低音响板 4E 弱音蟋蟀声 4F 空心蟋蟀声 50 弱音三角铁 51 空心三角铁
52 高沙锤 53 铃铛 54 铃树 55 响板 56 弱音瑟多
57 空心瑟多 58 欢呼声2
---------------------------------------------------------------------------------------
表9 打击乐器与五线谱对应表
---------------------------------------
位置 打击乐
---------------------------------------
第一线 低音鼓1
第一间 低音鼓2
第二线 不同音高的中音鼓
第二间 同上
第三线 同上
第三间 军鼓
第四线 (带×号) 轻音铜钹
第四线 不同音高的中音鼓
第四间 同上
第五线 同上
第五线 (带×号) 轻音铜钹
上加一间(带×号) 脚踏钹
上加一线 脆音铜钹
上加一间 同上
---------------------------------------
表10 控制器名称及对应编号
----------------------------------------------------------------------------------------------
编号 控制器 编号 控制器 编号 控制器 编号 控制器
----------------------------------------------------------------------------------------------
00 音色库选择 01 颤音深度粗调 02 吹管控制器粗调 03 N/A
04 踏板控制器粗调 05 连滑音速度粗调 06 高位元组数据输入 07 主音量粗调
08 平衡控制粗调 09 N/A 0A 声像调整粗调 0B 情绪控制器粗调
0C—0F N/A 10—13 一般控制器 14—1F N/A 20 插口选择
21 颤音速度微调 22 吹管控制器微调 23 N/A 24 踏板控制器微调
25 连滑音速度微调 26 低位元组数据输入 27 主音量微调 28 平衡控制微调
29 N/A 2A 声像调整微调 2B 情绪控制器微调 2C 效果FX控制1微调
2D 效果FX控制2微调 2E—3F N/A 40 延音踏板1 41 滑音
42 持续音 43 弱音踏板 44 连滑音踏板控制器 45 保持音踏板2
46 变调 47 音色 48 放音时值 49 起音时值
4A 亮音 4B—4F 声音控制 50—53 一般控制器5#—8#54 连滑音控制
55—5A N/A 5B 混响效果深度 5C 未定义的效果深度 5D 合唱效果深度
5E 未定义的效果深度 5F 移调器深度 60 数据累增 61 数据递减
62 未登记的低元组数值 63 未登记的高元组数值 64 已登记的低元组数值 65 已登记的高元组数值
66—77 N/A 78 关闭所有声音 79 关闭所有控制器 7A 本地键盘开关
7B 关闭所有音符 7C Omni模式关闭 7D Omni模式开启 7E 单音模式
7F 复音模式
----------------------------------------------------------------------------------------------
说明:
①MIDI控制器的参数变化范围都为00—7F。对于开关型的控制器,小于3F为关闭,大于40为开启,例如7A控制器,vv=00关机,vv=FF开启。而模式控制器的参数为通道号,例如7F控制器,vv=要开启复音的通道号。
②每个控制器对应于一种控制事件,但是,并不是每个编号的控制器对音源都有同样效果,还要看音源的型号。
③20—3F号控制器是为了发送提高00—1F号控制器精度的LSB数据而准备的,因为00—1F号控制器的调整效果为粗调,如果要细调,发送的参数就需要14位,这时就可以利用20—3F号控制器了。
④10—13和50-53号控制器被定义为通用控制器,10—13号是2个字节,50—53号为1个字节,这些控制器号可以指定为任何控制器,用来控制内部参数。
⑤相当于踏板开关类型的控制器分布在40—5F号之间。
四、时间差的计算
时间差是一个可变长度的值,但其长度最多4字节。4字节的时间差,除了最后一个字节的最高位为0(>7F就必须进位),其余字节的最高位均为1。以下详细说明。
1.时间差长度为1字节
这个字节的最大值=7F。如果>7F,就要进位,变成2字节。
2.时间差长度为2字节
例如:时间差=B4,由于>7F,所以要用2字节表示。先确定第2字节=B4-80=34。第1个字节=81,转换成二进制就是10000001,最高位1是数值80,低7位都是进位标记,不表示数值,所以低7位的那个1表示这个字节中有1个80的一次方。进位标记可以累加,一直累加到7F,这就表示这个字节中已经有了7F个80的一次方。计算:B4=80^1×1+34。
3.时间差长度为3字节
2字节的时间差满值是FF 7F,要是再加个80,就要进位成3字节了:81 8000。这时,第1字节中的8表示80的二次方,1是进位标记,表示有1个80的二次方。要注意的是第2字节,虽然其值=0,但最高位的1不能丢:10000000,它表示有0个80的一次方。
4.时间差长度为4字节
3字节的时间差满值是FF FF 7F,要是再加个80,就要进位成4字节了:81 80 8000。这时,第1字节中的8表示80的三次方,1是进位标记,表示有1个80的三次方。注意第2、第3字节最高位的1不能丢。
例1:要表示10进制65535,可以先计算出:65535=128^2×3+128^1×127+128^0×127,然后得出结果:83 FF7F。
例2:由字节值计算出时间差,只要读到最高位=0的字节,就表示时间差结束。比如时间差有3个字节:82 C003,计算:128^2×2+128^1×64+128^0×3=40963。
提示:以上计算结果的单位是瞬时,如果要转化成微秒,那么计算结果还要乘上每瞬时的微秒数,而每瞬时的微秒数可以从文件头中的参数三计算而得。
五、实例解析
下面是MIDI钢琴曲《让我们荡起双浆》的部分代码:
-------------------------------------------------------
0000: 4D 54 68 64 00 00 00 06 00 01 00 03 00 78 4D 54
0010: 72 6B 00 00 02 45 00 FF 03 0E C8 C3 CE D2 C3 C7
0020: B5 B4 C6 F0 CB AB BD B0 00 FF 03 14 D3 B0 C6 AC
0030: A1 B6 D7 E6 B9 FA BB A8 B6 E4 A1 B7 B2 E5 C7 FA
0040: 00 FF 02 1D 43 6F 70 79 72 69 67 68 74 20 5F 20
0050: 31 39 39 38 20 62 79 20 54 61 6E 67 20 42 6F 71
0060: 69 00 FF 01 07 CC C0 B2 AE E8 BD 0A 00 FF 01 09
0070: B8 D6 C7 D9 B6 C0 D7 E0 0A 00 FF 01 0A 54 61 6E
0080: 67 20 42 6F 71 69 0A 00 FF 58 04 02 02 18 08 00
0090: FF 59 02 01 00 00 FF 51 03 09 70 3D
……
0257: 00 FF 2F 00 4D 54 72 6B 00
0260: 00 05 A6 00 FF 21 01 00 00 FF 03 0B 50 49 41 4E
0270: 4F 20 72 69 67 68 74 00 C0 00 00 B0 07 7D 00 0A
0280: 32 3C 90 40 64 3C 40 00 00 43 64 3C 43 00 00 45
0290: 64 3C 45 00 00 47 64 81 34 47 00 00 4A 64 3C 4A
02A0: 00 00 47 64 3C 47 00 00 43 64 3C 43 00 00 45 64
02B0: 78 45 00 00 40 64 81 70 40 00
……
--------------------------------------------------------
解析:
0000—000D(文件头)
4D 54 68 64:MThd
00 00 00 06:参数总长度=6字节
00 01 00 03 00 78:3条同步音轨,每秒=120瞬时
000F—025A(第1音轨,全局音轨)
4D 54 72 6B:MTrk
00 00 02 45:音轨长度=581字节(不包括音轨头)
00 FF 03 0E:设置歌曲标题,长度=14字节
C8 C3 CE D2 C3 C7 B5 B4 C6 F0 CB AB BD B0:让我们荡起双桨
00 FF 03 14:设置歌曲标题,长度=20字节
D3 B0 C6 AC A1 B6 D7 E6 B9 FA BB A8 B6 E4 A1 B7 B2 E5 C7FA:影片《祖国花朵》插曲
00 FF 02 1D:设置版权信息,长度=29字节
43 6F 70 79 72 69 67 68 74 20 5F 20 31 39 39 38 20 62 79 20 54 616E 67 20 42 6F 71 69:Copyright _ 1998 by Tang Boqi
00 FF 01 07:注释文本,长度=7字节
CC C0 B2 AE E8 BD 0A:汤伯杞(0A是换行符)
00 FF 01 09:注释文本,长度=9字节
B8 D6 C7 D9 B6 C0 D7 E0 0A:钢琴独奏(0A是换行符)
00 FF 01 0A:注释文本,长度=10字节
54 61 6E 67 20 42 6F 71 69 0A:Tang Boqi(0A是换行符)
00 FF 58 04:设置节拍,长度=4字节
02 02 18 08:每小节2拍,四分音符为一拍,每个时钟拍=24瞬时,每个四分音符包含8个时钟拍
00 FF 59 02:设置音调,长度=2字节
01 00:G大调(1=G)
00 FF 51 03:设置节拍速度,长度=3字节
09 70 3D:一拍的时长=618557微秒=618.6毫秒=0.619秒
……
00 FF 2F 00:第1音轨结束
025B—0808(第2音轨,音符音轨)
4D 54 72 6B
00 00 05 A6:音轨长=1446字节
00 FF 21 01 00:元事件21命令未知何意
00 FF 03 0B:设置音轨名,长度=11字节
50 49 41 4E 4F 20 72 69 67 68 74:PIANO right
00 C0 00:原声钢琴音色
00 B0 07 7D:0通道7#控制器(主音量控制)设置音量=7D
00 0A 32:
3C 90:3C个瞬时后(休止了半拍),0通道打开声音
40 64,3C 40 00,00 43 64,3C 43 00,00 45 64,3C 45 00:简谱 0612(力度=0的代码不必写出,下同)
00 47 64,81 34 47 00,00 4A 64,3C 4A 00:简谱 3 ˙5
00 47 64,3C 47 00 00 43 64,3C 43 00,00 45 64,78 45 00,00 40 64 8170 40 00:简谱:31 2 6 -
……
六、实例制作
下面我们自己动手,来制作一个简单的MIDI文件。我选择了歌曲《东方红》,因为该曲子既熟悉又简单,我这里只写第一句的音乐代码。
为了简单起见,尽可能采用默认值,即:C大调(1=C),四分音符为一拍,每分钟120拍,原声钢琴演奏。
《东方红》的第一句简谱是:5 56| 2 -| 1 16| 2 -|。
数据如下:
----------------------------------------------------------------------------------------
4D 54 68 64 00 00 00 06 00 00 00 01 00 78(文件头:单音轨,每秒=78瞬时)
4D 54 72 6B 00 00 00 1F(音轨头:音轨长度=1F字节)
00 90 43 64 78 43 64 3C 45 64 3C 3E 64(5 56 2 -,2后面的延长符号是由下一句的8170决定的)
81 70 3C 64 78 3C 64 3C 39 64 3C 3E 64 81 70 FF 2F 00(1 16 2-)
----------------------------------------------------------------------------------------
把这些数据复制到Hex编辑器(去掉括号内容),另存为“东方红.mid”,再点击这个文件播放,OK了!
实验1:加快节奏。
把文件头中的78改为F0(加快一倍),保存,再播放,是不是快了?
实验2:改变音色为中提琴(中提琴编号=29),代码改动一下:
------------------------------------------------------
4D 54 68 64 00 00 00 06 00 00 00 01 00 78 4D 54 72 6B
00 00 00 3A:音轨长度=3A
00 C0 29:音色=中提琴
00 90 43 64 78 43 00:打开声音设备,简谱:5(一拍)
00 43 64 3C 43 00:简谱:5(半拍)
00 45 64 3C 45 00:简谱:6(半拍)
00 3E 64 81 70 3E 00:简谱:2 -(二拍)
00 3C 64 78 3C 00:简谱:1(一拍)
00 3C 64 3C 3C 00:简谱:1(半拍)
00 39 64 3C 39 00:简谱:6(半拍,低音)
00 3E 64 81 70 3E 00:简谱:2 -(二拍)
00 FF 2F 00
------------------------------------------------------
在以上的音符代码中,每个音符经过预定的时间,都要关闭(力度=00),但发音设备不关闭。似乎除了钢琴,别的乐器都要这么写代码。
当然,如果你有兴趣,可以自己把这首曲子写完全,还可以写成同步多音轨的,再添加一些设置或信息,成为一首真正的MIDI乐曲。
七、提取一个音轨保存为一个单独的mid文件
注意三点:
1.必须是同步多音轨文件,且>2个音轨。也就是说,除了全局音轨,还必须至少有2个音符音轨。
2.一般来说,紧接着全局音轨的,是主旋律音轨,其它的音符音轨都是伴奏或副声部的。但也有一些乐曲的主旋律是各种乐器轮流担当,比方说,通道0的乐器演奏了一段时间就彻底停止,让通道1的乐器作为主旋律,由此类推……碰到这种情况,就没有提取必要了。附件中“北京的金山上.mid”就是这样的文件。
3.提取出来的mid文件中,全局音轨中那些针对其它通道的命令理论上都应删除,但这样做起来很麻烦,笔者播放了几个提取的mid文件,发现不删除那些多余的命令似乎也没有什么影响。
下面是源代码:
Option Explicit
Private Sub Command1_Click()
On Error GoTo 200
Dim DAT() As Byte, teme() As Byte, tLen1 As Long, tLen2 As Long, L1As Long, L2 As Long
Dim j1 As Long, j2 As Long, k As Long, i As Long, OpenName AsString, SaveName As String
OpenName = Text1
SaveName = Left(OpenName, Len(OpenName) - 4) & "(1).mid"
If InStr(LCase(OpenName), "mid") = 0 Then Exit Sub
ReDim DAT(FileLen(OpenName) - 1) As Byte
Open OpenName For Binary As #1
Get #1, , DAT
Close #1
If DAT(9) <> 1 Then MsgBox "不是同步多音轨MIDI文件": Exit Sub
If DAT(11) < 3 Then MsgBox "至少要有3个音轨才能提取": Exit Sub
teme = StrConv("MTrk", vbFromUnicode)
k = InStrB(DAT, teme) '查找音轨0起点(全局音轨)
tLen1 = DAT(k + 4) * 65536 + DAT(k + 5) * 256 + DAT(k + 6) +8
L1 = k - 1
k = InStrB(k + 7, DAT, teme) '查找音轨1起点
tLen2 = DAT(k + 4) * 65536 + DAT(k + 5) * 256 + DAT(k + 6) +8
L2 = k - 1
DAT(11) = 2 '修改音轨数
Open SaveName For Binary As #2
k = 0: j1 = 0: j2 = 13: GoSub 100
k = 0: j1 = L1: j2 = tLen1 - 1: GoSub 100
k = 0: j1 = L2: j2 = tLen2 - 1: GoSub 100
Close #2
MsgBox "提取完成"
Exit Sub
100
ReDim teme(j2)
For i = j1 To j2 + j1: teme(k) = DAT(i): k = k + 1: Next
Put #2, , teme
Return
200
Close
End Sub