/*
 *	main.c
 *
 *	dppcmcnv - WAV to PPD コンバータ
 *	Copyright (C) 2002-2014 Naoyuki Sawa
 *
 *	* Sun Jul 21 04:37:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Mon Jul 17 22:43:46 JST 2006 Naoyuki Sawa
 *	- 以下のバグを修正しました。
 *	  - 変換元のwavファイルが大きい場合に、不正なppdファイルを出力することがあった。
 *	    具体的には、変換元のwavファイルが数十秒以上の長さで、出力形式が可変周波数ADPCM形式の場合、
 *	    出力されるppdファイルのPCEWAVEINFO.lenフィールドが、誤ってマイナスになることがありました。
 *	  今回の修正により、このバグは無くなりました。
 *	* Tue Dec 19 18:05:32 JST 2006 Naoyuki Sawa
 *	- 2bitADPCM可変周波数(独自拡張)を追加しました。
 *	  CLiPライブラリで再生します。
 *	* Sun Jun 05 23:48:49 JST 2011 Naoyuki Sawa
 *	- 無音カット機能のオプション(-c[level])を追加しました。
 *	* Sat Jun 11 00:34:52 JST 2011 Naoyuki Sawa
 *	- ノーマライズ機能のオプション(-n)を追加しました。
 *	* Tue Sep 17 00:00:02 JST 2013 Naoyuki Sawa
 *	- 入力ファイル名のサフィックスによる、暗黙の出力フォーマット指定を追加しました。
 *	* Mon Jul 07 01:54:09 JST 2014 Naoyuki Sawa
 *	- DSP Group TrueSpeech(独自拡張)を追加しました。
 *	  CLiPライブラリで再生します。
 */
#include "app.h"

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

/* メッセージ出力 */
#define NULDEV	"NUL"
FILE* msg = stdout;

/* 出力ファイル形式 */
#define TYPE_TEXT	0
#define TYPE_BINARY	1
char* out_type_desc[] = {
	"TEXT",
	"BINARY",
};
int out_type = -1;

/* 出力データ形式 */
#define FMT_PCM		0
#define FMT_ADPCM	1
#define FMT_ADPCM_V	2
#define FMT_ADPCM_X	3	/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
#define FMT_TRUESPEECH	4	/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)}}*/
char* out_fmt_desc[] = {
	"PCM",
	"ADPCM",
	"ADPCM_V",
	"ADPCM_X",		/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
	"TRUESPEECH",		/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)}}*/
};
int out_fmt = -1;

/* サンプルサイズ(PCM時のみ)
 * -1: 入力ファイルと同じ
 *  8:  8ビット
 * 16: 16ビット
 */
int out_smpl = -1;

/* 変換レート(ADPCM可変周波数時のみ)
 *       -1: 入力ファイルと同じ
 * 1～16000: 周波数指定
 */
#define RATE_MIN	1
#define RATE_MAX	16000
#define RATE_DEFAULT	RATE_MAX
int out_rate = -1;

/*{{2011/06/11追加: ノーマライズ*/
/* ノーマライズ
 * 0:行わない
 * 1:行う
 */
int out_norm = 0;
/*}}2011/06/11追加: ノーマライズ*/

/*{{2011/06/06追加: 無音カット*/
/* 無音カットレベル
 *    -1: カット無し
 * 0～99: 先頭と末尾から、指定された音量[%]以下の部分をカットする
 */
int out_cut = -1;
/*}}2011/06/06追加: 無音カット*/

/* 入出力ファイル名 */
char in_path[_MAX_PATH];
char out_path[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];

/* 出力ラベル */
char out_label[32];
char out_prefix[32];

/* テキスト出力時、一行に何バイト出力するか？ */
#define LINEBYTES	32

/****************************************************************************
 *	main
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	int retval, c_fmt;
	WAV *wav1, *wav2;
	FILE* fp;

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

	/* フォーマットの最大サイズを取得しておきます。 */
	retval = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &c_fmt);
	if(retval != 0) die("acmMetrics()が失敗しました。");

	/* WAVデータを読み込みます。 */
	wav1 = wav_load(in_path);

/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
	/* 出力形式がTrueSpeech形式で、入力ファイルも既にTrueSpeechに圧縮されていたら… */
	if((out_fmt == FMT_TRUESPEECH) && (wav1->fmt->wFormatTag == WAVE_FORMAT_DSPGROUP_TRUESPEECH)) {
		/* ノーマライズも、無音カットも指定されていなければ… */
		if((out_norm == 0) && (out_cut == -1)) {
			/* PCM形式を経由せずに、入力ファイルのTrueSpeechデータをそのまま出力します。 */
			fprintf(msg, "PCM形式を経由せずに､入力ﾌｧｲﾙのTrueSpeechﾃﾞｰﾀをそのまま出力します｡\n");
			goto L_TRUESPEECH_PASSTHROUGH;
		}
	}
/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/

	/* 一時的なPCM形式に変換します。
	 * ※注意！
	 *   変換元が既にWAVE_FORMAT_PCMの場合、
	 *   WAVE_FORMAT_PCMからWAVE_FORMAT_PCMへの変換（無意味）を指定してacmFormatSuggest()を呼ぶと、
	 *   acmFormatSuggest()の中で落ちるようです。（Win2K+SP2で確認）
	 *   これを防ぐために、変換元がWAVE_FORMAT_PCMでない場合のみ、
	 *   一時的なPCM形式への変換を行うことにします。
	 */
	if(wav1->fmt->wFormatTag != WAVE_FORMAT_PCM) {
		wav2 = wav_new();
		wav2->fmt = (WAVEFORMATEX*)calloc(1, c_fmt);
		if(wav2->fmt == NULL) die("メモリ不足です。");
		wav2->fmt->wFormatTag = WAVE_FORMAT_PCM; /* リニアPCM */
		retval = acmFormatSuggest(NULL, wav1->fmt, wav2->fmt, c_fmt, ACM_FORMATSUGGESTF_WFORMATTAG);
		if(retval != 0) die("acmFormatSuggest()が失敗しました。");
		wav_to_wav(wav1, wav2);
		wav_free(wav1); /* 変換元のWAVを開放             */
		wav1 = wav2;    /* 変換後のWAVを次の変換元に設定 */
	}

	/* 最終的なPCM形式のサンプルサイズ・変換レートを決定します。 */
	switch(out_fmt) {
	case FMT_PCM:
		/* サンプルサイズが指定されていなければ、元のWAVファイルと同じにします。 */
		if(out_smpl == -1) {
			out_smpl = wav1->fmt->wBitsPerSample;
			if(out_smpl != 8 && out_smpl != 16) { /* ありえないとは思うが... */
				/* 8/16bit以外なら、近い方のサンプルサイズに合わせます。 */
				out_smpl = out_smpl < 12 ? 8 : 16;
			}
		}
		/* 変換レートは16000[Hz]固定です。 */
		out_rate = RATE_DEFAULT;
		break;
	case FMT_ADPCM:
		/* サンプルサイズは16ビット固定です。 */
		out_smpl = 16;
		/* 変換レートは16000[Hz]固定です。 */
		out_rate = RATE_DEFAULT;
		break;
	case FMT_ADPCM_V:
	case FMT_ADPCM_X:	/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
		/* サンプルサイズは16ビット固定です。 */
		out_smpl = 16;
		/* 変換レートが指定されていなければ、元のWAVファイルと同じにします。 */
		if(out_rate == -1) {
			out_rate = wav1->fmt->nSamplesPerSec;
			if(out_rate < RATE_MIN) out_rate = RATE_MIN; /* 下限切り詰め */
			if(out_rate > RATE_MAX) out_rate = RATE_MAX; /* 上限切り詰め */
		}
		break;
	case FMT_TRUESPEECH:	/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)}}*/
		/* サンプルサイズは16ビット固定です。 */	//┐この変換は必要である。
		out_smpl = 16;					//│8kHz以外のPCMを、直接、TrueSpeech(8kHz固定)に変換しようとすると、acmFormatSuggest()が失敗するようだ。
		/* 変換レートは8000[Hz]固定です。 */		//│8kHz以外のPCM⇒8kHzのPCM⇒TrueSpeech(8kHz固定)、という手順を踏まなければならない。
		out_rate = 8000;				//┘2回変換することによる音質劣化が懸念されるが、やむをえない。(実際には、さほど音質の影響は無いと思う。)
		break;
	}
	fprintf(msg, "OUT SMPL : %d\n", out_smpl);
	fprintf(msg, "OUT RATE : %d\n", out_rate);

	/* 最終的なPCM形式に変換します。 */
#define CHANNELS 1
	wav2 = wav_new();
	wav2->fmt = (WAVEFORMATEX*)calloc(1, c_fmt);
	if(wav2->fmt == NULL) die("メモリ不足です。");
	wav2->fmt->wFormatTag = WAVE_FORMAT_PCM;
	wav2->fmt->nChannels = CHANNELS;
	wav2->fmt->nSamplesPerSec = out_rate;
	wav2->fmt->nAvgBytesPerSec = out_rate * (out_smpl / 8);
	wav2->fmt->nBlockAlign = (out_smpl / 8) * CHANNELS;
	wav2->fmt->wBitsPerSample = out_smpl;
	wav_to_wav(wav1, wav2);
	wav_free(wav1); /* 変換元のWAVを開放             */
	wav1 = wav2;    /* 変換後のWAVを次の変換元に設定 */
#undef CHANNELS

	/*{{2011/06/11追加: ノーマライズ*/
	if(out_norm) {
		switch(out_smpl) {
		case 8:
			do_norm8(wav1);
			break;
		case 16:
			do_norm16(wav1);
			break;
		default:
			abort();
		}
	}
	/*}}2011/06/11追加: ノーマライズ*/

	/* ↑ノーマライズしてから↓指定レベル以下を無音カットする。この処理順序、必須です。 */

	/*{{2011/06/06追加: 無音カット*/
	if(out_cut != -1) {
		switch(out_smpl) {
		case 8:
			do_cut8(wav1, out_cut);
			break;
		case 16:
			do_cut16(wav1, out_cut);
			break;
		default:
			abort();
		}
	}
	/*}}2011/06/06追加: 無音カット*/

/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
L_TRUESPEECH_PASSTHROUGH://PCM形式を経由せずに、入力ファイルのTrueSpeechデータをそのまま出力する時のジャンプ位置
/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/

	/* 出力ファイルを作成します。 */
	switch(out_type) {
	case TYPE_TEXT:
		fp = fopen(out_path, "wt");
		break;
	case TYPE_BINARY:
		fp = fopen(out_path, "wb");
		break;
	default:
		abort();
	}
	if(fp == NULL) die("%sが作成できません。", out_path);

	/* C言語配列の開始。 */
	if(out_type == TYPE_TEXT) {
		fprintf(fp, "const unsigned char %s[] = {\n", out_label);
	}

	/* データを出力します。 */
	switch(out_fmt) {
	case FMT_PCM:
		writepcm(fp, wav1);
		break;
	case FMT_ADPCM:
	case FMT_ADPCM_V:
		writeadpcm(fp, wav1);
		break;
	/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/
	case FMT_ADPCM_X:
		writeadpcm2(fp, wav1);
		break;
	/*}}2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/
	/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)}}*/
	case FMT_TRUESPEECH:
		writetsp(fp, wav1);
		break;
	/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)}}*/
	default:
		abort();
	}

	/* C言語配列の終了。 */
	if(out_type == TYPE_TEXT) {
		fprintf(fp, "};\n");
	}

	/* 出力ファイルを閉じます。 */
	fclose(fp);

	/* WAV構造体を開放します。 */
	wav_free(wav1);

	return 0;
}

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

void
usage()
{
	fprintf(stderr, "dppcmcnv - WAV to PPD converter (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2002-2014 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "    dppcmcnv [options] <filename.wav>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "TYPE OPTIONS:\n");
	fprintf(stderr, "    -t         テキスト\n");
	fprintf(stderr, "  * -b         バイナリ\n");
	fprintf(stderr, "FORMAT OPTIONS:                               --[bit]--  ---[Ｈz]---\n");
	fprintf(stderr, "  * -fp[smpl]  PCM                            16or8        16000固定\n");
	fprintf(stderr, "    -fa        ADPCM                          16->4固定    16000固定\n");
	fprintf(stderr, "    -fv[rate]  ADPCM可変周波数                16->4固定  1-16000\n");
	fprintf(stderr, "    -fx[rate]  2bitADPCM可変周波数(独自拡張)  16->2固定  1-16000\n");	/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
	fprintf(stderr, "    -ft        DSP Group TrueSpeech(独自拡張) 16->1固定     8000固定\n");	/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
	fprintf(stderr, "    smpl・rate省略時は、入力ファイルと同じ値になります。\n");
	fprintf(stderr, "    ファイル名サフィックスによるフォーマット指定も可能です。\n");
	fprintf(stderr, "    (-p[smpl],-a,-v[rate],-x[rate],-t)\n");
	fprintf(stderr, "    オプション指定はサフィックス指定よりも優先されます。\n");
	fprintf(stderr, "OTHER OPTIONS:\n");
	fprintf(stderr, "    -n         ノーマライズ\n");	/*{{2011/06/11追加: ノーマライズ}}*/
	fprintf(stderr, "    -c[level]  無音カットレベル（0～99[％]以下をカット。level省略時は0[％]）\n");	/*{{2011/06/05追加: 無音カット}}*/
	fprintf(stderr, "               -c[level]オプションを指定しなければ、無音でもカットしません。\n");	/*{{2011/06/05追加: 無音カット}}*/
	fprintf(stderr, "    -o<fname>  出力ファイル名\n");
	fprintf(stderr, "    -l<label>  ラベル名（テキスト時のみ）\n");
	fprintf(stderr, "    -p<prefix> ラベルプレフィクス（テキスト時のみ）\n");
	fprintf(stderr, "    -q         メッセージ抑制\n");
	fprintf(stderr, "（* 初期値）\n");
	exit(1);
}

/****************************************************************************
 *	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);
}

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

void
cmdline(int argc, char* argv[])
{
	int i;
	char *opt, *p;

	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 't':
				if(out_type != -1) usage();
				out_type = TYPE_TEXT;
				break;
			case 'b':
				if(out_type != -1) usage();
				out_type = TYPE_BINARY;
				break;
			/* 出力データ形式 */
			case 'f':
				switch(opt[2]) {
				case 'p':
					if(out_fmt != -1) usage();
					out_fmt = FMT_PCM;
					/* 追加パラメータ=サンプルサイズ */
					p = &opt[3];
					if(*p != '\0') {
						if(out_smpl != -1) usage();
						out_smpl = strtol(p, &p, 10);
						if(*p != '\0') usage();
						if(out_smpl != 8 && out_smpl != 16) usage();
					}
					break;
				case 'a':
					if(out_fmt != -1) usage();
					out_fmt = FMT_ADPCM;
					/* 追加パラメータ=なし */
					p = &opt[3];
					if(*p != '\0') usage();
					break;
				case 'v':
					if(out_fmt != -1) usage();
					out_fmt = FMT_ADPCM_V;
					/* 追加パラメータ=周波数 */
					p = &opt[3];
					if(*p != '\0') {
						if(out_rate != -1) usage();
						out_rate = strtol(p, &p, 10);
						if(*p != '\0') usage();
						if(out_rate < RATE_MIN || RATE_MAX < out_rate) usage();
					}
					break;
				/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/
				case 'x':
					if(out_fmt != -1) usage();
					out_fmt = FMT_ADPCM_X;
					/* 追加パラメータ=周波数 */
					p = &opt[3];
					if(*p != '\0') {
						if(out_rate != -1) usage();
						out_rate = strtol(p, &p, 10);
						if(*p != '\0') usage();
						if(out_rate < RATE_MIN || RATE_MAX < out_rate) usage();
					}
					break;
				/*}}2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/
				/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
				case 't':
					if(out_fmt != -1) usage();
					out_fmt = FMT_TRUESPEECH;
					/* 追加パラメータ=なし */
					p = &opt[3];
					if(*p != '\0') usage();
					break;
				/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
				default:
					usage();
				}
				break;
			/*{{2011/06/11追加: ノーマライズ*/
			/* ノーマライズ */
			case 'n':
				if(out_norm != 0) usage();
				out_norm = 1;
				break;
			/*}}2011/06/11追加: ノーマライズ*/
			/*{{2011/06/06追加: 無音カット*/
			/* 無音カット */
			case 'c':
				if(out_cut != -1) usage();
				/* 追加パラメータ=無音カットレベル */
				p = &opt[2];
				if(*p != '\0') {
					if(out_cut != -1) usage();
					out_cut = strtol(p, &p, 10);
					if(*p != '\0') usage();
					if(out_cut < 0 || 99 < out_cut) usage();
				} else {
					out_cut = 0;	/* 追加パラメータ無しならば、無音カットレベル0%が指定されたものと見なす */
				}
				break;
			/*}}2011/06/06追加: 無音カット*/
			/* 出力ファイル名 */
			case 'o':
				if(strlen(out_path) != 0) usage();
				strcpy(out_path, &opt[2]);
				break;
			/* 出力ラベル */
			case 'l':
				if(strlen(out_label) != 0) usage();
				strcpy(out_label, &opt[2]);
				break;
			case 'p':
				if(strlen(out_prefix) != 0) usage();
				strcpy(out_prefix, &opt[2]);
				break;
			/* メッセージ抑制 */
			case 'q':
				if(msg != stdout) usage();
				msg = fopen(NULDEV, "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");
	}

	/* 出力ファイル形式が指定されていなければ、デフォルト値を設定します。 */
	if(out_type == -1) out_type = TYPE_BINARY;

//{{2013/09/17:変更
//	/* 出力データ形式が指定されていなければ、デフォルト値を設定します。 */
//	if(out_fmt == -1) out_fmt = FMT_PCM;
//↓2013/09/17:変更
	/* 出力フォーマットが指定されていなければ... */
	if(out_fmt == -1) {
		_splitpath(in_path, drive, dir, fname, ext);
		/* 入力ファイル名のサフィックスが無ければ… */
		if(!(p = strchr(fname, '-'))) {
			out_fmt = FMT_PCM;
		/* 入力ファイル名のサフィックスが有れば… */
		} else {
			/* 入力ファイル名のサフィックスから出力フォーマットを自動決定する場合は、出力ラベルにサフィックス部分を含めない。 */
			*p++ = '\0'; /* fnameを'-'の位置で分割する。pは'-'の次の文字を指す */
			if(strlen(out_label) == 0) { /* 出力ラベルが明示されていなければ… */
				strcpy(out_label, fname);
				_strupr(out_label);
			}
			/* 入力ファイル名のサフィックスから、出力フォーマットを決定します。 */
			switch(*p++) {
			case 'p':
				out_fmt = FMT_PCM;
				/* 追加パラメータ=サンプルサイズ */
				if(*p) {
					if(out_smpl != -1) { usage(); }
					out_smpl = strtol(p, &p, 10);
					if(*p) { usage(); }
					if((out_smpl != 8) && (out_smpl != 16)) { usage(); }
				}
				break;
			case 'a':
				out_fmt = FMT_ADPCM;
				/* 追加パラメータ=なし */
				if(*p) { usage(); }
				break;
			case 'v':
				out_fmt = FMT_ADPCM_V;
				/* 追加パラメータ=周波数 */
				if(*p) {
					if(out_rate != -1) { usage(); }
					out_rate = strtol(p, &p, 10);
					if(*p) { usage(); }
					if((out_rate < RATE_MIN) || (out_rate > RATE_MAX)) { usage(); }
				}
				break;
			case 'x':
				out_fmt = FMT_ADPCM_X;
				/* 追加パラメータ=周波数 */
				if(*p) {
					if(out_rate != -1) { usage(); }
					out_rate = strtol(p, &p, 10);
					if(*p) { usage(); }
					if((out_rate < RATE_MIN) || (out_rate > RATE_MAX)) { usage(); }
				}
				break;
			/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
			case 't':
				out_fmt = FMT_TRUESPEECH;
				/* 追加パラメータ=なし */
				if(*p) { usage(); }
				break;
			/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/
			default:
				usage();
			}
		}
	}
//}}2013/09/17:変更

	/* 出力ファイル名が省略されていたら、入力ファイル名から生成します。 */
	if(strlen(out_path) == 0) {
		_splitpath(in_path, drive, dir, fname, ext);
		switch(out_type) {
		case TYPE_TEXT:
			_makepath(out_path, drive, dir, fname, ".c");
			break;
		case TYPE_BINARY:
			_makepath(out_path, drive, dir, fname, ".ppd");
			break;
		default:
			abort();
		}
	}

	/* 出力ラベルが省略されていたら、入力ファイル名から生成します。 */
	if(strlen(out_label) == 0) {
		_splitpath(out_path, drive, dir, fname, ext);
		for(i = 0; fname[i] != '\0'; i++) {
			out_label[i] = toupper(fname[i]);
		}
	}
	if(strlen(out_prefix) != 0) {
		strcat(out_prefix, out_label); /* out_prefixは破壊されます。もう使わないので大丈夫 */
		strcpy(out_label, out_prefix);
	}

	fprintf(msg, "IN  FILE : %s\n", in_path);
	fprintf(msg, "OUT FILE : %s\n", out_path);
	fprintf(msg, "OUT TYPE : %s\n", out_type_desc[out_type]);
	fprintf(msg, "OUT FMT  : %s", out_fmt_desc[out_fmt]);
	if(out_smpl != -1) fprintf(msg, "(%d)", out_smpl);
	if(out_rate != -1) fprintf(msg, "(%d)", out_rate);
	fprintf(msg, "\n");
	/*{{2011/06/11追加: ノーマライズ*/
	fprintf(msg, "NORMALIZE: %s\n", out_norm ? "ON" : "OFF");
	/*}}2011/06/11追加: ノーマライズ*/
	/*{{2011/06/06追加: 無音カット*/
	if(out_cut != -1) {
		fprintf(msg, "CUT LEVEL: %d[%%]\n", out_cut);
	} else {
		fprintf(msg, "CUT LEVEL: NONE\n");
	}
	/*}}2011/06/06追加: 無音カット*/
	fprintf(msg, "OUT LABEL: %s\n", out_label);
	/* サンプルサイズと周波数は入力ファイルを読むまで決まらないので、後で表示します。 */
}

/****************************************************************************
 *	writepcm
 ****************************************************************************/

/* テキスト時は、呼ばれた単位で改行するので、一行ごとに呼び出してください。 */
void
writedata(FILE* fp, void* data, int len)
{
	int i;
	unsigned char* p;

	switch(out_type) {
	case TYPE_TEXT:
		p = (unsigned char*)data;
		for(i = 0; i < len; i++) {
			fprintf(fp, "0x%02x,", *p++);
		}
		fprintf(fp, "\n");
		break;
	case TYPE_BINARY:
		fwrite(data, 1, len, fp);
		break;
	default:
		abort();
	}
}

/****************************************************************************
 *	writepcm
 ****************************************************************************/

void
writepcm(FILE* fp, WAV* wav)
{
	int ofs, len;
	PPDHDR hdr;
	PCEWAVEINFO info;

	/* ヘッダ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*PPCM*/\n");
	memset(&hdr, 0, sizeof hdr);
	hdr.sig = PPDSIG;
	hdr.len = sizeof(PCEWAVEINFO) + wav->c_data;
	writedata(fp, &hdr, sizeof hdr);

	/* WAVE情報出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*info*/\n");
	memset(&info, 0, sizeof info);
	info.stat = PW_STAT_START;
	switch(out_smpl) {
	case 8:
		info.type = PW_TYPE_8BITPCM;
		info.len = wav->c_data;
		break;
	case 16:
		info.type = PW_TYPE_16BITPCM;
		info.len = wav->c_data / 2;
		break;
	default:
		abort();
	}
	writedata(fp, &info, sizeof info);

	/* データ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*data*/\n");
	ofs = 0;
	while(ofs < wav->c_data) {
		len = wav->c_data - ofs;
		if(len > LINEBYTES) len = LINEBYTES;
		writedata(fp, &wav->data[ofs], len);
		ofs += len;
	}
}

/****************************************************************************
 *	writeadpcm
 ****************************************************************************/

void
writeadpcm(FILE* fp, WAV* wav)
{
	int c_smpl, ofs, len;
	unsigned char* adp;
	PPDHDR hdr;
	PCEWAVEINFO info;

	/* ADPCMは2サンプル単位で変換するので、サンプル数を2サンプル単位に切り上げます。 */
	c_smpl = wav->c_data / sizeof(short); /* 元のサンプル数 */
	if(c_smpl & 1) { /* サンプル数が奇数なら... */
		c_smpl++; /* サンプル数を偶数単位に切り上げ */
		wav->c_data = sizeof(short) * c_smpl; /* パディング追加 */
		wav->data = (char*)realloc(wav->data, wav->c_data);
		if(wav->data == NULL) die("メモリ不足です。");
		((short*)wav->data)[c_smpl - 1] = 0; /* パディング部分をクリア */
	}

	/* ADPCM変換。 */
	adp = (unsigned char*)malloc(c_smpl / 2);
	if(adp == NULL) die("メモリ不足です。");
	encode(adp, (short*)wav->data, wav->c_data);

	/* ヘッダ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*PPCM*/\n");
	memset(&hdr, 0, sizeof hdr);
	hdr.sig = PPDSIG;
	hdr.len = sizeof(PCEWAVEINFO) + c_smpl / 2;
	writedata(fp, &hdr, sizeof hdr);

	/* WAVE情報出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*info*/\n");
	memset(&info, 0, sizeof info);
	info.stat = PW_STAT_START;
	switch(out_fmt) {
	case FMT_ADPCM:
		info.type = PW_TYPE_ADPCM;
		info.len = c_smpl;
		break;
	case FMT_ADPCM_V:
		info.type = PW_TYPE_ADPCM_V;
		info.resv = out_rate; /* 可変周波数時のみ設定 */
		/* 超重要！
		 * ADPCM可変周波数時のlen設定は、実サンプル数ではありません！
		 * 16000Hz換算でのサンプル数（＝再生秒数*16000）です！
		 * （C:\usr\PIECE\app\adpcm\hello.c 53行目あたり参照）
		 */
		//info.len = c_smpl * RATE_DEFAULT / out_rate;
		//↓2006/07/17 バグ修正 (オーバーフロー回避)
		info.len = (int)((__int64)c_smpl * RATE_DEFAULT / out_rate);
		break;
	default:
		abort();
	}
	writedata(fp, &info, sizeof info);

	/* データ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*data*/\n");
	ofs = 0;
	while(ofs < c_smpl / 2) {
		len = c_smpl / 2 - ofs;
		if(len > LINEBYTES) len = LINEBYTES;
		writedata(fp, &adp[ofs], len);
		ofs += len;
	}

	/* ADPCMデータ開放。 */
	free(adp);
}

/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/

/****************************************************************************
 *	writeadpcm2
 ****************************************************************************/

void
writeadpcm2(FILE* fp, WAV* wav)
{
	int c_smpl, ofs, len;
	unsigned char* adp;
	PPDHDR hdr;
	PCEWAVEINFO info;

	/* 2bitADPCMは4サンプル単位で変換するので、サンプル数を4サンプル単位に切り上げます。 */
	c_smpl = wav->c_data / sizeof(short); /* 元のサンプル数 */
	while(c_smpl & 3) { /* サンプル数が4の倍数になるまで... */
		c_smpl++;   /* サンプル数を1づつ増加 */
		wav->c_data = sizeof(short) * c_smpl; /* パディング追加 */
		wav->data = (char*)realloc(wav->data, wav->c_data);
		if(wav->data == NULL) die("メモリ不足です。");
		((short*)wav->data)[c_smpl - 1] = 0; /* パディング部分をクリア */
	}

	/* ADPCM変換。 */
	adp = (unsigned char*)malloc(c_smpl / 4);
	if(adp == NULL) die("メモリ不足です。");
	encode2(adp, (short*)wav->data, wav->c_data);

	/* ヘッダ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*PPCM*/\n");
	memset(&hdr, 0, sizeof hdr);
	hdr.sig = PPDSIG;
	hdr.len = sizeof(PCEWAVEINFO) + c_smpl / 4;
	writedata(fp, &hdr, sizeof hdr);

	/* WAVE情報出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*info*/\n");
	memset(&info, 0, sizeof info);
	info.stat = PW_STAT_START;
	switch(out_fmt) {
	case FMT_ADPCM_X:
		info.type = PW_TYPE_ADPCM_X;
		info.resv = out_rate;
		info.len = (int)((__int64)c_smpl * RATE_DEFAULT / out_rate);
		break;
	default:
		abort();
	}
	writedata(fp, &info, sizeof info);

	/* データ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*data*/\n");
	ofs = 0;
	while(ofs < c_smpl / 4) {
		len = c_smpl / 4 - ofs;
		if(len > LINEBYTES) len = LINEBYTES;
		writedata(fp, &adp[ofs], len);
		ofs += len;
	}

	/* ADPCMデータ開放。 */
	free(adp);
}

/*}}2006/12/19追加: 2bitADPCM可変周波数(独自拡張)*/

/*{{2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/

/****************************************************************************
 *	writetsp
 ****************************************************************************/

void
writetsp(FILE* fp, WAV* wav)
{
	int retval, c_fmt, c_smpl, ofs, len;
	PPDHDR hdr;
	PCEWAVEINFO info;
	WAV* tsp = NULL;

	/* 入力wavがTrueSpeech形式でなければ… */
	if(wav->fmt->wFormatTag != WAVE_FORMAT_DSPGROUP_TRUESPEECH) {
		/* TrueSpeechは240サンプル単位で変換するので、サンプル数を240サンプル単位に切り上げます。 */
		c_smpl = wav->c_data / sizeof(short); /* 元のサンプル数 */
		wav->c_data = sizeof(short) * (((c_smpl + 239) / 240) * 240);	//240サンプル単位に切り上げる。既に240サンプル単位だったら無駄処理。
		wav->data = (char*)realloc(wav->data, wav->c_data);		//パディングを追加する。既に240サンプル単位だったら無駄処理。
		if(wav->data == NULL) die("メモリ不足です。");
		while(c_smpl % 240) { ((short*)wav->data)[c_smpl++] = 0; }	//パディング部分をクリアする。既に240サンプル単位だったら無駄処理。

		/* フォーマットの最大サイズを取得しておきます。 */
		retval = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &c_fmt);
		if(retval != 0) die("acmMetrics()が失敗しました。");

		/* TrueSpeech変換。 */
		tsp = wav_new();
		tsp->fmt = (WAVEFORMATEX*)calloc(1, c_fmt);
		if(tsp->fmt == NULL) die("メモリ不足です。");
		tsp->fmt->wFormatTag = WAVE_FORMAT_DSPGROUP_TRUESPEECH; /* DSP Group TrueSpeech */
		retval = acmFormatSuggest(NULL, wav->fmt, tsp->fmt, c_fmt, ACM_FORMATSUGGESTF_WFORMATTAG);
		if(retval != 0) die(
			"TrueSpeech変換時に、acmFormatSuggest()が失敗しました。\n"
			"│「tssoft32.acm」(DSP GROUP Windows NT(TM) TrueSpeech CODEC)がインストールされていない可能性があります。\n"
			"│WindowsXpまでは、最初からインストールされていますが、WindowsVista以降には、インストールされていません。\n"
			"│インターネットで、「truespeech」「codec」「windows7」等のキーワードで検索し、解決方法を探してください。\n"
			"│■参考URL(2014/7/7現在)\n"
			"│①http://www.bytescribe.com/windows-7-or-8/\n"
			"│②http://www.bytescribe.com/download/tsp_codec_install.exe");
		wav_to_wav(wav, tsp);
		wav = tsp;	//出力対象はwavを参照するため
	}

	/* ヘッダ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*PPCM*/\n");
	memset(&hdr, 0, sizeof hdr);
	hdr.sig = PPDSIG;
	hdr.len = sizeof(PCEWAVEINFO) + wav->c_data;
	writedata(fp, &hdr, sizeof hdr);

	/* WAVE情報出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*info*/\n");
	memset(&info, 0, sizeof info);
	info.stat = PW_STAT_START;
	info.type = PW_TYPE_TRUESPEECH;
	info.len  = (((wav->c_data / 32) * 240) * 2);
	//            ~~~~~~~~~~~~~~~~~~             フレーム数
	//           ~~~~~~~~~~~~~~~~~~~~~~~~~~      サンプル数
	//          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ サンプル数を16kHz換算した値。TrueSpeechは8kHz固定であるが、実際のサンプルレートに関係無く、PCEWAVEINFO.lenには16kHz換算のサンプル数を格納する。(P/ECE仕様)
	writedata(fp, &info, sizeof info);

	/* データ出力。 */
	if(out_type == TYPE_TEXT) fprintf(fp, "/*data*/\n");
	ofs = 0;
	while(ofs < wav->c_data) {
		len = wav->c_data - ofs;
		if(len > LINEBYTES) len = LINEBYTES;
		writedata(fp, &wav->data[ofs], len);
		ofs += len;
	}

	/* TrueSpeechデータ開放。 */
	wav_free(tsp);	//nullok
}

/*}}2014/07/07追加: DSP Group TrueSpeech(独自拡張)*/

/*{{2011/06/11追加: ノーマライズ*/

/****************************************************************************
 *	do_norm8
 ****************************************************************************/

void
do_norm8(WAV* wav)
{
	int c_smpl;
	unsigned char* p;
	int i;
	int v;
	int v_max;

	c_smpl = wav->c_data;
	p = (unsigned char*)wav->data;

	/* 最大振幅を検索します。 */
	v_max = 0;
	for(i = 0; i < c_smpl; i++) {
		v = abs(p[i] - 128);
		if(v > v_max) {
			v_max = v;
		}
	}

	/* 完全に無音か、既に最大ならば、ノーマライズ不要です。そうでなければ、ノーマライズを実行します。 */
	if((v_max == 0) || (v_max >= 128)) {
		/** no job **/
	} else {
		for(i = 0; i < c_smpl; i++) {
			v = p[i] - 128;
			v = v * 128 / v_max;
			if(v < -128) v = -128;	/* アルゴリズム上、有り得ないはずだが、念のため */
			if(v >  127) v =  127;	/* アルゴリズム上、有り得ないはずだが、念のため */
			p[i] = v + 128;
		}
	}
}

/****************************************************************************
 *	do_norm16
 ****************************************************************************/

void
do_norm16(WAV* wav)
{
	int c_smpl;
	short* p;
	int i;
	int v;
	int v_max;

	c_smpl = wav->c_data / 2;
	p = (short*)wav->data;

	/* 最大振幅を検索します。 */
	v_max = 0;
	for(i = 0; i < c_smpl; i++) {
		v = abs(p[i]);
		if(v > v_max) {
			v_max = v;
		}
	}

	/* 完全に無音か、既に最大ならば、ノーマライズ不要です。そうでなければ、ノーマライズを実行します。 */
	if((v_max == 0) || (v_max >= 32768)) {
		/** no job **/
	} else {
		for(i = 0; i < c_smpl; i++) {
			v = p[i];
			v = v * 32768 / v_max;
			if(v < -32768) v = -32768;	/* アルゴリズム上、有り得ないはずだが、念のため */
			if(v >  32767) v =  32767;	/* アルゴリズム上、有り得ないはずだが、念のため */
			p[i] = v;
		}
	}
}

/*}}2011/06/11追加: ノーマライズ*/

/*{{2011/06/06追加: 無音カット*/

/****************************************************************************
 *	do_cut8
 ****************************************************************************/

void
do_cut8(WAV* wav, int cut_level)
{
	int c_smpl;
	unsigned char* p1;
	unsigned char* p2;

	/* カットレベルを計算します。
	 * 8bit PCMは、128を中央として0～255に振れるので、100%振幅は128です。
	 */
	cut_level = (cut_level * 128) / 100;

	/* カットする前のサンプル数、及び、先頭と末尾のサンプルへのポインタを取得します。 */
	c_smpl = wav->c_data;
	p1 = (unsigned char*)wav->data;
	p2 = p1 + c_smpl - 1;

	/* 先頭の無音をカットします。 */
	while(p1 <= p2) {
		if(abs(*p1 - 128) > cut_level) {
			break;
		}
		p1++;
	}

	/* 末尾の無音をカットします。 */
	while(p1 <= p2) {
		if(abs(*p2 - 128) > cut_level) {
			break;
		}
		p2--;
	}

	/* 残ったサンプル数を求めます。 */
	c_smpl = p2 - p1 + 1;

	/* 全部カットしてしまった場合は、無音の1サンプルを残して帰ります。 */
	if(c_smpl == 0) {
		*(unsigned char*)wav->data = 128;	/* 確実に無音とする。元データは、カットレベル以下ではあるが、無音とは限らないから */
		wav->c_data = 1;
		return;	/* ここまで */
	}

	/* 残った部分をデータ先頭へ移動し、サイズを変更します。 */
	wav->c_data = c_smpl;
	memcpy(wav->data, p1, wav->c_data);	/* 前方コピーなので、memmove()でなくても大丈夫 */
}

/****************************************************************************
 *	do_cut16
 ****************************************************************************/

void
do_cut16(WAV* wav, int cut_level)
{
	int c_smpl;
	short* p1;
	short* p2;

	/* カットレベルを計算します。
	 * 16bit PCMは、0を中央として-32768～+32767に振れるので、100%振幅は32768です。
	 */
	cut_level = (cut_level * 32768) / 100;

	/* カットする前のサンプル数、及び、先頭と末尾のサンプルへのポインタを取得します。 */
	c_smpl = wav->c_data / 2;
	p1 = (short*)wav->data;
	p2 = p1 + c_smpl - 1;

	/* 先頭の無音をカットします。 */
	while(p1 <= p2) {
		if(abs(*p1) > cut_level) {
			break;
		}
		p1++;
	}

	/* 末尾の無音をカットします。 */
	while(p1 <= p2) {
		if(abs(*p2) > cut_level) {
			break;
		}
		p2--;
	}

	/* 残ったサンプル数を求めます。 */
	c_smpl = p2 - p1 + 1;

	/* 全部カットしてしまった場合は、無音の1サンプルを残して帰ります。 */
	if(c_smpl == 0) {
		*(short*)wav->data = 0;	/* 確実に無音とする。元データは、カットレベル以下ではあるが、無音とは限らないから */
		wav->c_data = 2;
		return;	/* ここまで */
	}

	/* 残った部分をデータ先頭へ移動し、サイズを変更します。 */
	wav->c_data = c_smpl * 2;
	memcpy(wav->data, p1, wav->c_data);	/* 前方コピーなので、memmove()でなくても大丈夫 */
}

/*}}2011/06/06追加: 無音カット*/
