EternalWindows
マルチメディア入力 / RMIの作成

多くのマルチメディアファイルがRIFF形式を採用すれば、 それだけ各種ファイルを統一的に扱いやすくなります。 こうした考えのもとで登場したファイルとして、RMIファイルがあります。 このファイル形式は、いわばMIDIファイルをRIFF形式にしたものであり、 dataチャンクにMIDIファイルの全データを格納し、 タイトルなどの情報を他のチャンクに格納するようにしています。 次にRMIファイルにおけるチャンク構造を示します。

RMIファイルにおけるRIFFチャンクのフォームタイプは、RMIDとなります。 LISTチャンクとは、内部に複数のチャンクを格納できるチャンクのことです。 LISTチャンクはリストタイプを持ち、これはLISTチャンクがどのような系統のチャンクを格納しているかを表します。 RMIファイルの場合はINFOとなります。 つまり、タイトル(ISBJ)や著作権(IOCP)といった情報をを表すチャンクが格納されます。 ただし、タイトルや著作権については、MIDIデータ内のメタイベントとして存在させることもできます。 リストタイプがINFOであるLISTチャンクはINFOチャンクと呼ばれることがありますが、 これは便宜上の呼び方であり、実際に存在しているのはLISTチャンクです。

MIDIファイルをRMIファイルに変換することは、RIFF形式のファイルを作成することを意味します。 このため、mmioCreateChunkを呼び出してチャンクを作成する必要があります。

MMRESULT mmioCreateChunk(
  HMMIO hmmio,      
  LPMMCKINFO lpck,  
  UINT wFlags       
);

hmmioは、マルチメディアファイル入出力のハンドルを指定します。 lpckは、MMCKINFO構造体のアドレスを指定します。 この構造体のfccTypeまたは、ckidは適切に初期化されている必要があります。 wFlagsは、RIFFチャンクを作成する場合にMMIO_CREATERIFF、 LISTチャンクを作成する場合にMMIO_CREATELISTを指定します。 それ以外のチャンクを作成する場合は、0を指定します。

mmioCreateChunkの呼び出しによって、ファイル位置は作成されたチャンクへ移動します。 後はmmioWriteを呼び出せば、そのチャンクにデータを書き込むことができます。

LONG mmioWrite(
  HMMIO hmmio,      
  char _huge* pch,  
  LONG cch          
);

hmmioは、マルチメディアファイル入出力のハンドルを指定します。 pchは、書き込みたいデータを指定します。 cchは、pchに指定したデータのサイズを指定します。

今回のプログラムは、既存のMIDIファイルからデータを取得し、 それを新しく作成したRMIファイルのdataチャンクに格納します。 また、LISTチャンクを作成し、その下にICOPチャンクとISBJチャンクを作成します。

#include <windows.h>

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

BOOL GetMidiFile(LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize);
BOOL CreateRmiFile(LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	LPBYTE lpData;
	DWORD  dwDataSize;

	if (!GetMidiFile(TEXT("sample.mid"), &lpData, &dwDataSize)) {
		MessageBox(NULL, TEXT("MIDIファイルのデータの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (CreateRmiFile(TEXT("sample.rmi"), lpData, dwDataSize))
		MessageBox(NULL, TEXT("RMIファイルを作成しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("RMIファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

	HeapFree(GetProcessHeap(), 0, lpData);

	return 0;
}

BOOL GetMidiFile(LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	HMMIO  hmmio;
	LPBYTE lpData;
	DWORD  dwFileSize;
	
	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL)
		return FALSE;

	dwFileSize = mmioSeek(hmmio, 0, SEEK_END);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwFileSize);
	mmioSeek(hmmio, 0, SEEK_SET);
	mmioRead(hmmio, (HPSTR)lpData, dwFileSize);

	mmioClose(hmmio, 0);

	*lplpData = lpData;
	*lpdwDataSize = dwFileSize;

	return TRUE;
}

BOOL CreateRmiFile(LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckData;
	MMCKINFO mmckInfo;
	MMCKINFO mmck;
	int      i;
	int      nChunk = 2;
	LPSTR    lpszData[] = {"著作権", "タイトル"};
	LPTSTR   lpszChunk[] = {TEXT("ICOP"), TEXT("ISBJ")};

	hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
	if (hmmio == NULL)
		return FALSE;

	mmckRiff.fccType = mmioStringToFOURCC(TEXT("RMID"), 0);
	mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);

	mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
	mmioCreateChunk(hmmio, &mmckData, 0);
	mmioWrite(hmmio, (char *)lpData, dwDataSize);
	mmioAscend(hmmio, &mmckData, 0);
	
	mmckInfo.fccType = mmioStringToFOURCC(TEXT("INFO"), 0);
	mmioCreateChunk(hmmio, &mmckInfo, MMIO_CREATELIST);
	for (i = 0; i < nChunk; i++) {
		mmck.ckid = mmioStringToFOURCC(lpszChunk[i], 0);
		mmioCreateChunk(hmmio, &mmck, 0);
		mmioWrite(hmmio, (char *)lpszData[i], lstrlenA(lpszData[i]) + 1);
		mmioAscend(hmmio, &mmck, 0);
	}

	mmioAscend(hmmio, &mmckInfo, 0);
	mmioAscend(hmmio, &mmckRiff, 0);
	mmioClose(hmmio, 0);

	return TRUE;
}

既存のMIDIファイルからデータを取得しているのは、GetMidiFileという自作関数です。 この関数では、mmioSeekにSEEK_ENDを指定してファイル全体のサイズを取得し、 その分のメモリを確保してからファイル全体をmmioReadで読み取ります。 mmioReadの呼び出しの前に、ファイル位置をSEEK_SETに戻しておく点が重要です。 取得したデータとサイズは呼び出し側へと返され、 RMIファイルを作成するCreateRmiFileに指定されます。

hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
if (hmmio == NULL)
	return FALSE;

mmckRiff.fccType = mmioStringToFOURCC(TEXT("RMID"), 0);
mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);

mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
mmioCreateChunk(hmmio, &mmckData, 0);
mmioWrite(hmmio, (char *)lpData, dwDataSize);
mmioAscend(hmmio, &mmckData, 0);

新しくファイルを作成するため、mmioOpenにMMIO_CREATEとMMIO_WRITEを指定します。 RMIファイルはRIFF形式であるため、まず最初にすべきことはRIFFチャンクの作成です。 これは、mmioCreateChunkにMMIO_CREATERIFFを指定することで可能です。 このとき、RMIファイルであることを示すために、 フォームタイプにRMIDを指定している点が重要です。 次に、チャンクIDにdataを指定してdataチャンクを作成します。 RIFFやLIST以外のチャンクを作成する場合は、mmioCreateChunkの第3引数が0になります。 mmioWriteにMIDIファイルから取得したデータを指定することで、 dataチャンクにデータが書き込まれることになります。 次に作成するLISTチャンクはdataチャンクの下ではなく、 RIFFチャンクの下に位置する必要があるため、dataチャンクからアセンドしておきます。

mmckInfo.fccType = mmioStringToFOURCC(TEXT("INFO"), 0);
mmioCreateChunk(hmmio, &mmckInfo, MMIO_CREATELIST);
for (i = 0; i < nChunk; i++) {
	mmck.ckid = mmioStringToFOURCC(lpszChunk[i], 0);
	mmioCreateChunk(hmmio, &mmck, 0);
	mmioWrite(hmmio, (char *)lpszData[i], lstrlenA(lpszData[i]) + 1);
	mmioAscend(hmmio, &mmck, 0);
}

LISTチャンクを作成するには、mmioCreateChunkにMMIO_CREATELISTを指定します。 今回のように情報について格納する場合は、リストタイプがINFOとなります。 FOURCC_LISTという専用の定数を指定しても構いません。 ループ文では、配列として宣言されていた数だけチャンクを作成し、データを書き込んでいきます。

情報の表示

次のコードは、RMIファイルのLISTチャンクにどれだけの情報が存在するかを表示します。

#include <windows.h>

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

HMMIO OpenRmiFile(LPTSTR lpszFileName);
BOOL ReadInfo(HMMIO hmmio, LPTSTR lpszChunk, LPSTR *lplpszData);
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 HWND hwndListBoxLeft = NULL;
	static HWND hwndListBoxRight = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		int    i;
		int    nCount = 23;
		HMMIO  hmmio;
		LONG   lOffset;
		LPSTR  lpszData;
		LPTSTR lpszInfo[] = {
			TEXT("IARL"), // アーカイブの場所
			TEXT("IART"), // アーティスト
			TEXT("ICMS"), // コミッション
			TEXT("ICMT"), // コメント
			TEXT("ICOP"), // 著作権
			TEXT("ICRD"), // 作成日
			TEXT("ICRP"), // クロップ
			TEXT("IDIM"), // 大きさ
			TEXT("IDPI"), // ドット/インチ
			TEXT("IENG"), // エンジニア
			TEXT("IGNR"), // ジャンル
			TEXT("IKEY"), // キーワード
			TEXT("ILGT"), // 明るさ
			TEXT("IMED"), // 中間
			TEXT("INAM"), // 名前
			TEXT("IPLT"), // パレットの設定
			TEXT("IPRD"), // 製品
			TEXT("ISBJ"), // タイトル
			TEXT("ISFT"), // ソフトウェア
			TEXT("ISHP"), // 鮮明度
			TEXT("ISRC"), // ソース
			TEXT("ISRF"), // ソースの形式
			TEXT("ITCH"), // 技術者
		};
		
		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		hmmio = OpenRmiFile(TEXT("sample.rmi"));
		if (hmmio == NULL)
			return -1;

		lOffset = mmioSeek(hmmio, 0, SEEK_CUR);

		for (i = 0; i < nCount; i++)
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)lpszInfo[i]);
		
		for (i = 0; i < nCount; i++) {
			if (ReadInfo(hmmio, lpszInfo[i], &lpszData)) {
				SendMessageA(hwndListBoxRight, LB_INSERTSTRING, i, (LPARAM)lpszData);
				HeapFree(GetProcessHeap(), 0, lpszData);
			}
			else {
				SendMessage(hwndListBoxRight, LB_INSERTSTRING, i, (LPARAM)"");
				mmioSeek(hmmio, lOffset, SEEK_SET);
			}
		}

		mmioClose(hmmio, 0);

		return 0;
	}


	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

HMMIO OpenRmiFile(LPTSTR lpszFileName)
{
	HMMIO    hmmio;
	MMCKINFO mmckRiff;
	MMCKINFO mmckList;

	hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	mmckRiff.fccType = mmioStringToFOURCC(TEXT("RMID"), 0);
	if (mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR) {
		MessageBox(NULL, TEXT("RMIファイルではありません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}
	
	mmckList.fccType = mmioStringToFOURCC(TEXT("INFO"), 0);
	if (mmioDescend(hmmio, &mmckList, NULL, MMIO_FINDLIST) != MMSYSERR_NOERROR) {
		MessageBox(NULL, TEXT("LISTチャンクが存在しません。"), NULL, MB_ICONWARNING);
		mmioClose(hmmio, 0);
		return FALSE;
	}

	return hmmio;
}

BOOL ReadInfo(HMMIO hmmio, LPTSTR lpszChunk, LPSTR *lplpszData)
{
	MMCKINFO mmck;

	mmck.ckid = mmioStringToFOURCC(lpszChunk, 0);
	if (mmioDescend(hmmio, &mmck, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR) 
		return FALSE;

	*lplpszData = (LPSTR)HeapAlloc(GetProcessHeap(), 0, mmck.cksize);
	mmioRead(hmmio, (HPSTR)*lplpszData, mmck.cksize);
	
	mmioAscend(hmmio, &mmck, 0);
	
	return TRUE;
}

lpszInfoは、存在するかもしれないチャンクの名前を配列として定義しています。 この1つずつに対してディセンドし、成功すれば格納されているデータを表示するという仕組みになります。 まず、自作関数のOpenRmiFileを呼び出して、 マルチメディアファイル入出力のハンドルを取得します。 この時点で、既にLISTチャンクにはディセンドしています。 次に、mmioSeekで現在の位置を保存します。 これは、存在しないチャンクに対してディセンドして失敗した場合、 ファイル位置が不正になるという問題に対処するためです。 自作関数のReadInfoは、指定されたチャンクからデータを取得しますが、 これが失敗した場合はmmioSeekで保存された位置へシークします。



戻る