/*
 *	wav.c
 *
 *	dppcmcnv - WAV to PPD コンバータ
 *	Copyright (C) 2002-2006 Naoyuki Sawa
 *
 *	* Sun Jul 21 04:37:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 */
#include "app.h"

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

/****************************************************************************
 *	wav_new
 ****************************************************************************/

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

/****************************************************************************
 *	wav_free
 ****************************************************************************/

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

/****************************************************************************
 *	wav_load
 ****************************************************************************/

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;
}

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

void
wav_to_wav(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)) {
			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()が失敗しました。");
}
