SMFのバイナリを読んでみる

電通大生もすなるバイナリ解読といふものを、私もしてみむとて、するなり


この記事はUEC Advent Calendar 2024の15日目の記事となります。

# 電通大生による電通大生のためのAdvent Calendar 2024
## これはなに?
12/1から12/25のクリスマスまで、毎日電通大生の有志が記事を書いて、カレンダーを電通大に染める企画です。例年、知らぬ間に有志によって動いています。

早々に埋まったので[その2](https://adventar.org/calendars/10198)も作りました。

## 何を書けばいいの?
技術的なお話はもちろん、真面目なネタやオタクなネタでも何でもいいです。なんなら作り話でもなんでもいいです。後ろにあるリンクから過去のアドベントカレンダーを見れば雰囲気がわかります。

## 誰が書けるの?
電通大生っぽかったら、誰でもいいです。電通大生っぽかったら、誰でもいいんですよ。

## 参加したいが、ネタがないです
まずは参加登録してから悩みましょう。あなたの身の回りには、知らぬ間にネタで溢れかえっています。学校のことやバイトのこと、生涯を誓った推しのことやライフハックなど、書けそうって思えるものならなんでもOKです。


過去のAdvent Calendar
[UEC Advent Calendar 2013](https://adventar.org/calendars/113)
[UEC Advent Calendar 2014](https://adventar.org/calendars/335)
[UEC Advent Calendar 2015](https://adventar.org/calendars/726)
[UEC Advent Calendar 2016](https://adventar.org/calendars/1953)
[UEC Advent Calendar 2017](https://adventar.org/calendars/2376)
[UEC Advent Calendar 2018](https://adventar.org/calendars/3569)
[UEC Advent Calendar 2019](https://adventar.org/calendars/4393)
[UEC Advent Calendar 2020](https://adventar.org/calendars/5070)
[UEC 2 Advent Calendar 2020](https://adventar.org/calendars/5276)
[UEC Advent Calendar 2021](https://adventar.org/calendars/6400)
[UEC 2 Advent Calendar 2021](https://adventar.org/calendars/6598)
[UEC Advent Calendar 2022](https://adventar.org/calendars/7581)
[UEC 2 Advent Calendar 2022](https://adventar.org/calendars/7586)
[UEC Advent Calendar 2023](https://adventar.org/calendars/8698)
[UEC 2 Advent Calendar 2023](https://adventar.org/calendars/8704)


### P.S.
2025年のUEC Advent Calendarを作ろうとしているそこのあなた
開発者ツールでheadタグの中を見るとMarkdownが載ってるからコピペしてね

UEC Advent Calendar 2024 - Adventar

# 電通大生による電通大生のためのAdvent Calendar 2024 ## これはなに? 12/1から12/25のクリスマスまで、毎日電通大生の有志が記事を書いて、カレンダーを電通大に染める企画です。例年、知らぬ間に有志によって動いています。 早々に埋まったので[その2](https://adventar.org/calendars/10198)も作りました。 ## 何を書けばいいの? 技術的なお話はもちろん、真面目なネタやオタクなネタでも何でもいいです。なんなら作り話でもなんでもいいです。後ろにあるリンクから過去のアドベントカレンダーを見れば雰囲気がわかります。 ## 誰が書けるの? 電通大生っぽかったら、誰でもいいです。電通大生っぽかったら、誰でもいいんですよ。 ## 参加したいが、ネタがないです まずは参加登録してから悩みましょう。あなたの身の回りには、知らぬ間にネタで溢れかえっています。学校のことやバイトのこと、生涯を誓った推しのことやライフハックなど、書けそうって思えるものならなんでもOKです。 過去のAdvent Calendar [UEC Advent Calendar 2013](https://adventar.org/calendars/113) [UEC Advent Calendar 2014](https://adventar.org/calendars/335) [UEC Advent Calendar 2015](https://adventar.org/calendars/726) [UEC Advent Calendar 2016](https://adventar.org/calendars/1953) [UEC Advent Calendar 2017](https://adventar.org/calendars/2376) [UEC Advent Calendar 2018](https://adventar.org/calendars/3569) [UEC Advent Calendar 2019](https://adventar.org/calendars/4393) [UEC Advent Calendar 2020](https://adventar.org/calendars/5070) [UEC 2 Advent Calendar 2020](https://adventar.org/calendars/5276) [UEC Advent Calendar 2021](https://adventar.org/calendars/6400) [UEC 2 Advent Calendar 2021](https://adventar.org/calendars/6598) [UEC Advent Calendar 2022](https://adventar.org/calendars/7581) [UEC 2 Advent Calendar 2022](https://adventar.org/calendars/7586) [UEC Advent Calendar 2023](https://adventar.org/calendars/8698) [UEC 2 Advent Calendar 2023](https://adventar.org/calendars/8704) ### P.S. 2025年のUEC Advent Calendarを作ろうとしているそこのあなた 開発者ツールでheadタグの中を見るとMarkdownが載ってるからコピペしてね

adventar.org

前日の記事はもっちゃんさんの「大学構内で雑にコスプレし続けた話」です。

この記事を書いているもっちゃんをボイスチャットで見守りながら自分の記事を書いていましたので、早く書けとせかしていたところ、もっちゃんが書き上げてしまったので今逆にせかされています。ぎえ~~~。


今年もその2が生えております。

# 電通大生による電通大生のためのAdvent Calendar 2024 その2
## これはなに?
12/1から12/25のクリスマスまで、毎日電通大生の有志が記事を書いて、カレンダーを電通大に染める企画です。例年、知らぬ間に有志によって動いています。

このAdvent Calendarはその2です。
[その1](https://adventar.org/calendars/10127)はこっち

## 何を書けばいいの?
技術的なお話はもちろん、真面目なネタやオタクなネタでも何でもいいです。なんなら作り話でもなんでもいいです。後ろにあるリンクから過去のアドベントカレンダーを見れば雰囲気がわかります。

## 誰が書けるの?
電通大生っぽかったら、誰でもいいです。電通大生っぽかったら、誰でもいいんですよ。

## 参加したいが、ネタがないです
まずは参加登録してから悩みましょう。あなたの身の回りには、知らぬ間にネタで溢れかえっています。学校のことやバイトのこと、生涯を誓った推しのことやライフハックなど、書けそうって思えるものならなんでもOKです。


過去のAdvent Calendar
[UEC Advent Calendar 2013](https://adventar.org/calendars/113)
[UEC Advent Calendar 2014](https://adventar.org/calendars/335)
[UEC Advent Calendar 2015](https://adventar.org/calendars/726)
[UEC Advent Calendar 2016](https://adventar.org/calendars/1953)
[UEC Advent Calendar 2017](https://adventar.org/calendars/2376)
[UEC Advent Calendar 2018](https://adventar.org/calendars/3569)
[UEC Advent Calendar 2019](https://adventar.org/calendars/4393)
[UEC Advent Calendar 2020](https://adventar.org/calendars/5070)
[UEC 2 Advent Calendar 2020](https://adventar.org/calendars/5276)
[UEC Advent Calendar 2021](https://adventar.org/calendars/6400)
[UEC 2 Advent Calendar 2021](https://adventar.org/calendars/6598)
[UEC Advent Calendar 2022](https://adventar.org/calendars/7581)
[UEC 2 Advent Calendar 2022](https://adventar.org/calendars/7586)
[UEC Advent Calendar 2023](https://adventar.org/calendars/8698)
[UEC 2 Advent Calendar 2023](https://adventar.org/calendars/8704)

### P.S.
2025年のUEC Advent Calendarを作ろうとしているそこのあなた
開発者ツールでheadタグの中を見るとMarkdownが載ってるからコピペしてね

UEC 2 Advent Calendar 2024 - Adventar

# 電通大生による電通大生のためのAdvent Calendar 2024 その2 ## これはなに? 12/1から12/25のクリスマスまで、毎日電通大生の有志が記事を書いて、カレンダーを電通大に染める企画です。例年、知らぬ間に有志によって動いています。 このAdvent Calendarはその2です。 [その1](https://adventar.org/calendars/10127)はこっち ## 何を書けばいいの? 技術的なお話はもちろん、真面目なネタやオタクなネタでも何でもいいです。なんなら作り話でもなんでもいいです。後ろにあるリンクから過去のアドベントカレンダーを見れば雰囲気がわかります。 ## 誰が書けるの? 電通大生っぽかったら、誰でもいいです。電通大生っぽかったら、誰でもいいんですよ。 ## 参加したいが、ネタがないです まずは参加登録してから悩みましょう。あなたの身の回りには、知らぬ間にネタで溢れかえっています。学校のことやバイトのこと、生涯を誓った推しのことやライフハックなど、書けそうって思えるものならなんでもOKです。 過去のAdvent Calendar [UEC Advent Calendar 2013](https://adventar.org/calendars/113) [UEC Advent Calendar 2014](https://adventar.org/calendars/335) [UEC Advent Calendar 2015](https://adventar.org/calendars/726) [UEC Advent Calendar 2016](https://adventar.org/calendars/1953) [UEC Advent Calendar 2017](https://adventar.org/calendars/2376) [UEC Advent Calendar 2018](https://adventar.org/calendars/3569) [UEC Advent Calendar 2019](https://adventar.org/calendars/4393) [UEC Advent Calendar 2020](https://adventar.org/calendars/5070) [UEC 2 Advent Calendar 2020](https://adventar.org/calendars/5276) [UEC Advent Calendar 2021](https://adventar.org/calendars/6400) [UEC 2 Advent Calendar 2021](https://adventar.org/calendars/6598) [UEC Advent Calendar 2022](https://adventar.org/calendars/7581) [UEC 2 Advent Calendar 2022](https://adventar.org/calendars/7586) [UEC Advent Calendar 2023](https://adventar.org/calendars/8698) [UEC 2 Advent Calendar 2023](https://adventar.org/calendars/8704) ### P.S. 2025年のUEC Advent Calendarを作ろうとしているそこのあなた 開発者ツールでheadタグの中を見るとMarkdownが載ってるからコピペしてね

adventar.org

前日の記事はきゅ~さんの「財布の盗難には気を付けよう!」です。

財布を盗まれた話がかなりリアルに語られております。私は財布にあまり現金を入れていませんのでよく「私は財布落としても盗まれるものはない」と冗談半分で言っていますが、証明書の再発行は確かに面倒なので財布を落とさないように気を付けようと思った次第です。


こんにちは、へるくんです。最近、バイナリを読むのが周りで流行っているようです。pngを読んでいる人もいれば、flacを読んでいる人もいますね。

というわけで、「電通大生もすなるバイナリ解読といふものを、私もしてみむとて、するなり」という言葉もありますので、今回は流行りに乗ってバイナリを読んでみたという記事です。ついでにアウトプットがてら拙い文章で解説もしたいと思います(できるだけ噛み砕いてわかりやすくできたらと思います)。

何のバイナリを読むの?

今回はタイトルにもある通りSMFというファイルのバイナリを読みます。SMFはStandard Midi Fileの略で、要するにmidiに関するファイルフォーマットです。

これを読もうと思った主な理由は2つありまして、

  • midi規格書をはじめとする記事がネット上に多いため、バイナリ解読入門としてちょうど良い
  • midiのピアノロールWebアプリを作る予定なのでちょうど良い

というものがあります。あと、私はDTMが趣味なので、これを機にmidiに関する理解を深めたいというのもあります。

解析の準備

まず、解析をするSMFファイルを作らないとなりませんので、Signalというものを使用して適当に音階を打ち込んでいきます。 SignalはWeb上で動く簡易的なDAWみたいなもので、メモ程度に何か音階を打ち込みたい時にとても便利です。 今回は簡単に説明したいのでこんな感じに打ち込みました。

C+E+G
なんというか...こうよ!

なんの変哲もないCメジャーですね。

打ち込んだものを保存してエクスポートすると、.midという拡張子のものができました。これがSMFファイルです。

SMFの主な構造

まず、SMFにはFormat0, Format1, Format2の3種類のフォーマットがあります。

Format0は全てのチャンネルデータが一つのトラックにまとめられているフォーマットです。

Format1はマルチトラックフォーマットで、楽器ごとにトラックが分けられて保存されていると言えばイメージがつくと思います。おそらくこのフォーマットが最も使用されているはずですので、今回はFormat1について話していく予定です。

Format2は正直よくわからない上にあまり普及もしていないので説明は割愛します(というか、誰か教えて下さい)。


ところで、「トラック」という音楽やってない人からするとよくわからないであろう単語が出てきました。音楽におけるトラックは、各楽器や音のデータの情報をひとつずつに分けたものです。DAWの画面でいうこれです(多分)。

track
枠で囲ったやつがトラックひとつ分

SMFのバイナリは一番最初に1つのheaderチャンク(headerに関する情報がある部分)があり、その後に複数のTrackチャンク(トラックに関する情報がある部分)が続いていくような構造が基本的です。Trackチャンクには、音階情報や後述するmetadata情報など、数々の情報が含まれています。

それでは、先ほど作ったSMFのバイナリを早速見ていきましょう。

c.mid
4D 54 68 64 00 00 00 06 00 01 00 02 01 E0 4D 54 72
6B 00 00 00 17 00 FF 03 00 00 FF 58 04 04 02 18 08
00 FF 51 03 07 A1 20 00 FF 2F 00 4D 54 72 6B 00 00
00 7F 00 B0 79 00 00 FF 03 00 00 B0 0A 40 00 B0 07
64 00 B0 0B 7F 00 B0 65 00 00 B0 64 02 00 B0 06 40
00 B0 65 00 00 B0 64 01 00 B0 06 40 00 B0 26 00 00
B0 65 00 00 B0 64 00 00 B0 06 0C 00 E0 00 40 00 B0
01 00 00 C0 00 00 90 30 64 87 40 80 30 00 00 90 34
64 87 40 80 34 00 00 90 37 64 87 40 80 37 00 00 90
30 64 00 90 34 64 00 90 37 64 87 40 80 30 00 00 80
34 00 00 80 37 00 00 FF 2F 00

4D 54 68 64(utf-8に変換するとMThd)がheaderチャンク、4D 54 72 6B(utf-8変換するとMTrk)がTrackチャンクの開始合図となっております。 今回のバイナリを見ると、4D 54 72 6Bがある箇所が2箇所あるため、trackは2つだということがわかります。

では、headerから順にバイナリを紐解いていきたいと思います。

Headerのバイナリを解析してみる

header部分のバイナリはこのようになっています。

header Chunk
4d 54 68 64 00 00 00 06 00 01 00 02 01 e0

4d 54 68 64は先ほどもあった通り、このチャンクタイプがheaderだということを表しています。


その後の00 00 00 06はheaderチャンクの長さを表しています。16進数表記である06を10進数に直しても6ですね。というわけで、この先6byte分がheaderチャンクの部分だということです。


次は00 01の部分です。これはSMFのフォーマットタイプを表しています。今回はFormat1であるということがここでわかります。


00 02はトラック数を表しており、今回は2トラックあることがわかります(4d 54 72 6bがある箇所を数えるよりも、headerのこの部分を見た方がトラックの個数はわかりやすいと思います)。


最後の01 E0は1拍あたり何tickかという情報(要するに時間分解能)を表しており、今回は16進数表記である1e0を10進数に直した480tickとなっています。

ところで、いきなり「tick」というよくわからない単位が登場しました。これはとても重要な単位ですので、説明したいと思います。

tickとは

tickというのは、1拍をさらに細かく分割した単位となってます。今回は1拍当たり480tickとなっていますので、1tickは480分の1拍となります。


この先、Trackチャンクを紐解くにあたって、「前回のイベントから何tick後(デルタタイム)にこれこれのイベントを実行」という構造が頻繁にあります。 この時の「何tick後」を表す方法が少し特殊なので先に説明します。


例えば、2拍後(すなわち960tick後)は、普通なら03 C0だと思いますが、実は違います。87 40と表記します。

どういうことかというと、「128*7 + 64 = 960」という計算になります。 この「128*7」の7に0x80を加えた値が「87」に反映されており、余りの64がもう1byte先の40(10進数64を16進数に直すと40)となります。

これは可変長数値表現というもので、こうすることでより長いデルタタイムを表現することが可能です。詳しくは調べてみて下さい(一応Wikipediaの記事貼っておきます)。

Metadataを解析してみる

Trackチャンクは最初の8byteを除いて「デルタタイム + イベント」という構造が繰り返されています。

MetadataはTrackチャンクに入っており、今回は1つめのTrackチャンクにMetadataがあるので紐解いていきます。

1つめのTrackチャンクのバイナリはこのようになっています。

Track Chunk
4D 54 72 6B 00 00 00 17 00 FF 03 00 00 FF 58 04
04 02 18 08 00 FF 51 03 07 A1 20 00 FF 2F 00

4D 54 72 6Bは先ほどから言っている通り、チャンクタイプが何かを表しています。

00 00 00 17はこのTrackチャンクの長さを表しており、今回はこの後に0x17 = 23byte続いていることになります。


この後から「デルタタイム + イベント」という構造が繰り返されます。

とりあえず、00 FF 03 00を見てみましょう。最初の00はデルタタイムすなわち前回のイベントから何tick後にこの後のイベントを実行するかを表しています。 今回は0tick後ということになります。

FFはこのイベントがMetadataイベントであることを表しています。その後の03がMetadataイベントの種類を示しており、03はトラックの名前であることを表しています。 その後の00はこのメタイベントのデータがこの先何byteまで続くかを表しています。今回は1byteも続かないようです。


メタイベントは基本的に「ff + Metaイベントの種類 + 長さ + データ」というような構造になっています。


メタイベントの種類はこんな感じになっています。詳しくはmidi1.0規格書を読んでください。

バイナリメタイベント内容備考
FF 00 02 ssssシーケンス番号ssssは上位バイトが先にストアされた16bitの値
FF 01 len textテキストイベントlenはメタイベント中でその後に続くデータ長
textはテキストデータ
FF 02 len textコピーライト表示
FF 03 len textトラック名
FF 04 len textインストゥルメント名
FF 05 len text歌詞
FF 06 len textマーカー
FF 07 len textキューポイント
FF 20 01 ccMIDIチャンネルプリフィックス
FF 2F 00エンドオブトラックトラックチャンクの終了合図
FF 51 03 ttttttテンポttttttは1拍あたりのマイクロ秒数(μs)の16進数表示
FF 54 05 hr mn se fr ffSMPTEオフセットあまりよくわかってないが、知らなくても問題なさそう
FF 58 04 nn dd cc bb拍子nnは分子
ddは分母で2の累乗(ddが02の場合は分母は2^2で4)
ccはmidiクロック数
bbは4分音符一つ分と同じ32分音符の数
FF 59 02 sf mi調号sf=-7は7フラット、sf=-1は1フラット
sf=0はC調、sf=1は1シャープ
sf=7は7シャープ、mi=0は長調
mi=1は短調
FF 7F len dataシーケンサー固有のメタイベント

多分この表とmidiの規格書があればMetaDataは読めると思います。FF 2F 00がトラックチャンク終了の合図です。

1つ目のトラックチャンクはMetaData情報のみで終わりです。

もう一つのTrackチャンクを見てみる

さて、2つ目のトラックチャンクを見てみます。

Track Chunk
4D 54 72 6B 00 00 00 7F 00 B0 79 00 00 FF 03 00 00 B0
0A 40 00 B0 07 64 00 B0 0B 7F 00 B0 65 00 00 B0 64 02
00 B0 06 40 00 B0 65 00 00 B0 64 01 00 B0 06 40 00 B0
26 00 00 B0 65 00 00 B0 64 00 00 B0 06 0C 00 E0 00 40
00 B0 01 00 00 C0 00 00 90 30 64 87 40 80 30 00 00 90
34 64 87 40 80 34 00 00 90 37 64 87 40 80 37 00 00 90
30 64 00 90 34 64 00 90 37 64 87 40 80 30 00 00 80 34
00 00 80 37 00 00 FF 2F 00

4D 54 72 6Bはもはやいつものやつです。 00 00 00 7Fを見ると、今回のTrackチャンクは0x7f=127byte先まで続いていることが分かります。

00 FF 03 00は先ほども出ましたメタデータ情報で、トラック名を表しています。相変わらず長さは0なので今回も無視です。


ところで、ここまではFF(メタデータ)のイベントしか扱ってませんでしたが、もちろんFF以外もあります。 以下がイベントの種類となっています。

バイナリイベント内容備考
8n kk vvCh.n+1 ノートオフnはチャンネル番号-1、kkはノート番号
9n kk vvCh.n+1 ノートオンnはチャンネル番号-1、kkはノート番号
vv=00の時は実質ノートオフ
An kk vvCh.n+1 ポリフォニックキープレッシャーnはチャンネル番号-1、kkはノート番号
vvはベロシティ
Bn cc vvCh.n+1 コントロールチェンジccはコントロールナンバー
チャンネル番号でコントロールナンバーccに値vvを送信
Cn ppCh.n+1 プログラムチェンジnはチャンネル番号、音色をppに変更
Dn vvチャンネルプレッシャーvvはプレッシャー情報
En mm llピッチベンドmmはLSB、llはMSB
FF dataメタデータイベントdataはメタイベント内容

コントロールチェンジ

さて、これらのイベント内容を把握したところで、次の部分の00 B0 0A 40という部分を見てみましょう。

00はいつもの前回のイベントから0tick後に行うイベントという意味です。 B0とあるのでCh.1のコントロールチェンジであることが分かります。

コントロールチェンジのコントロールナンバーはそこそこ種類があります。コントロールナンバー一覧はこの記事を見た方が早いと思われます。

ちなみに今回は0Aがコントロールナンバーですので、パンに関するコントロールナンバーであることが分かります。 40は10進数で64ですので、パンの値は真ん中であることが分かります。


この先もしばらくコントロールチェンジイベントが続きますので、上記の参考記事を基にバイナリを読み進めてみてください。

プログラムチェンジとピッチベンド

プログラムチェンジに関するバイナリを読み進めると、00 E0 00 40という部分が出てきますが、これはピッチベンドです。00はLSB、40はMSBとなっています。


また読み進めていくと、00 C0 00という部分が出てきますが、これがチャンネル1へのプログラムチェンジです。要するに音色を指定しています。 今回は00ということなのでアコースティックピアノが指定されていることが分かります。

チャンネルのノートオン、ノートオフ

お待たせしました。ようやく音階の情報がここで出てきました。 00 90 30 64から紐解いていきます。


00はいつものデルタタイムです。90はチャンネル1のノートオンイベントです。要するに、「音を出すよ」ということです。

30はどの音を出すかを表しています。これはドの音を出そうとしていますね。

64はベロシティです。まあ、音の強さだと思ってくれれば大丈夫です。今回はベロシティは100です。


次の部分を見てみましょう。 87 40 80 30 00です。

まず。87 40はデルタタイム(前回のイベントから何tick後のイベントか)です。これはちょっと前に説明した通り、「128*(0x87-0x80) + 0x40 = 960」ということで、2拍後に実行するイベントであるということが分かります。

80はチャンネル1のノートオフです。要は「音を鳴らすのをやめるよ」ということです。

30なので、ドの音を消すことになります。00はベロシティです。音を消すので00以外あり得ません。


こんな感じの要領で残りのバイナリを紐解いていくと、先ほど打ち込んだとおりの音階になっていることが分かると思います。 そして、最後に00 FF 2F 00でこのトラックチャンクはおしまいです。

SMFのパーサを作ってnpmパッケージにしたい

「ここまでSMFを読み解いてお前どうするの?」という話ですが、現在SMFのパーサをゆっくり作成している途中です。 これをnpmパッケージにして自分が今一番作りたいピアノロールWebアプリを作ることができたらと思っています。


ただ、課題もありまして、どうしてもノートオンとノートオフの処理が大変です。元々SMFはMIDIのリアルタイム信号を扱っているので、ノートオンされた音がノートオフされる保証がないです。 そのため、音階を処理する際に返す形として

export type Note = {
scale: Scale; //音階
octave: Octave;
timing: number; // 音が鳴るタイミング
length: number; //音を鳴らす長さ
velocity: number;
}

とするべきか

export type Note = {
scale: Scale; //音階
octave: Octave;
timing: number; // イベントタイミング
noteType: NoteType; //NoteTypeは'noteon'か'noteoff'
velocity: number;
}

にすべきか悩んでいます。 前者の場合、ノートオフされていない音があった瞬間にバグを踏んでしまいます。後者はその心配はないものの、この先ピアノロールを実装する際に面倒なことになりそうです。

今のところは前者にする予定で作っていますが、実際どうなるかは分かりません。 完成までゆっくり見守っていただければと思います。

終わりに

バイナリを読むのはあまりにも

苦行すぎる!!!


明日の記事はkanaruさんの「TerraformでProxmoxの自宅鯖を管理する」です。

参考文献