/*	
 *	framgym.c
 *
 *	P/ECE GYM Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Thu Dec 9 18:16:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 *	* Fri Apr 01 21:48:44 JST 2016 Naoyuki Sawa
 *	- gym_mix()内でARRAY_SIZE()の引数にポインタを指定していたバグを修正しました。
 *	  ASSERT()内だけのバグであり動作には問題有りません。
 *	  P/ECE用ではASSERT()を展開しないのでこれまで気付きませんでした。
 *	  本日ARRAY_SIZE()マクロを変更した事によってコンパイルエラーとして検出出来ました。
 *	  詳細はclipmisc.hの同日のコメントを参照して下さい。
 */
#include "clip.h"

/* * YM2612構造体を高速RAMに置かないと、速度が間に合いません。
 * - 本当はconstではありませんが、.codeセクションに配置するため、偽のconstとしてあります。
 *   (高速RAMに配置指定したモジュールの.codeセクションのみが、実際に高速RAMに置かれます。)
 * - P/ECEは.code,.data,.bssすべてをRAMに展開して実行するので、constでも書き換え可能です。
 *   Win32もconst変数を書き換えできるようですが、念のためWin32ではconstを外しました。
 */
#ifdef PIECE
const YM2612 gym_ym2612 = { 0 }; /* 初期化子必須。初期化子が無いと.bssに入ってしまう */
#else /*PIECE*/
      YM2612 gym_ym2612;
#endif /*PIECE*/

/****************************************************************************
 *	GYMドライバ内部関数
 ****************************************************************************/

/* * Sun Dec 12 10:44:00 JST 2004 Naoyuki Sawa
 * - まだ高速RAMにやや余裕があるので、gym_mix()も高速RAMへ移しました。
 *   もしも今後高速RAMが不足したら、gym_mix()はSRAMに戻してください。
 *   高速化が必要な関数の中で、gym_mix()が一番重要度が低いはずです。
 */
int
gym_mix(GYMDRIVER* driver, short wbuff[/*GYMBUFLEN*/])
{
	int result = -1;
	//
	static short dacbuf[GYMBUFLEN_DAC]; /* スタック溢れ防止のためSTATIC */
	int daccnt = 0;
	//
	YM2612* const ym2612 = driver->ym2612;
	SN76489* const sn76489 = driver->sn76489;
	unsigned char* const datbuf = driver->datbuf;
	short (*tmpbuf/*[3]*/)[GYMBUFLEN + GYMBUFLEN_60] = driver->tmpbuf;
	//
	int datlen = driver->datlen; /* 取り出し */
	int datpos = driver->datpos; /* 取り出し */
	int tmpcnt = driver->tmpcnt; /* 取り出し */
	//
	int op;
	int reg;
	int data;

	while(tmpcnt < GYMBUFLEN) {

		/* バッファ内の残りデータが少なくなったら、次のデータを読み込みます。 */
		ASSERT(datlen >= datpos);
		if(datlen - datpos < 3/*1命令当りの最大バイト数*/) {
			datlen -= datpos;
			memmove(datbuf, &datbuf[datpos], datlen);
			datpos = 0;
			datlen += fread(&datbuf[datlen], 1, BLOCKSIZE, driver->fp);

			/* ファイル終端ならば... (バッファ内の残りデータが0バイトで、かつ、ファイルから読めない場合)
			 * 注意!! ファイルから1バイト以上読めたかどうかだけで判断してはダメ!!
			 *        バッファに1〜2バイトの有効データしか残っていなくて、もうファイルから読めなくても、
			 *        その1〜2バイトが正しいコマンドならば、まだファイル終端まで処理していないからです。
			 */
			if(!datlen) {
				/* ループ開始位置未検出ならば、ループしません。 */
				if(!driver->looppos) {
					goto L_EXIT; /* 非ループ曲 */
				}
				/* ループ開始位置へシークします。 */
				fseek(driver->fp, driver->looppos, SEEK_SET);
				/* ループ開始位置からもういちどファイル読み込み。 */
				datlen += fread(&datbuf[datlen], 1, BLOCKSIZE, driver->fp);
				/* それでも読めなければ、ループしません。(ファイル末尾に0バイト区間のループ設定!?) */
				if(!datlen) {
					goto L_EXIT; /* ループ不正 */
				}
			}
		}

		op = datbuf[datpos++];
		switch(op) {
		case 0: /* 1/60[sec]経過 (引数無し)*/

			/* 1/60[sec]分ミキシングします。 */
//{{2016/04/01修正:gym_mix()内でARRAY_SIZE()の引数にポインタを指定していたバグを修正しました。ASSERT()内だけのバグであり動作には問題有りません。P/ECE用ではASSERT()を展開しないのでこれまで気付きませんでした。本日ARRAY_SIZE()マクロを変更した事によってコンパイルエラーとして検出出来ました。詳細はclipmisc.hの同日のコメントを参照して下さい。
//			ASSERT(tmpcnt + GYMBUFLEN_60 <= ARRAY_SIZE(tmpbuf));
//↓2016/04/01修正:gym_mix()内でARRAY_SIZE()の引数にポインタを指定していたバグを修正しました。ASSERT()内だけのバグであり動作には問題有りません。P/ECE用ではASSERT()を展開しないのでこれまで気付きませんでした。本日ARRAY_SIZE()マクロを変更した事によってコンパイルエラーとして検出出来ました。詳細はclipmisc.hの同日のコメントを参照して下さい。
			ASSERT((tmpcnt + GYMBUFLEN_60) < (GYMBUFLEN + GYMBUFLEN_60));	//tmpcntは現在の書き込み位置を指しており(tmpcnt<GYMBUFLEN)であるから、ASSERT()の条件式は'(tmpcnt+GYMBUFLEN_60)<=(GYMBUFLEN+GYMBUFLEN_60)'ではなく'(tmpcnt+GYMBUFLEN_60)<(GYMBUFLEN+GYMBUFLEN_60)'です。
//}}2016/04/01修正:gym_mix()内でARRAY_SIZE()の引数にポインタを指定していたバグを修正しました。ASSERT()内だけのバグであり動作には問題有りません。P/ECE用ではASSERT()を展開しないのでこれまで気付きませんでした。本日ARRAY_SIZE()マクロを変更した事によってコンパイルエラーとして検出出来ました。詳細はclipmisc.hの同日のコメントを参照して下さい。
			ym2612_mix(ym2612, &tmpbuf[0][tmpcnt], GYMBUFLEN_60);
			sn76489_mix(sn76489, &tmpbuf[1][tmpcnt], GYMBUFLEN_60);
			gym_dacmix(&tmpbuf[2][tmpcnt], dacbuf, daccnt);
			tmpcnt += GYMBUFLEN_60;

			/* ループ開始位置を検出します。(非ループGYMや二周目の判定不要です。clipgym.hのloopcntの説明を参照) */
			if(!--driver->loopcnt) { /* "if(!driver->loopcnt--)"ではダメ!! 非ループ曲の初回に間違ってループ検出してしまう */
				driver->looppos = ftell(driver->fp) - (datlen - datpos);
				//                                    ~~~~~~~~~~~~~~~~~バッファ中のまだ消費していないデータ分戻す。忘れないで!!
				ASSERT(driver->looppos >= 0);
			}

			break;

		case 1: /* YM2612 Part I出力 (引数=レジスタ番号, レジスタ値) */

			if(datlen - datpos < 2) {
				goto L_EXIT; /* データエラー */
			}
			reg  = datbuf[datpos++];
			data = datbuf[datpos++];
			if((reg == 0x2a) && (daccnt < GYMBUFLEN_DAC)) {
				/* * 高速化のため、YM2612 EmulatorのDAC機能は使いません。
				 *   DAC出力を横取りして記録し、GYM Driverにてミキシングします。
				 * * DAC en(0x2b)の設定は横取りせずにYM2612 Emulatorへ渡します。
				 *   DAC使用時は、YM2612のCh6出力をOffにする必要があるからです。
				 */
				dacbuf[daccnt++] = (data - 128) << 7/*DAC音量、調整可*/;
			} else {
				ym2612_write_reg(ym2612, 0, reg, data);
			}

			break;

		case 2: /* YM2612 Part II出力 (引数=レジスタ番号, レジスタ値) */

			if(datlen - datpos < 2) {
				goto L_EXIT; /* データエラー */
			}
			reg  = datbuf[datpos++];
			data = datbuf[datpos++];
			ym2612_write_reg(ym2612, 1, reg, data);

			break;

		case 3: /* SN76489出力 (引数=レジスタ値) */

			if(datlen - datpos < 1) {
				goto L_EXIT; /* データエラー */
			}
			data = datbuf[datpos++];
			sn76489_write(sn76489, (unsigned char)data);

			break;

		default:
			goto L_EXIT; /* データエラー */
		}
	}

	/* 一時バッファから出力バッファへコピーします。 */
	gym_copy(tmpbuf[0], tmpbuf[1], tmpbuf[2], wbuff);

	/* コピーした分、一時バッファを前方へ詰めます。 */
	tmpcnt -= GYMBUFLEN;
	memmove(tmpbuf[0], &tmpbuf[0][GYMBUFLEN], sizeof(short) * tmpcnt);
	memmove(tmpbuf[1], &tmpbuf[1][GYMBUFLEN], sizeof(short) * tmpcnt);
	memmove(tmpbuf[2], &tmpbuf[2][GYMBUFLEN], sizeof(short) * tmpcnt);

	result = 0; /* 成功 */

L_EXIT:
	driver->datlen = datlen;	/* 書き戻し */
	driver->datpos = datpos;	/* 書き戻し */
	driver->tmpcnt = tmpcnt;	/* 書き戻し */

	return result;
}

/* daccntサンプルのDAC出力をGYMBUFLEN_60サンプルに引き伸ばします。 */
#ifndef PIECE
void
gym_dacmix(short dst[/*GYMBUFLEN_60*/], const short src[/*daccnt*/], int daccnt)
{
	int d;
	int n;
	short v;

	n = GYMBUFLEN_60;
	d = 0;
	v = 0; /* daccnt=0の場合のため */
	do {
		d -= daccnt;
		if(d < 0) {
			d += GYMBUFLEN_60;
			v = *src++;
		}
		*dst++ = v;
	} while(--n);
}
#else /*PIECE*/
asm("
	.code
	.align 1
	.global gym_dacmix
gym_dacmix:
	; %r12 = dst
	; %r13 = src
	; %r14 = daccnt
	xld.w %r4, 267			; GYMBUFLEN_60 (★clipgym.hの定義と合っていること!!★)
	ld.w %r5, %r4			; n = GYMBUFLEN_60
	xld.w %r6, 0			; d = 0
	xld.w %r7, 0			; v = 0
gym_dacmix_L10:				; do {
	sub %r6, %r14			;   d -= daccnt
	xjrge gym_dacmix_L20		;   if(d < 0) {
	add %r6, %r4			;     d += GYMBUFLEN_60
	ld.h %r7, [%r13]+		;     v = *src++
gym_dacmix_L20:				;   }
	ld.h [%r12]+, %r7		;   *dst++ = v
	xsub %r5, %r5, 1		; } while(--n)
	xjrne gym_dacmix_L10
	ret
");
#endif /*PIECE*/

/* YM2612:SN76489:DACを適切な比率でミキシングして、出力バッファへコピーします。 */
#ifndef PIECE
void
gym_copy(const short src1[/*GYMBUFLEN*/], const short src2[/*GYMBUFLEN*/], const short src3[/*GYMBUFLEN*/], short dst[/*GYMBUFLEN*/])
{
	int n;
	int v;

	n = GYMBUFLEN;
	do {
		v = (*src1++ >> 0/*調整可*/)	/* YM2612 */
		  + (*src2++ >> 2/*調整可*/)	/* SN76489 */
		  + (*src3++ >> 0/*調整可*/);	/* DAC */
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*dst++ = v;
	} while(--n);
}
#else /*PIECE*/
asm("
	.code
	.align 1
	.global gym_copy
gym_copy:
	; %r12 = src1 (YM2612)
	; %r13 = src2 (SN76489)
	; %r14 = src3 (DAC)
	; %r15 = dst
	xld.w %r4, 320			; GYMBUFLEN
	xld.w %r10, 32767
	not %r11, %r10			; -32768
gym_copy_L10:
	ld.h %r5, [%r12]+		; YM2612  = *src1++
	ld.h %r6, [%r13]+		; SN76489 = *src2++
	ld.h %r7, [%r14]+		; DAC     = *src3++
	xsra %r6, 2			; v = YM2612 + (SN76489 >> 2) + DAC
	add %r5, %r6
	add %r5, %r7
	cmp %r5, %r10
	xjrle.d gym_copy_L20
	cmp %r5, %r11			; *delay*
	ld.w %r5, %r10
gym_copy_L20:
	xjrge.d gym_copy_L30
	sub %r4, 1			; *delay*
	ld.w %r5, %r11
gym_copy_L30:
	ld.h [%r15]+, %r5
	xjrne gym_copy_L10
	ret
");
#endif /*PIECE*/
