EternalWindows
MIDI / 現在位置とイベント監視

MIDIストリームを用いたMIDIの再生では、再生の詳細がアプリケーションから抽象化され、 直観的に再生の情報を制御できるようになっています。 前節で述べたように、一時停止はmidiStreamPauseを呼び出すだけで実現できますし、 現在位置の取得もmidiStreamPositionで容易に可能です。

MMRESULT midiStreamPosition(
  HMIDISTRM hms, 
  LPMMTIME pmmt, 
  UINT cbmmt     
);

hmsは、MIDIストリームのハンドルを指定します。 pmmtは、MMTIME構造体のアドレスを指定します。 cbmmtは、pmmtのサイズを指定します。

MIDIストリームではバッファにイベントを設定することになっていますが、 このイベントにMEVT_F_CALLBACKというフラグを指定した場合、 そのイベントが処理された場合にMM_MOM_POSITIONCBというメッセージをポストされることになっています。 当然ながら、midiStreamOpenにコールバック機能を利用する旨を指定していることが前提です。 MM_MOM_POSITIONCBでは、処理されたイベントを参照できることになっているため、 このイベントを表示することにより、イベントの監視が可能になります。

case MM_MOM_POSITIONCB: {
	LPMIDIHDR    lpMhdr;
	LPSHORTEVENT lpShort;
	
	lpMhdr = (LPMIDIHDR)lParam;
	lpShort = (LPSHORTEVENT)&lpMhdr->lpData[lpMhdr->dwOffset];

	// lpShortを利用した処理を行う

	return 0;
}

lParamからMIDIHDR構造体を参照することができます。 問題は、MIDIHDR.lpDataのどの位置にフラグを設定したイベントが設定されているかですが、 これはMIDIHDR.dwOffsetをインデックスとして指定することにより特定できます。

今回のプログラムは、再生からの経過時間をウインドウのタイトルに表示します。 また、ウインドウの右半分にリストボックスを表示し、監視したイベントを表示します。

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

struct SHORTEVENT {
	DWORD dwDeltaTime;
	DWORD dwStreamID;
	DWORD dwEvent;
};
typedef struct SHORTEVENT SHORTEVENT;
typedef struct SHORTEVENT *LPSHORTEVENT;

WORD    g_wTime = 0;
BOOL    g_bPlayMusic = FALSE;
HANDLE  g_hheap = NULL;
LPEVENT g_lpHeader = NULL;

DWORD GetBufferCount(DWORD dwMaxBufferSize);
void SetBuffer(LPMIDIHDR *lplpBuffer, DWORD dwBufferCount, DWORD dwMaxBufferSize);
void PlayMusic(HMIDISTRM hms, LPMIDIHDR lpBuffer, DWORD dwBufferCount);
void DestroyMusic(HMIDISTRM hms, LPMIDIHDR lpBuffer, LPDWORD lpdwBufferCount);
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);
LPVOID Alloc(DWORD dwSize);
BOOL SelectMidiFile(HWND hwnd, LPTSTR lpszFileName);
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 HMIDISTRM hms = NULL;
	static LPMIDIHDR lpBuffer = NULL;
	static DWORD     dwBufferCount = 0;
	static DWORD     dwBufferResetCount = 0;
	static HWND      hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		UINT     uId = MIDI_MAPPER;
		MMRESULT mr;
		
		hwndListBox = CreateWindowEx(WS_EX_DLGMODALFRAME, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		mr = midiStreamOpen(&hms, &uId, 1, (DWORD_PTR)hwnd, 0, CALLBACK_WINDOW);
		
		SetTimer(hwnd, 1, 200, NULL);
		
		return mr == MMSYSERR_NOERROR ? 0 : -1;
	}
	
	case WM_LBUTTONDOWN: {
		TCHAR szFileName[MAX_PATH];
		DWORD dwMaxBufferSize = 65536 - sizeof(MIDIHDR);

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

		dwBufferResetCount = dwBufferCount;
		DestroyMusic(hms, lpBuffer, &dwBufferCount);

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

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

		dwBufferCount = GetBufferCount(dwMaxBufferSize);
		SetBuffer(&lpBuffer, dwBufferCount, dwMaxBufferSize);
		PlayMusic(hms, lpBuffer, dwBufferCount);

		g_bPlayMusic = TRUE;

		return 0;
	}
	
	case WM_TIMER:
		if (g_bPlayMusic) {
			MMTIME mmt;
			TCHAR  szBuf[256];
			DWORD  dwSecond;
				
			mmt.wType = TIME_MS;
			midiStreamPosition(hms, &mmt, sizeof(MMTIME));
			
			dwSecond = mmt.u.ms / 1000;
			
			wsprintf(szBuf, TEXT("%02d:%02d"), dwSecond / 60, dwSecond % 60);
			SetWindowText(hwnd, szBuf);
		}
		return 0;
	
	case MM_MOM_DONE:
		if (dwBufferResetCount == 0) {
			LPMIDIHDR lpMhdr = (LPMIDIHDR)lParam;

			midiOutUnprepareHeader((HMIDIOUT)wParam, lpMhdr, sizeof(MIDIHDR));
			if (lpMhdr->dwUser == dwBufferCount - 1) {
				midiStreamStop(hms); // 再生位置を初期化する
				PlayMusic(hms, lpBuffer, dwBufferCount);
			}
		}
		else // midiStreamStopによってバッファが返ってきたため、バッファ数を減らす
			dwBufferResetCount--;
		return 0;
	
	case MM_MOM_POSITIONCB: {
		TCHAR        szBuf[256];
		LPMIDIHDR    lpMhdr;
		LPSHORTEVENT lpShort;
		
		lpMhdr = (LPMIDIHDR)lParam;
		lpShort = (LPSHORTEVENT)&lpMhdr->lpData[lpMhdr->dwOffset];

		wsprintf(szBuf, TEXT("Tempo %d"), MEVT_EVENTPARM(lpShort->dwEvent));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBox, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;
	
	case WM_DESTROY:
		if (hms != NULL) {
			DestroyMusic(hms, lpBuffer, &dwBufferCount);
			midiStreamClose(hms);
		}

		KillTimer(hwnd, 1);
		
		PostQuitMessage(0);

		return 0;

	default:
		break;

	}

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

DWORD GetBufferCount(DWORD dwMaxBufferSize)
{
	DWORD   dwTotalSize = 0;
	LPEVENT lpEvent = g_lpHeader;

	while (lpEvent != NULL) {
		dwTotalSize += sizeof(SHORTEVENT);
		if (lpEvent->state == 0xF0)
			dwTotalSize += ((lpEvent->nData + 3) / 4 ) * 4;

		lpEvent = lpEvent->lpNext;
	}

	return (dwTotalSize / dwMaxBufferSize) + 1;
}

void SetBuffer(LPMIDIHDR *lplpBuffer, DWORD dwBufferCount, DWORD dwMaxBufferSize)
{
	DWORD        i;
	DWORD        dwSize;
	LPEVENT      lpEvent = g_lpHeader;
	LPMIDIHDR    lpBuffer;
	LPSHORTEVENT lpShort;

	lpBuffer = (LPMIDIHDR)Alloc(sizeof(MIDIHDR) * dwBufferCount);

	for (i = 0; i < dwBufferCount; i++) {
		lpBuffer[i].lpData          = (LPSTR)Alloc(dwMaxBufferSize);
		lpBuffer[i].dwBufferLength  = dwMaxBufferSize;
		lpBuffer[i].dwBytesRecorded = 0;
		lpBuffer[i].dwUser          = i; // バッファのインデックスを格納
		lpBuffer[i].dwFlags         = 0;
	}
	
	lpShort = (LPSHORTEVENT)lpBuffer[0].lpData;
	lpShort->dwDeltaTime = 0;
	lpShort->dwStreamID  = 0;
	lpShort->dwEvent     = ((DWORD)MEVT_TEMPO << 24) | 500000;
	lpBuffer[0].dwBytesRecorded = sizeof(SHORTEVENT);

	i = 0;
	
	while (lpEvent != NULL) {
		dwSize = sizeof(SHORTEVENT);
		if (lpEvent->state == 0xF0)
			dwSize += ((lpEvent->nData + 3) / 4 ) * 4;

		if (lpBuffer[i].dwBytesRecorded + dwSize > dwMaxBufferSize)
			i++;
		
		lpShort = (LPSHORTEVENT)(lpBuffer[i].lpData + lpBuffer[i].dwBytesRecorded);
		lpShort->dwDeltaTime = lpEvent->dwDelta;
		lpShort->dwStreamID  = 0;
		
		if (lpEvent->state == 0xFF) {
			if (lpEvent->type == 0x51) {
				lpShort->dwEvent  = (DWORD)(lpEvent->lpData[2] | (lpEvent->lpData[1] << 8) | (lpEvent->lpData[0] << 16));
				lpShort->dwEvent |= ((DWORD)MEVT_TEMPO << 24);
				lpShort->dwEvent |= MEVT_F_CALLBACK; // コールバックフラグを指定
			}
			else
				lpShort->dwEvent = (DWORD)(MEVT_NOP << 24);
		}
		else if (lpEvent->state == 0xF0) {
			int       j;
			int       nData;
			LPBYTE    lp;
			MIDIEVENT *lpSysEx = (MIDIEVENT *)lpShort;

			nData = ((lpEvent->nData + 3) / 4 ) * 4;
			lpSysEx->dwEvent = MEVT_F_LONG | nData;

			lp = (LPBYTE)lpSysEx->dwParms;

			for (j = 0; j < lpEvent->nData; j++)
				lp[j] = lpEvent->lpData[j];
			
			for (; j < nData; j++)
				lp[j] = 0;
		}
		else {
			lpShort->dwEvent = (DWORD)(lpEvent->state | (lpEvent->data1 << 8) | (lpEvent->data2 << 16));
			lpShort->dwEvent |= MEVT_F_SHORT;
		}
		
		lpBuffer[i].dwBytesRecorded += dwSize;

		lpEvent = lpEvent->lpNext;
	}

	*lplpBuffer = lpBuffer;
}

void PlayMusic(HMIDISTRM hms, LPMIDIHDR lpBuffer, DWORD dwBufferCount)
{
	DWORD           i;
	MIDIPROPTIMEDIV mptv;
	
	mptv.cbStruct  = sizeof(MIDIPROPTIMEDIV);
	mptv.dwTimeDiv = g_wTime;
	midiStreamProperty(hms, (LPBYTE)&mptv, MIDIPROP_TIMEDIV | MIDIPROP_SET);

	for (i = 0; i < dwBufferCount; i++) {
		midiOutPrepareHeader((HMIDIOUT)hms, &lpBuffer[i], sizeof(MIDIHDR));
		midiStreamOut(hms, &lpBuffer[i], sizeof(MIDIHDR));
	}

	midiStreamRestart(hms);
}

void DestroyMusic(HMIDISTRM hms, LPMIDIHDR lpBuffer, LPDWORD lpdwBufferCount)
{
	if (g_bPlayMusic) {
		DWORD i;

		midiStreamStop(hms);
		
		for (i = 0; i < *lpdwBufferCount; i++)
			midiOutUnprepareHeader((HMIDIOUT)hms, &lpBuffer[i], sizeof(MIDIHDR));

		*lpdwBufferCount = 0;
		g_bPlayMusic = FALSE;
	}
	
	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;
	}
}

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

WM_CREATEでSetTimerを呼び出しているため、WM_TIMERが周期的に送られることになります。 midiStreamPositionを呼び出すためには、MMTIME.wTypeに現在位置をどのような形式で 取得するかを指定しなければならないため、TIME_MSを指定しています。 この定数を指定した場合、MMTIME.u.msに現在位置がミリ秒単位で格納されることになります。 よって、1000で割ることで秒単位に変換することができます。

midiStreamPositionにおける現在位置は、ループ再生の際に自動的に初期化されません。 現在位置を初期化するにはmidiStreamStopを呼び出すしかありませんが、 これはどのようなタイミングで呼び出せばよいのでしょうか、 ループ再生は、バッファがアプリケーションに返ってきたときに、 再度バッファを書き込むという方法で成り立っています。 しかし、このような常にバッファを書き込む方法では、 midiStreamStopの呼び出しによってMIDIは停止してしまいます。 よって、既に再生が停止している状態でmidiStreamStopを呼び出し、 それと同時にループ再生の処理を行うことになります。

case MM_MOM_DONE:
	if (dwBufferResetCount == 0) {
		LPMIDIHDR lpMhdr = (LPMIDIHDR)lParam;

		midiOutUnprepareHeader((HMIDIOUT)wParam, lpMhdr, sizeof(MIDIHDR));
		if (lpMhdr->dwUser == dwBufferCount - 1) {
			midiStreamStop(hms); // 再生位置を初期化する
			PlayMusic(hms, lpBuffer, dwBufferCount);
		}
	}
	else // midiStreamStopによってバッファが返ってきたため、バッファ数を減らす
		dwBufferResetCount--;
	return 0;

MIDIHDR.dwUserは、アプリケーションが自由に使ってもよいメンバであり、 今回のプログラムではバッファのインデックスが格納されています。 この値がdwBufferCount - 1であるバッファは、 アプリケーションに返された最後のバッファであることを意味し、 MIDIストリームにはもうバッファが存在していませんから、再生が既に終了しています。 よって、midiStreamStopを呼び出しても再生中の音が停止するようなことはなく、 単純に再生位置のみを初期化することができます。 PlayMusicで全てのバッファを書き込むことで、ループ再生を実現できます。

イベントを監視するための処理は、SetBufferで次のように行われています。

lpShort->dwEvent |= MEVT_F_CALLBACK; // コールバックフラグを指定

この処理は、セットテンポイベントで行われていますから、 セットテンポイベントが処理された場合に、 MM_MOM_POSITIONCBが送られることになります。

case MM_MOM_POSITIONCB: {
	TCHAR        szBuf[256];
	LPMIDIHDR    lpMhdr;
	LPSHORTEVENT lpShort;
	
	lpMhdr = (LPMIDIHDR)lParam;
	lpShort = (LPSHORTEVENT)&lpMhdr->lpData[lpMhdr->dwOffset];

	wsprintf(szBuf, TEXT("Tempo %d"), MEVT_EVENTPARM(lpShort->dwEvent));
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	return 0;
}

実際にイベントの値を参照する場合は、MEVT_EVENTPARMを利用することになります。 このマクロを指定すれば、最上位バイトに設定されたMEVT_F_CALLBACKが取り除かれることになるため、 本来の値のみを参照することができます。


戻る