EternalWindows
MIDI / MIDIファイルかどうか

MIDIを再生するためには、MIDIファイルから再生に必要なデータを取得する必要があります。 このためには、何よりもMIDIファイルの形式というものを理解しなければならないため、 今回から数回に渡って確認していきたいと思います。 MIDIファイルの先頭4バイトとその後の4バイトには、次のような値が格納されています。

データ内容 バイト数 データ値
チャンクタイプ 4byte MThd
データ長 4byte 6

先頭4バイトのMThdという値とその後の4バイトの6という値は、 どのMIDIファイルにも格納されることになっています。 よって、この値を確認することで、ファイルがMIDIファイルかどうかを特定することができます。 ただし、データ長の6という値については、ビッグエンディアン形式で格納されているため、 リトルエンディアン形式に変換してから確認する必要があります。

ビッグエンディアンやリトルエンディアンという言葉は、バイトをどのようにメモリに格納するかを表し、 バイトオーダーと呼ばれることもあります。 次に、例を示します。

WORD   w = 0x012C;
LPBYTE lp = (LPBYTE)&w;

WORDは、2バイトの値を格納できる型であり、この例では0x012cという値を格納しています。 これを1バイトを表すBYTE型のポインタで指した場合、 *lpは0x2Cとなり、*(lp + 1)は0x01となります。 これはメモリに、小さいバイト(第1バイト)から順に書き込まれているからです。 このような形式をリトルエンディアンと呼びます。 もし、0x012Cがビッグエンディアン形式でメモリに格納されていたならば、 *lpは0x01となり、*(lp + 1)は0x2Cとなります。 つまり、大きいバイトからメモリに書き込まれていることになります。

先に示したデータ長を例に考えてみましょう。 サイズが4バイトということから、16進数で表すと次のようになります。

0x 00 00 00 06

そして、このビッグエンディアン形式は次のようになります。

0x 06 00 00 00

よって、通常通りデータ長を4バイト読み取った場合、 値を受け取る変数には0x06000000という値が格納されることになります。 これは0x00000006という値に変換しなければならないため、 ビッグエンディアンをリトルエンディアンに変換するコードが必要になります。

今回のプログラムは、チャンクタイプとデータ長を確認することで、 指定されたファイルがMIDIファイルであるかどうかを確認します。

#include <windows.h>

#pragma comment (lib, "winmm.lib")

BOOL ReadMidiFile(LPTSTR lpszFileName);
void ReadAndReverse(HMMIO hmmio, LPVOID lpData, DWORD dwSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	if (ReadMidiFile(TEXT("sample.mid")))
		MessageBox(NULL, TEXT("指定されたファイルはMIDIファイルです。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("指定されたファイルはMIDIファイルではありません。"), NULL, MB_ICONWARNING);

	return 0;
}

BOOL ReadMidiFile(LPTSTR lpszFileName)
{
	HMMIO hmmio;
	DWORD dwMagic;
	DWORD dwDataLen;

	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	mmioRead(hmmio, (HPSTR)&dwMagic, sizeof(DWORD));
	if (dwMagic != *(LPDWORD)"MThd") {
		mmioClose(hmmio, 0);
		return FALSE;
	}

	ReadAndReverse(hmmio, &dwDataLen, sizeof(DWORD));
	if (dwDataLen != 6) {
		mmioClose(hmmio, 0);
		return FALSE;
	}
	
	return TRUE;
}

void ReadAndReverse(HMMIO hmmio, LPVOID lpData, DWORD dwSize)
{
	BYTE   i;
	BYTE   tmp;
	LPBYTE lp = (LPBYTE)lpData;
	LPBYTE lpTail = lp + dwSize - 1;

	mmioRead(hmmio, (HPSTR)lp, dwSize);
	
	for (i = 0; i < dwSize / 2; i++) {
		tmp = *lp;
		*lp = *lpTail;
		*lpTail = tmp;

		lp++;
		lpTail--;
	}
}

自作関数のReadMidiFileでは、マルチメディアファイル入出力関数を利用してファイルを読み取っています。 CreateFileやReadFileなどの関数を呼び出してもよいのですが、 引数の少ないマルチメディアファイル入出力関数の方が使いやすいといえるでしょう。 チャンクタイプの確認は、次のようになっています。

mmioRead(hmmio, (HPSTR)&dwMagic, sizeof(DWORD));
if (dwMagic != *(LPDWORD)"MThd") {
	mmioClose(hmmio, 0);
	return FALSE;
}

チャンクタイプは4バイトで格納されているので、 mmioReadの第3引数にsizeof(DWORD)を指定して4バイト読み取ります。 そして、その値がMThdでない場合は、MIDIファイルでないことを意味するため、 ファイルハンドルを閉じてFALSEを返すようにします。 データ長の確認は、次のようになります。

ReadAndReverse(hmmio, &dwDataLen, sizeof(DWORD));
if (dwDataLen != 6) {
	mmioClose(hmmio, 0);
	return FALSE;
}

ReadAndReverseという自作関数は内部で第3引数に指定されたサイズを読み取り、 その読み取った値のバイトを逆転させます。 これにより、ビッグエンディアンはリトルエンディアンに変換されます。 そして、その変換した値を第2引数に返し、 呼び出し側ではそれが6であるかを確認します。 ReadAndReverseの内部は、次のようになっています。

void ReadAndReverse(HMMIO hmmio, LPVOID lpData, DWORD dwSize)
{
	BYTE   i;
	BYTE   tmp;
	LPBYTE lp = (LPBYTE)lpData;
	LPBYTE lpTail = lp + dwSize - 1;

	mmioRead(hmmio, (HPSTR)lp, dwSize);
	
	for (i = 0; i < dwSize / 2; i++) {
		tmp = *lp;
		*lp = *lpTail;
		*lpTail = tmp;

		lp++;
		lpTail--;
	}
}

lpはデータの先頭を指し、lpTailはデータの最後尾を指しています。 これを入れ替えるようにし、ループの回数だけ互いを1バイトずつ接近させます。


戻る