SMFの読み込み

前回の記事でSMF(Standard Midi File)の構造について解説しました。
これを踏まえ、今回はSMFを読み込むプログラムについて解説していきます。

SMFはヘッダチャンクの後にトラックチャンクが続く構造になります。
したがって、これらを順番に読み込んでいけば良いです。

読み込み処理の大枠は以下のようになります。

ReadHeader()がヘッダチャンクを読み込むメソッド、ReadTrack()がトラックチャンクを読み込むメソッドです。
トラックチャンクはヘッダチャンク内のトラック数(m_header.trackNum)だけ格納されているため、トラックの数だけReadTrack()を呼び出しています。

ReadHeader()メソッドの実装は以下のようになります。

チャンクタイプとデータ長は決まっているため、いずれも読み込んだ後にチェックするようにしています。
読み出されるヘッダ長、フォーマット、トラック数、時間単位はビッグエンディアンであるため、リトルエンディアンに変換しています。
今回はフォーマット0と1のみ扱うことにするため、それ以外のフォーマットの場合はエラーとします。
フォーマット0の場合は単一のトラックしか存在してはいけないため、複数あった場合はエラーとします。

ReadTrack()メソッドの実装は以下のようになります。

ヘッダチャンクの読み込み同様、まずチャンクタイプのチェックを行い、トラックのデータ長を読み込んでリトルエンディアンに変換しています。
その後にはイベントが続くため、イベントの読み込みをループ内で行っています。
イベントの末尾には終了イベントが入っているため、これが来たらループを抜けてトラックの読み込みを終了します。

イベントを読み込むループ内では、ReadDelta()でデルタタイムを読み込んだ後にReadEvent()でイベントを読み込んでいます。

ReadDelta()、ReadEvent()メソッドの実装は以下のようになります。

データの構造については前回の記事をご参照下さい。
注意すべきところは、ReadEvent()メソッド内でランニングステータスが来た場合の動作です。

MSBが0のときはステータスバイトが省略されてランニングステータスとなるため、
in.putback()でストリームポインタをひとつ戻して以前のステータスバイトの値を採用しています。

メタイベントの読み込みでは、イベント長チェックもしっかりと行うようにしています。

上記を踏まえ、SMFを読み込むまでのプログラムを以下に記しておきます。

SmfData.h

SmfData.cpp

Main.cpp

プロジェクトフォルダ上にtest.midファイルを置くとこのファイルを読み込みます。

このように、多少の手間はかかりますが、一度読み込み処理をクラス化してしまえば楽です。
MIDI再生を行うには複数のトラックをマージする必要があり、更に手間が増えますが
これもワンパターンなのでクラス化すれば問題ありません。

■参考サイト
EternalWindows MIDI / MIDI再生サンプル