1.概述:
一个MIDI文件基本上由两个部分组成,头块和轨道块。第二节讲述头块,第三节讲述轨
道块。一个MIDI文件有一个头块用来描述文件的格式、许多的轨道块等内容。一个轨道可以
想象为像一个大型多音轨录音机那样,你可以为某种声音、某种乐谱、某种乐器或者你需要
的任何东西分配一个轨道。
2.头块:
头块出现在文件的开头,有三种方式来描述文件。头块看起来一直是这样的:
4D 54 68 64 00 00 00 06 ff ff nn nn dd dd
前4个字节等同于ASCII码MThd,接着MThd之后的4个字节是头的大小。它将一直是00
00 00 00 06,因为现行的头信息将一直是6字节。
ff ff是文件的格式,有3种格式:
0-单轨
1-多规,同步
2-多规,异步
单轨,很显然就只有一个轨道。同步多轨意味着所有轨道都是垂直同步的,或者其他的
措辞为他们都在同一时间开始,并且可以表现一首歌的不同部分。异步多轨没有必要同时开
始,而且可以完全的不同步。
nn nn 是MIDI文件中的轨道数。
dd dd 是每个4分音符delta-time节奏数(这之后将做详细介绍)。
3.轨道块:
头块之后剩下的文件部分是轨道块。每一个轨道包含一个头,并且可以包含你所希望的
许多MIDI命令。轨道头与文件头及其相似:
4D 54 72 6B xx xx xx xx
与头一致,前4个字节是ASCII吗,这个是MTrk,紧跟MTrk的4个字节给出了以字节为单
位的轨道的长度(不包括轨道头)。
在头之下是MIDI事件,这些事件同现行的可以被带有累加的MIDI合成器端口接受和发送
的数据是相同的。一个MIDI事件先于一个delta-time。一个delta-time是一个MIDI事件被
执行后的节奏数,每个四分之一音符的节奏数先前已经定义在了文件的头块中。这个delta-
time是一个可变长度的编码值。这种格式虽然混乱,可是允许根据需要利用多位表示较大的
数值,这不会因为需求小的数值情况下以添零的方式浪费掉一些字节!数值被转换为7位的
字节,并且除了最后一个字节以最高有效位是0外,各个字节最有意义的一位是1,。这就允
许一个数值被一次一个字节地读取,你如果发现最高有效位是0,则这就是这个数值的最后
一位(意义比较小)。依照MIDI说明,全部delta-time的长度最多超过4字节。
delta-time 之后就是MIDI事件,每个MIDI事件(除了正在运行的事件外)带有一个最
高有效位总是1的命令字节(值将>128)。大部分命令的列表在附录A中。每个命令都有不同
的参数和长度,但是接下来的数据将是最高有效位为零(值将<128)。这里有个例外就是
meta-event,最高有效位可以是1。然而,meta-events需要一个长的参数以区分。
微小失误就可以导致混乱的是运行模式,这是现行MIDI命令所忽略的地方,并且最终发
行的MIDI命令是假定的。这就意味这如果包含了命令,那么MIDI事件就是由delta-time与参
数组成而转换的。
4.综述:
如果这份说明仅仅是使问题更加混乱,那么以下提供的例子可能有助于澄清问题!同
时,两个公用程序和一个图解文件包含在这个文档里面:
DEC.EXE——这个公共程序是将二进制文件(比如.MID)转换成以十进制表示的对应每
个字节的有标记界限的文本文件。
REC.EXE——这个公共程序是将有标记界限的十进制数文本文件对应的每一字节转换成
二进制文件。
MIDINOTE.PS——这是一个对应键盘和五线谱的音符数字附录页。
附录A
1.MIDI事件命令
每个命令字节有两部分,左nybble(4位)包含现行的命令,右nybble包含将被执行的命
令的通道号,这里有16各MIDI通道8个MIDI命令(命令nybble必须最高有效位是1的)。在下
表中,X表示MIDI通道号。所有的音符即数据字节都<128(最高有效位是0)。
十六进制 二进制 数据 描述
8x1000xxxxnnvv音符关闭(释放键盘)
nn=音符号
vv=速度
9x1001xxxxnnvv音符打开(按下键盘)
nn=音符号
vv=速度
Ax1010xxxxnnvv触摸键盘以后
nn=音符号
vv=速度
Bx1011xxxxccvv调换控制
cc=控制号
vv=新值
Cx1100xxxxpp改变程序(片断)
pp=新的程序号
Dx1101xxxxcc在通道后接触
cc=管道号
Ex1110xxxxbbtt改变互相咬和的齿轮(2000H 表明缺省或没有改变)
(什么意思搞不懂:)
bb=值的低7位(leastsig)
tt=值的高7位(most sig)
下表是没有通道的 meta-events列表 ,他们的格式是:
FF xx nn dd
所有的 meta-events 是以 FF 开头的命令(xx),长度,或者含在数据的字节数(nn),
现行的数据(dd)
十六进制 二进制 数据 描述
0000000000nnssss设定轨道的序号
nn=02(两字节长度的序号)
ssss=序号
0100000001nntt..你需要的所有文本事件
nn=以字节为单位的文本长度
tt=文本字符
0200000010nntt..同文本的事件,但是用于版权信息
nntt=同文本事件
0300000011nntt..序列或者轨道名
nntt=同文本事件
0400000100nntt..轨道乐器名
nntt=同文本事件
0500000101nntt..歌词
nntt=同文本事件
0600000110nntt..标签
nntt=同文本事件
0700000111nntt..浮点音符
nntt=同文本事件
2F0010111100这个事件一定在每个轨道的结尾出现
510101000103tttttt设定拍子
tttttt=微秒/四分音符
580101100004nn dd cc bb 拍子记号
nn=拍子记号分子
dd=拍子记号分母2=四分之一
3=8分拍,等等.
cc=节拍器的节奏
bb=对四分之一音符标注的第32号数字
590101100102sfmi音调符号
sf=升调/降调(-7=7降调, 0=基准C调,7=7 升调)
mi=大调/小调(0=大调,1=小调)
7F01111111xxdd..音序器的详细信息
xx=被发送的字节数
dd=数据
下表列出了控制整个系统的系统消息。这里没有MIDI通道数 (这些一般仅应用于MIDI键
盘等.)
十六进制二进制数据描述
F811111000同步所必须的计时器
FA11111010开始当前的队列
FB11111011从停止的地方继续一个队列
FC11111100停止一个队列
下表列出的是与音符相对应的命令标记。
八度音阶||音符号
#||
||C|C#|D|D#|E|F|F#|G|G#|A|A#| B
-----------------------------------------------------------------------------
0||0|1|2|3|4|5|6|7|8|9|10 | 11
1||12|13|14|15|16|17|18|19|20|21|22 | 23
2||24|25|26|27|28|29|30|31|32|33|34 | 35
3||36|37|38|39|40|41|42|43|44|45|46 | 47
4||48|49|50|51|52|53|54|55|56|57|58 | 59
5||60|61|62|63|64|65|66|67|68|69|70 | 71
6||72|73|74|75|76|77|78|79|80|81|82 | 83
7||84|85|86|87|88|89|90|91|92|93|94 | 95
8||96|97|98|99 | 100 | 101 | 102 | 103 |104 | 105 | 106 | 107
9||108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |119
10||120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
参考资料:
"MIDI Systems and Control" FrancisRumsey1990 Focal Press
"MIDI and Sound Book for the Atari ST" Bernd Endersand Wolfgang Klem 1989 M&T
Publishing, Inc.
MIDI file specs and general MIDI specs were alsoobtained by sending e-mail to
LISTSERV@AUVM.AMERICAN.EDUwith the phrase GET MIDISPEC PACKAGE in the message.
------------------------------ DEC.CPP------------------------------------
#include
#include
#include
void helpdoc();
main()
{
FILE*fp;
unsignedchar ch, c;
if((fp=fopen(_argv[1],"rb"))==NULL)
{
printf("cannotopen file %sn",_argv[1]);
helpdoc();
exit(-1);
}
c=0;
ch=fgetc(fp);
while(!feof(fp))
{
printf("%ut",ch);
c++;
if(c>8)
{
c=0;
printf("n");
}
ch=fgetc(fp);
}
fclose(fp);
}
voidhelpdoc()
{
printf("nBinaryFile Decodernn");
printf("nSyntax:decbinary_file_namenn");
printf("byDustinCaldwell(dustin@gse.utah.edu)nn");
printf("Thisis a filter program that reads a binary filen");
printf("andprints the decimal equivalent of each byten");
printf("tab-separated.This is mostly useful when piped n");
printf("intoanother file to be editedmanually.eg:nn");
printf("c:>decsonata3.mid > son3.txtnn");
printf("Thiswill creat e a file called son3.txt which cann");
printf("beedited with any ascii editor. nn");
printf("(rec.exemay also be useful, as it reencodes the n");
printf("asciitext file).nn");
printf("HaveFun!!n");
}
---------------------------- REC.CPP----------------------------------
#include
#include
#include
#include
void helpdoc();
main()
{
FILE*rfp, *wfp;
unsignedchar ch, c;
chars[20];
if((rfp=fopen(_argv[1],"r"))==NULL)
{
printf("cannotopen file %s n",_argv[1]);
helpdoc();
exit(-1);
}
if((wfp=fopen(_argv[2],"wb"))==NULL)
{
printf("cannotopen file %s n",_argv[1]);
helpdoc();
exit(-1);
}
c=0;
ch=fgetc(rfp);
while(!feof(rfp))
{
if(isalnum(ch))
{
c=0;
while(isdigit(ch))
{
s[c]=ch;
c++;
ch=fgetc(rfp);
}
s[c]=NULL;
fputc(atoi(s),wfp);
}
ch=fgetc(rfp);
}
fclose(rfp);
fclose(wfp);
}
voidhelpdoc()
{
printf("nTextFile Encodernn");
printf("nSyntax:rec text_file_namebinary_file_namenn");
printf("byDustinCaldwell(dustin@gse.utah.edu)nn");
printf("Thisis a program that reads an ascii tab-n");
printf("delimitedfile and builds a binary file wheren");
printf("eachbyte of the binary file is one of the decimaln");
printf("digitsin the text file.n");
printf("eg:nn");
printf("c:>recson3.txt son3.midnn");
printf("(Thiswill create a file called son3.mid which isn");
printf("avalid binary file)nn");
printf("(dec.exemay also be useful, as it decodes binary files)nn");
printf("HaveFun!!n");
}