SMF(Standard MIDI File)メモ
拡張子は .mid
, .smf
General MIDI
(GM)とも。
SMFに含まれるもの
- 調性
- 曲名
- テンポ
- 音の長さ
フォーマット3種
0
→ すべてのデータが1つのトラックに格納、トラックが1つしかないため、対応プレーヤの制作が容易で互換性も保ち易やすい。市販のSMFデータは大抵がこの形式。
1
→ 同期演奏される複数のトラックデータが格納される。トラックデータが順に結合されたもの。
2
→ 複数の曲を一つのファイルに格納する(まったく普及していないため無視してよいらしい)
引用元
構造概略
+--------+------------------+--------+-------------------------------------------------------------+
| 分類 | 種別 |データ長 | 内容 |
+--------+------------------+--------+-------------------------------------------------------------+
|ヘッダ |ID |4バイト |"MThd" アスキー・コード 'M','T','h','d' の 4バイト |
| +------------------+--------+-------------------------------------------------------------+
| |サイズ |4バイト |以降のヘッダのバイト数をビッグエンディアンで格納 |
| | | |ヘッダの場合は値6固定なので、00,00,00,06 の4バイト |
| +------------------+--------+-------------------------------------------------------------+
| |フォーマットタイプ |2バイト |値0, 1, 2 のいづれかをビッグエンディアンで格納 |
| | | |ここではフォーマットタイプ 0 の SMF のみ扱うので、 |
| | | |00,00 の2バイト |
| +------------------+--------+-------------------------------------------------------------+
| |トラック数 |2バイト |トラック数をビッグエンディアンで格納 |
| | | |フォーマットタイプが0の場合は、トラック数は1固定なので、 |
| | | |00,01 の2バイト |
| +------------------+--------+-------------------------------------------------------------+
| |時間分解能 |2バイト |四分音符の分割数をビッグエンディアンで格納 |
| | | |四分音符1つをここで指定した値で分割した長さが、時間の最小単位 |
| | | |ただし1バイト目の最上位ビットが'1'の場合は、別の扱いとなる |
| | | |ここでは1バイト目の最上位ビットが'0'のSMFのみを扱う |
+--------+------------------+--------+-------------------------------------------------------------+
|トラック |ID |4バイト |"MTrk" アスキー・コード 'M','T','r','k' の 4バイト |
| +------------------+--------+-------------------------------------------------------------+
| |サイズ |4バイト |以降のトラックのバイト数をビッグエンディアンで格納 |
| +------------------+--------+-------------------------------------------------------------+
| |デルタタイム |可変長 | |
| +------------------+--------+-------------------------------------------------------------+
| |イベント |可変長 | |
| +------------------+--------+-------------------------------------------------------------+
| |デルタタイム |可変長 | |
| +------------------+--------+-------------------------------------------------------------+
| |イベント |可変長 | |
| +------------------+--------+-------------------------------------------------------------+
| | : |
| +------------------+--------+-------------------------------------------------------------+
| |デルタタイム |可変長 | |
| +------------------+--------+-------------------------------------------------------------+
| |イベント |可変長 | |
+--------+------------------+--------+-------------------------------------------------------------+
トラック
- いわゆる楽器パートのこと。
- 1チャンネル(楽器):nトラックの関係
- ただしふつうは
1:1
- ただしふつうは
- ギターという楽器(チャンネル)で二つのパート(トラック)を演奏させることは可能(つまりギタリスト2人)
- 1チャンネル(楽器):nトラックの関係
メタイベント
演奏に直接関係ない情報
シーケンス名やコピーライトなど「曲」として必要な付加情報
テンポイベントなど演奏に欠かせない情報など
SMFファイル
- MIDIは基本的にビッグエンディアン
-
0x12345678
→0x12
,0x34
,0x56
,0x78
という順に低いアドレスから格納される。- MIDIイベントの
0xEn
だけはリトルエンディアン
- MIDIイベントの
-
SMFの構造
SMF = ヘッダ + トラック
ヘッダ
- ヘッダ= [|
"MThd"
; 実データの大きさ(バイト数); フォーマット; トラック数; タイムベース |]- 例: MThd(
0x4D
,0x54
,0x68
,0x64
)+データ量(0x00,0x00,0x00,0x06
)+フォーマット(0x00,0x01
)+トラック数(2byte:0xXXXX
)+デルタタイム(0xXXXX
)
- 例: MThd(
ヘッダの大きさは常に6byte
ヘッダに含まれる実データの大きさを4byteのbig endianで格納する。ヘッダでの実データの大きさは
フォーマットに2byte、
トラック数に 2byte、
タイムベースで2byte
なので、合計6byte。 結局、ここには (現状では) 常に 00 00 00 06 が格納される
タイムベース(時間単位)
- タイムベース > 0 (
MSB
が0
)のとき- 小節・拍の基準とする → 四分音符の分解能
- 上述のタイムベースの設定の項目を参照のこと
- 小節・拍の基準とする → 四分音符の分解能
- タイムベース < 0(
MSB
が1
)のとき- タイムベースを、実時間のタイムコードで表現
- 2バイトで以下のように扱う
- 上位1バイト → SMPTEフォーマットの負数
- 下位1バイト → フレームあたりの分解能
- 2バイトで以下のように扱う
- タイムベースを、実時間のタイムコードで表現
トラックデータ
MTrkイベントとも呼ばれる。
- トラックデータ = `"MTrk" + データ長 + 実データ
- ( デルタタイム , イベント ) の組がトラック末尾まで続く
イベントの種類
- 標準MIDIイベント
- SysExイベント
- メタイベント
デルタタイム
次のMIDIイベントまでの時間。
- ヘッダでタイムベースが
n > 0
のとき → 次のイベントまでの時間間隔- タイムベース120のとき、デルタタイムが120なら四分音符分の時間を表す
- デルタタイムが0なら同時にイベントが発生する
- ヘッダでタイムベースが
n < 0
のとき → 省略- タイムベースが負の設定は音楽関係ではまず見ないだろうとのこと
デルタタイムの表現
- 1byte~4byteの可変長形式で表現する。
- MSBが
1
なら後続の1byteも続く。。が最大3回ということ。
ソフトウェア
シンプルなSMFパーシング
シンプルなSMFプレイヤ
SMFビューア
MIDIの時間表現
ティック(ticks
) → SMFの時間の最小単位
タイムベース(分解能)→ ティックの長さを決めるパラメータ
タイムベースの設定
-
4分音符が何ティックかを設定する
- タイムベースが120のとき
- 8分音符 → 60ティック(四分音符の半分)
- 三連8分音符 → 40ティック(四分音符の3分の1)
- 全音符 → 480ティック(四分音符4つ分)
- タイムベースが120のとき
-
タイムベースには
120
,196
,240
,480
のいずれかが設定されるのが普通 -
1ティックの時間 → テンポパラメータも必要
- 1ティックの時間(秒) =
60
/ テンポ / タイムベース
- 1ティックの時間(秒) =
テンポ120
でタイムベースが480
→ 1ティックは1/960
秒(約1ミリ秒)
- 60 / 120 / 480
= 1 / 2 / 480
= 1 / 960
デュレーション(ゲートタイム)
ノートオンからノートオフまでのティック数 = 音の長さ
ゲートタイムともいいます。ノートオンからノートオフまでのティック数、つまり音の長さを表わします。
注意しなければならないのは、記譜上の音の長さではない、ということです。
実際の演奏で音符の長さいっぱいいっぱい音を出すことはまれです。
また、スタッカートが付いていたら非常に短くなります。
逆に、次の音が鳴っても前の音が残っている場合もあります(同じ高さの音に対してこれをやってはいけません)。
音楽における時間単位
- SMPTE →
1μs=1
とする単位系 - ティック → 四分音符(♩)を基準値として、ほかの音符(♪, ♫)などを基準値の倍数として表現する単位系
SMPTE / ticks のどちらを使うかはSMFのヘッダで示す。
- 上記のタイムベースを参照
- 正の数ならticks
- 負の数ならSMPTE
ティックは四分音符という概念をどう表すかという数値 → 実際の演奏時に四分音符が何秒であるか?という情報がない
時間情報 → テンポデータ
以下のサイトのサンプルSMFを愚直に書き込むC言語の例
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int
main(int argc, char **argv)
{
FILE *fp;
if ((fp = fopen("out.mid", "wb")) == NULL) {
fprintf(stderr, "failed open file.");
exit(1);
}
/* MIDI は基本的にビッグエンディアン */
/*
* MIDIヘッダ
*/
fwrite("MThd", sizeof(char), 4, fp);
/* 00 00 00 06 => 実データの大きさ(MIDIヘッダのブロック長は6固定) */
fwrite(&(uint8_t[]){0x00, 0x00, 0x00, 0x06}, sizeof(uint8_t), 4, fp);
/* フォーマット */
fwrite(&(uint8_t[]){0x00, 0x01}, sizeof(uint8_t), 2, fp);
/* トラック数 */
fwrite(&(uint8_t[]){0x00, 0x02}, sizeof(uint8_t), 2, fp);
/* 時間単位(デルタタイム) 0x30 = 48 */
fwrite(&(uint8_t[]){0x00, 0x30}, sizeof(uint8_t), 2, fp);
/*
* トラックデータ1
*/
fwrite("MTrk", sizeof(char), 4, fp);
/* ブロック長 0x0B=11 */
fwrite(&(uint8_t[]){0x00, 0x00, 0x00, 0x0B}, sizeof(uint8_t), 4, fp);
/* テンポ */
fwrite(&(uint8_t[]){0x00, 0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20}, sizeof(uint8_t), 7, fp);
/* トラックエンド */
fwrite(&(uint8_t[]){0x00, 0xFF, 0x2F, 0x00}, sizeof(uint8_t), 4, fp);
/*
* トラックデータ2
*/
fwrite("MTrk", sizeof(char), 4, fp);
/* ブロック長 0x18=24 */
fwrite(&(uint8_t[]){0x00, 0x00, 0x00, 0x18}, sizeof(uint8_t), 4, fp);
/* ベロシティ127でノート3Cをノートオン */
fwrite(&(uint8_t[]){0x00, 0x90, 0x3C, 0x7F}, sizeof(uint8_t), 4, fp);
/* 48tick後、ノート3Cをノートオフ */
fwrite(&(uint8_t[]){0x30, 0x3C, 0x00}, sizeof(uint8_t), 3, fp);
/* 直後に、ベロシティ127でノート3Eをノートオン */
fwrite(&(uint8_t[]){0x00, 0x3E, 0x7F}, sizeof(uint8_t), 3, fp);
/* 48tick後、ノート3Eをノートオフ */
fwrite(&(uint8_t[]){0x30, 0x3E, 0x00}, sizeof(uint8_t), 3, fp);
/* 直後に、ベロシティ127でノート40をノートオン */
fwrite(&(uint8_t[]){0x00, 0x40, 0x7F}, sizeof(uint8_t), 3, fp);
/* 192tick後、ノート40をノートオフ */
fwrite(&(uint8_t[]){0x81, 0x40, 0x40, 0x00}, sizeof(uint8_t), 4, fp);
/* トラックエンド */
fwrite(&(uint8_t[]){0x00, 0xFF, 0x2F, 0x00}, sizeof(uint8_t), 4, fp);
fclose(fp);
return 0;
}