EternalWindows
MIDI / MIDI再生サンプル

今回は、フォーマット0及びフォーマット1のMIDIを再生します。 フォーマット1で必要になるトラックの混ぜ合わせについて取り上げるため、 それ以外の部分については前節を参照してください。

#include <windows.h>

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

struct EVENT {
	BYTE   state;   // ステータスバイト
	BYTE   data1;   // 第1データバイト
	BYTE   data2;   // 第2データバイト
	BYTE   type;    // タイプ
	int    nData;   // データ長
	LPBYTE lpData;  // 可変長データ
	DWORD  dwDelta; // デルタタイム

	struct EVENT *lpNext; // 次のイベントへのポインタ
};
typedef struct EVENT EVENT;
typedef struct EVENT *LPEVENT;

WORD    g_wTime = 0;
DWORD   g_dwTempo = 0;
BOOL    g_bPlayThread = FALSE;
HANDLE  g_hheap = NULL;
LPEVENT g_lpHeader = NULL;

void StopMusic(HMIDIOUT hmo, HANDLE hThread);
BOOL ReadMidiFile(LPTSTR lpszFileName);
BOOL ReadTrack(HMMIO hmmio, LPEVENT *lplpEvent);
LPEVENT MargeTrack(LPEVENT *lplpEvent, WORD wTruck);
void ReadAndReverse(HMMIO hmmio, LPVOID lpData, DWORD dwSize);
void ReadDelta(HMMIO hmmio, LPDWORD lpdwDelta);
double DeltaToMilliSecond(DWORD dwDelta);
LPVOID Alloc(DWORD dwSize);
BOOL SelectMidiFile(HWND hwnd, LPTSTR lpszFileName);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HANDLE   hThread = NULL;
	static HMIDIOUT hmo = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		MMRESULT mr;

		mr = midiOutOpen(&hmo, MIDIMAPPER, 0, 0, CALLBACK_NULL);

		return mr == MMSYSERR_NOERROR ? 0 : -1;
	}
	
	case WM_LBUTTONDOWN: {
		TCHAR szFileName[MAX_PATH];
		DWORD dwThreadId;

		if (!SelectMidiFile(hwnd, szFileName))
			return 0;

		StopMusic(hmo, hThread);

		g_hheap = HeapCreate(0, 4096, 0);
		if (g_hheap == NULL)
			return 0;

		if (!ReadMidiFile(szFileName)) {
			MessageBox(NULL, TEXT("MIDIファイルの読み込みに失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}

		g_dwTempo = 500000; // テンポのデフォルト値
		g_bPlayThread = TRUE;
			
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, hmo, 0, &dwThreadId);

		return 0;
	}

	case WM_DESTROY:
		if (hmo != NULL) {
			StopMusic(hmo, hThread);
			midiOutClose(hmo);
		}
		
		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HMIDIOUT hmo = (HMIDIOUT)lpParameter;
	MIDIHDR  mh;
	LPEVENT  lpEvent = NULL;
	
	while (g_bPlayThread) {
		if (lpEvent == NULL)
			lpEvent = g_lpHeader;
		
		if (lpEvent->dwDelta > 0)
			Sleep((DWORD)DeltaToMilliSecond(lpEvent->dwDelta));
		
		if (lpEvent->state == 0xFF) { // メタイベント
			if (lpEvent->type == 0x51) // セットテンポ
				g_dwTempo = (DWORD)(lpEvent->lpData[2] | (lpEvent->lpData[1] << 8) | (lpEvent->lpData[0] << 16));
		}
		else if (lpEvent->state == 0xF0) { // SysExイベント
			mh.lpData         = (LPSTR)lpEvent->lpData;
			mh.dwBufferLength = lpEvent->nData;
			mh.dwFlags        = 0;
		
			midiOutPrepareHeader(hmo, &mh, sizeof(MIDIHDR));
			midiOutLongMsg(hmo, &mh, sizeof(MIDIHDR));

			while ((mh.dwFlags & MHDR_DONE) == 0);

			midiOutUnprepareHeader(hmo, &mh, sizeof(MIDIHDR));
		}
		else { // MIDIイベント
			DWORD dwMsg = (DWORD)(lpEvent->state | (lpEvent->data1 << 8) | (lpEvent->data2 << 16));
			
			midiOutShortMsg(hmo, dwMsg);
		}

		lpEvent = lpEvent->lpNext;
	}

	return 0;
}

void StopMusic(HMIDIOUT hmo, HANDLE hThread)
{
	if (g_bPlayThread) {
		int   i, j;
		DWORD dwMsg;

		g_bPlayThread = FALSE;
		WaitForSingleObject(hThread, INFINITE); // スレッドが終了するまで待機
		
		for (i = 0; i < 16; i++) {
			for (j = 0; j < 128; j++) {
				dwMsg = (0x80 + i) | (j << 8);
				midiOutShortMsg(hmo, dwMsg); // 全てのノートnoの音を消す
			}
		}
	}
	
	if (g_hheap != NULL) {
		HeapDestroy(g_hheap); // スレッドが終了してから
		g_hheap = NULL;
	}
}

BOOL ReadMidiFile(LPTSTR lpszFileName)
{
	HMMIO   hmmio;
	WORD    i;
	WORD    wTrack;
	WORD    wFormat;
	DWORD   dwMagic;
	DWORD   dwDataLen;
	LPEVENT *lplpEvent; // 各トラック内の最初のイベントを指すポインタ配列

	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;
	}

	ReadAndReverse(hmmio, &wFormat, sizeof(WORD));
	ReadAndReverse(hmmio, &wTrack, sizeof(WORD));
	ReadAndReverse(hmmio, &g_wTime, sizeof(WORD));

	lplpEvent = (LPEVENT *)Alloc(sizeof(DWORD) * wTrack);
	
	for (i = 0; i < wTrack; i++) {
		if (!ReadTrack(hmmio, &lplpEvent[i])) {
			MessageBox(NULL, TEXT("不正なトラックが存在します。"), NULL, MB_ICONWARNING);
			mmioClose(hmmio, 0);
			return FALSE;
		}
	}

	if (wFormat == 0)
		g_lpHeader = lplpEvent[0];
	else
		g_lpHeader = MargeTrack(lplpEvent, wTrack);
	
	mmioClose(hmmio, 0);
	
	return TRUE;
}

BOOL ReadTrack(HMMIO hmmio, LPEVENT *lplpEvent)
{
	BYTE    statePrev = 0; // 前のイベントのステータスバイト
	DWORD   dwLen;
	DWORD   dwMagic;
	LPEVENT lpEvent;
	
	mmioRead(hmmio, (HPSTR)&dwMagic, sizeof(DWORD));
	if (dwMagic != *(LPDWORD)"MTrk")
		return FALSE;
	
	ReadAndReverse(hmmio, &dwLen, sizeof(DWORD));

	lpEvent = (LPEVENT)Alloc(sizeof(EVENT)); // 最初のイベントのメモリを確保

	*lplpEvent = lpEvent; // *lplpEventは常に最初のイベントを指す
	
	for (;;) {
		ReadDelta(hmmio, &lpEvent->dwDelta); // デルタタイムを読み込む
		
		mmioRead(hmmio, (HPSTR)&lpEvent->state, sizeof(BYTE)); // ステータスバイトを読み込む
		if (!(lpEvent->state & 0x80)) { // ランニングステータスか
			lpEvent->state = statePrev; // 一つ前のイベントのステータスバイトを代入
			mmioSeek(hmmio, -1, SEEK_CUR); // ファイルポインタを一つ戻す
		}
		
		switch (lpEvent->state & 0xF0) { // ステータスバイトを基にどのイベントか判別

		case 0x80:
		case 0x90:
		case 0xA0:
		case 0xB0:
		case 0xE0:
			mmioRead(hmmio, (HPSTR)&lpEvent->data1, sizeof(BYTE));
			mmioRead(hmmio, (HPSTR)&lpEvent->data2, sizeof(BYTE));
			break;
		case 0xC0:
		case 0xD0:
			mmioRead(hmmio, (HPSTR)&lpEvent->data1, sizeof(BYTE));
			lpEvent->data2 = 0;
			break;
		
		case 0xF0:
			if (lpEvent->state == 0xF0) { // SysExイベント
				mmioRead(hmmio, (HPSTR)&lpEvent->nData, sizeof(BYTE));

				lpEvent->lpData = (LPBYTE)Alloc(lpEvent->nData + 1); // 先頭の0xF0を含める
				lpEvent->lpData[0] = lpEvent->state; // 可変長データの先頭は0xF0
				mmioRead(hmmio, (HPSTR)(lpEvent->lpData + 1), lpEvent->nData);

				lpEvent->nData++;
			}
			else if (lpEvent->state == 0xFF) { // メタイベント
				DWORD dw;
				DWORD tmp;
				
				mmioRead(hmmio, (HPSTR)&lpEvent->type, sizeof(BYTE)); // typeの取得

				dw = (DWORD)-1;

				switch (lpEvent->type) {

				case 0x00: dw = 2; break;
				case 0x01:
				case 0x02:
				case 0x03:
				case 0x04:
				case 0x05:
				case 0x06:
				case 0x07:
				case 0x08:
				case 0x09: break;
				case 0x20: dw = 1; break; 
				case 0x21: dw = 1; break; 
				case 0x2F: dw = 0; break; // エンドオブトラック
				case 0x51: dw = 3; break; // セットテンポ
				case 0x54: dw = 5; break;
				case 0x58: dw = 4; break;
				case 0x59: dw = 2; break;
				case 0x7F: break;

				default:
					MessageBox(NULL, TEXT("存在しないメタイベントです。"), NULL, MB_ICONWARNING);
					return FALSE;

				}
				
				tmp = dw;

				if (dw != -1) { // データ長は固定か
					ReadDelta(hmmio, &dw);
					if (dw != tmp) {
						MessageBox(NULL, TEXT("固定長メタイベントのデータ長が不正です。"), NULL, MB_ICONWARNING);
						return FALSE;
					}
				}
				else 
					ReadDelta(hmmio, &dw); // 任意のデータ長を取得

				lpEvent->nData  = dw;
				lpEvent->lpData = (LPBYTE)Alloc(lpEvent->nData);
				mmioRead(hmmio, (HPSTR)lpEvent->lpData, lpEvent->nData); // データの取得
				
				if (lpEvent->type == 0x2F) // トラックの終端
					return TRUE;
			}
			else
				;

			break;

		default:
			MessageBox(NULL, TEXT("ステータスバイトが不正です。"), NULL, MB_ICONWARNING);
			return FALSE;

		}
		
		statePrev = lpEvent->state; // 次のイベントが前のイベントのステータスバイトを確認できるように保存する
		
		lpEvent->lpNext = (LPEVENT)Alloc(sizeof(EVENT)); // 次のイベントのためにメモリを確保
		lpEvent = lpEvent->lpNext;
		if (lpEvent == NULL)
			break;
	}

	return FALSE;
}

LPEVENT MargeTrack(LPEVENT *lplpEvent, WORD wTruck)
{
	int     i;
	int     nIndex;         // トラックのインデックス
	DWORD   dwAbsolute;     // 絶対時間
	DWORD   dwPrevAbsolute; // 一つ前の絶対時間
	LPEVENT lpHeader;       // 新しい一連のイベントの先頭を指す
	LPEVENT lpEvent;        // 現在のイベント
	LPDWORD lpdwTotal;      // 各トラックの絶対時間

	lpHeader = (LPEVENT)Alloc(sizeof(EVENT));
	
	lpEvent = lpHeader;

	dwPrevAbsolute = 0;
	
	lpdwTotal = (LPDWORD)Alloc(sizeof(DWORD) * wTruck);

	for (;;) {
		nIndex = -1;
		dwAbsolute = (DWORD)-1; // 0xFFFFFFFF

		for (i = 0; i < wTruck; i++) {
			if (lplpEvent[i]->lpNext == NULL) // トラックの終端まで走査した
				continue;

			if (lpdwTotal[i] + lplpEvent[i]->dwDelta < dwAbsolute) { // 最も絶対時間が低いイベントを見つける
				nIndex = i; // イベントがどのトラックのものかを識別するため
				dwAbsolute = lpdwTotal[i] + lplpEvent[i]->dwDelta;
			}
		}

		if (nIndex == -1) // 全てのトラックを走査した
			break;

		lpEvent->state   = lplpEvent[nIndex]->state;
		lpEvent->data1   = lplpEvent[nIndex]->data1;
		lpEvent->data2   = lplpEvent[nIndex]->data2;
		lpEvent->type    = lplpEvent[nIndex]->type;
		lpEvent->nData   = lplpEvent[nIndex]->nData;
		lpEvent->dwDelta = dwAbsolute - dwPrevAbsolute;

		if (lpEvent->nData != 0) {
			lpEvent->lpData = (LPBYTE)Alloc(lpEvent->nData);
			CopyMemory(lpEvent->lpData, lplpEvent[nIndex]->lpData, lpEvent->nData);
		}
		
		dwPrevAbsolute = dwAbsolute;
		
		lpdwTotal[nIndex] += lplpEvent[nIndex]->dwDelta; // 各トラックの絶対時間を更新

		lplpEvent[nIndex] = lplpEvent[nIndex]->lpNext;
		
		lpEvent->lpNext = (LPEVENT)Alloc(sizeof(EVENT));
		lpEvent = lpEvent->lpNext;
	}

	return lpHeader;
}

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--;
	}
}

void ReadDelta(HMMIO hmmio, LPDWORD lpdwDelta)
{
	int  i;
	BYTE tmp;
	
	*lpdwDelta = 0;

	for (i = 0; i < sizeof(DWORD); i++) {
		mmioRead(hmmio, (HPSTR)&tmp, sizeof(BYTE));

		*lpdwDelta = ( (*lpdwDelta) << 7 ) | (tmp & 0x7F);

		if (!(tmp & 0x80)) // MSBが立っていないならば、次のバイトはデルタタイムではないので抜ける
			break;
	}
}

double DeltaToMilliSecond(DWORD dwDelta)
{
	return (dwDelta * ((double)g_dwTempo / 1000) ) / g_wTime;
}

LPVOID Alloc(DWORD dwSize)
{
	return HeapAlloc(g_hheap, HEAP_ZERO_MEMORY, dwSize);
}

BOOL SelectMidiFile(HWND hwnd, LPTSTR lpszFileName)
{
	OPENFILENAME ofn;

	lpszFileName[0] = '\0'; // 要初期化

	ZeroMemory(&ofn, sizeof(OPENFILENAME));
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.hwndOwner   = hwnd;
	ofn.lpstrFilter = TEXT("MIDI File (*.mid)\0*.mid\0\0");
	ofn.lpstrFile   = lpszFileName;
	ofn.nMaxFile    = MAX_PATH;
	ofn.lpstrTitle  = TEXT("MIDIファイル読み込み");
	ofn.Flags       = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

	if (!GetOpenFileName(&ofn))
		return FALSE;

	return TRUE;
}

フォーマット1のMIDIファイルではトラックが複数存在するため、 どのイベントから再生すればよいかを判断する必要があります。 そこで、MargeTrackという自作関数で、各トラック内のイベントを再生すべき順から取得し、 それらを一連のイベントに繋げて呼び出し側に返します。 トラックを混ぜ合す例を次に示します。

トラック1 トラック2 トラック3
50 (50) 10 (10) 20 (20)
20 (70) 20 (30) 40 (60)

トラック1には2つのイベントがあり、1番目のイベントのデルタタイムは50で、 2番目のイベントのデルタタイムは20といった要領で考えてください。 トラック2とトラック3についても、同じ要領で考えてください。 括弧内の数値は、そのイベントを再生開始から何チック後に処理すべきかを表しています。 たとえば、トラック1の2番目のイベントは、1番目のイベントの20チック後に再生するということであり、 1番目のイベントが開始から50チック後に再生することになっているため、 全体から見て再生すべき時間は70チック後になります。 ではここで、処理すべきイベントの順番を確認してみましょう。 まず、最初に処理すべきなのはトラック2の第1イベントであるため、 次のようにマークしておきます。

トラック1 トラック2 トラック3
50 (50) 10 (10) 20 (20)
20 (70) 20 (30) 40 (60)

それでは、2番目に処理すべきイベントはどれになるでしょうか。 これは、トラック3の第1イベントです。 理由は、絶対時間(括弧内の20)がトラック2の第1イベントの次に高いからです。 決して、デルタタイムの値で順序を決めていけません。 たとえば、トラック2の第2イベントはデルタタイムが20となっていますが、 これはトラック2の第1イベントを送信してからの相対時間であり、 実際にこのイベントを処理すべき時間は30チック後です。 よって、これより短いトラック3の第1イベントが2番目に処理すべきイベントとなります。 このように、絶対時間の低いイベントから処理していけば、 一連のイベントは次のような順序になります。

新しいイベント群
10 (10) [トラック2]
20 (20) [トラック3]
20 (30) [トラック2]
50 (50) [トラック1]
40 (60) [トラック3]
20 (70) [トラック1]

絶対時間(括弧内の値)は、実際にそのイベントを処理すべき時間を表しているため、 当然ながらこれが低いものから順に処理していくことになります。 上記の表ではそのように並んでいるため、これで混ぜ合わせは完了のように思えますが、 実際にはまだ行うべきことが残っています。 それは、デルタタイムの値を1つ前のイベントの相対時間にすることです。 たとえば、2つ目のイベントのデルタタイムは20となっていますが、 これは10でなければなりません。 このイベントを処理すべき時間は開始から20チック後ですが、 1つ目のイベントで既に10チック経過しているため、 2つ目のイベントを処理するまで待機する時間は10チックになります。 このようにデルタタイムを正しく相対時間に設定した場合、 一連のイベントは次のようになります。

新しいイベント群
10 (10) [トラック2]
10 (20) [トラック3]
10 (30) [トラック2]
20 (50) [トラック1]
10 (60) [トラック3]
10 (70) [トラック1]

MargeTrackの処理は、どのトラックの何番目のイベントを選ぶかを決めるコードと、 選んだイベントを新しいイベント郡に含めるコードが主となります。 前者のコードは次の部分です。

for (i = 0; i < nTruck; i++) {
	if (lplpEvent[i]->lpNext == NULL) // トラックの終端まで走査した
		continue;

	if (lpdwTotal[i] + lplpEvent[i]->dwDelta < dwAbsolute) { // 最も絶対時間が低いイベントを見つける
		nIndex = i; // イベントがどのトラックのものかを識別するため
		dwAbsolute = lpdwTotal[i] + lplpEvent[i]->dwDelta;
	}
}

if (lpdwTotal[i] + lplpEvent[i]->dwDelta < dwAbsolute)が、 最も絶対時間が低いイベントを選ぶ処理です。 絶対時間はそれまでのチックと今回のチックを足すことによって算出できます。 それまでのチックというのが、lpdwTotal[i]です。 今回のチックはlplpEvent[i]->dwDeltaにあたります。 このif文は、dwAbsoluteを予め-1(0xFFFFFFFF)に初期化しているので、 最初のイベントが最も絶対時間が低いイベントと仮定されます。 このif文が実行されないときは、全てのトラックを終端まで走査したという意味です。

続いて、新しいイベント郡を作成するコードを見てみます。

lpEvent->state   = lplpEvent[nIndex]->state;
lpEvent->data1   = lplpEvent[nIndex]->data1;
lpEvent->data2   = lplpEvent[nIndex]->data2;
lpEvent->type    = lplpEvent[nIndex]->type;
lpEvent->nData   = lplpEvent[nIndex]->nData;
lpEvent->dwDelta = dwAbsolute - dwPrevAbsolute;

if (lpEvent->nData != 0) {
	lpEvent->lpData = (LPBYTE)Alloc(lpEvent->nData);
	CopyMemory(lpEvent->lpData, lplpEvent[nIndex]->lpData, lpEvent->nData);
}

lpEventが新しいイベント郡を表しているので、取り出したlplpEvent[nIndex]の内容をコピーします。 nDataが0でないときは、可変長データもコピーします。 デルタタイムの代入だけ少し変わった処理になっていますが、 間違ってもここでdwAbsoluteを代入してはいけません。 dwAbsoluteは、このイベントを再生から何チック後に処理するかを表した絶対時間です。 デルタタイムは相対時間、つまり前のイベントから何チック後に処理するかを表すので、 今回の絶対時間と前のイベントの絶対時間を引けば、 前のイベントから何チック経ったかが求まります。

コントロールチェンジ111について

MIDIイベントの1つであるコントロールチェンジは、 チャンネルのボリュームやエクスプレションなどを調整することができます。 しかし、このメッセージは時に、ループの開始位置を示すためにMIDIファイルに含まれていることがあります。 この場合、コントロールチェンジのコントロールNo(第1データバイト)は111になっているため、 これを発見した場合は、ループの開始位置を記憶しておく必要があります。

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HMIDIOUT hmo = (HMIDIOUT)lpParameter;
	MIDIHDR  mh;
	LPEVENT  lpLoop = g_lpHeader;
	LPEVENT  lpEvent = NULL;
	
	while (g_bPlayThread) {
		if (lpEvent == NULL)
			lpEvent = lpLoop;
		
		if (lpEvent->dwDelta > 0)
			Sleep((DWORD)DeltaToMilliSecond(lpEvent->dwDelta));
		
		if (lpEvent->state == 0xFF) { // メタイベント
			if (lpEvent->type == 0x51) // セットテンポ
				g_dwTempo = (DWORD)(lpEvent->lpData[2] | (lpEvent->lpData[1] << 8) | (lpEvent->lpData[0] << 16));
		}
		else if (lpEvent->state == 0xF0) { // SysExイベント
			mh.lpData         = (LPSTR)lpEvent->lpData;
			mh.dwBufferLength = lpEvent->nData;
			mh.dwFlags        = 0;
		
			midiOutPrepareHeader(hmo, &mh, sizeof(MIDIHDR));
			midiOutLongMsg(hmo, &mh, sizeof(MIDIHDR));

			while ((mh.dwFlags & MHDR_DONE) == 0);

			midiOutUnprepareHeader(hmo, &mh, sizeof(MIDIHDR));
		}
		else if ((lpEvent->state & 0xF0) == 0xB0 && lpEvent->data1 == 111) // コントロールチェンジ111
			lpLoop = lpEvent->lpNext;
		else { // MIDIイベント
			DWORD dwMsg = (DWORD)(lpEvent->state | (lpEvent->data1 << 8) | (lpEvent->data2 << 16));
			
			midiOutShortMsg(hmo, dwMsg);
		}

		lpEvent = lpEvent->lpNext;
	}

	return 0;
}

lpLoopがループの開始位置を表しています。 コントロールチェンジ111が存在しない場合は先頭からループ再生できるように、 予めg_lpHeaderを指定しておくようにします。 コントロールチェンジはステータスバイトが0xBnであるため、 これに一致すると共に第1データバイトが111である場合は、 コントロールチェンジ111のイベントを発見したことになります。 このときは、lpLoopを更新するために次のイベントを指定します。 これにより、ループはこのイベントから始まります。



戻る