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

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

#ifdef SAP_IO_READ /* I/O READ対応時のみ */
unsigned char
sap_m6502_in(M6502* m6502, unsigned short addr)
{
	SAPDRIVER* sap = (SAPDRIVER*)m6502;
	unsigned char* mem = sap->mem;
	POKEY* pokey = &sap->pokey;

	/* 0xd200-0xd20f: POKEY */
	if(addr >= 0xd200 && addr <= 0xd20f) {
		return pokey_read(pokey, addr - 0xd200);
	}

	return mem[addr];
}
#endif /*SAP_IO_READ*/

void
sap_m6502_out(M6502* m6502, unsigned short addr, unsigned char data)
{
	SAPDRIVER* sap = (SAPDRIVER*)m6502;
	unsigned char* mem = sap->mem;
	POKEY* pokey = &sap->pokey;

	/* 0xd200-0xd20f: POKEY */
	if(addr >= 0xd200 && addr <= 0xd20f) {
		pokey_write(pokey, addr - 0xd200, data);
		return;
	}

	mem[addr] = data;
}

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

/* 初期化ルーチン/演奏ルーチンの呼び出し元プログラム */
static const unsigned char sap_program[] = {
	'\x20',		/* +0: JSR Absolute */
	'\x00',		/* +1: ここに呼び出し先アドレスの下位バイトをセット */
	'\x00',		/* +2: ここに呼び出し先アドレスの上位バイトをセット */
	'\xcb',		/* +3: WAI */
};			/* =4 */
/* 呼び出し元プログラムを転送するアドレス
 * Operating System ROM area(0xf000〜0xffff)の末尾近く、
 * ベクタテーブル(0xfffa〜0xffff)の直前近くに置くことにします。
 */
#define SAP_PROGRAM_ADDRESS	0xfff0 /* 調整可 */
/* 呼び出し先(初期化ルーチン/演奏ルーチン)アドレスをセットするアドレス
 * 呼び出し元プログラムの「JSR Absolute」命令のオペランドとなります。
 */
#define SAP_RUN_PC_ADDRESS	(SAP_PROGRAM_ADDRESS + 1)

void
sap_m6502_setup(SAPDRIVER* sap, int a, int x, int y, int pc)
{
	M6502* m6502 = &sap->m6502;
	unsigned char* mem = sap->mem;

	/* 呼び出し元プログラム経由で、呼び出し先プログラムを呼び出します。 */
	mem[SAP_RUN_PC_ADDRESS + 0] = pc & 0xff;
	mem[SAP_RUN_PC_ADDRESS + 1] = pc >> 8;
	m6502->a = a;
	m6502->x = x;
	m6502->y = y;
	m6502->pc = SAP_PROGRAM_ADDRESS;
	m6502->sp = 0xff;
	m6502->wait = 0;  /* 必要!! */
	m6502->cycle = 0; /* 必要!! */
}

int
sap_init(SAPDRIVER* sap, const void* _data, int len, int i_song)
{
	M6502* m6502 = &sap->m6502;
	unsigned char* mem = sap->mem;
	POKEY* pokey = &sap->pokey;
	SAPHEADER* header = &sap->header;
	//
	const unsigned char* data = (const unsigned char*)_data;
	const unsigned char* data_end = data + len; /* SAPデータ終端アドレス */
	unsigned short load_addr;
	unsigned short end_addr;
	int load_size;

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

	/* 初期化ルーチン/演奏ルーチン呼び出し元プログラムを転送します。 */
	memcpy(&mem[SAP_PROGRAM_ADDRESS], sap_program, sizeof sap_program);

	/* ファイルヘッダを解析します。 */
	data = (const unsigned char*)sap_init_header(header, data);
	if(!data) return -1; /* ファイルヘッダ不正 */

	/* 要注意!!ロードブロックは複数あります!! */
	while(data < data_end) {
		/* ロードアドレス範囲を取得します。 */
		load_addr = data[0] | data[1] << 8; data += 2;	/* ロードブロック先頭アドレス */
		end_addr  = data[0] | data[1] << 8; data += 2;	/* ロードブロック終端アドレス(最終バイトを指しています) */
		load_size = (end_addr - load_addr) + 1;		/* ロードブロックサイズ */

		//if(load_size < 0) return -1; /* ロードアドレス不正 */
		//if(data + load_size > data_end) return -1; /* ロードブロックがSAPデータ終端を超過している */
		//↓2004/01/29 Naoyuki Sawa
		//↓稀にロードブロックがファイル終端を超過してしまっているSAPファイルがあります。
		//↓エラー扱いでもいいと思うのですが、既存のSAPプレイヤーでは演奏できているので、
		//↓ロードサイズをファイル終端までに修正して救済することにしました。
		if(data + load_size > data_end) load_size = data_end - data;
		if(load_size < 0) return -1; /* ロードアドレス不正 */

		/* プログラムをロードします。 */
		memcpy(&mem[load_addr], data, load_size);
		data += load_size;
	}

	/* 演奏する曲番号を決定します。 */
	if(i_song < 0) i_song = header->defsong; /* 引数による曲番号指定無し(i_song<0)なら、DEFSONGを使用 */
	if(i_song < 0 || header->songs - 1 < i_song) return -1;

	/* CPUリセット。 */
#ifdef SAP_IO_READ /* I/O READ対応時のみ */
	m6502_reset(m6502, sap_m6502_read, sap_m6502_write);
#else /*SAP_IO_READ*/
	m6502_reset(m6502, NULL, sap_m6502_write);
#endif /*SAP_IO_READ*/

	/* POKEYリセット。 */
	pokey_reset(pokey, SAP_CLOCK);

	/* TYPEによって... */
	switch(header->type) {
	case 'B': /* Any player */
		if(!header->init || !header->player) return -1; /* INIT,PLAYER必須 */
		sap_m6502_setup(sap,
			/*A */i_song,
			/*X */0,
			/*Y */0,
			/*PC*/header->init);
		m6502_run(m6502, SAP_CLOCK); /* 1秒以内に完了すると仮定  */
		if(!m6502->wait) return -1;  /* 完了しなければエラー扱い */
		break;
	case 'C': /* Player from CMC (Chaos Music Composer) */
		if(!header->music || !header->player) return -1; /* MUSIC,PLAYER必須 */
		sap_m6502_setup(sap,
			/*A */0x70,
			/*X */(unsigned char)header->music,
			/*Y */(unsigned char)(header->music >> 8),
			/*PC*/header->player + 3);
		m6502_run(m6502, SAP_CLOCK); /* 1秒以内に完了すると仮定  */
		if(!m6502->wait) return -1;  /* 完了しなければエラー扱い */
		sap_m6502_setup(sap,
			/*A */0x00,
			/*X */i_song,
			/*Y */0,
			/*PC*/header->player + 3);
		m6502_run(m6502, SAP_CLOCK); /* 1秒以内に完了すると仮定  */
		if(!m6502->wait) return -1;  /* 完了しなければエラー扱い */
		break;
	case 'D': /* Digital */
		/* ※TODO: 未対応 */
		return -1;
	case 'S': /* SoftSynth */
		/* ※TODO: 未対応 */
		return -1;
	default:
		return -1; /* TYPE不正 */
	}

	/* 割り込み間隔の出力サンプル計算用DDAセットアップ。
	 * (FASTPLAY=312で1/50秒分、FASTPLAY=156で1/100秒分、...)
	 */
	if(header->fastplay <= 0) return -1;
	sap->samples_dda.u = SAPBUFLEN * header->fastplay / 312;
	sap->samples_dda.n = SAPBUFLEN * header->fastplay % 312;
	sap->samples_dda.d =                               -312;
	sap->samples       = sap->samples_dda.u; /* 初回割り込みまでの出力サンプル数設定 */

	return 0;
}

/* メモリから一行読み込みます。
 * [in]
 *	buf		読み込んだ行を格納するバッファ。
 *	size		バッファサイズ[バイト]。
 *	data		読み込み元メモリアドレス。
 * [out]
 *	戻り値		次の行の開始メモリアドレス。
 *			行が長すぎてバッファサイズを超える場合は失敗と見なし、NULLを返します。
 *	buf[...]	読み込んだ行を格納します。
 *			終端の改行文字{CR,LF}は格納しません。
 * [note]
 *	* sap_init_header()から利用するためだけに用意しました。
 *	  SAPファイルのテキストセクション読み込み用に特化しています。
 *	* 行の終端は{CR,LF}です。'\0'は終端とは見なしません。
 *	  ↓2004/01/27変更
 *	  SAPファイルによって稀にCRだけorLFだけのものがあるので、対応しました。
 *	* バイナリセクションの開始マーク{'\xff','\xff'}は先に判断してください。
 *	  sap_gets()はバイナリセクションの開始を認識しません。
 */
static const char*
sap_gets(char buf[/*size*/], int size, const char* data)
{
	char c;

	/* CRorLFに出会うまでコピーします。 */
	for(;;) {
		if(!size) return NULL; /* 行長すぎ */
		c = *data++;
		if(c == '\xd' || c == '\xa') break;
		*buf++ = c;
		size--;
	}
	*buf = '\0'; /* 終端格納 */

	/* 連続するCRorLFを読み飛ばします。 */
	for(;;) {
		c = *data;
		if(c != '\xd' && c != '\xa') break;
		data++;
	}
	return data;
}

const void*
sap_init_header(SAPHEADER* header, const void* _data)
{
	const char* data = (const char*)_data;
	char buf[256]; /* テキストセクションの各行は最長でも256文字未満と仮定 */

	/* まずクリア。 */
	memset(header, 0, sizeof(SAPHEADER));

	/* 規定値格納。 */
	header->songs = 1; /* 仕様には規定値0と書いてありますが、1の間違いだと思います */
	header->defsong = 0;
	header->fastplay = 312;

	/* シグネチャ検査。 */
	data = sap_gets(buf, sizeof buf, data);
	if(!data) return NULL;
	if(strcmp(buf, "SAP") != 0) return NULL;

	for(;;) {
		/* バイナリセクション開始? */
		if(data[0] == '\xff' && data[1] == '\xff') { data += 2; break; }

		/* 一行読み込み。 */
		data = sap_gets(buf, sizeof buf, data);
		if(!data) return NULL;

		/* 一行解析。 */
#if 0
		if(sscanf(buf, "AUTHOR \"%31[^\"]\"", header->author  ) == 1) continue;
		if(sscanf(buf, "NAME \"%31[^\"]\"",   header->name    ) == 1) continue;
		if(sscanf(buf, "DATE \"%31[^\"]\"",   header->date    ) == 1) continue;
#else		// EPSONライブラリのsscanf()は"[...]"書式に対応していないので↑のコードは使えません。
		// 仕方がないので自力で読み込みます。いずれちゃんとしたsscanf()関数を実装しましょう。
		#define SAPSCANF(IN, TAG, OUT, SIZE) {							\
			unsigned char* in = (IN);							\
			int taglen = strlen(TAG);							\
			int i, c;									\
			if(strncmp(in, (TAG), taglen) == 0) {	/* タグ文字が一致したら... */		\
				in += taglen;			/* タグを飛ばす */			\
				while(isspace(*in)) in++;	/* セパレータを飛ばす */		\
				if(*in != '"') return NULL;	/* 「"」で開始していなければエラー */	\
				in++;				/* 「"」を飛ばす */			\
				for(i = 0; i < (SIZE); i++) {	/* 終端のnulを除いてSIZE[文字]まで */	\
					c = *in++;		/* 一文字読み込み */			\
					if(!c) return NULL;	/* 「"」より先に終端に達したらエラー */	\
					if(c == '"') break;	/* 「"」を見つけたら終端。抜ける */	\
					(OUT)[i] = c;		/* 一文字格納 */			\
				}									\
				(OUT)[i] = '\0';		/* 終端のnulを格納 */			\
				continue;			/* 読み込み成功、次の行へ */		\
			}										\
		}
		SAPSCANF(buf, "AUTHOR", header->author, 31)
		SAPSCANF(buf, "NAME",   header->name,   31)
		SAPSCANF(buf, "DATE",   header->date,   31)
		#undef SAPSCANF
#endif
		if(sscanf(buf, "TYPE %1s",           &header->type    ) == 1) continue;
		if(sscanf(buf, "PLAYER %hx",         &header->player  ) == 1) continue;
		if(sscanf(buf, "MUSIC %hx",          &header->music   ) == 1) continue;
		if(sscanf(buf, "INIT %hx",           &header->init    ) == 1) continue;
		if(sscanf(buf, "SONGS %d",           &header->songs   ) == 1) continue;
		if(sscanf(buf, "DEFSONG %d",         &header->defsong ) == 1) continue;
		if(sscanf(buf, "FASTPLAY %d",        &header->fastplay) == 1) continue;
		if(strcmp(buf, "STEREO") == 0)      { header->stereo = 1;     continue; }
		return NULL; /* タグ不正 */
	}

	return data;
}

int
sap_mix(SAPDRIVER* sap, short* wbuff)
{
	M6502* m6502 = &sap->m6502;
	POKEY* pokey = &sap->pokey;
	SAPHEADER* header = &sap->header;
	//
	int cnt1 = SAPBUFLEN;	/* 残りサンプル数 */
	int cnt2;

	do {
		/* バッファが満たされるか、または割り込み発生までのサンプル数... */
		cnt2 = sap->samples;
		if(cnt2 > cnt1) cnt2 = cnt1;

		/* ミキシングを行います。 */
		pokey_mix(pokey, wbuff, cnt2);
		wbuff += cnt2;
		cnt1 -= cnt2;

		/* 割り込み発生までの残り出力サンプル数を減らして、0になったら... */
		sap->samples -= cnt2;
		if(!sap->samples) {
			/* 次回割り込み発生までの残り出力サンプル数を計算します。 */
			sap->samples = sap->samples_dda.u;
			sap->samples_dda.d += sap->samples_dda.n;
			if(sap->samples_dda.d >= 0) {
				sap->samples_dda.d -= 312;
				sap->samples++;
			}

			/* 割り込み演奏ルーチンを呼びます。 */
			switch(header->type) {
			case 'B': /* Any player */
				sap_m6502_setup(sap,
					/*A */0,
					/*X */0,
					/*Y */0,
					/*PC*/header->player);
				m6502_run(m6502, SAP_CLOCK); /* 1秒以内に完了すると仮定  */
				if(!m6502->wait) return -1;  /* 完了しなければエラー扱い */
				break;
			case 'C': /* Player from CMC (Chaos Music Composer) */
				sap_m6502_setup(sap,
					/*A */0,
					/*X */0,
					/*Y */0,
					/*PC*/header->player + 6);
				m6502_run(m6502, SAP_CLOCK); /* 1秒以内に完了すると仮定  */
				if(!m6502->wait) return -1;  /* 完了しなければエラー扱い */
				break;
			case 'D': /* Digital */
				/* ※TODO: 未対応 */
				return -1;
			case 'S': /* SoftSynth */
				/* ※TODO: 未対応 */
				return -1;
			default:
				return -1; /* TYPE不正 */
			}
		}
	} while(cnt1);

	return 0;
}

int
sap_stream_callback(short* wbuff, int param)
{
	return sap_mix((SAPDRIVER*)param, wbuff);
}

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

int
sap_play(const void* data, int len, int i_song)
{
	static SAPDRIVER sap; /* STATICです! */

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

	/* SAPドライバを初期化します。 */
	if(sap_init(&sap, data, len, i_song) != 0) return -1;

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

	return 0;
}

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

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

