|
標(biāo)準(zhǔn)MIDI文件格式 達(dá)思挺·考德威爾
標(biāo)準(zhǔn)的MIDI文件格式就像奇異的獸??傮w看來(lái),它是那樣的讓你無(wú)法抗拒。當(dāng)然,你怎樣看它無(wú)關(guān)緊要,可是用足夠多的描述符描述一段音樂并使它能夠重現(xiàn),可不是很少的工作就可以完成的。然而,它雖然復(fù)雜,但是真正理解之后,MIDI文件格式的結(jié)構(gòu)還是很直觀明了的。
在這里我必須放棄一些東西,因?yàn)楫吘刮也皇荕IDI也不是MIDI文件專家!最近我為我的PC準(zhǔn)備了一塊Gravis 超音頻音效卡,利用它聽完幾段MIDI文件(.mid)之后,想:“呵,我要是能夠制作自己的MIDI(.mid)文件該多好啊!”嗯,經(jīng)過(guò)煩人的幾個(gè)小時(shí)之后,我發(fā)現(xiàn),那些并不是沒有價(jià)值的工作。但是,我是不會(huì)讓一個(gè)冗長(zhǎng)的文件格式就能夠阻止的(此外,我告訴過(guò)我的妻子,計(jì)算機(jī)不是很難用的,而且我十分憎恨當(dāng)一個(gè)偽君子)。那么,在這篇文章中如果發(fā)現(xiàn)什么錯(cuò)誤,請(qǐng)讓我知道,我會(huì)修改它的。同時(shí),這份文檔的范圍并沒有提供所有類型的MIDI命令和任何可能的文件配置!這篇基本指南將使讀者能夠(以中等的時(shí)間投資)制作出MIDI類型的文件。
1.概述:
一個(gè)MIDI文件基本上由兩個(gè)部分組成,頭塊和軌道塊。第二節(jié)講述頭塊,第三節(jié)講述軌道塊。一個(gè)MIDI文件有一個(gè)頭塊用來(lái)描述文件的格式、許多的軌道塊等內(nèi)容。一個(gè)軌道可以想象為像一個(gè)大型多音軌錄音機(jī)那樣,你可以為某種聲音、某種樂譜、某種樂器或者你需要的任何東西分配一個(gè)軌道。
2.頭塊:
頭塊出現(xiàn)在文件的開頭,有三種方式來(lái)描述文件。頭塊看起來(lái)一直是這樣的: 4D 54 68 64 00 00 00 06 ff ff nn nn dd dd
前4個(gè)字節(jié)等同于ASCII碼MThd,接著MThd之后的4個(gè)字節(jié)是頭的大小。它將一直是00 00 00 00 06,因?yàn)楝F(xiàn)行的頭信息將一直是6字節(jié)。
ff ff是文件的格式,有3種格式: 0-單軌 1-多規(guī),同步 2-多規(guī),異步
單軌,很顯然就只有一個(gè)軌道。同步多軌意味著所有軌道都是垂直同步的,或者其他的措辭為他們都在同一時(shí)間開始,并且可以表現(xiàn)一首歌的不同部分。異步多軌沒有必要同時(shí)開始,而且可以完全的不同步。
nn nn 是MIDI文件中的軌道數(shù)。 dd dd 是每個(gè)4分音符delta-time節(jié)奏數(shù)(這之后將做詳細(xì)介紹)。
3.軌道塊:
頭塊之后剩下的文件部分是軌道塊。每一個(gè)軌道包含一個(gè)頭,并且可以包含你所希望的許多MIDI命令。軌道頭與文件頭及其相似:
4D 54 72 6B xx xx xx xx
與頭一致,前4個(gè)字節(jié)是ASCII嗎,這個(gè)是MTrk,緊跟MTrk的4個(gè)字節(jié)給出了以字節(jié)為單位的軌道的長(zhǎng)度(不包括軌道頭)。
在頭之下是MIDI事件,這些事件同現(xiàn)行的可以被帶有累加的MIDI合成器端口接受和發(fā)送的數(shù)據(jù)是相同的。一個(gè)MIDI 事件先于一個(gè)delta-time。一個(gè)delta-time是一個(gè)MIDI事件被執(zhí)行后的節(jié)奏數(shù),每個(gè)四分之一音符的節(jié)奏數(shù)先前已經(jīng)定義在了文件的頭塊中。這個(gè)delta-time是一個(gè)可變長(zhǎng)度的編碼值。這種格式雖然混亂,可是允許根據(jù)需要利用多位表示較大的數(shù)值,這不會(huì)因?yàn)樾枨笮〉臄?shù)值情況下以添零的方式浪費(fèi)掉一些字節(jié)!數(shù)值被轉(zhuǎn)換為7位的字節(jié),并且除了最后一個(gè)字節(jié)以最高有效位是0外,各個(gè)字節(jié)最有意義的一位是1,。這就允許一個(gè)數(shù)值被一次一個(gè)字節(jié)地讀取,你如果發(fā)現(xiàn)最高有效位是0,則這就是這個(gè)數(shù)值的最后一位(意義比較小)。依照MIDI說(shuō)明,全部delta-time的長(zhǎng)度最多超過(guò)4字節(jié)。
delta-time 之后就是MIDI事件,每個(gè)MIDI事件(除了正在運(yùn)行的事件外)帶有一個(gè)最高有效位總是1的命令字節(jié)(值將>128)。大部分命令的列表在附錄A中。每個(gè)命令都有不同的參數(shù)和長(zhǎng)度,但是接下來(lái)的數(shù)據(jù)將是最高有效位為零(值將<128)。這里有個(gè)例外就是meta-event,最高有效位可以是1。然而,meta-events需要一個(gè)長(zhǎng)的參數(shù)以區(qū)分。
微小失誤就可以導(dǎo)致混亂的是運(yùn)行模式,這是現(xiàn)行MIDI命令所忽略的地方,并且最終發(fā)行的MIDI命令是假定的。這就意味這如果包含了命令,那么MIDI事件就是由delta-time與參數(shù)組成而轉(zhuǎn)換的。
4.綜述:
如果這份說(shuō)明僅僅是使問題更加混亂,那么以下提供的例子可能有助于澄清問題!同時(shí),兩個(gè)公用程序和一個(gè)圖解文件包含在這個(gè)文檔里面:
DEC.EXE——這個(gè)公共程序是將二進(jìn)制文件(比如.MID)轉(zhuǎn)換成以十進(jìn)制表示的對(duì)應(yīng)每個(gè)字節(jié)的有標(biāo)記界限的文本文件。
REC.EXE——這個(gè)公共程序是將有標(biāo)記界限的十進(jìn)制數(shù)文本文件對(duì)應(yīng)的每一字節(jié)轉(zhuǎn)換成二進(jìn)制文件。
MIDINOTE.PS——這是一個(gè)對(duì)應(yīng)鍵盤和五線譜的音符數(shù)字附錄頁(yè)。
附錄A
1.MIDI事件命令
每個(gè)命令字節(jié)有兩部分,左nybble(4位)包含現(xiàn)行的命令,右nybble包含將被執(zhí)行的命令的通道號(hào),這里有16各MIDI通道8個(gè)MIDI命令(命令nybble必須最高有效位是1的)。在下表中,X表示MIDI通道號(hào)。所有的音符即數(shù)據(jù)字節(jié)都<128(最高有效位是0)。
十六進(jìn)制 二進(jìn)制 數(shù)據(jù) 描述
8x 1000xxxx nn vv 音符關(guān)閉 (釋放鍵盤) nn=音符號(hào) vv=速度
9x 1001xxxx nn vv 音符打開 (按下鍵盤) nn=音符號(hào) vv=速度
Ax 1010xxxx nn vv 觸摸鍵盤以后 nn=音符號(hào) vv=速度
Bx 1011xxxx cc vv 調(diào)換控制 cc=控制號(hào) vv=新值
Cx 1100xxxx pp 改變程序(片斷) pp=新的程序號(hào)
Dx 1101xxxx cc 在通道后接觸 cc=管道號(hào)
Ex 1110xxxx bb tt 改變互相咬和的齒輪 (2000H 表明缺省或沒有改變)(什么意思搞不懂:) bb=值的低7位(least sig) tt=值的高7位 (most sig)
下表是沒有通道的 meta-events列表 ,他們的格式是:
FF xx nn dd
所有的 meta-events 是以 FF 開頭的命令 (xx),長(zhǎng)度,或者含在數(shù)據(jù)的字節(jié)數(shù)(nn),現(xiàn)行的數(shù)據(jù)(dd)
十六進(jìn)制 二進(jìn)制 數(shù)據(jù) 描述 00 00000000 nn ssss 設(shè)定軌道的序號(hào) nn=02 (兩字節(jié)長(zhǎng)度的序號(hào)) ssss=序號(hào)
01 00000001 nn tt .. 你需要的所有文本事件 nn=以字節(jié)為單位的文本長(zhǎng)度 tt=文本字符
02 00000010 nn tt .. 同文本的事件, 但是用于版權(quán)信息 nn tt=同文本事件
03 00000011 nn tt .. 序列或者軌道名 nn tt=同文本事件
04 00000100 nn tt .. 軌道樂器名 nn tt=同文本事件
05 00000101 nn tt .. 歌詞 nn tt=同文本事件
06 00000110 nn tt .. 標(biāo)簽 nn tt=同文本事件
07 00000111 nn tt .. 浮點(diǎn)音符 nn tt=同文本事件
2F 00101111 00 這個(gè)事件一定在每個(gè)軌道的結(jié)尾出現(xiàn)
51 01010001 03 tttttt 設(shè)定拍子 tttttt=微秒/四分音符
58 01011000 04 nn dd cc bb 拍子記號(hào) nn=拍子記號(hào)分子 dd=拍子記號(hào)分母2=四分之一 3=8分拍, 等等. cc=節(jié)拍器的節(jié)奏 bb=對(duì)四分之一音符標(biāo)注的第32號(hào)數(shù)字
59 01011001 02 sf mi 音調(diào)符號(hào) sf=升調(diào)/降調(diào)(-7=7 降調(diào), 0=基準(zhǔn)C調(diào),7=7 升調(diào)) mi=大調(diào)/小調(diào)(0=大調(diào), 1=小調(diào))
7F 01111111 xx dd .. 音序器的詳細(xì)信息 xx=被發(fā)送的字節(jié)數(shù) dd=數(shù)據(jù)
下表列出了控制整個(gè)系統(tǒng)的系統(tǒng)消息。這里沒有MIDI通道數(shù) (這些一般僅應(yīng)用于MIDI鍵盤等.)
十六進(jìn)制 二進(jìn)制 數(shù)據(jù) 描述
F8 11111000 同步所必須的計(jì)時(shí)器 FA 11111010 開始當(dāng)前的隊(duì)列 FB 11111011 從停止的地方繼續(xù)一個(gè)隊(duì)列 FC 11111100 停止一個(gè)隊(duì)列
下表列出的是與音符相對(duì)應(yīng)的命令標(biāo)記。 八度音階¦¦ 音符號(hào) # ¦¦ ¦¦ 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" Francis Rumsey 1990 Focal Press "MIDI and Sound Book for the Atari ST" Bernd Enders and Wolfgang Klem 1989 M&T Publishing, Inc. MIDI file specs and general MIDI specs were also obtained by sending e-mail to LISTSERV@AUVM.AMERICAN.EDU with the phrase GET MIDISPEC PACKAGE in the message. ------------------------------ DEC.CPP ------------------------------------
/* file dec.cpp
by Dustin Caldwell (dustin@gse.utah.edu)
*/
#include <dos.h> #include <stdio.h> #include <stdlib.h>
void helpdoc();
main() { FILE *fp;
unsigned char ch, c;
if((fp=fopen(_argv[1], "rb"))==NULL) /* open file to read */ { printf("cannot open file %s\n",_argv[1]); helpdoc(); exit(-1); }
c=0; ch=fgetc(fp);
while(!feof(fp)) /* loop for whole file */ { printf("%u\t", ch); /* print every byte‘s decimal equiv. */ c++; if(c>8) /* print 8 numbers to a line */ { c=0; printf("\n"); }
ch=fgetc(fp); }
fclose(fp); /* close up */ }
void helpdoc() /* print help message */ { printf("\n Binary File Decoder\n\n");
printf("\n Syntax: dec binary_file_name\n\n");
printf("by Dustin Caldwell (dustin@gse.utah.edu)\n\n"); printf("This is a filter program that reads a binary file\n"); printf("and prints the decimal equivalent of each byte\n"); printf("tab-separated. This is mostly useful when piped \n"); printf("into another file to be edited manually. eg:\n\n"); printf("c:\>dec sonata3.mid > son3.txt\n\n"); printf("This will create a file called son3.txt which can\n"); printf("be edited with any ascii editor. \n\n"); printf("(rec.exe may also be useful, as it reencodes the \n"); printf("ascii text file).\n\n"); printf("Have Fun!!\n"); }
---------------------------- REC.CPP ----------------------------------
/* File rec.cpp by Dustin Caldwell (dustin@gse.utah.edu) */
#include <dos.h> #include <stdio.h> #include <ctype.h> #include <stdlib.h>
void helpdoc();
main() { FILE *rfp, *wfp;
unsigned char ch, c; char s[20];
if((rfp=fopen(_argv[1], "r"))==NULL) /* open the read file */ { printf("cannot open file %s \n",_argv[1]); helpdoc(); exit(-1); }
if((wfp=fopen(_argv[2], "wb"))==NULL) /* open the write file */ { printf("cannot open file %s \n",_argv[1]); helpdoc(); exit(-1); }
c=0;
ch=fgetc(rfp);
while(!feof(rfp)) /* loop for whole file */ {
if(isalnum(ch)) /* only ‘see‘ valid ascii chars */ { c=0; while(isdigit(ch)) /* only use decimal digits (0-9) */ { s[c]=ch; /* build a string containing the number */ c++; ch=fgetc(rfp); } s[c]=NULL; /* must have NULL terminator */
fputc(atoi(s), wfp);/* write the binary equivalent to file */
}
ch=fgetc(rfp); /* loop until next number starts */
}
fclose(rfp); /* close up */ fclose(wfp); }
void helpdoc() /* print help message */ { printf("\n Text File Encoder\n\n");
printf("\n Syntax: rec text_file_name binary_file_name\n\n");
printf("by Dustin Caldwell (dustin@gse.utah.edu)\n\n"); printf("This is a program that reads an ascii tab-\n"); printf("delimited file and builds a binary file where\n"); printf("each byte of the binary file is one of the decimal\n"); printf("digits in the text file.\n"); printf(" eg:\n\n"); printf("c:\>rec son3.txt son3.mid\n\n"); printf("(This will create a file called son3.mid which is\n"); printf("a valid binary file)\n\n"); printf("(dec.exe may also be useful, as it decodes binary files)\n\n"); printf("Have Fun!!\n"); }

|