/*
 *	main.c
 *
 *	dwav2gsm - WAV to GSM コンバータ
 *	Copyright (C) 2001-2002 Naoyuki Sawa
 *
 *	* Mon Jun 19 19:00:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Mon Jun 20 12:30:00 JST 2002 Naoyuki Sawa
 *	- usage()修正、バージョン表示追加。
 *	* Mon Jun 21 04:39:00 JST 2002 Naoyuki Sawa
 *	- "-b"オプション(中間ファイルを残す)追加。
 */
#define STRICT
#include <windows.h>
#include <mmreg.h>
#include <msacm.h>
#include <stdio.h>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "msacm32.lib")
/* コマンドライン引数を展開するために、
 * プロジェクトの設定で、setargv.objもリンクしてください
 */
#include "gsm/gsm.h"

#define VERSION			"20020621"
#define DEFAULT_RATE		8000		/* GSM標準の変換レート      */
#define IN_BUFFER_SIZE		65536		/* 何バイトづつ変換するか？ */

int rate = DEFAULT_RATE;	/* 変換レート         */
int keep = 0;			/* 中間ファイルを残す */

void wav2pcm(const char* in_path, const char* out_path);
void pcm2gsm(const char* in_path, const char* out_path);

void
usage()
{
	fprintf(stderr, "dwav2gsm - WAV to GSM コンバータ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2002 Naoyuki Sawa <nsawa@north.hokkai.net>\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE  ： wav2gsm [options] [filename[.wav] [filename[.wav] [...]]]\n");
	fprintf(stderr, "OPTIONS： -r<rate> ... 変換レートを指定します。(指定なし=%d[Hz])\n", DEFAULT_RATE);
	fprintf(stderr, "          -b       ... 中間ファイル(.pcm)を残します。\n");
	exit(1);
}

int
main(int argc, char* argv[])
{
	int i;
	char* p;
	char wav_path[_MAX_PATH];
	char wav_drive[_MAX_PATH];
	char wav_dir[_MAX_PATH];
	char wav_fname[_MAX_PATH];
	char wav_ext[_MAX_PATH];
	char pcm_path[_MAX_PATH];
	char gsm_path[_MAX_PATH];

	if(argc == 1) usage();
	i = 1;

	/* コマンドラインオプションを解析します。 */
	while(i < argc) {
		p = argv[i];
		if(p[0] != '-') break;
		switch(p[1]) {
		case 'r': /* 変換レート */
			rate = atoi(&p[2]);
			if(rate == 0) usage();
			break;
		case 'b': /* 中間ファイルを残す。'b'の由来：pcc33に合わせた */
			keep = 1;
			break;
		}
		i++;
	}

	/* 引数に指定された全ファイルを処理します。 */
	while(i < argc) {
		p = argv[i];
		strcpy(wav_path, p);
		_splitpath(wav_path, wav_drive, wav_dir, wav_fname, wav_ext);
		if(wav_ext[0] == '\0') {
			strcpy(wav_ext, ".wav");
			_makepath(wav_path, wav_drive, wav_dir, wav_fname, wav_ext);
		}
		_makepath(pcm_path, wav_drive, wav_dir, wav_fname, ".pcm");
		_makepath(gsm_path, wav_drive, wav_dir, wav_fname, ".gsm");
		fprintf(stderr, "%s\n", wav_path);
		wav2pcm(wav_path, pcm_path);
		pcm2gsm(pcm_path, gsm_path);
		if(!keep) remove(pcm_path);
		i++;
	}

	return 0;
}

void
wav2pcm(const char* wav_path, const char* pcm_path)
{
	int retval, remain;
	HMMIO wav_mmio;
	MMCKINFO ck_riff;
	MMCKINFO ck_fmt;
	MMCKINFO ck_data;
	WAVEFORMATEX* wav_fmt;
	FILE* pcm_file;
	WAVEFORMATEX pcm_fmt;
	HACMSTREAM acm;
	ACMSTREAMHEADER stream;
	int wav_unit;
	int pcm_unit;

	/* WAVファイルを開きます。 */
	wav_mmio = mmioOpen((char*)wav_path, NULL, MMIO_READ);
	if(wav_mmio == NULL) {
		fprintf(stderr, "### WAVファイルが開けません。\n");
		abort();
	}

	/* PCMファイルを開きます。 */
	pcm_file = fopen(pcm_path, "wb");
	if(pcm_file == NULL) {
		fprintf(stderr, "### PCMファイルが作成できません。\n");
		abort();
	}

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

	/* fmtチャンクに入ります。 */
	memset(&ck_fmt, 0, sizeof(MMCKINFO));
	ck_fmt.ckid = mmioStringToFOURCC("fmt", 0);
	retval = mmioDescend(wav_mmio, &ck_fmt, &ck_riff, MMIO_FINDCHUNK);
	if(retval != 0) {
		fprintf(stderr, "### fmtチャンクが見つかりません。\n");
		abort();
	}

	/* WAVヘッダを読み込みます。 */
	wav_fmt = (WAVEFORMATEX*)malloc(ck_fmt.cksize);
	if(wav_fmt == NULL) {
		fprintf(stderr, "### メモリ不足です。\n");
		abort();
	}
	retval = mmioRead(wav_mmio, (char*)wav_fmt, ck_fmt.cksize);
	if(retval != (int)ck_fmt.cksize) {
		fprintf(stderr, "### WAVファイルが不正です。\n");
		abort();
	}

	/* fmtチャンクを抜けます。 */
	retval = mmioAscend(wav_mmio, &ck_fmt, 0);
	if(retval != 0) {
		fprintf(stderr, "### WAVファイルが不正です。\n");
		abort();
	}

	/* dataチャンクに入ります。 */
	memset(&ck_data, 0, sizeof(MMCKINFO));
	ck_data.ckid = mmioStringToFOURCC("data", 0);
	retval = mmioDescend(wav_mmio, &ck_data, &ck_riff, MMIO_FINDCHUNK);
	if(retval != 0) {
		fprintf(stderr, "### dataチャンクが見つかりません。\n");
		abort();
	}

	/* ACMストリームを開きます。
	 * ※WAVファイルがリニアPCM以外の形式(例えばADPCMとか)だと、失敗します。
	 *   別形式・別サンプリリングレートへの変換は、一度では行えません。
	 *   二回変換を行うことで対応可能ですが、現在のところは未対応としました。
	 */
	memset(&pcm_fmt, 0, sizeof pcm_fmt);
	pcm_fmt.wFormatTag = WAVE_FORMAT_PCM;	/* リニアPCM           */
	pcm_fmt.nChannels = 1;			/* モノラル            */
	pcm_fmt.nSamplesPerSec = rate;		/* 8KHz                */
	pcm_fmt.nAvgBytesPerSec = rate * 2 * 1;	/* 8KHz,16bit,モノラル */
	pcm_fmt.nBlockAlign = 2 * 1;		/* 16bit,モノラル      */
	pcm_fmt.wBitsPerSample = 16;		/* 16bit               */
	retval = acmStreamOpen(&acm, NULL, wav_fmt, &pcm_fmt, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
	if(retval != 0) {
		fprintf(stderr, "### ACMストリームが開けません。\n");
		fprintf(stderr, "### たぶんWAVファイルがリニアPCM以外の形式です。\n");
		fprintf(stderr, "### リニアPCM以外のWAVファイル形式は未対応です。\n");
		abort();
	}

	/* ストリームバッファを作成します。
	 * 変換単位は IN_BUFFER_SIZE以上 かつ nBlockAlignの整数倍 のサイズとします。
	 */
	wav_unit = (IN_BUFFER_SIZE + (wav_fmt->nBlockAlign - 1)) / wav_fmt->nBlockAlign * wav_fmt->nBlockAlign;
	retval = acmStreamSize(acm, wav_unit, &pcm_unit, ACM_STREAMSIZEF_SOURCE);
	if(retval != 0) {
		fprintf(stderr, "### acmStreamSize()が失敗しました。\n");
		abort();
	}
	memset(&stream, 0, sizeof stream);
	stream.cbStruct = sizeof stream;
	stream.pbSrc = (unsigned char*)malloc(wav_unit);
	stream.cbSrcLength = wav_unit;
	stream.pbDst = (unsigned char*)malloc(pcm_unit);
	stream.cbDstLength = pcm_unit;
	if(stream.pbSrc == NULL || stream.pbDst == NULL) {
		fprintf(stderr, "### メモリ不足です。\n");
		abort();
	}
	retval = acmStreamPrepareHeader(acm, &stream, 0);
	if(retval != 0) {
		fprintf(stderr, "### acmStreamPrepareHeader()が失敗しました。\n");
		abort();
	}

	/* 変換ループ。
	 * * dataチャンクの残りバイト数はプログラム側ちゃんと数えなければいけないようです。
	 *   mmioRead()に任せると、dataチャンクの後ろのパディング？まで読んでしまうみたい。
	 */
	remain = ck_data.cksize; /* 残りデータサイズ */
	while(remain > 0) {
		/* 入力データを読み込みます。 */
		stream.cbSrcLength = remain > wav_unit ? wav_unit : remain; /* 今回の読み込みデータサイズ */
		retval = mmioRead(wav_mmio, (char*)stream.pbSrc, stream.cbSrcLength);
		if(retval != (int)stream.cbSrcLength) {
			fprintf(stderr, "### 入力ファイルが不正です。\n");
			abort();
		}
		remain -= stream.cbSrcLength; /* 残りデータサイズを減らす */

		/* 変換します。 */
		retval = acmStreamConvert(acm, &stream, 0);
		if(retval != 0) {
			fprintf(stderr, "### acmStreamConvert()が失敗しました。\n");
			abort();
		}

		/* 変換単位はnBlockAlignの整数倍なので、一部未変換はありえないはず。 */
		if(stream.cbSrcLengthUsed != stream.cbSrcLength) {
			fprintf(stderr, "### cbSrcLengthUsed != cbSrcLength\n");
			abort();
		}

		/* PCMデータを書き込みます。 */
		retval = fwrite((char*)stream.pbDst, 1, stream.cbDstLengthUsed, pcm_file);
		if(retval != (int)stream.cbDstLengthUsed) {
			fprintf(stderr, "### PCMファイルへ書き込めません。\n");
			abort();
		}
	}

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

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

	/* dataチャンクを抜けます。 */
	retval = mmioAscend(wav_mmio, &ck_data, 0);
	if(retval != 0) {
		fprintf(stderr, "### WAVファイルが不正です。\n");
		abort();
	}

	/* RIFFチャンクを抜けます。 */
	retval = mmioAscend(wav_mmio, &ck_riff, 0);
	if(retval != 0) {
		fprintf(stderr, "### WAVファイルが不正です。\n");
		abort();
	}

	/* PCMファイルを閉じます。 */
	retval = fclose(pcm_file);
	if(retval != 0) {
		fprintf(stderr, "### PCMファイルが閉じられません。\n");
		abort();
	}

	/* WAVファイルを閉じます。 */
	retval = mmioClose(wav_mmio, 0);
	if(retval != 0) {
		fprintf(stderr, "### WAVファイルが閉じられません。\n");
		abort();
	}

	/* WAVヘッダを開放します。 */
	free(wav_fmt);
}

void
pcm2gsm(const char* pcm_path, const char* gsm_path)
{
	int retval;
	FILE *pcm_file, *gsm_file;
	gsm g;
	gsm_frame f;
	gsm_signal s[160];

	/* PCMファイルを開きます。 */
	pcm_file = fopen(pcm_path, "rb");
	if(pcm_file == NULL) {
		fprintf(stderr, "### PCMファイルが開けません。\n");
		abort();
	}

	/* GSMファイルを作成します。 */
	gsm_file = fopen(gsm_path, "wb");
	if(gsm_file == NULL) {
		fprintf(stderr, "### GSMファイルが作成できません。\n");
		abort();
	}

	/* GSMストリームを作成します。 */
	g = gsm_create();
	if(g == NULL) {
		fprintf(stderr, "### gsm_create()が失敗しました。\n");
		abort();
	}

	/* 変換ループ。 */
	for(;;) {
		/* PCMデータを読み込みます。 */
		retval = fread(s, sizeof(gsm_signal), 160, pcm_file);
		if(retval == 0) break; /* もうデータがない */
		memset(&s[retval], 0, sizeof(gsm_signal) * (160 - retval));

		/* 変換します。 */
		gsm_encode(g, s, f);

		/* GSMデータを書き込みます。 */
		retval = fwrite(f, 1, sizeof f , gsm_file);
		if(retval != sizeof f) {
			fprintf(stderr, "### GSMファイルへ書き込めません。\n");
			abort();
		}
	}

	/* GSMストリームを削除します。 */
	gsm_destroy(g);

	/* GSMファイルを閉じます。 */
	retval = fclose(gsm_file);
	if(retval != 0) {
		fprintf(stderr, "### GSMファイルが閉じられません。\n");
		abort();
	}

	/* PCMファイルを閉じます。 */
	retval = fclose(pcm_file);
	if(retval != 0) {
		fprintf(stderr, "### PCMファイルが閉じられません。\n");
		abort();
	}
}
