要書(shū)寫二進(jìn)制(十六進(jìn)制)文件亚隙,應(yīng)該準(zhǔn)備好一些工具箫攀,比如我自己用的是VC++肠牲,因?yàn)閷W(xué)習(xí)MIDI格式無(wú)非是想寫它的軟件,既然VC++可以編輯二進(jìn)制文件靴跛,就將就著用吧缀雳。其次,應(yīng)該找個(gè)可以編輯和播放MIDI文件的軟件梢睛,比如Cakewalk肥印,這樣就可以開(kāi)始了。
首先書(shū)寫文件頭“4d 54 68 64 00 00 00 06”绝葡,我們直接寫同步多音軌的格式深碱,先寫1個(gè)音軌,并以120為一個(gè)音符的基本時(shí)間藏畅。這樣敷硅,隨后的字節(jié)是:“00 01 00 01 00 78”。
現(xiàn)在愉阎,如果用Cakewalk打開(kāi)會(huì)失敗绞蹦,因?yàn)槲覀冎付ǖ囊糗墧?shù)為1,但是并沒(méi)有書(shū)寫任何音軌榜旦,如果改成“00 0100 0000 78”再打開(kāi)幽七,就不會(huì)出問(wèn)題了。所以溅呢,今后如果更改了音軌數(shù)澡屡,千萬(wàn)不要忘記向“上頭”匯報(bào)。
把軌道數(shù)改回01咐旧,繼續(xù)我們的實(shí)驗(yàn)驶鹉。先寫音軌的頭信息:“4D 54 72 6B”(MTrk),因?yàn)槲覀冞€不能確定后面有多少字節(jié)休偶,所以先把它假設(shè)成“00 00 00 00”梁厉,以后再回來(lái)改辜羊。
我們先嘗試設(shè)置歌曲的速度和節(jié)拍等基本信息踏兜。假設(shè)一個(gè)四分音符的時(shí)間是半秒,即0.5*106微秒八秃。它的十六進(jìn)制數(shù)是07A120,再看事件表碱妆,設(shè)置速度是51,但是在其前面必須是FF,然后它須要3個(gè)字節(jié)作為參數(shù)昔驱,因此字節(jié)數(shù)為03疹尾,參數(shù)為“07
A1 20”,也就是“FF 51 03 07 A1
20”。這是事件部分纳本,不要忘記在其之前有個(gè)參數(shù)??時(shí)間差窍蓝。這是一開(kāi)始就應(yīng)該設(shè)置的參數(shù),因此時(shí)間差為00繁成。所以吓笙,完整的事件應(yīng)該是“00 FF 51
03 07 A1 20”,我們把這一段追加在Midi文件末尾巾腕。
這時(shí)先不要急著用Cakewalk驗(yàn)證面睛,因?yàn)槲覀冞€沒(méi)有向“上級(jí)”報(bào)告,沒(méi)錯(cuò)尊搬,把前面表示字節(jié)數(shù)的“00
00 00 00”改成“00 00 00
07”叁鉴,如果用VC++作為二進(jìn)制文件的編輯器,選擇了事件后佛寿,可以在狀態(tài)欄看到選擇的字節(jié)長(zhǎng)幌墓。保存后,再用Cakewalk打開(kāi)冀泻,就可以看見(jiàn)速度是120克锣。
我們?cè)賮?lái)設(shè)置節(jié)拍和調(diào)號(hào),因?yàn)橐话阌肅akewalk新建一個(gè)Midi會(huì)默認(rèn)地設(shè)置成4/4腔长,C大調(diào)袭祟,我們就改設(shè)成6/8,A大調(diào)捞附。查閱事件表知道巾乳,58和59是分別用來(lái)設(shè)置節(jié)拍和調(diào)號(hào)的。雖然設(shè)置節(jié)拍的參數(shù)很多鸟召,但在現(xiàn)在的系統(tǒng)中胆绊,后兩個(gè)參數(shù)是被忽略的,而且Cakewalk還會(huì)對(duì)其進(jìn)行修正欧募。因此压状,我們只要設(shè)置好實(shí)際有用的就可以了。分子是6跟继,分母是8种冬,所以第一個(gè)參數(shù)是06,第二個(gè)參數(shù)是03(23=8)舔糖。最后娱两,補(bǔ)上前面的時(shí)間差和后面的兩個(gè)被忽略的參數(shù),它應(yīng)該是“00 FF 58 04 06 03 00 00”金吗;再看調(diào)號(hào)十兢,A調(diào)有3個(gè)升號(hào)趣竣,因此可以這樣的事件可以表示為“00 FF 59 02 03 00”。事實(shí)上旱物,大小調(diào)是個(gè)被忽略的參數(shù)遥缕。我們統(tǒng)計(jì)一下至今為止事件的字節(jié)數(shù),然后更改前面的參數(shù)宵呛,即把“00 00 00 07”改成“00 00 00 15”通砍。保存后用Cakewalk打開(kāi),再進(jìn)入五線譜窗口烤蜕,就可以馬上驗(yàn)證了封孙。細(xì)心的你可能已經(jīng)發(fā)現(xiàn),進(jìn)入五線譜窗口前和平常有些延遲讽营,這是因?yàn)槲覀儾](méi)有設(shè)置好那些可以忽略的字節(jié)虎忌,而Cakewalk就是在對(duì)其進(jìn)行重新驗(yàn)證,這一點(diǎn)橱鹏,我們以后再討論膜蠢。
用同樣的方法,您可以很容易地設(shè)置歌曲的標(biāo)題和版權(quán)莉兰,這作為一個(gè)練習(xí)挑围,在這里就不多寫了。我們現(xiàn)在學(xué)習(xí)寫一個(gè)含有音符的軌道糖荒。首先您應(yīng)該知道要做哪些事:1杉辙、寫新音軌的信息頭;2捶朵、向上級(jí)匯報(bào)多了一個(gè)音軌蜘矢。接下來(lái),我們開(kāi)始寫入一個(gè)簡(jiǎn)單的音符综看。
假設(shè)向第一拍寫一個(gè)中音A品腹,這里可能要先說(shuō)明一下,音符是從C0開(kāi)始一起向上數(shù)的红碑,數(shù)到中央C(C5)是十六進(jìn)制的3C舞吭,則中音A應(yīng)該為45,在附件中有詳細(xì)的計(jì)算方法析珊。我們知道在音樂(lè)中一個(gè)音符通常有三個(gè)屬性:音高羡鸥、力度和時(shí)值⊥偾恚可是我們?cè)谑录碇胁](méi)有看見(jiàn)有什么可以直接設(shè)置音符時(shí)值的標(biāo)志兄春。不錯(cuò)澎剥,事實(shí)上锡溯,音符的時(shí)值是由按下的時(shí)間和松開(kāi)的時(shí)間決定的赶舆。我們假設(shè)要寫入一個(gè)八分音符。它的Tick數(shù)是四分音符的一半祭饭,即60芜茵,十六進(jìn)制表示成3C。我們先來(lái)看看與音符有關(guān)的標(biāo)志倡蝙。
在事件表中九串,9x是用來(lái)打開(kāi)一個(gè)音符,我們這里假設(shè)使用第7個(gè)通道(注意到MIDI有16個(gè)通道(Channel)寺鸥,而第10個(gè)被默認(rèn)地用作打擊樂(lè)猪钮,所以,我們?cè)谶@個(gè)階段(沒(méi)有學(xué)習(xí)Sysx之前)胆建,先不要使用第10個(gè)通道)烤低,則9x中的x是6;再看它的參數(shù)笆载,一個(gè)是音符扑馁,這里我們寫入45,第二個(gè)是力度凉驻,我們用70腻要,因?yàn)槭且婚_(kāi)始就觸發(fā)的,所以前面的時(shí)間差還是00涝登。這樣我們就在第5個(gè)通道以力度112按下了一個(gè)中音A雄家。對(duì)應(yīng)的字節(jié)描述是“00 96 45 70”。它的時(shí)值不用想都知道一定是0胀滚,這取決于什么時(shí)候把它松開(kāi)咳短。
特別地,如果一個(gè)音符的力度為0蛛淋,則MIDI認(rèn)為用戶想松開(kāi)這個(gè)鍵咙好,因?yàn)?x已經(jīng)打開(kāi)了通道,所以我們直接寫入一個(gè)帶00力度的同一音符就可以決定這個(gè)音符的時(shí)值了褐荷。根據(jù)前面的分析勾效,這個(gè)時(shí)間差應(yīng)該是3C,所以我們?cè)趯懭?C后寫上音符45和它的力度00叛甫,即“3C
45 00”层宫。統(tǒng)計(jì)好字節(jié)數(shù)并向這一軌的頭信息中更新,然后保存到磁盤其监,用Cakewalk打開(kāi)并進(jìn)入事件列表窗口便可以驗(yàn)證了萌腿。
在這個(gè)基礎(chǔ)上,我們?cè)賴L試在A的后面增加一個(gè)四分音符中音#G抖苦。因?yàn)?6已經(jīng)打開(kāi)了通道毁菱,我們沒(méi)有必要每次都使用9x米死,只要輸入事件信息即可。對(duì)于中音#G贮庞,它的十六進(jìn)制是44峦筒,相對(duì)剛才輸入00力度的A來(lái)說(shuō)時(shí)間差為00,因此可以表示成“00
44 64”窗慎,這里我們已經(jīng)假設(shè)力度為100物喷;然后是松開(kāi)它,因?yàn)槭撬姆忠舴诔猓詴r(shí)間差是78H峦失,別忘記力度是00,它的字節(jié)應(yīng)表示成“78 44
00”术吗,做好后面的工作宠进,然后驗(yàn)證看對(duì)不對(duì)。
我們?cè)僮鰝€(gè)稍微復(fù)雜一點(diǎn)的實(shí)驗(yàn):在原來(lái)的基礎(chǔ)上藐翎,在同一軌的第一拍加上一個(gè)附點(diǎn)四分中音D材蹬。這里就不能再使用追加的方法了,因?yàn)榍懊娴氖录呀?jīng)過(guò)了3個(gè)八分音符的時(shí)間吝镣,無(wú)論再加上什么堤器,都只會(huì)發(fā)生在后面,所以我們要在前面插入一些字節(jié)末贾。
9x已經(jīng)打開(kāi)了通道闸溃,我們直接在9x按下的音符后加上一個(gè)音符事件“00 3E 64”,這里的00顯然是個(gè)時(shí)間差拱撵,3E是中音D辉川,64是力度,也就是說(shuō)拴测,在按下中音A的同時(shí)按下了中音D乓旗。我們又按下一個(gè)鍵了,要在什么時(shí)候集索,在哪里松開(kāi)才能保證輸入的是個(gè)附點(diǎn)八分音符呢屿愚?首先,它的時(shí)值是3個(gè)八分音符务荆,即180妆距,這里還有一點(diǎn)要注意,180是個(gè)大于128的數(shù)函匕,它的動(dòng)態(tài)字節(jié)就應(yīng)該表示成“81 34”娱据,在哪里輸入才好呢?如果你覺(jué)得在按下D后輸入盅惜,或者在任何什么地方輸入這個(gè)時(shí)間差中剩,然后再寫上“3E 00”可以表示松開(kāi)的話就完全誤解了時(shí)間差的概念忌穿。其實(shí),我們只要簡(jiǎn)單地在松開(kāi)#G的時(shí)候松開(kāi)D就可以了咽安,所以應(yīng)該在末尾補(bǔ)上“00 3E 00”伴网。統(tǒng)計(jì)好字節(jié)數(shù)后到Cakewalk中去驗(yàn)證驗(yàn)證吧蓬推。這里附有我們目前寫下的Midi文件樣本妆棒。
到目前為此,我們應(yīng)該可以輸入任何形式的音符了沸伏,不過(guò)MIDI除了音符以外糕珊,還可以包括各種控制器和系統(tǒng)碼,它們的地位不亞于音符毅糟,我們現(xiàn)在馬上學(xué)習(xí)如何使用控制器红选。
控制器比音符要簡(jiǎn)單多了,我們嘗試在#G前加入相位控制(Pan)姆另,它的十進(jìn)制代碼是10喇肋,十六進(jìn)制是0A,我們將參數(shù)設(shè)置成111迹辐,即十六進(jìn)制的6F蝶防。首先查得控制器是Bx,這里的x和上面一樣明吩,也是6间学。接下來(lái)寫入控制器號(hào)0A,然后是參數(shù)6F印荔,別忘了前面的時(shí)間差是00低葫。所以這段字節(jié)是“00
B6 0A
6F”,它應(yīng)放在松開(kāi)#G的事件之前仍律,與按下#G同時(shí)嘿悬。不過(guò),一旦使用了非音符水泉,而后面還有音符事件時(shí)鹊漠,則必須重新通知打開(kāi)音符,這說(shuō)起來(lái)復(fù)雜茶行,做起來(lái)還是比較容易的躯概,我們只要稍微改寫下一個(gè)音符事件即可:原本是“時(shí)間差+音符+力度”,我們加入一個(gè)打開(kāi)音符的標(biāo)志畔师,成為“時(shí)間差+9x+音符+力度”即可娶靡。校驗(yàn)過(guò)頭信息后,去Cakewalk中進(jìn)行更進(jìn)一步的檢驗(yàn)便知它的可行看锉。
其實(shí)姿锭,時(shí)間差為00的控制事件如果出現(xiàn)的時(shí)間也是00塔鳍,則在Cakewalk中會(huì)盡可能地把它們放在軌道信息中,而不在事件列表中重復(fù)呻此。我們可以利用這一點(diǎn)給音軌設(shè)置初始樂(lè)器和音量轮纫,這就作為一個(gè)練習(xí),在此就不再說(shuō)明了焚鲜。
至于其他的諸如觸后鍵等與控制器類似的格式在此就不多說(shuō)了掌唾。在這里有必要提醒的是滑音》薨酰滑音的樂(lè)理范圍是-8192~8191糯彬,但是在使用時(shí)參數(shù)是個(gè)正數(shù),比如要設(shè)置成0葱她,則應(yīng)該是0-(-8192)=8192撩扒,它才是參數(shù)。8192的7位雙字節(jié)表示成“8192 mod 128=00H吨些;8192 div 128=40H”搓谆。如果時(shí)間差是00,則應(yīng)表示成“00 E6 00 40”
最后我們看看系統(tǒng)碼豪墅。系統(tǒng)碼的構(gòu)成本來(lái)是“F0 廠家ID 設(shè)備號(hào)碼 格式代碼 傳送命令 具體參數(shù) F7”泉手,而在文件中,則不以開(kāi)頭的“F0”為系統(tǒng)碼但校,而字節(jié)數(shù)也僅記錄剩余的系統(tǒng)碼螃诅,比如XG的復(fù)位碼是“F0 43 10 4C 00 00 7E 00 F7”,則在文件中應(yīng)寫成“00 F0 08 43 10 4C 00 00 7E 00 F7”状囱,其中第一個(gè)00是時(shí)間差术裸,F(xiàn)0是系統(tǒng)碼標(biāo)志,08是后面的字節(jié)數(shù)亭枷。有一點(diǎn)要注意的是袭艺,幾個(gè)系統(tǒng)碼不可以寫在一起,比如“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”都是不好的寫法叨粘。如果存在以上系統(tǒng)碼集猾编,可以分成兩個(gè)事件:“00 F0 08 43 10 4C 00 00 7E 00 F7 00 F0 04 AA BB CC F7”
當(dāng)然系統(tǒng)碼可以寫在任何音軌,不過(guò)一般我們會(huì)考慮把歌曲播放前發(fā)送的系統(tǒng)碼寫在全局音軌中升敲,并把時(shí)間差設(shè)成00答倡。
作為一個(gè)參考,這里再附上一個(gè)MIDI樣本驴党。
雖然我們只討論了同步多音軌的格式瘪撇,其實(shí)對(duì)于其他兩種,比如較常見(jiàn)的單音軌格式,所有的事件只寫在一個(gè)音軌中倔既,即只要存在一個(gè)“MTrk”就足夠了恕曲。而相對(duì)地,用于記錄音軌數(shù)的兩個(gè)字節(jié)也永遠(yuǎn)為“00
01”渤涌,連續(xù)事件如果出現(xiàn)的通道不同佩谣,也必須重新指定通道(8x~Ex)。在此不詳細(xì)討論了实蓬。
到目前為此茸俭,我們應(yīng)該可以構(gòu)造出任何MIDI音樂(lè)了,作為一些輔助性的參考瞳秽,可以查閱下一篇連載《MIDI文件格式分析??附件篇》瓣履。另外率翅,對(duì)于擴(kuò)展名為.rmi的格式练俐,可以參閱下下篇連載《MIDI文件格式分析??RMI篇》。