SMF内部構造


全体構造

SMFの内部はチャンクと呼ばれるブロックに分割されています。これはWindowsのRIFFファイルなどの一般的なマルチメディアファイルと同様の構造になっています。 チャンクはファイルの先頭にあってSMF全体の情報を格納しているヘッダチャンクと、トラックごとの情報を格納しているトラックチャンクに分類されます。 トラックチャンクには実際の演奏データとそれに付随する情報が格納されます。



SMFファイルはチャンク構造の違いで3つのフォーマットに分類されます。 ヘッダチャンクとトラックチャンク一つで構成されるフォーマット0、複数トラックを持つフォーマット2、 マルチシーケンスでシーケンスパターンを指定するフォーマット2の3種類です。 SMFをサポートするシーケンサはフォーマット0か1、またはその両方に対応していなければいけません。 フォーマットタイプはヘッダチャンクに記述されます。
それでは、各チャンクについて説明します。


ヘッダチャンク

ヘッダチャンクにはそのファイルの基本的な情報が格納され、ファイルの先頭14バイトに記述されます。


それぞれのデータは次のようになります。
チャンクタイプ 4byte MThd 常にアスキー文字列"MThd"4文字
データ長 4byte 6 常にフォーマット、トラック数、時間単位のデータ長の合計6
フォーマット 2byte 0,1,2 SMFのフォーマットタイプ
トラック数 2byte 0...n トラックチャンクの数
時間単位 2byte Time 4分音符分のデルタタイム或いはタイムコードに基づいた1秒の分数

チャンクタイプとデータ長は不変です。フォーマット0の場合、トラック数は1になります。 フォーマットが1、2の場合はトラック数は1以上の任意の値になります。時間単位は通常、 4分音符分の長さのデルタタイムを表します。多くのシーケンサでは384(180h)、 480(1E0h)、960(3C0h)などの値が使われます。


トラックチャンク

ヘッダチャンクは常に14バイトで構成されますが、トラックチャンクは実際の演奏データが格納がされるのでデータ長は可変になります。



トラックチャンクの構造は、フォーマット0、1、2すべてで共通で、トラックチャンクを表すチャンクタイプ、データ長のあとに実際の演奏情報が続いていきます。 実際の演奏情報データはトラックイベントと呼ばれ、MIDIイベント、SyeExイベント、メタイベントがあります。 それぞれのイベントには時間を示すデルタタイムが付随しています。
デルタタイムは、ヘッダチャンクの時間単位情報にしたがって可変長数値(後述)で表され、直前のイベントタイムからの差分が示されます。 デルタタイムを省略することはできず、曲先頭のイベントはデルタタイム0になります。
したがってトラックイベントはすべて”[Delta Time] + [トラックイベント]”の形で格納されることになります。
次にデルタタイムと各イベントについて説明します。


デルタタイムについて

デルタタイムは各イベントのSMF内での相対的位置を表すため使われます。 ヘッダチャンクの時間単位で示した値が4分音符分の長さとして扱われます。 例えば、その値が480で、あるイベントのデルタタイムが240であった場合直前のイベントから4分音符の半分の8分音符分後にそのイベントが譜面上位置することを意味します。 デルタタイムは曲の先頭からの絶対位置ではなく、直前のイベントからの相対位置です。二つのイベントが同じ位置に存在する場合、 後に記述するイベントのデルタタイムは0になります。


MIDIイベント

MIDIチャンネルメッセージを表すイベントで、ランニングステータスも扱うことができます。



デルタタイムに続いて、MIDIチャンネルメッセージがそのまま記述されます。


SysExイベント

システム エクスクルーシブ メッセージを格納するイベントで、2種類あります。


最初のSysExイベントは、MIDIエクスクルーシブメッセージにデータ長を付け加えた形になっていて、エクスクルーシブメッセージをそのまま使います。
次のイベントは、エクスクルーシブメッセージの他に、リアルタイムメッセージやコモンメッセージなどに使われます。
それぞれのデータ長は可変長数値で記述されます。また、SysExイベントはランニングステータスを無効にします。


メタイベント

メタイベントはMIDIメッセージ以外の情報を格納するに使われます。


メタイベントは”FFh”に続く、1バイトのイベントタイプ、可変長数値で表されるデータ長、 イベントデータから構成されます。メタイベントはMIDI演奏情報ではないので無視することができますが、その場合でも適切なバイト数を算出して読み飛ばさなくてはいけません。 メタイベントはランニングステータスを無効にします。



可変長数値について

SMFにおいて、デルタタイムやデータ長などを表す数値は可変長数値で表現されます。 これは、表現できる数値を制限せずに、効率よくデータを保持するためです。
例えばSMFで3バイトの可変長数値は21ビットの数値を表します。



可変長数値を表す各バイトのビット7はフラグとして使われ、0の場合はそのバイトが 数値を構成する最後のバイトとみなされます。
例えばSMF内に”941Dh(=37917)”という2バイトの可変長数値が格納されていると します。これを実際のデータに変換してみましょう。
まず、これを2進数で表すと次のようになります。
1バイト目:0010100(ビット7は1)
2バイト目:0011101(ビット7は0)
1バイト目のビット7は1です。つまりこのバイトは数値を表す最初か中程に位置するバイトです。
2バイト目のビット7は0です。つまりこのバイトは数値を表す最後のバイトです。

次にそれぞれのバイトのビット7を取り去り、1バイト目のビット0からビット6と 2バイト目のビット0からビット6をつなぎあわせます。
1バイト目:0010100
2バイト目:0011101
つなぎあわせると:0010100 0011101

この値が可変長数値で表されていた実際の数値です。つまり”A1Dh(=2589)という 値になります。逆に数値データを可変長数値でSMF内に格納するには今の逆の計算をします。

ところで、たかだか2、3バイトの数値を格納するのになんでこんなに面倒なことをするのかと疑問に思う方もいると思います。 しかし、可変長数値で表現されるデルタタイムはすべてのイベントに付随しています。 これを4バイトなどの固定長で表現してしまうとかなりの無駄なスペースを占有してしまうことは明らかです。 可変長数値で表現すれば大概のデルタタイムは1、2バイトで済みます。 (例えばSMFに含まれるすべてのイベントが3バイトのMIDIイベントであったと仮定した場合、 デルタタイムを4バイト固定で表現すると、一つのイベントに7バイトを使いますが、 デルタタイムを可変長数値で平均2バイトで表現できたとすると一つのイベントにつき5バイトです。 つまり各イベントにつき2バイトの節約で全体では30%近くファイルが小さくなる計算になります。)