/*	
 *	clipay.c
 *
 *	P/ECE AY Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2003 Naoyuki Sawa
 *
 *	* Sat Nov 22 06:00:00 JST 2003 Naoyuki Sawa
 *	- 作成開始。
 *	* Sat Feb 07 22:29:00 JST 2004 Naoyuki Sawa
 *	- PSG(AY-3-8910)エミュレーションを、clippsg.*からclipay3.*に変更しました。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

/* 要注意!!
 * FFFD: WR=レジスタ選択、RD=データ読み込み
 * BFFD:                  WR=データ書き込み
 * データ読み書きポートが異なることに注意!!
 */

unsigned char
ay_z80_in(Z80* z80, unsigned short addr)
{
	AYDRIVER* ay = (AYDRIVER*)z80;
	AY38910* ay38910 = &ay->ay38910;
	unsigned char n;
	switch(addr) {
	case 0xfffd:
		n = ay38910_read(ay38910);
		break;
	default:
		TRACE("unknown in(%04x)\n", addr);
		n = 0xff;
		break;
	}
	return n;
}

void
ay_z80_out(Z80* z80, unsigned short addr, unsigned char data)
{
	AYDRIVER* ay = (AYDRIVER*)z80;
	AY38910* ay38910 = &ay->ay38910;
	switch(addr) {
	case 0xbffd:
		ay38910_write(ay38910, data);
		break;
	case 0xfffd:
		ay38910_address(ay38910, data);
		break;
	default:
		TRACE("unknown out(%04x, %02x)\n", addr, data);
		break;
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

int
ay_init(AYDRIVER* ay, const void* data, int i_song)
{
	static const unsigned char player[]= {
		0xf3,		/* + 0,1:   DI         */
		0xcd, 0, 0,	/* + 1,3:   CALL init  */
				/*        loop:        */
		0xed, 0x56,	/* + 4,2:   IM 1       */
		0xfb,		/* + 6,1:   EI         */
		0x76,		/* + 7,1:   HALT       */
		0xcd, 0, 0,	/* + 8,3:   CALL inter */
		0x18, 0xf7,	/* +11,2:   JR loop    */
	};			/* =13 */

	const AYHEADER* header;
	const AYSONGSTRUCTURE* song_structure;
	const AYSONGDATA* song_data;
	const AYPOINTDATA* point_data;
	const AYBLOCKDATA* block_data;
	int init, inter, addr, len;
	unsigned char* ptr;
	Z80* z80;
	AY38910* ay38910;

	/* ヘッダを検査します。 */
	header = (const AYHEADER*)data;
	if(memcmp(header->FileID, "ZXAY", 4) != 0) return -1;
	if(memcmp(header->TypeID, "EMUL", 4) != 0) return -1;
	if(i_song < 0 || header->NumOfSongs < i_song) return -1;

	song_structure = (AYSONGSTRUCTURE*)AYPTR(header->PSongsStructure) + i_song;
	song_data = (AYSONGDATA*)AYPTR(song_structure->PSongData);
	point_data = (AYPOINTDATA*)AYPTR(song_data->PPoints);
	block_data = (AYBLOCKDATA*)AYPTR(song_data->PAddresses);

	/* メモリ全体を規定値でクリアします。 */
	memset(ay->mem + 0x0000, 0xc9, 0x0100);
	memset(ay->mem + 0x0100, 0xff, 0x3f00);
	memset(ay->mem + 0x4000, 0x00, 0xc000);
	ay->mem[0x0038] = 0xfb; /* EI */

	/* メモリ先頭に既定のプログラムをコピーします。 */
	if(point_data->Init) {
		init = AYHALF(point_data->Init);
	} else {
		init = AYHALF(block_data->Address);
	}
	if(point_data->Inter) {
		inter = AYHALF(point_data->Inter);
		memcpy(ay->mem, player, sizeof player);
		memcpy(ay->mem + 2, &init , 2); /* +1,3: CALL *init*  */
		memcpy(ay->mem + 9, &inter, 2); /* +8,3: CALL *inter* */
	} else {
		return -1; /* TODO: 未対応 */
	}

	/* AYファイル内のブロックデータをコピーします。 */
	while(block_data->Address) {
		addr = AYHALF(block_data->Address);
		len  = AYHALF(block_data->Length);
		ptr  = AYPTR(block_data->Offset);
		if(addr + len > 0x10000) return -1;
		memcpy(ay->mem + addr, ptr, len);
		block_data++;
	}

	/* Z80を初期化します。 */
	z80 = &ay->z80;
	/* メインメモリREAD/WRITEは、Z80コア内蔵の外部関数をそのまま使います。 */
	z80_reset(z80, NULL, NULL, ay_z80_in, ay_z80_out);
	z80->af  = z80->bc  = z80->de  = z80->hl  = 
	z80->af2 = z80->bc2 = z80->de2 = z80->hl2 = 
	z80->ix = z80->iy = song_data->HiReg << 8 | song_data->LoReg;
	z80->sp = AYHALF(point_data->Stack);

	/* AY-3-8910を初期化します。 */
	ay38910 = &ay->ay38910;
	ay38910_reset(ay38910, AY_ZX_AY38910_CLOCK, NULL, NULL);

	return 0;
}

int
ay_stream_callback(short* wbuff, int param)
{
	AYDRIVER* ay = (AYDRIVER*)param;

	z80_run(&ay->z80, AY_ZX_Z80_CLOCK / 50);
	z80_int(&ay->z80, 0);
	ay38910_mix(&ay->ay38910, wbuff, AYBUFLEN);

	return 0;
}

/****************************************************************************
 *	
 ****************************************************************************/

int
ay_play(const void* data, int i_song)
{
	static AYDRIVER ay; /* STATICです! */

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

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

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

	return 0;
}

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

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

void
ay_dump(const void* data)
{
	AYHEADER* header = (AYHEADER*)data;
	AYSONGSTRUCTURE* song_structure;
	AYSONGDATA* song_data;
	AYPOINTDATA* point_data;
	AYBLOCKDATA* block_data;
	int i_song;

	TRACE("FileID		%.4s\n",	header->FileID);
	TRACE("TypeID		%.4s\n",	header->TypeID);
	TRACE("FileVersion	%d\n",		header->FileVersion);
	TRACE("PlayerVersion	%d\n",		header->PlayerVersion);
	TRACE("PSpecialPlayer	%04x\n",	AYHALF(header->PSpecialPlayer));
	TRACE("PAuthor		%s\n",		AYPTR(header->PAuthor));
	TRACE("PMisc		%s\n",		AYPTR(header->PMisc));
	TRACE("NumOfSongs	%d\n",		header->NumOfSongs);
	TRACE("FirstSong	%d\n",		header->FirstSong);
	TRACE("PSongsStructure	%04x\n",	AYHALF(header->PSongsStructure));

	song_structure = (AYSONGSTRUCTURE*)AYPTR(header->PSongsStructure);
	for(i_song = 0; i_song < header->NumOfSongs + 1; i_song++) {
		TRACE("========== Song#%2d ==========\n", i_song);
		TRACE("PSongName	%s\n",		AYPTR(song_structure->PSongName));
		TRACE("PSongData	%04x\n",	AYHALF(song_structure->PSongData));

		song_data = (AYSONGDATA*)AYPTR(song_structure->PSongData);
		TRACE("AChan		%d\n",		song_data->AChan);
		TRACE("BChan		%d\n",		song_data->BChan);
		TRACE("CChan		%d\n",		song_data->CChan);
		TRACE("Noise		%d\n",		song_data->Noise);
		TRACE("SongLength	%04x\n",	AYHALF(song_data->SongLength));
		TRACE("FadeLength	%04x\n",	AYHALF(song_data->FadeLength));
		TRACE("HiReg		%d\n",		song_data->HiReg);
		TRACE("LoReg		%d\n",		song_data->LoReg);
		TRACE("PPoints		%04x\n",	AYHALF(song_data->PPoints));
		TRACE("PAddresses	%04x\n",	AYHALF(song_data->PAddresses));

		point_data = (AYPOINTDATA*)AYPTR(song_data->PPoints);
		TRACE("Stack		%04x\n",	AYHALF(point_data->Stack));
		TRACE("Init		%04x\n",	AYHALF(point_data->Init));
		TRACE("Inter		%04x\n",	AYHALF(point_data->Inter));

		block_data = (AYBLOCKDATA*)AYPTR(song_data->PAddresses);
		while(block_data->Address) {
			TRACE("Address		%04x\n",	AYHALF(block_data->Address));
			TRACE("Length		%04x\n",	AYHALF(block_data->Length));
			TRACE("Offset		%04x\n",	AYHALF(block_data->Offset));
			block_data++;
		}

		song_structure++;
	}
}

