這篇是基於我一年前的 MIDI 筆記整理,可能只有 cover 到當時需要的部份。
這篇筆記主要針對 MIDI file,沒有 MIDI protocol。
About MIDI
MIDI, Musical Instrument Digital Interface, 是一種 1980 年代就存在的 open format, 常被用於操控音樂相關的訊號。
MIDI 本身不包含音訊,只包含了要彈哪顆音、彈多大力、這些音之間間隔多久這類的訊息, 所以傳輸 MIDI 訊號不需要很大的流量,檔案也不佔什麼空間。
更多關於 MIDI 檔案的介紹可以參考 wiwi 的這篇。 這篇文章會專注在如何看懂 MIDI 檔案本身,像是這樣:

當然這裡無法窮盡所有可能出現在 MIDI 檔案裡的訊息, 但應該能包含足夠的資訊讓讀者知道如何解讀 MIDI 的"文法", 再進一步透過查表得知感興趣的"單字",或是正確的跳過它。
Binary
MIDI 檔案和大部分電腦裡的檔案一樣,不是設計給人讀的,而是以 binary (二進位資料)的形式儲存, 以方便電腦讀取。
雖然是這樣,但還是要有些人懂這些檔案,才能寫出相關的軟體。 因為 MIDI 有開放它的 spec,所以任何人都可以(至少試著)讀懂 MIDI。
二進位的原始形式就是一堆 0 和 1,直接拿它來編一首 djent 可能都比讀懂它容易, 所以我們通常會用 16 進位(Hexadecimal)的形式來看, 在 linux 你可以直接用 xxd 把 MIDI 轉成 16 進位:
$ xxd input.mid >> output.hex
或是直接用 vim 打開 MIDI 檔,輸入指令:
:%!xxd
Hexadecimal
本段落簡單講解二進位和 16 進位的關係,如果已經了解可以跳到下一段。
Binary 只有 0 和 1,我們可以用多個 Binary 位數來表達一個數值, 在常見的編碼方式,如果不考慮負數的話 (這邊不贅述,有興趣的可以查 signed number 和 unsigned number) 有 n 個位數可以從 0 數到 2^n - 1,所以 16 進位需要四位二進位(4 bit):
| Binary | Hexadecimal | Decimal(十進位) |
|---|---|---|
| 0000 | 0x0 | 0 |
| 0001 | 0x1 | 1 |
| 0010 | 0x2 | 2 |
| 0011 | 0x3 | 3 |
| 0100 | 0x4 | 4 |
| 0101 | 0x5 | 5 |
| 0110 | 0x6 | 6 |
| 0111 | 0x7 | 7 |
| 1000 | 0x8 | 8 |
| 1001 | 0x9 | 9 |
| 1010 | 0xA | 10 |
| 1011 | 0xB | 11 |
| 1100 | 0xC | 12 |
| 1101 | 0xD | 13 |
| 1110 | 0xE | 14 |
| 1111 | 0xF | 15 |
注意到我們用 0x 開頭來表示這是 16 進位。
8 個 bit 稱為 1 byte,有 256 種可能值,需要兩個 hex number 表示, 因為我們常用 byte 當最小單位,可以習慣一下兩個 hex number 是 1 byte。 1 byte 已經夠存 ASCII 了,所以 hex 也能顯示成對應到的 ASCII 字元。
把 1 byte 展開,例如 0xAC = 1010 1100,我們稱最左邊的那一個二進位 most significant bit (MSB),而最右邊的叫做 least significant bit(LSB)。
Chunks
MIDI 檔案由多個 chunks 組成,分為 header chunk 和 track chunk, header chunk 會是檔案的開頭,固定有以下格式:
4d54 6864 0000 0006
{MT hd} {length=6}
000X XXXX XXXX
{type} {ntrks} {division}
開頭一定是 4d54 6864(對應到 ASCII 的 MThd),標示這個檔案是 MIDI,以及 header chunk 的開頭。
所有 chunk 都會用 4 byte 標示這個 chunk 有多少 byte 的長度(不含表示長度的 4 byte 本身), header chunk 固定是 6 byte 長。
接下來固定按照以下順序出現,各 2 byte: {type}:
- X 可能是 0/1/2
- 0: 檔案中只有一個 track,可以有多個 channel
- 1: 每個 track 只有一個 channel,同時撥放
- 2: 檔案中有一個或以上個獨立的 track,可能像是多首曲子
{ntrks}: 這個檔案中有多少 track chunks
{division}:
- MSB = 0:後面的 15 bits 表示一個 quarter note 等於多少 ticks
- MSB = 1:後面的 15 bits 可以換算一個 tick 對應到多少秒
Header chunk 結束後會是 track chunk,開頭固定是 4d54 726b(ASCII:MTrk),標示 track chunk 的開始。
4d54 726b XXXX XXXX
{MT rk} {length}
{dt}{event} {dt}{event}....
之後一樣是固定 4 byte 的 track 長度,接下來就是這個 chunk 的"內文",透過 midi message 紀錄, midi message 有固定的格式: {delta time} {event},意思是經過多少時間後執行下一個 event, 如此不斷的累積時間、進行 event,我們就可以得到一個"在什麼時間做什麼事"的譜。
delta time
- variable-length quanity
MIDI 為了省空間(那時記憶體還不是可以盡情揮霍的東西)利用一種可變長度的格式 (variable-length quanity)表示 delta time,每個 delta time 至少 1 byte,但是我們只用其中的 7 bit 存資料, 剩下的 1 bit(MSB) 則用來表示這筆資料是否還有接續,MSB = 1 表示這筆資料還沒完,所以我們會持續讀取 1 byte, 直到 MSB = 0,再把之前所有讀到的 7 bits 合併。
for example:
| variable-length(hex) | variable-length(bin) | data read(bin) | data read(hex) |
|---|---|---|---|
| 7E | 01111110 | 1111110 | 7E |
| C001 | 1100 0000 0000 0001 | 10 0000 0000 0001 | 2001 |
| BABE00 | 1011 1010 1011 1110 0000 0000 | 0 1110 1001 1111 0000 0000 | 0E9F00 |
如此就可以最多存到等效 28 bit 的資料,且不用每次都花那麼多空間存小的 delta time。
- delta time
現在我們了解如何判斷 delta time 的長度以及如何換算它,所以我們可以得到一個代表 delta time 的 數值,這個值的單位是 tick,在 header chunck: {division} 我們知道一個 tick 代表什麼, 例如 division MSB = 0 的情況下,可以換算一個 tick 是多少個四分音符,並且再進一步利用 meta event 的 temple 換算對應到的時間。
events
event 分為三種: MIDI event, sysex event, meta event,所有 event 都包含兩部份: {status byte}{data bytes}。
所有 event 都由 status byte 開始,status byte 的 MSB = 1,用來宣告接下來的訊息代表什麼,data bytes 則是 接在 status byte 後面的內容,視 status byte 而言長度不一定。
- sysex, meta event
sysex(System Exclusive) event 看起來是主要用於和 MIDI devices 溝通的,我們之前的應用中沒有用到 MIDI file 裡 的這種 event 所以沒有深究,只知道這種 event 和 meta event 的類似,它們沒有固定長度而是在 data byte 中宣告。 (宣告長度的 data byte 也是用之前提到的 variable-length quanity 形式紀錄)
sysex event 的 status byte 只會是 F0 或 F7,接著用 {length} 宣告在這之後有多少 byte 的資料。
| status | data |
|---|---|
| F0 | {length}{{length} bytes of data … } |
| F7 | {length}{{length} bytes of data … } |
meta event 包含像是歌詞、節奏、time signature 等資訊,statu byte 是 FF,data byte 的第一個 byte {type} 決定接下來 是哪種資訊,接下來的 {length} 宣告之後還有多少長度,以下大概列出一些可能常用的 meta event,其他的可以去查表1。
| status | type | data | comment |
|---|---|---|---|
| FF | 51 | 03{TTTTTT} | Set tempo to {TTTTTT} microseconds per quarter note |
| FF | 58 | 04{NN}{DD}{CC}{BB} | Set time signature to {NN}/(2^{DD})* time, {CC} and {BB} are related to MIDI clock |
| FF | 59 | 02{SF}{MI} | {SF} = numbers of sharps/flats(-1 = 1 flat), {MI} = 0:major key, {MI} = 1:minor key |
| FF | 2F | 00 | End of Track |
* FF 58 04 04 02 18 08 = 4/4, FF 58 04 06 03 18 08 = 6/8
預設 120 BPM, 4/4。
- MIDI event:
| status | data | comment |
|---|---|---|
| 8{X} | {NN}00 | turn note {NN} off on channel {X} |
| 9{X} | {NN}{VV} | trun note {NN} on on channel {X}, with velocity {VV} |
| A{X} | {NN}{VV} | aftertouch, change the velocity to {VV} of note {NN} on channel {X} |
| B{X} | {CC}{DD} | Control Change(CC) on channel {X}, see the reference2 for detail |
| C{X} | {??} | program change |
| D{X} | {??} | channel pressure |
| E{X} | {XX}{XX} | pitch wheel |
MIDI event 的長度都是固定的,讀取到 status byte 就決定了接下來要再讀幾個 byte 表示該 event 的 結束。
- running status
所以我們現在知道如何判斷 delta time 和 event 的長度。理論上我們從 delta time 開始讀,直到讀到一個 byte 的 MSB = 0,這時我們知道這是 delta time 的最後一個 byte,下一個 byte 是 event 的 status byte,所以我們 預期它的 MSB = 1,但其實我們常常會遇到 delta time 的下一個 byte 的 MSB = 0,這是 MIDI 的另一個小規則: running status,當這個 MIDI event 的 status byte 和上一個一樣時,該 status byte 會被省略(對,連 1 byte 都要偷), 例如我要同時把兩顆音打開,MIDI 會長這樣:
00 90 3C
{dt} {Note ON} {0x3C=60,Note=C4}
50 00 40 50
{velocity = 0x50} {dt} {Note: E4} {Velocity}
而不是:
00 90 3C 50
{dt} {Note ON} {Note: C4} {velocity}
00 90 40 50
{dt} {Note On} {Note: E4} {Velocity}
所以如果要停止一顆音,我們可能更常看到利用 Velocity = 0 的 Note ON(9X),而不是直接用 Note OFF(8X), 因為通常這樣可以省一個 byte。注意只有 MIDI message 會用 running message, sysex 和 meta event 不會。
Conclusion
至今我們應該要可以正確的解析一個 MIDI 檔案,我們知道 chunks 的開頭、長度,以及在一個 track 中,如何 判斷哪裡表示時間,哪裡告訴我們該做什麼事,只要注意 variable-length quanity 格式以及 running status 應該就能 避免混淆,流程大概是這樣:

大概吧