/*
 *	main.c
 *
 *	dppcmcnv - WAV to PPD コンバータ
 *	Copyright (C) 2002-2006 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ライブラリで再生します。
 */
#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可変周波数(独自拡張)}}*/
char* out_fmt_desc[] = {
	"PCM",
	"ADPCM",
	"ADPCM_V",
	"ADPCM_X",		/*{{2006/12/19追加: 2bitADPCM可変周波数(独自拡張)}}*/
};
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;

/* 入出力ファイル名 */
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);

	/* 一時的な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;
	}
	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

	/* 出力ファイルを作成します。 */
	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可変周波数(独自拡張)*/
	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-2006 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, "    smpl・rate省略時は、入力ファイルと同じ値になります。\n");
	fprintf(stderr, "OTHER OPTIONS:\n");
	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可変周波数(独自拡張)*/
				default:
					usage();
				}
				break;
			/* 出力ファイル名 */
			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;

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

	/* 出力ファイル名が省略されていたら、入力ファイル名から生成します。 */
	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");
	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可変周波数(独自拡張)*/
