/*	
 *	clipgsm.c
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2002 Naoyuki Sawa
 *
 *	* Sat Oct 12 06:07:00 JST 2002 Naoyuki Sawa
 *	- clippce.cからGSM関数だけを分離しました。
 *	  アプリケーションがこれらの関数を使わなければ、
 *	  これらの関数が利用しているGSMライブラリもリンクされないので、
 *	  GSMを使わないアプリケーションのサイズが小さくなるはずです。
 *	* Thu Oct 24 20:00:00 JST 2002 Naoyuki Sawa
 *	- c33209.hに割り込みベクタ番号を定義したので、それを使うようにしました。
 */
#include "clip.h"

/****************************************************************************
 *	GSM再生
 ****************************************************************************/

#define MAXCH		4	/* チャネル数：P/ECE仕様                    */
#define BLKN		2	/* ブロック数：調整可、2以上。たぶん2で充分 */
#define BLKS		320	/* ブロックサイズ(サンプル数)：調整可、64xN */
#define GSMS		160	/* GSMフレームサイズ(サンプル数)：GSM仕様   */

typedef struct _GSMINFO {
	PCEKSENT old_isr;	/* +0,4:元のサウンド割り込みルーチン       */
	volatile char busy;	/* +4,1:gsm_fill()再入禁止フラグ           */
	volatile char bc;	/* +5,1:空きブロック数                     */
	         char bi;	/* +6,1:空きブロックの先頭インデクス       */
	volatile char loop;	/* +7,1:残りループ回数(現在のループも含む) */
	/* ファイル関連 */
	FILEACC fa;		/* GSMファイル               */
	int ofs;		/* GSMファイルの読み込み位置 */
	/* GSM関連 */
	gsm g;			/* GSMコンバータ               */
	gsm_frame gf;		/* GSMフレーム読み込みバッファ */
	gsm_signal gs[GSMS];	/* GSMフレーム展開バッファ     */
	int rest;		/* GSMフレームの残りサンプル数 */
	/* Wave関連 */
	int ch;			/* 再生チャネル */
	PCEWAVEINFO wi[BLKN];	/* WAVEヘッダ   */
	short wb[BLKN][BLKS];	/* WAVEデータ   */
} GSMINFO;
static GSMINFO gsm_info;

static void gsm_isr();
static void gsm_fill();
static void gsm_fill_busy();
static void gsm_endproc(PCEWAVEINFO* wi);

int
gsm_play(int ch, const char* fname, int loop)
{
	GSMINFO* gi = &gsm_info;

	int retval, i;
	PCEWAVEINFO* wi;

	/* 確実に停止します。 */
	gsm_stop();

	/* 全体をクリアします。 */
	memset(gi, 0, sizeof(GSMINFO));

	/* ファイルを開きます。 */
	retval = pceFileOpen(&gi->fa, fname, FOMD_RD);
	if(retval != 0) {
		return -1;
	}

	/* GSMコンバータを作成します。 */
	gi->g = gsm_create();
	if(gi->g == NULL) {
		pceFileClose(&gi->fa);
		return -1;
	}

	/* 再生チャネルを格納します。 */
	if(ch < 0 || MAXCH - 1 < ch) {
		gsm_destroy(gi->g);
		pceFileClose(&gi->fa);
		return -1;
	}
	gi->ch = ch;

	/* ループ回数を格納します。 */
	gi->loop = loop;

	/* WAVE情報を初期化します。 */
	for(i = 0, wi = gi->wi; i < BLKN; i++, wi++) {
		wi->type = PW_TYPE_16BITPCM; /* 輪にするのでPW_TYPE_CONT不要 */
		wi->pData = &gi->wb[i][0];
		wi->len = BLKS;
		wi->next = &gi->wi[(i + 1) % BLKN]; /* 輪にします */
		wi->pfEndProc = gsm_endproc;
	}

	/* 再生開始前に、全ブロックにデータを満たしておきます。 */
	gi->bc = BLKN;
	gsm_fill();
	gsm_fill_busy(); /* コンパイラの警告抑制 */

	/* サウンド割り込みルーチンをフックし、Wave再生を開始します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。
	 */
DISABLE;
	gi->old_isr = pceVectorSetTrap(TRAP_HDM1, gsm_isr);
	pceWaveDataOut(gi->ch, &gi->wi[0]);
ENABLE;

	return 0;
}

void
gsm_stop()
{
	GSMINFO* gi = &gsm_info;

	/* 再生中でなければ何もしません。 */
	if(gi->old_isr == NULL) return;

	/* WAVE再生を停止し、サウンド割り込みルーチンを元に戻します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。
	 */
DISABLE;
	pceWaveAbort(gi->ch);
	pceVectorSetTrap(TRAP_HDM1, gi->old_isr);
	gi->old_isr = NULL;
ENABLE;

	/* GSMコンバータを削除します。 */
	gsm_destroy(gi->g);

	/* ファイルを閉じます。 */
	pceFileClose(&gi->fa);
}

static void
gsm_isr()
{
	/* [%sp+ 0]:psr
	 * [%sp+ 1]:pc
	 */
	asm("
	pushn %r15
	ld.w %r0, %alr
	ld.w %r1, %ahr
	pushn %r1
	");
	/* [%sp+ 0]:alr
	 * [%sp+ 1]:ahr
	 * [%sp+ 2]:r0
	 * [%sp+ 3]:r1
	 * [%sp+ 4]:r2
	 * [%sp+ 5]:r3
	 * [%sp+ 6]:r4
	 * [%sp+ 7]:r5
	 * [%sp+ 8]:r6
	 * [%sp+ 9]:r7
	 * [%sp+10]:r8
	 * [%sp+11]:r9
	 * [%sp+12]:r10
	 * [%sp+13]:r11
	 * [%sp+14]:r12
	 * [%sp+15]:r13
	 * [%sp+16]:r14
	 * [%sp+17]:r15
	 * [%sp+18]:psr
	 * [%sp+19]:pc
	 */
	asm("
	 ld.w %r0, [%sp+18]		; psr
	xld.w %r3, gsm_info
	 ld.w %r4, [%r3]+		; old_isr
	xld.w %r1, gsm_fill_busy
	xld.w %r2, gsm_isr_ret_busy
	btst [%r3], 0			; busy?
	jrne gsm_isr_busy
	xld.w %r1, gsm_fill
	xld.w %r2, gsm_isr_ret
	bset [%r3], 0			; busy <- 1
gsm_isr_busy:
	pushn %r2
	");
	/* %r3     :&busy
	 * %r4     :old_isr
	 * [%sp+ 0]:psr
	 * [%sp+ 1]:gsm_fill/busy
	 * [%sp+ 2]:gsm_isr_ret/busy
	 * [%sp+ 3]:alr
	 * [%sp+ 4]:ahr
	 * [%sp+ 5]:r0
	 * [%sp+ 6]:r1
	 * [%sp+ 7]:r2
	 * [%sp+ 8]:r3
	 * [%sp+ 9]:r4
	 * [%sp+10]:r5
	 * [%sp+11]:r6
	 * [%sp+12]:r7
	 * [%sp+13]:r8
	 * [%sp+14]:r9
	 * [%sp+15]:r10
	 * [%sp+16]:r11
	 * [%sp+17]:r12
	 * [%sp+18]:r13
	 * [%sp+19]:r14
	 * [%sp+20]:r15
	 * [%sp+21]:psr
	 * [%sp+22]:pc
	 */
	asm("
	jp %r4				; * old_isr->(iret)->gsm_fill/busy->(ret)->gsm_isr_ret/busy
					;   長時間の割り込み禁止を防ぐため、old_isrがretiするときに
					;   割り込み発生時のpsr(たいていIE=1)を復元しています。
					; * 割り込みルーチン内ではフラグを保存する必要があるので、
					;   gsm_isrから帰るときもretではなくretiを使わなければいけません。
					;   つまり、割り込み発生時のpsrを、二回復元しています。
gsm_isr_ret:
	bclr [%r3], 0			; S1C33の関数規約により、r3は保持されているはず。
gsm_isr_ret_busy:
	");
	/* [%sp+ 0]:alr
	 * [%sp+ 1]:ahr
	 * [%sp+ 2]:r0
	 * [%sp+ 3]:r1
	 * [%sp+ 4]:r2
	 * [%sp+ 5]:r3
	 * [%sp+ 6]:r4
	 * [%sp+ 7]:r5
	 * [%sp+ 8]:r6
	 * [%sp+ 9]:r7
	 * [%sp+10]:r8
	 * [%sp+11]:r9
	 * [%sp+12]:r10
	 * [%sp+13]:r11
	 * [%sp+14]:r12
	 * [%sp+15]:r13
	 * [%sp+16]:r14
	 * [%sp+17]:r15
	 * [%sp+18]:psr
	 * [%sp+19]:pc
	 */
	asm("
	popn %r1
	ld.w %ahr, %r1
	ld.w %alr, %r0
	popn %r15
	");
	/* [%sp+ 0]:psr
	 * [%sp+ 1]:pc
	 */
	asm("
	reti
	");
}

static void
gsm_fill()
{
	GSMINFO* gi = &gsm_info;

	int retval, n, c;
	short s, *dst, *src;

	while(gi->bc != 0) {
		/* 空きブロックの先頭アドレスと、展開するGSMサンプル数を初期化します。
		 * * 8KHzのGSMデータを、16KHzのWAVEデータに変換しながら展開するので、
		 *   展開するGSMサンプル数は、ブロック当りのサンプル数の半分となります。
		 */
		n = BLKS / 2;
		dst = &gi->wb[(int)gi->bi][0];

		/* ループ中なら... */
		if(gi->loop != 0) {
			/* WAVEデータを作成します。 */
			do {
				if(gi->rest == 0) {
					/* GSMフレームを読み込みます。 */
					retval = file_read(&gi->fa, gi->gf, gi->ofs, sizeof(gsm_frame));

					/* 次回の読み込み位置を進めます。 */
					gi->ofs += retval;

					/* GSMフレームが正しく読み込めたら... */
					if(retval == sizeof(gsm_frame)) {
						/* GSMフレームを展開します。 */
						retval = gsm_decode(gi->g, gi->gf, gi->gs);
						if(retval != 0) {
							/* 展開に失敗したら、無音にします。(壊れてる？) */
							memset(gi->gs, 0, sizeof(gsm_signal) * GSMS);
						}
					/* ファイル終端なら... */
					} else if(retval == 0) {
						/* loop < 0 ... 無限ループなので、巻き戻す。
						 * loop = 0 ... 既に停止しているので、無音にする。
						 * loop = 1 ... loopを0(=停止)にして、無音にする。
						 * loop > 1 ... loopを1減らして、巻き戻す。
						 */
						if(gi->loop < 0 || (gi->loop != 0 && --gi->loop != 0)) {
							gi->ofs = 0;
							continue;
						} else {
							memset(gi->gs, 0, sizeof(gsm_signal) * GSMS);
						}
					/* GSMフレームが中途半端に読み込めたら... */
					} else {
						/* 無音にします。(壊れてる？) */
						memset(gi->gs, 0, sizeof(gsm_signal) * GSMS);
					}

					/* 展開データの全サンプルが有効です。 */
					gi->rest = GSMS;
				}

				/* GSMフレーム展開バッファ(8KHz)からWAVEデータ(16KHz)へコピーします。 */
				src = &gi->gs[GSMS - gi->rest];
				if(n <= gi->rest) {
					c = n;
					n = 0;
					gi->rest -= c;
				} else {
					c = gi->rest;
					n -= c;
					gi->rest = 0;
				}
				do {
					s = *src++;
					*dst++ = s;
					*dst++ = s;
				} while(--c != 0);
			} while(n != 0);

		/* 既にループ終了していたら... */
		} else {
			/* 無音にします。 */
			do {
				*dst++ = 0;
				*dst++ = 0;
			} while(--n != 0);
		}

		/* 空きブロックの先頭インデクスを進めます。 */
		if(++gi->bi == BLKN) gi->bi = 0;

		/* 空きブロック数を減らします。
		 * * gsm_fill()はgsm_endproc()に割り込まれる可能\性があるので、
		 *   「空きブロック数取得→1減らす→格納」の処理の前後で割り込み制御が必要です。
		 * * gsm_isr()経由でこの関数が呼ばれた時の、割り込み許可状態は不定なので、
		 *   単純なDISABLE/ENABLEではなく、状態退避・禁止・復元を行う必要があります。
		 * * →よく考えたら、gsm_isr()経由で呼ばれた時は、割り込み許可に決まってるような気がしてきました。
		 *   そもそもgsm_isr()は高速DMAの完了割り込みハンドラなのですから、
		 *   gsm_isr()が呼ばれた＝高速DMAの完了割り込みが処理された＝割り込みが許可されている。
		 * * というわけで、単純なDISABLE/ENABLEでも大丈夫かも知れません。
		 *   まあ、状態退避・禁止・復元した方が確実だし、少なくとも害はないので、このままにしておきます。
		 */
		asm("
		pushn %r2
		ld.w %r0, %psr			; %psr保存
		xand %r1, %r0, 0xffffffef	; 割り込み禁止
		ld.w %psr, %r1
		xld.w %r1, gsm_info + 5		; gsm_info.bc--
		ld.b %r2, [%r1]
		sub %r2, 1
		ld.b [%r1], %r2
		ld.w %psr, %r0			; %psr復元
		popn %r2
		");
	}
}

static void
gsm_fill_busy()
{
	/* 何もしません。 */
}

static void
gsm_endproc(PCEWAVEINFO* wi)
{
	GSMINFO* gi = &gsm_info;

	/* 空きブロック数を増やします。
	 * * PCEWAVEINFO.pfEndProcは割り込み許可状態で呼ばれますが、
	 *   サウンドドライバによって再入禁止されているので、
	 *   「gi->bc++」の前後での割り込み制御は必要ありません。
	 * * 再入禁止処理は、sound_isr3()の後半で行われています。
	 *   （snd.c 421〜430行参照、BIOS 1.18現在）
	 */
	gi->bc++;
}
