/*-----------------------------------------------------------------------------
	[CDROM.c]
		CD-ROM^2 僪儔僀僽傪婰弎偟傑偡丅
		Implements the CD-ROM^2 drive hardware.

	Copyright (C) 2004 Ki

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-----------------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vector>

#include "pce.h"
#include "psg.h"
#include "cdrom.h"
#include "adpcm.h"
#include "huc.h"
#include "../cdromif.h"

#define BCD(A)	((A / 10) * 16 + (A % 10))		// convert INT --> BCD
#define INT(B)	((B / 16) * 10 + (B % 16))		// convert BCD --> INT

static int32 CD_DATA_TRANSFER_RATE;

static bool8	bBRAMEnabled;
static uint8	_Port[15];
static uint32	SectorAddr, SectorCount;
static uint8	ReadBuffer[2048];
static uint32	ReadBufferIndex;		// 撉傒弌偟僶僢僼傽偺僀儞僨僢僋僗 
static uint32	ReadByteCount;		// 撉傒弌偣傞僶僀僩悢  拲丗僟僂儞僇僂儞僞 
static int32	CheckCountAfterRead;
static uint32	ResetDelayCount;

// 僐儅儞僪僶僢僼傽摍 
static int32	Command;
static uint32 	ArgsLeft;
static uint8	CmdArgBuffer[10];
static uint32	CmdArgBufferIndex;
static int32 cd_unit_delay;

static bool8	bCommandReset;
static bool8	bCommandReceived;
static bool8	bCommandDone;
static bool8	bDriveBusy;
static bool8	bError;		// set when command execution fail

typedef Blip_Synth<blip_good_quality, 65535> CDSynth;
extern Blip_Buffer sbuf[2];
static CDSynth CDDASynth;
static int16 last_sample[2], Fluffy[2];

static uint8 playMode, searchMode;
static bool8 bRepeat, bInterrupt;

static int16 CDDASectorBuffer[1176];
static uint32 CDDAReadPos;
//static // (used in adpcm.cpp for adpcm volume reduction when playing)
int8 CDDAStatus; // 1 = playing, 0 = stopped, -1 = paused

static int32 CDDADiv;
static uint32 read_sec_start;
static uint32 read_sec;
static uint32 read_sec_end;

static int16 RawPCMVolumeCache[2];

static int32 CurrentCdVolume;
static int32 InitialCdVolume;
static int32 VolumeStep;
static bool8 bFadeIn  = FALSE;
static bool8 bFadeOut = FALSE;

static int32 FadeClockCount;
static int32 FadeCycle;

static void SyncCDVolume(void)
{
	CDDASynth.volume(0.50f * CurrentCdVolume / 65536);
}

static INLINE void CDFADER_AdvanceClock(int32 cycles) // 21477270 / 3 = 7159090 [Hz] ^脗處眉^脭g^脨^脭^脗艞^脠^脗茅^脕B
{
        if (bFadeOut || bFadeIn)
        {
                FadeClockCount += cycles;

                while (FadeClockCount >= FadeCycle * pce_overclocked)
                {
                        FadeClockCount -= FadeCycle * pce_overclocked;

                        if (bFadeOut)
                        {
                                if (CurrentCdVolume > 0)
                                {
                                        CurrentCdVolume -= VolumeStep;
                                        if (CurrentCdVolume < 0)
                                        {
                                                CurrentCdVolume = 0;
                                                bFadeOut = FALSE;
                                        }
					SyncCDVolume();
                                }
                        }
                        else if (bFadeIn)
                        {
                                if (CurrentCdVolume < InitialCdVolume)
                                {
                                        CurrentCdVolume += VolumeStep;
                                        if (CurrentCdVolume > InitialCdVolume)
                                        {
                                                CurrentCdVolume = InitialCdVolume;
                                                bFadeIn = FALSE;
                                        }
					SyncCDVolume();
                                }
                        }
                }
        }
}



static void lba2msf(uint32 lba,uint8* m,uint8* s,uint8*	f)
{
	*m = lba / 75 / 60;
	*s = (lba - *m * 75 * 60) / 75;
	*f = lba - (*m * 75 * 60) - (*s * 75);
}


static uint8 get_first_track(void)
{
	return CDIF_GetFirstTrack();
}


static uint8 get_last_track(void)
{
	return CDIF_GetLastTrack();
}


static uint8 get_track_number_by_msf(uint8 m, uint8 s, uint8 f)
{
	int			firstTrack = get_first_track();
	int			track = get_last_track();
	int			msf;

	while (track >= firstTrack)
	{
		int min, sec, frame;

		CDIF_GetTrackStartPositionMSF(track, min, sec, frame);
		msf = (min << 16) + (sec << 8) + frame;
 
		if (((m << 16) + (s << 8) + f) >= msf)
			return track;
		track--;
	}
	
	return(0);
}

static void update_irq_state()
{
        uint8           irq = _Port[2] & _Port[3] & (0x4|0x8|/*0x10|*/0x20|0x40);
        if (irq != 0)
                HuC6280_IRQBegin(MDFN_IQIRQ2);
        else
                HuC6280_IRQEnd(MDFN_IQIRQ2);
}
static uint8 read_1801(void);

void adpcm_state_notification_callback_function(uint32 adpcmState)
{
	switch (adpcmState)
	{
		case ADPCM_STATE_NORMAL:
			_Port[3] &= ~(4 | 8);
			break;

		case ADPCM_STATE_HALF_PLAYED:
			_Port[3] |= 4;
			_Port[3] &= ~8;
			break;

		case ADPCM_STATE_FULL_PLAYED:
			_Port[3] &= ~4;
			_Port[3] |= 8;
			break;

		case ADPCM_STATE_STOPPED:
			_Port[3] &= ~4;
			_Port[3] |= 8;
			break;
	}
	update_irq_state();
}


/*-----------------------------------------------------------------------------
	[read_1801]
		CD-ROM 偐傜偺僨乕僞傪撉傒弌偡丅
-----------------------------------------------------------------------------*/

int32 needrbcreset;

#define RBCMOO	40

static uint8 read_1801(void)
{
	if (ReadByteCount > 0)
	{
		uint8	ret = ReadBuffer[ReadBufferIndex];
		
		if(!PCE_InDebug)
		{
			ReadBufferIndex++;

			if (--ReadByteCount > 0)
			{
			//_Port[0] = 0xc8;	// data still exist in buffer
			}
			else
			{
				_Port[3] &= ~0x40;	// "data transfer ready" 廔椆 
				if(!SectorCount)
				{
				 _Port[3] |= 0x20;	// data transfer done 
				 update_irq_state();
	                         _Port[0] = 0xd8;        // no more data left in buffer
				}
				else
				{
				 if(PCE_SherlockHack)
				  _Port[0] = 0xe8;
				 else
			          needrbcreset = RBCMOO;
				}
				ReadBufferIndex = 0;
	
				// 撉傒弌偟張棟偑廔傢偭偨屻偵
				// 張棟偑惓忢偵廔椆偟偨偐偳偆偐傪妋擣偡傞偨傔偺
				// 撉傒弌偟偑俀夞峴側傢傟傞丅
	                        if(!SectorCount)
	                         CheckCountAfterRead = 2;
			}
		}
		return ret;
	}
	else	// 僶僢僼傽偺僨乕僞偑慡偰撉傒弌偝傟偨屻偵妋擣偺偨傔偺 $1801 撉傒弌偟偑俀夞峴側傢傟傞丅 
	{
		if(SectorCount)
		{

		}
		else
		{
			if (CheckCountAfterRead == 2)
			{
				if(!PCE_InDebug)
				{
					--CheckCountAfterRead;
					_Port[0] = 0xf8;
				}

				if (bError)
				{
					if(!PCE_InDebug)
					 bError = FALSE;
					return 1;
				}
			}
			else if (CheckCountAfterRead == 1)
			{
				if(!PCE_InDebug)
				{
					--CheckCountAfterRead;
					_Port[0] &= ~0x80;
				}
			}
		}
	}

	return 0;
}


/*-----------------------------------------------------------------------------
	[read_1808]
		CD-ROM 偐傜偺僨乕僞傪撉傒弌偡丅

	[懡暘]
		read_1801 偲偺堘偄偼丄俠俢亅俼俷俵僶僢僼傽撪偺抣傪撉傒廔偊偨屻偺
		俀夞偺撉傒弌偟偺抣傪曉偝側偄揰丅 
-----------------------------------------------------------------------------*/
static uint8 read_1808(void)
{
	if (ReadByteCount > 0)
	{
		uint8	ret = ReadBuffer[ReadBufferIndex];

		if(!PCE_InDebug)
		{
			ReadBufferIndex++;
			if (--ReadByteCount > 0)
			{
			//_Port[0] = 0xc8;	// data still exist in buffer
			}
			else
			{
				_Port[3] &= ~0x40;		// "data transfer ready" 廔椆 
				if(!SectorCount)
				{
				 _Port[3] |= 0x20;		// data transfer done 
				 update_irq_state();
	 			 _Port[0] = 0xd8;	// no more data left in buffer
				}
				else
				{
				 if(PCE_SherlockHack)
				  _Port[0] = 0xe8;
	                         else
				  needrbcreset = RBCMOO;
				}

				ReadBufferIndex = 0;
	
				// 撉傒弌偟張棟偑廔傢偭偨屻偵
				// 張棟偑惓忢偵廔椆偟偨偐偳偆偐傪妋擣偡傞偨傔偺
				// 撉傒弌偟偑俀夞峴側傢傟傞丅
				if(!SectorCount)
				 CheckCountAfterRead = 2;
			}
		}
		return ret;
	}
	else
	{
		if(!PCE_InDebug)
		{
                	if(SectorCount)
	                {
			  _Port[0] = 0xe8;
	                }
			else
			{
			 _Port[0] = 0xd8;	// no more data left in buffer
			}
		}
	}

	return 0;
}

void show_command_string(
	const char*		pCmdName,
	const uint8*	pCmdString)
{
	// This function only used for hardware-level logging
	//
/*		printf("%u %s %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",MDFND_GetTime(),
			pCmdName,
			pCmdString[0],pCmdString[1],pCmdString[2],pCmdString[3],pCmdString[4],
 			pCmdString[5],pCmdString[6],pCmdString[7],pCmdString[8],pCmdString[9]);
*/
}


bool CD_PauseAudioTrack(bool bPause)
{
	int prev = CDDAStatus;
	if(CDDAStatus != 0)
	 CDDAStatus = bPause? -1 : 1;
	if(CDDAStatus == prev) return(0);

        bCommandDone = TRUE;

	if(bPause)
	{
	 _Port[0] = 0xd8;
	 ReadByteCount = 0;
	 CheckCountAfterRead = 2;
	 bError = FALSE;
	}
	return(1);
}


/*-----------------------------------------------------------------------------
	[Init]
-----------------------------------------------------------------------------*/
int32 CDROM_Init()
{
	if (!CDIF_Init())
		return -1;

	CD_DATA_TRANSFER_RATE = 126000 * MDFN_GetSettingUI("pce.cdspeed");

        CurrentCdVolume = InitialCdVolume = 65536;
        VolumeStep      = InitialCdVolume / 100;

        bFadeOut = FALSE;
        bFadeIn  = FALSE;

	ADPCM_SetNotificationFunction(adpcm_state_notification_callback_function);

	read_sec_end = ~0;
	bError = FALSE;
	SyncCDVolume();

	return 0;
}


/*-----------------------------------------------------------------------------
	[Deinit]
-----------------------------------------------------------------------------*/
void CDROM_Deinit(void)
{
	CDIF_Deinit();
}


/*-----------------------------------------------------------------------------
	[Reset]
-----------------------------------------------------------------------------*/
void CDROM_Reset(void)
{
        HuC6280_IRQEnd(MDFN_IQIRQ2);

        CDIF_Deinit();

        bBRAMEnabled = FALSE;
        memset(_Port, 0, sizeof(_Port));

	ResetDelayCount = 10;
        ReadBufferIndex = 0;
        ReadByteCount = 0;

        Command = 0;
        ArgsLeft = 0;
        CmdArgBufferIndex = 0;

        bCommandReset = TRUE;
        bCommandReceived = FALSE;
	bCommandDone = FALSE;

        CheckCountAfterRead = 0;

        bDriveBusy = FALSE;
        bError = FALSE;
	bFadeIn = 0;
	bFadeOut = 0;
	SectorAddr = 0;
	SectorCount = 0;
	CDDAStatus = 0;
	CDDAReadPos = 0;
	CDDADiv = 0;
	read_sec_start = 0;
	read_sec = 0;
	read_sec_end = 0;
	cd_unit_delay = 0;
	needrbcreset = 0;
}

bool CDROM_IsBRAMEnabled()
{
	return bBRAMEnabled;
}

uint8 CDROM_Read(uint32		physAddr)
{
	//printf("Read: %04x %04x\n", physAddr, HuCPU.PC);
	//if(HuCPU.PC == 0x2a72)
	// HuC6280_DumpMem("dmp", 0x2000, 0x3FFF);
	//if(HuCPU.PC == 0x2a72) return(0xc8);
	if ((physAddr & 0x18c0) == 0x18c0)
	{
		switch (physAddr & 0x18cf)
		{
			case 0x18c1:
				return 0xaa;
			case 0x18c2:
				return 0x55;
			case 0x18c3:
				return 0;
			case 0x18c5:
				return 0xaa;
			case 0x18c6:
				return 0x55;
			case 0x18c7:
				return 0x03;
		}
	}
/*
	1800:
		0x80 = BSY
		0x40 = REQ
		0x20 = C/D?
		0x10 = MSG?
		0x08 = I/O?
*/

/*	1802
		0x80 = ACK

*/

	if(!PCE_InDebug)
 	 ADPCM_Update();

	switch (physAddr & 0xf)
	{
		case 0x0:
		{
                        uint8 ret = _Port[0] | 0x40;

                        if (_Port[2] & 0x80)
                        {
                                if (CheckCountAfterRead == 0)
                                {
					if(!PCE_InDebug)
					{
                                         bDriveBusy = FALSE;
                                         update_irq_state();
					 _Port[3] &= ~0x20;
					}
                                }
                                ret = _Port[0] & ~0x40;
                        }
                        else if (bCommandReceived && !bCommandDone)
                        {
                                ret = _Port[0] & ~0x40;
                        }
                        else if (bDriveBusy)
                        {
                                ret = _Port[0] | 0x80;
                        }
                        else if (ResetDelayCount > 0)
                        {
				if(!PCE_InDebug)
                                 --ResetDelayCount;
                                ret = _Port[0] & ~0x40;
                        }
			return(ret);
		}
		case 0x1:
                        {
                         uint8 ret = read_1801();
                         //printf("Read: %04x %04x %02x\n", physAddr, HuCPU.PC, ret);
                         return(ret);
                        }

		case 0x2: // read/write port (control port)
			//printf("Read: %04x %04x %02x\n", physAddr, HuCPU.PC, _Port[2]);
			return _Port[2];

		case 0x3:	// 僶僢僋傾僢僾儊儌儕傪嬛巭偡傞丅 (read only)
					// status-read port
			{
			 uint8 ret;
			 bBRAMEnabled = FALSE;

			 /* switch left/right of digitized cd playback */

			 ret = _Port[3] | 0x10;
			 if(!PCE_InDebug)
			  _Port[3] ^= 2;
			 return(ret);
			}
		case 0x4:
			return _Port[4];

		case 0x5:			// lower 8 bits?
			if(_Port[3] & 0x2)
			 return(RawPCMVolumeCache[1]&0xff);		// Right
			else
			 return(RawPCMVolumeCache[0]&0xff);	// Left
		case 0x6:			// upper 8 bits.
			if(_Port[3] & 0x2)
			 return(((uint16)RawPCMVolumeCache[1]) >> 8);		// Right
			else
			 return(((uint16)RawPCMVolumeCache[0]) >> 8);	// Left
		case 0x7:
			// CD subchannel read
			//puts("subread");
			//if(rand() & 1)
 			// return(0x09);
			//else
			// return(0x01);
			return _Port[7];

		case 0x8:	// CD-ROM 偐傜僙僋僞傪撉傒弌偡丅
			{
			 uint8 ret = read_1808();
                         //printf("Read: %04x %04x %02x\n", physAddr, HuCPU.PC, ret);
			 return(ret);
			}
		case 0xa:
			return ADPCM_ReadBuffer();

		case 0xb:
			return _Port[0xb];

		case 0xc:
			// D0: 尰帪揰偱 ADPCM 偺嵞惗偑廔椆傑偨偼掆巭偝傟偰偄傞応崌偼侾 
			// D2: CD --> DMA 揮憲拞偼僛儘丠 (1: busy prepareing ADPCM data)
			// D3: ADPCM 嵞惗拞偼侾
			// D7: 慜夞偺 $180A 偺撉傒弌偟張棟偱俛倀俽倄偺応崌偼
			// D7 = 1 偲側傞丅
                        {
                         uint8 ret = 0x00;

                         if (!ADPCM_IsPlaying())
                                ret |= 0x1;
                         else
                                ret |= 0x8;
                         ret |= ADPCM_IsWritePending() ? 0x4 : 0x0;
                         ret |= ADPCM_IsBusyReading() ? 0x80 : 0x00;
                         return(ret);
                        }
		case 0xd:
			return(ADPCM_Read180D());
			//return(_Port[0xD]);
			//return(_Port[0xD] & 0x20); // It Came from the Desert
			//return(0); // See "Motoroader MC"
			//return _Port[0xd];
	}

	return 0;
}

static void execute_read_sector()
{
	show_command_string("[READ SECTOR]", CmdArgBuffer);

	CDDAStatus = 0;
	SectorAddr = CmdArgBuffer[1]*65536 + CmdArgBuffer[2]*256 + CmdArgBuffer[3];
	SectorCount = CmdArgBuffer[4];
        cd_unit_delay = (int64)1 * 2048 * 1789772.727272 * 4 * pce_overclocked / CD_DATA_TRANSFER_RATE;
        bCommandDone = TRUE;

	_Port[0] = 0xe8;
        bError = FALSE;

//	printf("%08x %08x\n", SectorAddr, SectorCount);
}


static void execute_cd_playback_start_position()
{
	show_command_string("[PLAY AUDIO1]", CmdArgBuffer);
        searchMode = CmdArgBuffer[1];
	switch (CmdArgBuffer[9] & 0xc0)
	{
		case 0x00:	// 俴俛俙巜掕儌乕僪
			read_sec_start = (CmdArgBuffer[2]<<16)|(CmdArgBuffer[3]<<8)|CmdArgBuffer[4];
			read_sec_start -= 150;
			break;

		case 0x40:	// 俵俽俥巜掕儌乕僪
			read_sec_start = INT(CmdArgBuffer[4]) + 75 * (INT(CmdArgBuffer[3]) + 60 * INT(CmdArgBuffer[2]));
                        read_sec_start -= 150;
			break;

		case 0x80:	// 僩儔僢僋斣崋巜掕儌乕僪
		{
			read_sec_start = CDIF_GetTrackStartPositionLBA(INT(CmdArgBuffer[2]));
			break;
		}

		case 0xc0:	// ???
			break;
	}

	read_sec = read_sec_start;
	CDDAReadPos = 588;
	CDDAStatus = -1;

	bCommandDone = TRUE;

	_Port[0] = 0xd8;
	ReadByteCount = 0;
	CheckCountAfterRead = 2;
	_Port[3] |= 0x20;
	update_irq_state();

	if(searchMode)
	{
	 bDriveBusy = TRUE;
	 CDDAStatus = 1;
	}
	bError = FALSE;
}


static void execute_cd_playback_end_position()
{
	show_command_string("[PLAY AUDIO2]", CmdArgBuffer);
        playMode = CmdArgBuffer[1];
	switch (CmdArgBuffer[9] & 0xc0)
	{
		case 0x00:	// 俴俛俙巜掕儌乕僪
			read_sec_end = (CmdArgBuffer[2]<<16)|(CmdArgBuffer[3]<<8)|CmdArgBuffer[4];
			read_sec_end -= 150;
			break;

		case 0x40:	// 俵俽俥巜掕儌乕僪
			read_sec_end = INT(CmdArgBuffer[4]) + 75 * (INT(CmdArgBuffer[3]) + 60 * INT(CmdArgBuffer[2]));
                        read_sec_end -= 150;
			break;

		case 0x80:	// 僩儔僢僋斣崋巜掕儌乕僪
		{
			read_sec_end = CDIF_GetTrackStartPositionLBA(INT(CmdArgBuffer[2]));
			break;
		}

		case 0xc0:	// 僩儔僢僋廔椆埵抲?? 
			break;
	}

	bRepeat = bInterrupt = FALSE;

	switch (CmdArgBuffer[1])
	{
		case 0x00:		// pause
			break;

		case 0x01:		// repeat play
			bRepeat = TRUE;
			break;

		case 0x02:		// play, IRQ2 when finished ??
			bInterrupt = TRUE;
			break;

		case 0x03:		// play without repeat
			break;
	}

	CDDAStatus = -1;

	if(playMode)
	{
         CDDAReadPos = 588;
	 CDDAStatus = 1;
	 bDriveBusy = TRUE;
	}

	bCommandDone = TRUE;
	_Port[0] = 0xd8;
	ReadByteCount = 0;
	CheckCountAfterRead = 2;
	bError = FALSE;
}


static void execute_pause_cd_playback()
{
	show_command_string("[PAUSE AUDIO]", CmdArgBuffer);
        if (!CD_PauseAudioTrack(TRUE))
        {
                /* play ^脗碌^脗膫垄^脗葌垄^脗苽芦^脗脡 pause ^脗鲁^脗陚絕脧陯嘵脗脤(^脗苽鑲燸脗娄^脗赂^脗脤)^脩螐^脠 */
                /* ^脰{^脫^脰^脗蛢G^脙^脡^脕[^脗冒^脮詡路^脗鎮脗 路^脗讉芦 */
                //bError = TRUE;
                _Port[0] = 0xd8;
                ReadByteCount = 0;
                CheckCountAfterRead = 2;
                bCommandDone = TRUE;
        }
}


static void execute_read_subchannel_q()
{
	//printf("subchannel: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", CmdArgBuffer[0], CmdArgBuffer[1], CmdArgBuffer[2], CmdArgBuffer[3], CmdArgBuffer[4], CmdArgBuffer[5], CmdArgBuffer[6], CmdArgBuffer[7], CmdArgBuffer[8], CmdArgBuffer[9]);
	uint32 lba = read_sec;
	uint32 lba_relative;
	uint32 track;
	uint32 ma,sa,fa;
	uint32 m, s, f;

	fa = (lba + 150) % 75;
	sa = ((lba + 150) / 75) % 60;
	ma = ((lba + 150) / 75 / 60);

	track = get_track_number_by_msf(ma,sa,fa);
	lba_relative = lba - CDIF_GetTrackStartPositionLBA(track);

	f = lba_relative % 75;
	s = (lba_relative / 75) % 60;
	m = (lba_relative / 75 / 60);

	memset(ReadBuffer, 0x00, 10);
	ReadBuffer[2] = BCD(get_track_number_by_msf(ma,sa,fa)); 
	ReadBuffer[4] = BCD(m);
	ReadBuffer[5] = BCD(s);
	ReadBuffer[6] = BCD(f);
	ReadBuffer[7] = BCD(ma);
	ReadBuffer[8] = BCD(sa);
	ReadBuffer[9] = BCD(fa);

        if (CDDAStatus == -1)
                ReadBuffer[0] = 2; // Pause
	else if (CDDAStatus == 1)
		ReadBuffer[0] = 0; // Playing
	else
		ReadBuffer[0] = 3; // Stopped

	ReadByteCount = 10;
	_Port[0] = 0xc8;
	bCommandDone = TRUE;
}


static void execute_get_dir_info()
{
	switch (CmdArgBuffer[1])
	{
		case 0:	// get first and last track number
			ReadBuffer[0] = BCD(get_first_track());
			ReadBuffer[1] = BCD(get_last_track());
			ReadByteCount = 2;
			break;

		case 1:	// get total running time of disc
			lba2msf(CDIF_GetSectorCountLBA() + 150, &ReadBuffer[0], &ReadBuffer[1], &ReadBuffer[2]);
			ReadBuffer[0] = BCD(ReadBuffer[0]);
			ReadBuffer[1] = BCD(ReadBuffer[1]);
			ReadBuffer[2] = BCD(ReadBuffer[2]);
			ReadByteCount = 3;
			break;

		case 2:	// get track starting position and mode
		{
			int		min;
			int		sec;
			int		frame;
			int track = INT(CmdArgBuffer[2]);
			CDIF_Track_Format	format;

			CDIF_GetTrackStartPositionMSF(track, min, sec, frame);
			CDIF_GetTrackFormat(track, format);

			ReadBuffer[0] = BCD(min);
			ReadBuffer[1] = BCD(sec);
			ReadBuffer[2] = BCD(frame);
			ReadBuffer[3] = (format == CDIF_FORMAT_AUDIO) ? 0x00 : 0x04;
			ReadByteCount = 4;
			break;
		}
	}
	_Port[0] = 0xc8;
	bCommandDone = TRUE;
}


static void execute_command(void)
{
	switch (CmdArgBuffer[0])
	{
		case 0x08:	// read sector
			execute_read_sector();
			break;

		case 0xd8:	// set audio playback start position
			execute_cd_playback_start_position();
			break;

		case 0xd9:	// set audio playback end position and start playing
			execute_cd_playback_end_position();
			break;

		case 0xda:	// pause audio
			execute_pause_cd_playback();
			break;

		case 0xdd:	// read Q sub-channel
			execute_read_subchannel_q();
			break;

		case 0xde:	// get dir info
			execute_get_dir_info();
			break;
	}
}


static void receive_command(uint8		data)
{
	if (bCommandReset)
	{
		bCommandDone = FALSE;
		Command = data;

		// 僐儅儞僪宯曄悢傪儕僙僢僩偡傞丅 
		bCommandReset = FALSE;
		bCommandReceived = FALSE;
		CmdArgBufferIndex = 0;
		ArgsLeft = 0;
		ReadBufferIndex = 0;
		ReadByteCount = 0;

		// 僐儅儞僪傪庴偗晅偗傞丅 
		switch (Command)
		{
			case 0x00:	// TEST UNIT READY 
				_Port[0] = 0xd8;	// no more data needed
				ArgsLeft = 0;
				ReadByteCount = 0;
				CheckCountAfterRead = 2;
				break;

			case 0x03:	// REQUEST SENSE
				break;

			case 0x08:	// read sector
				CmdArgBuffer[CmdArgBufferIndex++] = 0x08;
				ArgsLeft = 5;
				break;

			case 0xd8:	// play audio (start position)
				CmdArgBuffer[CmdArgBufferIndex++] = 0xd8;
				ArgsLeft = 9;
				break;

			case 0xd9:	// play audio (end position)
				CmdArgBuffer[CmdArgBufferIndex++] = 0xd9;
				ArgsLeft = 9;
				break;

			case 0xda:	// pause audio 
				CmdArgBuffer[CmdArgBufferIndex++] = 0xda;
				ArgsLeft = 9;
				ReadByteCount = 0;
				CheckCountAfterRead = 2;
				break;

			case 0xdd:
				CmdArgBuffer[CmdArgBufferIndex++] = 0xdd;
				ArgsLeft = 9;
				ReadByteCount = 10;
				break;

			case 0xde:	// get CD directory info
				CmdArgBuffer[CmdArgBufferIndex++] = 0xde;
				ArgsLeft = 9;
				ReadByteCount = 4;
				break;

			default: //CmdArgBuffer[CmdArgBufferIndex++] = Command;
				ArgsLeft = 0;
                	        _Port[0] = 0xd8;
	                        ReadByteCount = 0;
	                        CheckCountAfterRead = 2;
	                        bCommandDone = TRUE;
	                        break;
		}
	}
	else
	{
		// 堷悢傪庴偗晅偗傞 
		CmdArgBuffer[CmdArgBufferIndex++] = data;

		if (ArgsLeft && --ArgsLeft > 0)
		{
			_Port[0] = 0xd0;		// 0xd0: need more data
		}
		else
		{
			execute_command();
			bCommandReceived = TRUE;
		}
	}
}


bool CDROM_DoADFun(uint8 *data)
{
 if(_Port[0xb] & 0x3)
 {
  if(ReadByteCount > 0)
  {
   *data = read_1801();
   if(ReadByteCount <= 0) _Port[0xb] &= ~1;
   return(1);
  }
 }
 return(0);
}

static void CDFADER_FadeOut(int32 ms)
{
        if (ms == 0)
        {
                CurrentCdVolume = 0;
                bFadeOut = FALSE;
                bFadeIn  = FALSE;
                FadeCycle = 0;
		SyncCDVolume();
        }
        else if (CurrentCdVolume > 0)
        {
                FadeCycle = (int32)(((7159090.0 / ((double)CurrentCdVolume / (double)VolumeStep)) * (double)ms) / 1000.0);
                bFadeOut       = TRUE;
                bFadeIn        = FALSE;
        }
        else
        {
                bFadeOut = FALSE;
                bFadeIn  = FALSE;
                FadeCycle = 0;
        }
}

static void CDFADER_FadeIn(int32 ms)
{
        if (ms == 0)
        {
                CurrentCdVolume = InitialCdVolume;
                bFadeOut = FALSE;
                bFadeIn  = FALSE;
                FadeCycle = 0;
		SyncCDVolume();
        }
        else if (InitialCdVolume - CurrentCdVolume > 0)
        {
                FadeCycle = (int32)(((7159090.0 / (((double)InitialCdVolume - (double)CurrentCdVolume) / (double)VolumeStep)) * (double)ms) / 1000.0);
                bFadeOut = FALSE;
                bFadeIn  = TRUE;
        }
        else
        {
                bFadeOut = FALSE;
                bFadeIn  = FALSE;
                FadeCycle = 0;
        }
}


void CDROM_Write(uint32	physAddr, uint8 data)
{
	if(!PCE_InDebug)
	 ADPCM_Update();
	//printf("Write: %04x %02x\n", physAddr, data);
	switch (physAddr & 0xf)
	{
		case 0x0:		// $1800 write: resets the command input
			_Port[0] = 0xd0;	// = need more data
			bCommandReset = TRUE;
			bCommandReceived = FALSE;
			bCommandDone = FALSE;
			bDriveBusy = FALSE;
			ResetDelayCount = 10;
//			MDFN_printf("$1800 <-- $81: command reset?? $1800 = 0xd0");

			/* reset irq status */
			_Port[3] = 0;
			update_irq_state();
			return;

		case 0x1:		// $1801
			_Port[1] = data;
			if (data == 0x81)
			{
				// ArgsLeft > 0 偮傑傝僐儅儞僪堷悢傪庴晅拞偺偲偒偼
				// 儕僙僢僩偟側偄丅
				if (ArgsLeft == 0)
				{
//					MDFN_printf("$1801 <-- $81: cd reset?? $1800 = 0x00\n");
					bCommandReset = TRUE;
					bCommandReceived = FALSE;
					bCommandDone = FALSE;
					_Port[0] = 0x00;

					/* reset irq status */
					_Port[3] = 0;
					update_irq_state();
					return;
				}
			}
			receive_command(data);
			return;

		case 0x2:		// $1802
			_Port[2] = data;
			update_irq_state();
			return;

//		case 0x3:		// read only
//			return;

		case 0x4:
			if (data & 2)
			{
				// cd reset
				bCommandReset = TRUE;
				bCommandReceived = FALSE;
				bCommandDone = FALSE;
				bDriveBusy = FALSE;
				ResetDelayCount = 10;
				CDDAStatus = 0;
                                SectorCount = 0;
                                cd_unit_delay = 0;
				/* reset irq status */
				_Port[3] = 0;
				update_irq_state();
			}
			_Port[4] = data;
			return;
		case 0x5:
		case 0x6:
			 RawPCMVolumeCache[0] = (abs(Fluffy[0]) * CurrentCdVolume) >> 16;
			 RawPCMVolumeCache[1] = (abs(Fluffy[1]) * CurrentCdVolume) >> 16;
			 break;

		case 0x7:	// $1807: D7=1 enables backup ram 
			if (data & 0x80)
			{
				bBRAMEnabled = TRUE;
			}
			return;
	
		case 0x8:
			ADPCM_SetAddrLo(data);
			return;

		case 0x9:
			ADPCM_SetAddrHi(data);
			return;

		case 0xa:
			ADPCM_WriteBuffer(data);
			return;

		case 0xb:	// adpcm dma
			//printf("DMA: %02x\n", data);
                        _Port[0xb] = data;
			return;

//		case 0xc:		// read-only
//			return;

		case 0xd:
			ADPCM_Write180D(data);
			return;

		case 0xe:		// Set ADPCM playback rate
			ADPCM_SetFreq(data & 0xF);
			return;

		case 0xf:
			//printf("Fade: %02x\n", data & 0xF);
			switch (data & 0xf)
			{
				case 0:	// 僼僃乕僪傾僂僩夝彍 
					CDFADER_FadeIn(0);
					ADPCM_FadeIn(0);
					break;

				case 8:	// fade out CD (6[s])
				case 9:
					CDFADER_FadeOut(6000);
					break;

				case 0xa: // fade out ADPCM (6[s])
					//PRINTF("ADPCM fade (6[s])");
					ADPCM_FadeOut(6000);
					break;

				case 0xc:
				case 0xd:
					CDFADER_FadeOut(2500);
					ADPCM_FadeIn(0);
					break;

				case 0xe: // fade out ADPCM (2.5[s])
					//PRINTF("ADPCM fade (2.5[s])");
					ADPCM_FadeOut(2500);
					break;
			}
			return;
	}
}


void CDROM_TrackEnd(void)
{
 if(bRepeat)
 {
  read_sec = read_sec_start;
  CDDAStatus = 1;
 }
 else if (bInterrupt)
 {
  bInterrupt = FALSE;
  if (playMode == 2)
  {
   _Port[3] |= 0x20;
   update_irq_state();
  }
 }
}
/*
               0x20 = Data transfer done.
                0x00 = Data transfer not done(still reading from CD?)

                0x10 = No data left to read
                0x00 = Data left to read

                0x40 = Command received and done
                0x00 = Command not received

                0x80 = Drive busy???
                0x00 = Drive not busy???

                0x08 = Need more data for command??
                0x00 = No more data needed for command??
*/

uint32 CDROM_AdvanceClock(int32 basetime, int32	clock)
{
	uint32		ret = 0;
	int32 CDDADivAcc = (double)1789772.727272 * 4 * 65536 / 44100 * pce_overclocked;

	if(CDDAStatus == 1)
	{
         int16 sample[2];

	 CDDADiv -= clock << 16;

	 while(CDDADiv <= 0)
	 {
	  CDDADiv += CDDADivAcc;

	  if(CDDAReadPos == 588)
          {
           if(read_sec == read_sec_end)
           {
            CDDAStatus = 0;
            CDROM_TrackEnd();
            if(CDDAStatus != 1) break;
           }
	   else
            read_sec++;
           CDDAReadPos = 0;
	   CDIF_ReadAudioSector(CDDASectorBuffer, read_sec);
          }

	  sample[0] = CDDASectorBuffer[CDDAReadPos * 2];
          sample[1] = CDDASectorBuffer[CDDAReadPos * 2 + 1];

          uint32 synthtime = ((basetime + (CDDADiv >> 16)));

	  if(FSettings.SndRate)
	  {
	   if(pce_overclocked > 1)
	    synthtime /= pce_overclocked;

           CDDASynth.offset(synthtime, sample[0] - last_sample[0], &sbuf[0]);
	   CDDASynth.offset(synthtime, sample[1] - last_sample[1], &sbuf[1]);

	   Fluffy[0] = last_sample[0] = sample[0];
	   Fluffy[1] = last_sample[1] = sample[1];
	  }
	  CDDAReadPos++;
	 }
	}
	if(needrbcreset > 0)
	{
	 needrbcreset -= clock;
	 if(needrbcreset <= 0)
	 {
           //printf("AC Nyoo: %d\n", HuCPU.timestamp);
 	   _Port[0] = 0xe8;
 	   needrbcreset = 0;
	 }
	}

	if(cd_unit_delay > 0)
	{
	 cd_unit_delay -= clock;
	 if(cd_unit_delay <= 0)
	 {
	  if(ReadByteCount)
	  {
	    cd_unit_delay += (int64)1 * 2048 * 1789772.727272 * 4 * pce_overclocked / CD_DATA_TRANSFER_RATE; //150000; //3251; //153600;
	  }
	  else
	  {
	   CDIF_ReadSector(ReadBuffer, SectorAddr, 1);
	   SectorAddr++;
	   SectorCount--;
	   if(SectorCount)
            cd_unit_delay += (uint64)1 * 2048 * 1789772.727272 * 4 * pce_overclocked / CD_DATA_TRANSFER_RATE ; //3251; //150153600;
	   ReadByteCount = 2048;
	   _Port[0] = 0xc8;
	   _Port[3] |= 0x40;
           update_irq_state();
	  }
	 }
	}

	CDFADER_AdvanceClock(clock);
	return ret;
}

uint32 CDROM_GetNextEventTime(void)
{
 uint32 minimum = 1 << 30;

 if(cd_unit_delay > 0 && cd_unit_delay < minimum)
  minimum = cd_unit_delay;

 if(needrbcreset > 0 && needrbcreset < minimum)
  minimum = needrbcreset;

 return(minimum);
}

int CDROM_StateAction(StateMem *sm, int load, int data_only)
{
	SFORMAT StateRegs[] =
	{
	 SFVAR(SectorAddr),
	 SFVAR(SectorCount),
	 SFVAR(bBRAMEnabled),
	 SFVAR(ReadBufferIndex),
	 SFVAR(ReadByteCount),
	 SFVAR(Command),
	 SFVAR(ArgsLeft),
 	 SFVAR(CmdArgBufferIndex),
	 SFVAR(bCommandReset),
	 SFVAR(bCommandReceived),
	 SFVAR(bCommandDone),
	 SFVAR(bDriveBusy),
	 SFVAR(bError),
	 SFVAR(ResetDelayCount),
	 SFVAR(CheckCountAfterRead),
	 SFVAR(needrbcreset),
	 SFVAR(cd_unit_delay),
	 SFVAR(CurrentCdVolume),
	 SFVAR(bFadeIn),
	 SFVAR(bFadeOut),
	 SFVAR(FadeClockCount),
	 SFVAR(FadeCycle),

	 SFVAR(CDDAReadPos),
	 SFVAR(CDDAStatus),
	 SFVAR(CDDADiv),
	 SFVAR(read_sec_start),
	 SFVAR(read_sec_end),
	 SFVAR(read_sec),

	 SFVAR(playMode),
	 SFVAR(searchMode),
	 SFVAR(bRepeat),
	 SFVAR(bInterrupt),

	 SFARRAY16(RawPCMVolumeCache, 2),
	 SFARRAY16(Fluffy, 2),
	 SFEND
	};

        // Call StateAction here so that ReadBufferIndex and ReadByteCount are loaded with the
	// correct values for the function below(when loading).
        int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "CDRM");

	SFORMAT ArrayStateRegs[] =
	{
	 SFARRAY16(CDDASectorBuffer, 1176),
	 SFARRAY(_Port, sizeof(_Port)),
	 SFARRAY(ReadBuffer + ReadBufferIndex, ReadByteCount),
	 SFARRAY(CmdArgBuffer, sizeof(CmdArgBuffer)),
	 SFEND
	};

	ret &= MDFNSS_StateAction(sm, load, data_only, ArrayStateRegs, "CDAR");

	if(load)
	{
         if((ReadBufferIndex + ReadByteCount) > sizeof(ReadBuffer))
         {
          puts("RB Ack!!!");
          ReadBufferIndex = ReadByteCount = 0;
         }
         if((ArgsLeft + CmdArgBufferIndex) > sizeof(CmdArgBuffer))
         {
          puts("Args ack!!!");
          ArgsLeft = CmdArgBufferIndex = 0;
         }

	 SyncCDVolume();
	}
	return(ret);
}
