/*
 *	main.c
 *
 *	modsmpl - Mod Plug Tracker サンプル周波数変換ツール
 *	Copyright (C) 2003 Naoyuki Sawa <nsawa@north.hokkai.net>
 *
 *	* Sun Feb 02 19:41:00 JST 2003 Naoyuki Sawa
 *	- 作成開始。
 *	* Sun Feb 02 22:17:00 JST 2003 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Feb 03 05:31:00 JST 2003 Naoyuki Sawa
 *	- 出力ファイル名省略時に、楽器名を出力ファイル名とする機能追加。
 *	* Sun Feb 03 12:30:00 JST 2003 Naoyuki Sawa
 *	- 出力周波数可変等のオプション追加。
 *	* Sun Feb 03 20:00:00 JST 2003 Naoyuki Sawa
 *	- 楽器名からファイル名を作るとき、「.」などを「_」に置き換えるよう修正。
 *	* Sun Feb 03 21:00:00 JST 2003 Naoyuki Sawa
 *	- エクステンドオクターブ(c-0〜b-0、c-4〜b-4)に対応しました。
 */
#define STRICT
#include <windows.h>
#include <mmreg.h>
#include <msacm.h>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "msacm32.lib")

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <conio.h>

#define VERSION	"20030203c"

/****************************************************************************
 *
 ****************************************************************************/

#if 0 //この情報はもう古いです。ファイル名指定など、少し変わりました。
使い方：
１．Mod Plug Trackerを起動します。
２．[File]→[New]→[IT]で空のITファイルを作成します。
３．「Samples」タブをクリックして、Samples画面にします。
４．左上ペインの「GM.DLS」から、音色をSamplesにドロップします。
　　または、任意の音色ファイルを開きます。
５．Samples画面の左上にある保存アイコンで、音色をWAVファイルに保存します。
６．Mod Plug Trackerを終了します。
７．この時点では、WAVファイルの周波数はまちまちで、
　　そのままMODファイルの音色として使うと、音程がずれてしまいます。
８．そこで、このツールを使って、WAVファイルをC-2の周波数に変換します。
９．コマンドラインから、

　　　　modsmpl さきほど保存したファイル.wav [変換先のファイル.wav]

　　のようにタイプしてください。
　　（変換先のファイル名を省略すると、WAVファイル内の楽器名を使用します）
10．Mod Plug Trackerを起動します。
11．[File]→[New]→[MOD]で空のMODファイルを作成します。
　　または、既存のMODファイルを開きます。
12．「Samples」タブをクリックして、Samples画面にします。
13．さきほど変換したファイルを、Sample画面にドロップします。

以上です。元の音色がループ情報を含んでいたなら、ループ情報も復元されます。
#endif

/****************************************************************************
 *
 ****************************************************************************/

#if 0
Mod Plug TrackerでサンプルをWAV保存したときに、
WAVファイル内に生成されるsmplチャンクの内容です。
このチャンクがあれば、TrackerはWAVファイルからループ情報を復元します。

-----------------------------------------------------------------------------
ループなし時のsaplチャンク内容：
チャンクサイズは、0x24バイトです。

0   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
1  00 00 00 00 00 00 00 00 E9 EE 02 00 3C 00 00 00  ... (不明、0クリアしても問題ないようです)
2  00 00 00 00 00 00 00 00 00 00 00 00[00 00 00 00]  ... ループなし
3  00 00 00 00

-----------------------------------------------------------------------------
ループあり時のsaplチャンク内容：
チャンクサイズは、0x3cバイトです。

    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0  00 00 00 00 00 00 00 00 D5 DE 00 00 3C 00 00 00  ... (不明、0クリアしても問題ないようです)
1  00 00 00 00 00 00 00 00 00 00 00 00[01 00 00 00] ... ループあり
2  00 00 00 00 00 00 00 00 00 00 00 00[33 00 00 00] ... ループ開始位置
3 [76 00 00 00]00 00 00 00 00 00 00 00              ... ループ終了位置

-----------------------------------------------------------------------------
#endif

/****************************************************************************
 *
 ****************************************************************************/

/* smplチャンクの構造体 */
typedef struct _SMPL {
	int unk1[7];
	int loop;	/* 0:ループなし/1:ループあり */
	int unk2[3];	/* ループなし時はunk2[0]までで終了 */
	int start;	/* ループ開始位置 */
	int end;	/* ループ終了位置 */
	int unk3[2];
} SMPL;

typedef struct _WAV {
	/* --- RIFF.WAVE --- */
	/* fmt */
	int c_fmt;
	WAVEFORMATEX* fmt;
	/* data */
	int c_data;
	unsigned char* data;
	/* smpl */
	int c_smpl;
	SMPL* smpl;
	/* --- LIST.INFO --- */
	/* INAM */
	int c_inam;
	char* inam;	/* 楽器名 */
	/* ISFT */	/* ソフト名(ModPlugTracker) */
	/* xtra */	/* ？？？ */
} WAV;

/* 音程名と対応するピリオド値 */
char* note_name_tbl[] = {
"c-0","c#0","d-0","d#0","e-0","f-0","f#0","g-0","g#0","a-0","a#0","b-0",
"c-1","c#1","d-1","d#1","e-1","f-1","f#1","g-1","g#1","a-1","a#1","b-1",
"c-2","c#2","d-2","d#2","e-2","f-2","f#2","g-2","g#2","a-2","a#2","b-2",
"c-3","c#3","d-3","d#3","e-3","f-3","f#3","g-3","g#3","a-3","a#3","b-3",
"c-4","c#4","d-4","d#4","e-4","f-4","f#4","g-4","g#4","a-4","a#4","b-4",
NULL
};
int period_tbl[] = {
1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,906,
 856, 808, 762, 720, 678, 640, 604, 570, 538, 508,480,453,
 428, 404, 381, 360, 339, 320, 302, 285, 269, 254,240,226,
 214, 202, 190, 180, 170, 160, 151, 143, 135, 127,120,113,
 107, 101,  95,  90,  85,  80,  75,  71,  67,  63, 60, 56,
0
};

/* AmigaのPAL基本クロック = 7093789.2
 * C-2のピリオド値 = 428 (MOD仕様)
 * 出力周波数 = 基本クロック / (ピリオド値 * 2)
 *            = 7093789.2 / (428 * 2)
 * ※出力音程をC-2とした場合の例です。
 */
#define AMIGA_FREQ	7093789.2
#define MOD_BITS	8
#define MOD_CHANS	1

void die(const char* fmt, ...);
WAV* load_wav(const unsigned char* fname);
void save_wav(const WAV* wav, const unsigned char* fname);
void wav_to_wav(WAV* wav1, WAV* wav2);

WAV* wav1;
WAV* wav2;
int mod_period;	/* 実行時にnote_nameから計算 */
int mod_freq;	/* 実行時にmod_periodから計算 */

/* コマンドライン指定可能なパラメータの初期値 */
char in_path[_MAX_PATH];	/* 入力ファイル名 */
char out_path[_MAX_PATH];	/* 出力ファイル名 */
char note_name[256] = "c-2";	/* 出力音程 */
FILE* msg = stdout;		/* メッセージ出力先 */
int overwrite = 0;		/* 問い合わせなしで上書き */

/****************************************************************************
 *	usage
 ****************************************************************************/

void
usage()
{
	fprintf(stderr,
"modsmpl - Mod Plug Tracker サンプル周波数変換ツール ("VERSION")\n"
"Copyright (C) 2003 Naoyuki Sawa <nsawa@north.hokkai.net>\n"
"\n"
"USAGE:\n"
"    modsmpl [options] <filename[.wav]>\n"
"\n"
"OPTIONS:\n"
"    -o<fname>  出力ファイル名\n"
"               出力ファイル名を指定しなければ、「楽器名.wav」となります。\n"
"    -n<note>   出力音程（c-0,c#0,d-0,...b-4）\n"
"               出力音程を指定しなければ、「c-2」となります。\n"
"               Tracker上でこの音程で発声した時、元の音と同じ高さになります。\n"
"               出力音程を低くするほど、Tracker上では高い音に対応できます。\n"
"               出力音程を高くするほど、Tracker上では低い音に対応できます。\n"
"    -f         出力先ファイルが存在しても、問い合わせなしで上書きします。\n"
"    -q         メッセージ抑制\n"
	);
	exit(1);
}

/****************************************************************************
 *	cmdline
 ****************************************************************************/

void
cmdline(int argc, char* argv[])
{
	int i;
	char* opt;
	char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];

	for(i = 1; i < argc; i++) {
		opt = argv[i];
		if(opt[0] != '-') {
			/* 入力ファイル名 */
			if(strlen(in_path) != 0) usage(); /* 二重指定 */
			strcpy(in_path, opt);
		} else {
			switch(opt[1]) {
			/* 出力ファイル名 */
			case 'o':
				if(strlen(out_path) != 0) usage(); /* 二重指定 */
				strcpy(out_path, &opt[2]);
				break;
			/* 出力音程 */
			case 'n':
				strcpy(note_name, &opt[2]);
				break;
			/* 問い合わせなしで上書き */
			case 'f':
				overwrite = 1;
				break;
			/* メッセージ抑制 */
			case 'q':
				msg = fopen("NUL", "wt");
				break;
			default:
				usage();
			}
		}
	}

	/* 入力ファイル名は必須です。 */
	if(strlen(in_path) == 0) usage();

	/* 入力ファイル名の拡張子が省略されていたら、「.wav」とします。 */
	_splitpath(in_path, drive, dir, fname, ext);
	if(strlen(ext) == 0) {
		_makepath(in_path, drive, dir, fname, ".wav");
	}
}

/****************************************************************************
 *
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	int len, i;
	char* p;
	char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
	struct _stat st;

	/* コマンドライン解析。 */
	cmdline(argc, argv);

	/* 出力音程からピリオド値を求めます。 */
	i = 0;
	for(;;) {
		if(note_name_tbl[i] == NULL) die("出力音程が不正です。");
		if(strcmpi(note_name_tbl[i], note_name) == 0) {
			mod_period = period_tbl[i];
			break;
		}
		i++;
	}
	/* ピリオド値から出力周波数を求めます。 */
	mod_freq = (int)(AMIGA_FREQ / (mod_period * 2));

	/* WAVファイル読み込み。 */
	/*      ０１２３４５６７ */
	wav1 = load_wav(in_path);

	/* 変換先のフォーマットを作成します。
	 * （Mod Plug Trackerの出力形式は必ずリニアPCM形式なので、二段階変換は考慮しません）
	 */
	if(wav1->fmt->wFormatTag != WAVE_FORMAT_PCM) die("入力形式がWAVE_FORMAT_PCMでありません。");
	wav2 = (WAV*)calloc(sizeof(WAV), 1);
	if(wav2 == NULL) die("メモリ不足です。");
	wav2->c_fmt = wav1->c_fmt;
	wav2->fmt = (WAVEFORMATEX*)calloc(1, wav2->c_fmt);
	if(wav2->fmt == NULL) die("メモリ不足です。");
	memcpy(wav2->fmt, wav1->fmt, wav2->c_fmt);
	wav2->fmt->nChannels = MOD_CHANS;
	wav2->fmt->nSamplesPerSec = mod_freq;
	wav2->fmt->nAvgBytesPerSec = mod_freq * (MOD_BITS / 8);
	wav2->fmt->nBlockAlign = (MOD_BITS / 8) * MOD_CHANS;
	wav2->fmt->wBitsPerSample = MOD_BITS;

	/* 変換実行。 */
	wav_to_wav(wav1, wav2);

	/* mmio＊APIを使ってWAVファイルを作成すると、各チャンクが奇数バイトの場合、後ろにゼロがパディングされるようです。
	 * mmio＊APIを使って読み込めば、このゼロはちゃんと読み飛ばされるのですが、Mod Plug Trackerはmmio＊APIを使っていないようで、
	 * パディングの後ろのチャンクが読み込まれません。
	 * これが問題になるのは、出力サンプル数が奇数の場合、dataチャンクが奇数サイズになって、その後のsmplチャンクとの間に
	 * 1バイトのパディングが生成されてしまう場合です。
	 * Mod Plug Trackerはパディングの後ろのチャンクを認識できず、smplチャンクがないものとみなし、ループ情報が失われてしまいます。
	 * この問題を避けるため、出力サンプル数が奇数の場合は偶数に切り捨てて、dataチャンクを偶数サイズに保つようにします。
	 */
	wav2->c_data &= ~1;

	/* 変換先のサンプル情報を作成します。 */
	if(wav1->c_smpl >= sizeof(SMPL) && wav1->smpl->loop) { /* 入力ファイルがループ音の場合のみ、smplチャンクを出力 */
		wav2->c_smpl = wav1->c_smpl;
		wav2->smpl = (SMPL*)calloc(1, wav2->c_smpl);
		if(wav2->smpl == NULL) die("メモリ不足です。");
		memcpy(wav2->smpl, wav1->smpl, wav2->c_smpl);
		wav2->smpl->start = wav1->smpl->start * wav2->fmt->nSamplesPerSec / wav1->fmt->nSamplesPerSec;
		wav2->smpl->end   = wav1->smpl->end   * wav2->fmt->nSamplesPerSec / wav1->fmt->nSamplesPerSec;
		len = wav2->c_data / ((MOD_BITS / 8) * MOD_CHANS);
		if(wav2->smpl->start > len) wav2->smpl->start = len; /* ループ位置が終端を超えないように */
		if(wav2->smpl->end   > len) wav2->smpl->end   = len;
	}

	/* 出力ファイル名が省略されていたら、楽器名を使います。 */
	if(strlen(out_path) == 0) {
		if(wav1->c_inam == 0) die("楽器名がありません。出力ファイル名を指定してください。");
		memset(fname, 0, sizeof fname);
		strncpy(fname, wav1->inam, wav1->c_inam);
		p = fname;
		while(isspace(*p)) p++; /* 左トリム */
		i = strlen(p) - 1;      /* 右トリム */
		while(i >= 0) {
			if(!isspace(p[i])) break;
			p[i--] = '\0';
		}
		if(i < 0) die("楽器名が空です。出力ファイル名を指定してください。");
		strncpy(out_path, p, sizeof out_path);
		/* 楽器名にアルファベットと数字以外が含まれていたら、「_」に置き換えます。 */
		for(p = out_path; *p != '\0'; p++) {
			if(!isalnum(*p)) *p = '_';
		}
	}
	/* 出力ファイル名の拡張子が省略されていたら、「.wav」とします。 */
	_splitpath(out_path, drive, dir, fname, ext);
	if(strlen(ext) == 0) {
		_makepath(out_path, drive, dir, fname, ".wav");
	}

	/* 情報表示。 */
	fprintf(msg, "入力ファイル　　：%s\n", in_path);
	fprintf(msg, "入力周波数　　　：%d\n", wav1->fmt->nSamplesPerSec);
	if(wav1->c_inam != 0) {
	fprintf(msg, "楽器名：　　　　：「%s」\n", wav1->inam);
	} else {
	fprintf(msg, "楽器名：　　　　：なし\n");
	}
	if(wav1->c_smpl >= sizeof(SMPL) && wav1->smpl->loop) {
	fprintf(msg, "入力ループ位置　：(%d,%d)\n", wav1->smpl->start, wav1->smpl->end);
	} else {
	fprintf(msg, "入力ループ位置　：なし\n");
	}
	fprintf(msg, "\n");
	fprintf(msg, "出力ファイル　　：%s\n", out_path);
	fprintf(msg, "出力音程　　　　：%s\n", note_name);
	fprintf(msg, "ピリオド値　　　：%d\n", mod_period);
	fprintf(msg, "出力周波数　　　：%d\n", mod_freq);
	if(wav2->c_smpl >= sizeof(SMPL) && wav2->smpl->loop) {
	fprintf(msg, "出力ループ位置　：(%d,%d)\n", wav2->smpl->start, wav2->smpl->end);
	} else {
	fprintf(msg, "出力ループ位置　：なし\n");
	}
	fprintf(msg, "\n");

	/* 上書き検査。 */
	if(!overwrite && _stat(out_path, &st) == 0) {
		fprintf(stderr, "出力先のファイルが既に存在します。上書きしますか？[y/N] ");
		/* 超要注意！「_tolower()」を使うと、_tolower('y')が'y'になりません。なぜ！？
		 *           「tolower()」を使うこと！
		 */
		if(tolower(getche()) != 'y') die("中断しました。");
		fprintf(stderr, "\n");
	}

	/* WAVファイル書き込み。 */
	save_wav(wav2, out_path);

	printf("正常終了しました。\n");
	return 0;
}

/****************************************************************************
 *	die
 ****************************************************************************/

void
die(const char* fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	 fprintf(stderr, "\n### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);
	exit(1);
}

/****************************************************************************
 *
 ****************************************************************************/

WAV*
load_wav(const unsigned char* fname)
{
	int retval;
	WAV* wav;
	HMMIO mmio;
	MMCKINFO ck1, ck2, ck3;

	/* WAV構造体を作成します。 */
	wav = (WAV*)calloc(sizeof(WAV), 1);
	if(wav == NULL) die("メモリ不足です。");

	/* WAVファイルを開きます。 */
	mmio = mmioOpen((char*)fname, NULL, MMIO_READ);
	if(mmio == NULL) die("%sが開けません。", fname);

	/* RIFF:WAVEチャンクに入ります。 */
	memset(&ck1, 0, sizeof(MMCKINFO));
	ck1.ckid    = mmioStringToFOURCC("RIFF", 0); /* MMIO_FINDRIFF時は、ckifの指定は省略可能かも */
	ck1.fccType = mmioStringToFOURCC("WAVE", 0);
	retval = mmioDescend(mmio, &ck1, NULL, MMIO_FINDRIFF);
	if(retval != 0) die("RIFFチャンクが見つかりません。");

	/* fmtチャンクに入ります。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid = mmioStringToFOURCC("fmt", 0);
	retval = mmioDescend(mmio, &ck2, &ck1, MMIO_FINDCHUNK);
	if(retval != 0) die("fmtチャンクが見つかりません。");

	/* フォーマットを読み込みます。 */
	wav->c_fmt = ck2.cksize;
	wav->fmt = (WAVEFORMATEX*)calloc(wav->c_fmt, 1);
	if(wav->fmt == NULL) die("メモリ不足です。");
	retval = mmioRead(mmio, (char*)wav->fmt, wav->c_fmt);
	if(retval != (int)wav->c_fmt) die("フォーマットが読み込めません。");

	/* fmtチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck2, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* dataチャンクに入ります。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid = mmioStringToFOURCC("data", 0);
	retval = mmioDescend(mmio, &ck2, &ck1, MMIO_FINDCHUNK);
	if(retval != 0) die("dataチャンクが見つかりません。");

	/* データを読み込みます。 */
	wav->c_data = ck2.cksize;
	wav->data = (char*)calloc(wav->c_data, 1);
	if(wav->data == NULL) die("メモリ不足です。");
	retval = mmioRead(mmio, wav->data, wav->c_data);
	if(retval != wav->c_data) die("データが読み込めません。");

	/* dataチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck2, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* smplチャンクに入ります。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid = mmioStringToFOURCC("smpl", 0);
	retval = mmioDescend(mmio, &ck2, &ck1, MMIO_FINDCHUNK);
	if(retval == 0) { /* なければ読まない */
		/* サンプル情報を読み込みます。 */
		wav->c_smpl = ck2.cksize;
		wav->smpl = (SMPL*)calloc(wav->c_smpl, 1);
		if(wav->smpl == NULL) die("メモリ不足です。");
		retval = mmioRead(mmio, (char*)wav->smpl, wav->c_smpl);
		if(retval != wav->c_smpl) die("サンプル情報が読み込めません。");

		/* smplチャンクを抜けます。 */
		retval = mmioAscend(mmio, &ck2, 0);
		if(retval != 0) die("mmioAscend()が失敗しました。");
	}

	/* LIST:INFOチャンクに入ります。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid    = mmioStringToFOURCC("LIST", 0); /* MMIO_FINDLIST時は、ckifの指定は省略可能かも */
	ck2.fccType = mmioStringToFOURCC("INFO", 0);
	retval = mmioDescend(mmio, &ck2, NULL, MMIO_FINDLIST);
	if(retval == 0) { /* なければ読まない */
		/* INAMチャンクに入ります。 */
		memset(&ck3, 0, sizeof(MMCKINFO));
		ck3.ckid = mmioStringToFOURCC("INAM", 0);
		retval = mmioDescend(mmio, &ck3, &ck1, MMIO_FINDCHUNK);
		if(retval == 0) { /* なければ読まない */
			/* 楽器名を読み込みます。 */
			wav->c_inam = ck3.cksize;
			wav->inam = (char*)calloc(wav->c_inam, 1);
			if(wav->inam == NULL) die("メモリ不足です。");
			retval = mmioRead(mmio, wav->inam, wav->c_inam);
			if(retval != wav->c_inam) die("楽器名が読み込めません。");

			/* INAMチャンクを抜けます。 */
			retval = mmioAscend(mmio, &ck3, 0);
			if(retval != 0) die("mmioAscend()が失敗しました。");
		}
		/* LISTチャンクを抜けます。 */
		retval = mmioAscend(mmio, &ck2, 0);
		if(retval != 0) die("mmioAscend()が失敗しました。");
	}

	/* RIFFチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck1, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* WAVファイルを閉じます。 */
	retval = mmioClose(mmio, 0);
	if(retval != 0) die("mmioClose()が失敗しました。");

	return wav;
}

/****************************************************************************
 *
 ****************************************************************************/

void
save_wav(const WAV* wav, const unsigned char* fname)
{
	int retval;
	HMMIO mmio;
	MMCKINFO ck1, ck2;

	/* WAVファイルを作成します。 */
	mmio = mmioOpen((char*)fname, NULL, MMIO_CREATE | MMIO_WRITE); /* MMIO_WRITE必須！MMIO_CREATEだけではチャンク作成時にエラーになります */
	if(mmio == NULL) die("%sが作成できません。", fname);

	/* RIFF:WAVEチャンクを作成します。 */
	memset(&ck1, 0, sizeof(MMCKINFO));
	ck1.ckid    = mmioStringToFOURCC("RIFF", 0);
	ck1.fccType = mmioStringToFOURCC("WAVE", 0);
	retval = mmioCreateChunk(mmio, &ck1, MMIO_CREATERIFF);
	if(retval != 0) die("RIFFチャンクが作成できません。");

	/* fmtチャンクを作成します。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid = mmioStringToFOURCC("fmt", 0);
	retval = mmioCreateChunk(mmio, &ck2, 0);
	if(retval != 0) die("fmtチャンクが作成できません。");

	/* フォーマットを書き込みます。 */
	retval = mmioWrite(mmio, (char*)wav->fmt, wav->c_fmt);
	if(retval != (int)wav->c_fmt) die("フォーマットが書き込めません。");

	/* fmtチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck2, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* dataチャンクを作成します。 */
	memset(&ck2, 0, sizeof(MMCKINFO));
	ck2.ckid = mmioStringToFOURCC("data", 0);
	retval = mmioCreateChunk(mmio, &ck2, 0);
	if(retval != 0) die("dataチャンクが作成できません。");

	/* データを書き込みます。 */
	retval = mmioWrite(mmio, (char*)wav->data, wav->c_data);
	if(retval != (int)wav->c_data) die("データが書き込めません。");

	/* dataチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck2, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* ループありの場合のみ、smplチャンクｗ作成します。 */
	if(wav->c_smpl != 0) {
		/* smplチャンクを作成します。 */
		memset(&ck2, 0, sizeof(MMCKINFO));
		ck2.ckid = mmioStringToFOURCC("smpl", 0);
		retval = mmioCreateChunk(mmio, &ck2, 0);
		if(retval != 0) die("smplチャンクが作成できません。");

		/* サンプル情報を書き込みます。 */
		retval = mmioWrite(mmio, (char*)wav->smpl, wav->c_smpl);
		if(retval != (int)wav->c_smpl) die("サンプル情報が書き込めません。");

		/* smplチャンクを抜けます。 */
		retval = mmioAscend(mmio, &ck2, 0);
		if(retval != 0) die("mmioAscend()が失敗しました。");
	}

	/* RIFFチャンクを抜けます。 */
	retval = mmioAscend(mmio, &ck1, 0);
	if(retval != 0) die("mmioAscend()が失敗しました。");

	/* WAVファイルを閉じます。 */
	retval = mmioClose(mmio, 0);
	if(retval != 0) die("mmioClose()が失敗しました。");
}

/****************************************************************************
 *	wav_to_wav
 ****************************************************************************/

#define CONVERT_UNIT	65536	/* 何バイトづつ変換するか？ */

void
wav_to_wav(WAV* wav1, WAV* wav2)
{
	int retval, unit1, unit2, flag, ofs, len, cap;
	HACMSTREAM acm;
	ACMSTREAMHEADER stream;

	/* ACMストリームを開きます。 */
	retval = acmStreamOpen(&acm, NULL, wav1->fmt, wav2->fmt, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
	if(retval != 0) die("このWAVファイルは変換できません。");

	/* ストリームバッファを作成します。 */
	unit1 = (CONVERT_UNIT + (wav1->fmt->nBlockAlign - 1)) / wav1->fmt->nBlockAlign * wav1->fmt->nBlockAlign;
	retval = acmStreamSize(acm, unit1, &unit2, ACM_STREAMSIZEF_SOURCE);
	if(retval != 0) die("acmStreamSize()が失敗しました。");
	memset(&stream, 0, sizeof stream);
	stream.cbStruct = sizeof stream;
	stream.pbSrc = (unsigned char*)calloc(unit1, 1);
	stream.cbSrcLength = unit1;
	stream.pbDst = (unsigned char*)calloc(unit2, 1);
	stream.cbDstLength = unit2;
	if(stream.pbSrc == NULL || stream.pbDst == NULL) die("メモリ不足です。");
	retval = acmStreamPrepareHeader(acm, &stream, 0);
	if(retval != 0) die("acmStreamPrepareHeader()が失敗しました。");

	/* 変換ループ。 */
	ofs = 0;
	cap = 0;
	while(ofs < wav1->c_data) {
		/* 変換元のデータを読み込みます。 */
		len = wav1->c_data - ofs;
		if(len > unit1) len = unit1;
		memcpy(stream.pbSrc, wav1->data + ofs, len);
		stream.cbSrcLength = len;
		/* 変換元のデータが全て消費されるとは限らないので、まだofsは進めません。 */

		/* 変換します。 */
		flag = 0;
		if(ofs       == 0           ) flag |= ACM_STREAMCONVERTF_START;
		if(ofs + len == wav1->c_data) flag |= ACM_STREAMCONVERTF_END  ; /* これ忘れるとデータの最後が切れる恐れがあります！ */
		retval = acmStreamConvert(acm, &stream, flag);
		if(retval != 0) die("acmStreamConvert()が失敗しました。");
		/* 変換元のデータが消費された分だけ、ofsを進めます。 */
		ofs += stream.cbSrcLengthUsed;

		/* 変換後のデータを書き込みます。 */
		len = stream.cbDstLengthUsed;
		if(cap < (int)(wav2->c_data + len)) {
			do {
				cap = cap == 0 ? 1 : cap << 1;
				wav2->data = (char*)realloc(wav2->data, cap);
				if(wav2->data == NULL) die("メモリ不足です。");
			} while(cap < (int)(wav2->c_data + len));
		}
		memcpy(wav2->data + wav2->c_data, stream.pbDst, len);
		wav2->c_data += len;
	}

	/* ストリームバッファを削除します。
	 * * acmStreamUnprepareHeader()を呼ぶ前に、
	 *   cbSrcLengthをacmStreamPrepareHeader()時と同じ値に戻しておかないと、
	 *   acmStreamUnprepareHeader()がエラーを返すようです。
	 * * 最後の変換では、cbSrcLengthがunit1よりも小さな値になっているので、
	 *   そのままacmStreamUnprepareHeader()を呼んでしまうと、失敗します。
	 */
	stream.cbSrcLength = unit1; /* 必要!! */
	retval = acmStreamUnprepareHeader(acm, &stream, 0);
	if(retval != 0) die("acmStreamUnprepareHeader()が失敗しました。");
	free(stream.pbSrc);
	free(stream.pbDst);

	/* ACMストリームを閉じます。 */
	retval = acmStreamClose(acm, 0);
	if(retval != 0) die("acmStreamClose()が失敗しました。");
}
