/*
 *	main.c
 *
 *	dwav2gsm - WAV to GSM コンバータ
 *	Copyright (C) 2002 Naoyuki Sawa
 *
 *	* Mon Jun 22 22:44:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。（旧版を捨てて完全に作り直し）
 *	* Sun Jul 21 09:58:00 JST 2002 Naoyuki Sawa
 *	- acmStreamConvert()へのACM_STREAMCONVERTF_START/END指示を追加。
 */
#define STRICT
#include <windows.h>
#include <mmreg.h>
#include <msacm.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "msacm32.lib")
/* コマンドライン引数を展開するために、
 * [プロジェクトの設定]->[リンク]->[一般]->[オブジェクト/ライブラリモジュール]
 * で、setargv.objもリンクしてください
 */
#include "gsm/gsm.h"

#define VERSION		"20020721"

#define DEFAULT_RATE	8000	/* GSM標準の変換レート      */
#define CONVERT_UNIT	65536	/* 何バイトづつ変換するか？ */
#define PROGRESS	'.'	/* プログレス文字 */

int rate = DEFAULT_RATE;	/* 変換レート     */

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

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

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);
	exit(1);
}

/****************************************************************************
 *	WAV
 ****************************************************************************/

typedef struct WAV {
	WAVEFORMATEX* fmt;	/* フォーマット */
	int c_data;		/* データサイズ */
	char* data;		/* データ       */
} WAV;

WAV*
wav_new()
{
	WAV* wav;
	wav = (WAV*)calloc(1, sizeof(WAV));
	if(wav == NULL) die("メモリ不足です。");
	return wav;
}

void
wav_free(WAV* wav)
{
	if(wav == NULL) return;
	if(wav->fmt != NULL) free(wav->fmt);
	if(wav->data != NULL) free(wav->data);
}

WAV*
wav_load(const char* path)
{
	int retval;
	WAV* wav;
	HMMIO mmio;
	MMCKINFO ck1, ck2;

	/* WAV構造体を作成します。 */
	wav = wav_new();

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

	/* 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->fmt = (WAVEFORMATEX*)malloc(ck2.cksize);
	if(wav->fmt == NULL) die("メモリ不足です。");
	retval = mmioRead(mmio, (char*)wav->fmt, ck2.cksize);
	if(retval != (int)ck2.cksize) 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*)malloc(wav->c_data);
	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()が失敗しました。");

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

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

	return wav;
}

/****************************************************************************
 *	wav2wav
 ****************************************************************************/

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

	wav2->c_data = 0; /* 念のため */
	free(wav2->data); /* 念のため */

	/* 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*)malloc(unit1);
	stream.cbSrcLength = unit1;
	stream.pbDst = (unsigned char*)malloc(unit2);
	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)) {
			putc(PROGRESS, stderr); /* プログレス表示 */
			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()が失敗しました。");
}

/****************************************************************************
 *	wav2gsm
 ****************************************************************************/

/* wav2->fmtは使いません */
void
wav2gsm(WAV* wav1, WAV* wav2)
{
	int ofs, len, cap;
	gsm g;
	gsm_frame f;
	gsm_signal s[160];

	wav2->c_data = 0; /* 念のため */
	free(wav2->data); /* 念のため */

	/* GSMストリームを作成します。 */
	g = gsm_create();
	if(g == NULL) die("gsm_create()が失敗しました。");

	/* 変換ループ。 */
	ofs = 0;
	cap = 0;
	while(ofs < wav1->c_data) {
		/* 変換元のデータを読み込みます。 */
		len = wav1->c_data - ofs;
		if(len > sizeof s) len = sizeof s;
		memcpy((char*)s      , wav1->data + ofs,            len);
		memset((char*)s + len, 0               , sizeof s - len);
		ofs += len;

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

		/* 変換後のデータを書き込みます。 */
		if(cap < (int)(wav2->c_data + sizeof f)) {
			putc(PROGRESS, stderr); /* プログレス表示 */
			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 + sizeof f));
		}
		memcpy(wav2->data + wav2->c_data, f, sizeof f);
		wav2->c_data += sizeof f;
	}

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

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

int
main(int argc, char* argv[])
{
	int retval, i, c_fmt;
	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 gsm_path[_MAX_PATH];
	WAV *wav1, *wav2;
	FILE* fp;

	/* 引数がなければ、使い方を表示します。 */
	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;
		}
		i++;
	}

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

	/* 引数に指定された全ファイルを処理します。 */
	while(i < argc) {
		p = argv[i];

		/* WAVファイル名を解析。 */
		strcpy(wav_path, p);
		_splitpath(wav_path, wav_drive, wav_dir, wav_fname, wav_ext);
		if(wav_ext[0] == '\0') { /* 拡張子がなければ".wav"を付加 */
			strcpy(wav_ext, ".wav");
			_makepath(wav_path, wav_drive, wav_dir, wav_fname, wav_ext);
		}
		fprintf(stderr, "%s ", wav_path); /* 処理中のWAVファイル名表示 */

		/* GSMファイル名を作成。 */
		_makepath(gsm_path, wav_drive, wav_dir, wav_fname, ".gsm");

		/* WAVファイルを読み込みます。 */
		wav1 = wav_load(wav_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()が失敗しました。");
			wav2wav(wav1, wav2);
			wav_free(wav1); /* 変換元のWAVを開放             */
			wav1 = wav2;    /* 変換後のWAVを次の変換元に設定 */
			fprintf(stderr, " ");
		}

		/* 最終的なPCM形式に変換します。 */
		wav2 = wav_new();
		wav2->fmt = (WAVEFORMATEX*)calloc(1, c_fmt);
		if(wav2->fmt == NULL) die("メモリ不足です。");
		wav2->fmt->wFormatTag = WAVE_FORMAT_PCM;	/* リニアPCM             */
		wav2->fmt->nChannels = 1;			/* モノラル              */
		wav2->fmt->nSamplesPerSec = rate;		/* (8KHz)                */
		wav2->fmt->nAvgBytesPerSec = rate * 2 * 1;	/* (8KHz),16bit,モノラル */
		wav2->fmt->nBlockAlign = 2 * 1;			/* 16bit,モノラル        */
		wav2->fmt->wBitsPerSample = 16;			/* 16bit                 */
		wav2wav(wav1, wav2);
		wav_free(wav1); /* 変換元のWAVを開放             */
		wav1 = wav2;    /* 変換後のWAVを次の変換元に設定 */
		fprintf(stderr, " ");

		/* GSM形式に変換します。 */
		wav2 = wav_new();
		wav2gsm(wav1, wav2);

		/* GSMファイルを書き込みます。 */
		fp = fopen(gsm_path, "wb");
		if(fp == NULL) die("%sが作成できません。", gsm_path);
		retval = fwrite(wav2->data, 1, wav2->c_data, fp);
		if(retval != wav2->c_data) die("fwrite()が失敗しました。");
		retval = fclose(fp);
		if(retval != 0) die("fclose()が失敗しました。");

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

		fprintf(stderr, " OK\n");

		i++;
	}

	return 0;
}

