/*	
 *	clipspc.c
 *
 *	P/ECE SPC Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Sun Jan 06 06:00:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *	SSMPエミュレーション用外部関数
 ****************************************************************************/

unsigned char
spc_ssmp_in(SSMP* ssmp, unsigned short addr)
{
	SPCDRIVER* spc = (SPCDRIVER*)ssmp;
	unsigned char* mem = spc->mem;
	SPCTIMER* timer;
	//
	int i, count;

	switch(addr) {
	/*========== S-DSP ==========*/
	case 0xf2: /* Register Address */
		return sdsp_addr_read(&spc->sdsp);
	case 0xf3: /* Register Data */
		return sdsp_data_read(&spc->sdsp);
	/*========== Port,Timer ==========*/
	//case 0xf1: /* Control */ WRITE ONLY
	case 0xf4: /* Port-0r */
	case 0xf5: /* Port-1r */
	case 0xf6: /* Port-2r */
	case 0xf7: /* Port-3r */
		/* S-CPU=>S-SMP通知。 */
		return mem[addr];
	//case 0xfa: /* Timer-0 */ WRITE ONLY
	//case 0xfb: /* Timer-1 */ WRITE ONLY
	//case 0xfc: /* Timer-2 */ WRITE ONLY
	case 0xfd: /* Counter-0 */
	case 0xfe: /* Counter-1 */
	case 0xff: /* Counter-2 */
		i = addr - 0xfd;
		timer = &spc->timer[i];
		/* タイマ停止時のカウンタ値は不定です。(0を返すことにします) */
		if(!timer->start) return 0;
		/* 前回からの経過サイクル数を加算します。
		 * cycleはダウンカウンタなので
		 *	前回の値(timer->cycle) ≧ 現在の値(ssmp->cycle)
		 * となっているはずです。
		 */
		timer->progress += timer->cycle - ssmp->cycle;
		/* 経過サイクル数がカウントアップに不足な場合... */
		if(timer->period > timer->progress) {
			/* カウントアップまで仮想的にSLEEPしたと見なし、CPUサイクル数を進めます。 */
			ssmp->cycle -= timer->period - timer->progress;
			/* 仮想的なSLEEPにより、経過サイクル数はぴったり timer->period となります。
			 * 従ってカウントアップ数は1、カウントアップ後の端数サイクルは0となります。
			 */
			timer->progress = 0;
			count = 1;
		/* 経過サイクル数がカウントアップに充分な場合... */
		} else {
			/* カウントアップ数を算出します。 */
			count = (timer->progress / timer->period) & 0xf; /* 4bit Counter */
			/* カウントアップに満たない端数サイクルを書き戻します。 */
			timer->progress %= timer->period;
		}
		/* 次回の経過サイクル数算出のために、現在のサイクル値を保存しておきます。 */
		timer->cycle = ssmp->cycle;
		return count;
//#ifdef SPC_DEBUG
//	default:
//		TRACE("spc_ssmp_in: Unknown I/O READ %04x\n", addr);
//		break;
//#endif /*SPC_DEBUG*/
	}

	return 0;
}

void
spc_ssmp_out(SSMP* ssmp, unsigned short addr, unsigned char data)
{
	SPCDRIVER* spc = (SPCDRIVER*)ssmp;
	unsigned char* mem = spc->mem;
	SPCTIMER* timer;
	//
	int i;

	switch(addr) {
	/*========== S-DSP ==========*/
	case 0xf2: /* Register Address */
		sdsp_addr_write(&spc->sdsp, data);
		return;
	case 0xf3: /* Register Data */
		sdsp_data_write(&spc->sdsp, data);
		return;
	/*========== Port,Timer ==========*/
	case 0xf1: /* Control */
		spc->timer[0].start = (data >> 0) & 1;
		spc->timer[1].start = (data >> 1) & 1;
		spc->timer[2].start = (data >> 2) & 1;
		if(data & 0x10) { mem[0xf4] = mem[0xf5] = 0; } /* Port-0,1r Clear */
		if(data & 0x20) { mem[0xf6] = mem[0xf7] = 0; } /* Port-2,3r Clear */
		return;
	case 0xf4: /* Port-0w */
	case 0xf5: /* Port-1w */
	case 0xf6: /* Port-2w */
	case 0xf7: /* Port-3w */
		/* S-CPU<=S-SMP通知。処理しません。 */
		return;
	case 0xfa: /* Timer-0 */
	case 0xfb: /* Timer-1 */
	case 0xfc: /* Timer-2 */
		i = addr - 0xfa;
		timer = &spc->timer[i];
		timer->period = (data ? data : 0x100) << (i < 2 ? 8   /* Timer-0,1: 分周比256 (2.048MHz=>8KHz) */
								: 5); /* Timer-2  : 分周比32 (2.048MHz=>64KHz) */
		return;
	//case 0xfd: /* Counter-0 */ READ ONLY
	//case 0xfe: /* Counter-1 */ READ ONLY
	//case 0xff: /* Counter-2 */ READ ONLY
//#ifdef SPC_DEBUG
//	default:
//		TRACE("spc_ssmp_out: Unknown I/O WRITE %04x %02x\n", addr, data);
//		break;
//#endif /*SPC_DEBUG*/
	}
}

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

int
spc_init(SPCDRIVER* spc, const void* data)
{
	SSMP* ssmp = &spc->ssmp;
	unsigned char* mem = spc->mem;
	SDSP* sdsp = &spc->sdsp;
	//
	SPCHEADER* header = (SPCHEADER*)data;
	//
	int i;

	/* まずクリアします。 */
	memset(spc, 0, sizeof(SPCDRIVER));

	/* シグネチャ検査。 */
	//                            000000000011111111112222222222333
	//                            012345678901234567890123456789012
	if(memcmp(header->signature, "SNES-SPC700 Sound File Data v0.30", 27) != 0) return -1;
	//                                                      AA
	//                                                      ||
	//                       ここまで検査することにします --++--これ以降は検査しません
	/* minor_verは検査しません。 */

	/* メモリイメージ読み込み。 */
	memcpy(mem, header->main_ram, 0x10000);
	/* ※TODO: extra_ram の扱いは? */

	/* CPU初期化。 */
	ssmp_reset(ssmp, spc_ssmp_read, spc_ssmp_write);
	ssmp->psw = header->psw;
	ssmp->sp = header->sp;
	ssmp->pc = header->pc[0] | header->pc[1] << 8;
	ssmp->ya = header->a | header->y << 8;
	ssmp->x = header->x;

	/* DSP初期化。 */
	sdsp_reset(sdsp, mem);
	for(i = 0; i < 128; i++) { /* DSPレジスタデータを復元 */
		sdsp_addr_write(sdsp, (unsigned char)i);
		sdsp_data_write(sdsp, header->dsp_regs[i]);
	}
	sdsp_addr_write(sdsp, mem[0xf2]); /* DSPレジスタアドレス選択状態を復元 */

	/* Timer初期化 */
	spc_ssmp_write(ssmp, 0xf1, mem[0xf1]);	/* Control */
	spc_ssmp_write(ssmp, 0xfa, mem[0xfa]);	/* Timer-0 */
	spc_ssmp_write(ssmp, 0xfb, mem[0xfb]);	/* Timer-1 */
	spc_ssmp_write(ssmp, 0xfc, mem[0xfc]);	/* Timer-2 */

	return 0;
}

int
spc_mix(SPCDRIVER* spc, short wbuff[/*SPCBUFLEN*/])
{
#define CYCLE	(SPC_CLOCK / 50)	/* CPU実行サイクル数 */

	SSMP* ssmp = &spc->ssmp;
	SDSP* sdsp = &spc->sdsp;
	int cycle;

	/* CPU実行サイクル数を決定。(CYCLE−前回の超過分) */
	cycle = ssmp->cycle + CYCLE;

	/* タイマ経過サイクル数算出のための、基準サイクル値を格納。 */
	spc->timer[0].cycle = cycle;
	spc->timer[1].cycle = cycle;
	spc->timer[2].cycle = cycle;

	/* CPU実行。 */
	ASSERT(!ssmp->sleep); /* 今回はSLEEPで速度を稼ぐ方法を使いません!! */
	ssmp_run(ssmp, cycle);
	ASSERT(!ssmp->sleep); /* 今回はSLEEPで速度を稼ぐ方法を使いません!! */

	/* タイマ経過サイクル数加算。 */
	spc->timer[0].progress += spc->timer[0].cycle - ssmp->cycle;
	spc->timer[1].progress += spc->timer[1].cycle - ssmp->cycle;
	spc->timer[2].progress += spc->timer[2].cycle - ssmp->cycle;

	/* ミキシング。 */
	sdsp_mix(sdsp, wbuff, SPCBUFLEN);

	return 0;

#undef CYCLE
}

int
spc_stream_callback(short* wbuff, int param)
{
	return spc_mix((SPCDRIVER*)param, wbuff);
}

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

int
spc_play(const void* data)
{
	static SPCDRIVER spc; /* STATICです! */

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

	/* SPCドライバを初期化します。 */
	if(spc_init(&spc, data) != 0) return -1;

	/* ストリーム再生を開始します。 */
	stream_play(SPCBUFLEN, spc_stream_callback, (int)&spc, 1/*スタック切換使用*/);

	return 0;
}

void
spc_stop()
{
	/* ストリーム再生を停止します。 */
	stream_stop();

	/* SPCドライバのクリーンアップは不要です。 */
}

