/*****************************************************************************
 *
 *	「ときめきメモリアル」音声データコンバータ
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define USE_FXD	1

/*****************************************************************************
 *
 *			定数マクロ
 *
 *****************************************************************************/

#define	kNumOfSamples	224		/* １ＳＧのサンプル数 */
#define	kNumOfSGs		18		/* １ＳＳ内のＳＧ数 */

/* ADPCMデータの先頭オフセット */
#define XA_DATA_START	(0x44-48)

/* WAVEファイルのデータ開始オフセット */
#define WAV_DATA_START	0x2c

/* 真偽値 */
enum {
	FALSE,
	TRUE
};

/*****************************************************************************
 *
 *			処理マクロ
 *
 *****************************************************************************/

#define	FXD_FxdToPCM(dt)	(max(min((short)((dt)>>16), 32767), -32768))
#define	DblToPCM(dt)		(short)(max(min((dt), 32767), -32768))

#define WHP_READ68_AUTO(fp, dt)		WHP_Read68(dt, sizeof(*(dt)), 1, fp)
#define WHP_WRITE68_AUTO(fp, dt)	WHP_Write68(dt, sizeof(*(dt)), 1, fp)

#define WHP_CNV_SHORT68(dt, ndt) 		WHP_CnvEndianShort((dt), (ndt))
#define WHP_CNV_LONG68(dt, ndt) 		WHP_CnvEndianLong((dt), (ndt))

#if USE_FXD
#define FXD_FxdToPcm16(dt)	(max(min((dt)/2, 32767), -32768))
#define FXD_Pcm16ToFxd(dt)	((long)dt*2)
#endif

/*****************************************************************************
 *
 *			構造体
 *
 *****************************************************************************/

typedef int BOOL;

typedef char SoundGroup[128];

typedef struct SoundSector {
	char		sectorFiller[48];
	SoundGroup	SoundGroups[18];
} SoundSector;


typedef unsigned long DWORD;
typedef unsigned short WORD;

/*	チャンクヘッダ	*/
typedef struct {
	char id[4];
	DWORD size;
} CHK_HD;

/*	ウェーブフォーマットヘッダ	*/
typedef struct {
	WORD formatTag;					/*	フォームタイプ					*/
	WORD nChannels;					/*	チャンネル数					*/
	DWORD nSamplesPerSec;			/*	サンプリングレート				*/
	DWORD nAvgBytesPerSec;			/*	平均バイトレート				*/
	WORD nBlockAlign;				/*	プロックアライメント			*/
} WAVE_FMT;

/*	PCM ファイルのスペック	*/
typedef struct {
	WORD nbitsPerSample;			/*	サンプルサイズ					*/
} WAVE_PCM_SPEC;


#if USE_FXD
typedef	long FXD;
#endif

/*****************************************************************************
 *
 *			変数定義
 *
 *****************************************************************************/

char path[256];
char drive[3];
char dir[256];
char name[256];
char ext[5];

SoundSector ssct;
char decodeBuf[kNumOfSamples*2];

#if USE_FXD

static FXD	K0[4] = {
    0x00000000,
    0x0000F000,
    0x0001CC00,
    0x00018800
};

static FXD	K1[4] = {
    0x00000000,
    0x00000000,
    0xFFFF3000,
    0xFFFF2400
};
#else
static double	K0[4] = {
    0.0,
    0.9375,
    1.796875,
    1.53125
};

static double	K1[4] = {
    0.0,
    0.0,
    -0.8125,
    -0.859375
};
#endif


/*****************************************************************************
 *
 *			関数宣言
 *
 *****************************************************************************/

/* ＸＡファイルの変換 */
BOOL convXaToWave(FILE *adp, char *wave_name, int cn, int fn_s, int fn_e);

/* XAファイルのチェック */
BOOL checkXaHeader(char *xa_hdr);

/* １サウンドセクタを復号化 */
long decodeSoundSect(SoundSector *ssct, FILE *wav);

/* 出力ファイルオープン */
FILE *openWaveFile(char *fname, int cn, int fn);

/* WAVEファイルヘッダ出力 */
void writeWaveHeader(FILE *wav, DWORD cycle, long totalSize);

/*	ユニット番号とサンプル番号からサウンドデータを取得する	*/
signed char	getSoundData(char *buf, long unit, long sample);

/*	ユニット番号からフィルタ値を取得する	*/
signed char	getFilter(char *buf, long unit);

/*	ユニット番号からレンジ値を取得する	*/
signed char	getRange(char *buf, long unit);

#if USE_FXD
/* FXD 型の乗算 */
FXD FXD_FixMul(FXD a, FXD b);
#endif

/*****************************************************************************
 *
 *			関数定義
 *
 *****************************************************************************/

void main(int argc, char *argv[])
{
	FILE *adp;
	int cn, fn;
	static char cmdname[] = "xa2wave";

	fprintf(stderr, "%s Ver. 1.2\n", cmdname);
	if (argc < 3 || argc > 5) {
		fprintf(stderr,
		 "Usage : %s <XA_File> <WAV_File> <取出し音声列> [ｲﾝﾃﾞｯｸｽ]\n", cmdname);
		fprintf(stderr, "取出し音声列：0 から 31\n");
		fprintf(stderr, "ｲﾝﾃﾞｯｸｽ：1以上\n");
		exit(1);
	}

	/* 入力ファイルオープン */
	adp = fopen(argv[1], "rb");
	if (adp == NULL) {
		fprintf(stderr, "入力ファイル'%s'が開けません\n", argv[1]);
		exit(1);
	}
	if (argc > 3) {
		/* 取出し音声列取得 */
		cn = atoi(argv[3]);
		if (cn < 0 || 31 < cn) {
			fprintf(stderr, "取出し音声列は0から31までを指定してください\n");
			exit(2);
		}
	}
	if (argc == 3) {
		/* 全データ変換 */
		for (cn = 0; cn < 32; ++cn) {
			if (convXaToWave(adp, argv[2], cn, 1, 255)) {
				fclose(adp);
				exit(3);
			}
			fseek(adp, 0, SEEK_SET);
		}
	} else if (argc == 4) {
		/* １列変換 */
		convXaToWave(adp, argv[2], cn, 1, 255);
	} else {
		fn = atoi(argv[4]);
		if (fn < 1) {
			fprintf(stderr, "fnは1以上を指定してください\n");
			exit(2);
		}
		convXaToWave(adp, argv[2], cn, fn, fn);
	}
	fclose(adp);
}


/* ＸＡファイルの変換 */
/* エラーがあったらTRUEを返す */
BOOL convXaToWave(FILE *adp, char *wave_name, int cn, int fn_s, int fn_e)
{
	long totalSize;
	BOOL retVal = TRUE;
	int fn;
	FILE *wav;
	char xa_hdr[XA_DATA_START];

	fn = fn_s;
	if (fn_s == fn_e) {
		wav = fopen(wave_name, "wb");
	} else {
		wav = openWaveFile(wave_name, cn, fn);
	}
	if (fread(&xa_hdr, sizeof(xa_hdr), 1, adp) != 1) {
		fprintf(stderr, "入力ファイルが読み込めません\n");
		return retVal;
	}
	if (checkXaHeader(xa_hdr)) {
		fprintf(stderr, "入力ファイルが正しく読み込めません\n");
		return retVal;
	}
	fseek(wav, WAV_DATA_START, SEEK_SET);
	totalSize = 0;
	while (fread(&ssct, sizeof(SoundSector), 1, adp) == 1) {
		if (ssct.sectorFiller[45] == cn) {
			if (ssct.sectorFiller[44] > fn) {
				/* 次の音声 */
				if (ssct.sectorFiller[44] > fn_e) {
					/* 範囲外 */
					break;
				}
				writeWaveHeader(wav, 19200, totalSize);
				fclose(wav);
				/* 次のファイル */
				++fn;
				wav = openWaveFile(wave_name, cn, fn);
				fseek(wav, WAV_DATA_START, SEEK_SET);
				totalSize = 0;
			}
			if (ssct.sectorFiller[44] == fn) {
				totalSize += decodeSoundSect(&ssct, wav);
				retVal = FALSE;
			}
		}
	}
	writeWaveHeader(wav, 19200, totalSize);
	fclose(wav);
	return retVal;
}


/* XAファイルのチェック */
BOOL checkXaHeader(char *xa_hdr)
{
	if (memcmp(xa_hdr, "RIFF", 4)) {
		return TRUE;
	}
	if (memcmp(&xa_hdr[8], "CDXAfmt ", 8)) {
		return TRUE;
	}
	return FALSE;
}


/* １サウンドセクタを復号化 */
long decodeSoundSect(SoundSector *ssct, FILE *wav)
{
	long count, outputBytes;
	signed char snddat, filt, range;
	short decoded;
	long unit, sample;
	long sndgrp;
#if USE_FXD
	FXD t, t1, t2;
	FXD tmp2, tmp3, tmp4, tmp5;
#else
	double t, t1, t2;
	double tmp2, tmp3, tmp4, tmp5;
#endif

	sample = 16;
	outputBytes = 0;
	t1 = t2 = 0;
	for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++) {
		count = 0;
		for (unit = 0; unit < 8; unit++) {
			range = getRange(ssct->SoundGroups[sndgrp], unit);
			filt = getFilter(ssct->SoundGroups[sndgrp], unit);
			for (sample = 0; sample < 28; sample++) {
				snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
#if USE_FXD
				tmp2 = (long)(snddat) << (12 - range);
				tmp3 = FXD_Pcm16ToFxd(tmp2);
				tmp4 = FXD_FixMul(K0[filt], t1);
				tmp5 = FXD_FixMul(K1[filt], t2);
				t = tmp3 + tmp4 + tmp5;
				t2 = t1;
				t1 = t;
				decoded = FXD_FxdToPcm16(t);
#else
				tmp2 = (double)(1 << (12 - range));
				tmp3 = (double)snddat * tmp2;
				tmp4 = t1 * K0[filt];
				tmp5 = t2 * K1[filt];
				t = tmp3 + tmp4 + tmp5;
				t2 = t1;
				t1 = t;
				decoded = DblToPCM(t);
#endif
				decodeBuf[count++] = (char)(decoded & 0x0000ffff);
				decodeBuf[count++] = (char)(decoded >> 8);
			}
		}
		fwrite(decodeBuf, 1, sizeof(decodeBuf), wav);
		outputBytes += count;
	}
	return outputBytes;
}


/* 出力ファイルオープン */
FILE *openWaveFile(char *fname, int cn, int fn)
{
	FILE *wav;

	_splitpath(fname, drive, dir, name, ext);
	sprintf(name, "%.4s%02X%02X", name, cn, fn);
	_makepath(path, drive, dir, name, ".WAV");
	fprintf(stderr, "%s\n", path);
	wav = fopen(path, "wb");
	if (wav == NULL) {
		fprintf(stderr, "出力ファイル'%s'が開けません\n", path);
		exit(1);
	}
	return wav;
}


/* WAVEファイルヘッダ出力 */
void writeWaveHeader(FILE *wav, DWORD cycle, long totalSize)
{
	static CHK_HD rifhd = {{'R','I','F','F'}};
	static char wave_id[] = {'W','A','V','E'};
	static CHK_HD fmthd = {{'f','m','t',' '}};
	static WAVE_PCM_SPEC pcmspec;
	static WAVE_FMT fmt;
	static CHK_HD datahd = {{'d', 'a', 't', 'a'}};

	/*	フォーマット部の作成	*/
	fmthd.size = sizeof(WAVE_FMT)+sizeof(WAVE_PCM_SPEC);
	fmt.formatTag = 1;
	fmt.nChannels = 1;
	fmt.nSamplesPerSec = cycle;
	fmt.nAvgBytesPerSec = cycle * 2;
	fmt.nBlockAlign = 2;
	/*	PCM スペック部の生成	*/
	pcmspec.nbitsPerSample = 16;
	/*	データ部のヘッダの生成	*/
	datahd.size = totalSize;
	/*	RIFF ヘッダ	*/
	rifhd.size = sizeof(wave_id) +
				sizeof(fmthd) + fmthd.size + sizeof(datahd) + datahd.size;

    rewind(wav);
	fwrite(&rifhd, sizeof(rifhd), 1, wav);
	fwrite(wave_id, sizeof(wave_id), 1, wav);
	fwrite(&fmthd, sizeof(fmthd), 1, wav);
	fwrite(&fmt, sizeof(fmt), 1, wav);
	fwrite(&pcmspec, sizeof(pcmspec), 1, wav);
	fwrite(&datahd, sizeof(datahd), 1, wav);
}


/*	ユニット番号とサンプル番号からサウンドデータを取得する	*/
signed char getSoundData(char *buf, long unit, long sample)
{
	signed char ret;
	char *p;
	long offset, shift;

	p = buf;
	shift = (unit%2) * 4;

	offset = 16 + (unit / 2) + (sample * 4);
	p += offset;

	ret = (*p >> shift) & 0x0F;

	if (ret > 7) {
		ret -= 16;
	}
	return ret;
}


/*	ユニット番号からフィルタ値を取得する	*/
signed char	getFilter(char *buf, long unit)
{
	signed char ret;
	char *p;
	long offset;

	p = buf;

	offset = 4 + unit;
	p += offset;

	ret = (*p >> 4) & 0x0F;

	return ret;
}


/*	ユニット番号からレンジ値を取得する	*/
signed char getRange(char *buf, long unit)
{
	signed char ret;
	char *p;
	long offset;

	p = buf;

	offset = 4 + unit;
	p += offset;

	ret = *p & 0x0F;

	return ret;
}


#if USE_FXD

/* FXD 型の乗算 */
FXD FXD_FixMul(FXD a, FXD b)
{
    long		high_a, low_a, high_b, low_b;
    long		hahb, halb, lahb;
    unsigned long	lalb;
    FXD			ret;

    high_a = a >> 16;
    low_a = a & 0x0000FFFF;
    high_b = b >> 16;
    low_b = b & 0x0000FFFF;

    hahb = (high_a * high_b) << 16;
    halb = high_a * low_b;
    lahb = low_a * high_b;
    lalb = (unsigned long)(low_a * low_b) >> 16;

    ret = hahb + halb + lahb + lalb;

    return ret;
}
#endif

