Posts for: #筆記

讀 MIDI

這篇是基於我一年前的 MIDI 筆記整理,可能只有 cover 到當時需要的部份。
這篇筆記主要針對 MIDI file,沒有 MIDI protocol。

About MIDI

MIDI, Musical Instrument Digital Interface, 是一種 1980 年代就存在的 open format, 常被用於操控音樂相關的訊號。

MIDI 本身不包含音訊,只包含了要彈哪顆音、彈多大力、這些音之間間隔多久這類的訊息, 所以傳輸 MIDI 訊號不需要很大的流量,檔案也不佔什麼空間。

更多關於 MIDI 檔案的介紹可以參考 wiwi 的這篇。 這篇文章會專注在如何看懂 MIDI 檔案本身,像是這樣:

A MIDI file interpretation example

當然這裡無法窮盡所有可能出現在 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 應該就能 避免混淆,流程大概是這樣:

flow chart of reading a MIDI file

大概吧

如果想要研究細節,可以去看看官方的 MIDI spec,或是 這篇

為什麼我不用 Facebook

開始關注隱私不久後,就把所有和 Facebook 有關的帳號全刪掉了。 我記得當時只有發個文公告要刪帳,讓聯絡人有機會反應 (這倒不是問題,因為多數台灣人同時在用另外一個垃圾: line, 不幸的,很多人,包括我因此被迫使用 line),然後直接刪帳。 沒有那麼難,我沒有後悔過,而且我確定我打死也不會回去。

使用 Facebook 的原因

在討論可能寫不完,不用 Facebook 的原因之前,我先試著列出一些使用 Facebook 的理由:

  • 因為大家都在用
  • 因為方便(早期的聯絡,最近的線上服務一鍵登入)
  • 因為習慣
  • 上面的各種二手拍價格比較香
  • 某種要求(課程報告或其他團體的聯絡需求)

我認為以上幾點可以退化成前兩點。因為前兩點,Facebook 累積了很多用戶, 所以像二手市場這類的社群很發達。同時因為很多人都用習慣了, 就有人認為把資訊交換都託付給這平台是個好選擇,反正很方便。

不使用 Facebook 的原因

這應該不是什麼新聞,Meta 的主要收入來源是廣告,為了精準投放使用者有興趣的廣告, Meta 需要知道關於使用者的很多資訊。

Substantially all of our revenue is currently generated from advertising on Facebook and Instagram. We rely on targeting and measurement tools that incorporate data signals from user activity on websites and services that we do not control, as well as signals generated within our products, in order to deliver relevant and effective ads to our users.

我們先不戴 tin foil,假設沒有人有奇怪的動機或在暗地裡偷偷幹些壞事。

如果你是個"聽話"的 Facebook 使用者, 在帳號上使用真名、 個人電子郵件和電話號碼,並且因為在手機上使用 Facebook 的 App,所以給了聯絡人、麥克風、 相機、日曆、簡訊、電話、GPS位置等權限,然後 po 照片發文分享你的生活和想法, 順便 tag 一下在照片裡的朋友。至此 Facebook 當然已經知道你是誰、你的長相、常去哪裡、 現在在哪、你的朋友圈裡有誰,還有你的各種線上身份資訊。

天哪,好險知道這些的是可信的大公司,而不是什麼奇怪的人,Facebook 會保護好我的資料對吧。

Oops.

當然不是所有的使用者都主動給出那麼多資訊,這裡的重點是, 我們不能相信這些公司會保護好我們的資料,不論有意無意,這些資料都不是安全的。 不論是否使用 Facebook 的全部功能,Facebook 要求太多個資了。

如果我們懷疑多一點,Facebook 或是其他可以利用使用者的資料獲利的公司, 他們肯定會想辦法得到更多資料。只有帳號資料和使用他們服務時產生的各種資料顯然無法滿足 他們,所以他們會大力推薦手機 App,裝了手機 App 後就可以得到前面提到的各種資訊。 你可以試著把這些權限關掉,但對於大部分 Android 和 IOS 用戶而言,幾乎無法確認 App 是否真的沒有權限,因為 App 和作業系統(Android 和 IOS)都是 proprietary(專有) 的。 事實上 Facebook 根本不用說服你安裝 App ,它已經是系統 App 預裝好了,想刪也刪不掉。

為了得到更多資料,Meta 還做了一些額外的工作。 Meta 提供一段稱為 Meta Pixel 的 JavaScript 讓嵌入這段程式的網頁能更有效的追蹤網頁的訪客, 這些被蒐集的資料 當然也會傳到 Meta 那邊,所以 Meta 也知道你去過哪些網站以及你在裡面做什麼, 就算那些網站根本就不屬於 Meta。
他們甚至利用間諜軟體 (雖然他們的 App 本身就是了),去攔截被害人手機裡的網路訊號, 複製一份給 Meta,再重新傳出去假裝沒有任何事發生,讓他們可以追蹤被害人在其他 App 裡的行為,所以 Meta 可以知道競爭者的 App 有什麼酷功能吸引消費者,然後開始抄作業。 這基本上就是犯罪了,但因為他們是 Meta,所以可以賠錢當作沒事發生然後繼續幹骯髒事。1

有了這些資料,Meta 就能和它的快樂夥伴 們交換這些資料、知道如何利用使用者的弱點 在傷口上撒鹽投放精準的廣告、 讓你更沈迷甚至操控使用者。

此外,社群媒體本身就不是什麼對心靈健康有幫助的東西, Meta 自己也清楚這點2

結論

我認為拒絕使用 Facebook 和其他 Meta 的社群軟體是個顯然的答案, 使用 Meta 產品犧牲的隱私、時間和心裡能量換它帶來的收益(如果有的話),完全不值得。 你可以試著降低這些代價,小心的提供最小的資訊或假個資,培養好的線上衛生習慣, 不要被演算法牽著鼻子走,還要小心身邊天真的人在無意的情況下出賣你, 如果 somehow 你認為必須要用 Facebook。

但我認為這不值得嘗試,我們無法知道 Meta 在暗地裡又搞了什麼黑魔法, 在 proprietary 的平台上使用者向來是任人宰割的,這些大公司為了利潤什麼骯髒事都幹的出來, 而且通常被發現後也能拍拍屁股走人,所以也不要期望哪天有誰會來主持公道。

另外一層拒絕 Facebook 的理由是,我不想成為問題的一部分。 前面提到的,我能想出來使用 Facebook 的理由都源自於它龐大的使用者。因為有很多人用這些社群媒體, 所以有更多人為了只出現在這些平台上的聯絡人、資訊開始使用。這些企業因此從更多使用者身上得到 龐大的利益和權力(它們決定了多數人每天被餵食的資訊), 再進一步鞏固它。

講了這麼多可能有點 overwhelming,不是為了散撥恐懼和仇恨,但我認為一定程度的懷疑和記仇能力 在當今的環境下是必須的。我不奢望每個人都馬上完全斷絕這些平台,但我希望讀到這裡的你至少能:

  • 意識到這些 Big Tech 不是朋友。
  • 在使用這些平台時更有警覺,不要害了自己又賣了身邊的人。
  • 不要把自己或組織的聯絡方式和資訊都鎖在不開放的平台上,對大家都好

除此之外,就算不考慮這些隱私、道德問題,退出社群媒體,減少 doomscrolling 和不必要的資訊來源, 本身就是件不錯的事情,不是嗎。