//////////////////////////////////////////////////////////////////////////
//                                                                      //
//      NES APU core                                                    //
//                                                           Norix      //
//                                               written     2002/06/27 //
//                                               last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#include "DebugOut.h"
#include "App.h"
#include "Config.h"

#include "nes.h"
#include "mmu.h"
#include "cpu.h"
#include "ppu.h"
#include "rom.h"
#include "apu.h"

// Volume adjust
// Internal sounds
#define	RECTANGLE_VOL	(0x0F0)
#define	TRIANGLE_VOL	(0x130)
#define	NOISE_VOL	(0x0C0)
#define	DPCM_VOL	(0x0F0)
// Extra sounds
#define	VRC6_VOL	(0x0F0)
#define	VRC7_VOL	(0x130)
#define	FDS_VOL		(0x0F0)
#define	MMC5_VOL	(0x0F0)
#define	N106_VOL	(0x088)
#define	FME7_VOL	(0x130)

APU::APU( NES* parent )
{
	exsound_select = 0;

	nes = parent;
	internal.SetParent( parent );

	last_data = last_diff = 0;

	ZEROMEMORY( m_SoundBuffer, sizeof(m_SoundBuffer) );

	ZEROMEMORY( lowpass_filter, sizeof(lowpass_filter) );
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );

	for( INT i = 0; i < 16; i++ ) {
		m_bMute[i] = TRUE;
	}
}

APU::~APU()
{
}

void	APU::SetQueue( INT writetime, WORD addr, BYTE data )
{
	queue.data[queue.wrptr].time = writetime;
	queue.data[queue.wrptr].addr = addr;
	queue.data[queue.wrptr].data = data;
	queue.wrptr++;
	queue.wrptr&=QUEUE_LENGTH-1;
	if( queue.wrptr == queue.rdptr ) {
		DEBUGOUT( "queue overflow.\n" );
	}
}

BOOL	APU::GetQueue( INT writetime, QUEUEDATA& ret )
{
	if( queue.wrptr == queue.rdptr ) {
		return	FALSE;
	}
	if( queue.data[queue.rdptr].time <= writetime ) {
		ret = queue.data[queue.rdptr];
		queue.rdptr++;
		queue.rdptr&=QUEUE_LENGTH-1;
		return	TRUE;
	}
	return	FALSE;
}

void	APU::SetExQueue( INT writetime, WORD addr, BYTE data )
{
	exqueue.data[exqueue.wrptr].time = writetime;
	exqueue.data[exqueue.wrptr].addr = addr;
	exqueue.data[exqueue.wrptr].data = data;
	exqueue.wrptr++;
	exqueue.wrptr&=QUEUE_LENGTH-1;
	if( exqueue.wrptr == exqueue.rdptr ) {
		DEBUGOUT( "exqueue overflow.\n" );
	}
}

BOOL	APU::GetExQueue( INT writetime, QUEUEDATA& ret )
{
	if( exqueue.wrptr == exqueue.rdptr ) {
		return	FALSE;
	}
	if( exqueue.data[exqueue.rdptr].time <= writetime ) {
		ret = exqueue.data[exqueue.rdptr];
		exqueue.rdptr++;
		exqueue.rdptr&=QUEUE_LENGTH-1;
		return	TRUE;
	}
	return	FALSE;
}

void	APU::QueueClear()
{
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );
}

void	APU::QueueFlush()
{
	while( queue.wrptr != queue.rdptr ) {
		WriteProcess( queue.data[queue.rdptr].addr, queue.data[queue.rdptr].data );
		queue.rdptr++;
		queue.rdptr&=QUEUE_LENGTH-1;
	}

	while( exqueue.wrptr != exqueue.rdptr ) {
		WriteExProcess( exqueue.data[exqueue.rdptr].addr, exqueue.data[exqueue.rdptr].data );
		exqueue.rdptr++;
		exqueue.rdptr&=QUEUE_LENGTH-1;
	}
}

void	APU::SoundSetup()
{
	FLOAT	fClock = nes->nescfg->CpuClock;
	INT	nRate = (INT)Config.sound.nRate;
	internal.Setup( fClock, nRate );
	vrc6.Setup( fClock, nRate );
	vrc7.Setup( fClock, nRate );
	mmc5.Setup( fClock, nRate );
	fds.Setup ( fClock, nRate );
	n106.Setup( fClock, nRate );
	fme7.Setup( fClock, nRate );
}

void	APU::Reset()
{
	ZEROMEMORY( &queue, sizeof(queue) );
	ZEROMEMORY( &exqueue, sizeof(exqueue) );

	elapsed_time = 0;

	FLOAT	fClock = nes->nescfg->CpuClock;
	INT	nRate = (INT)Config.sound.nRate;
	internal.Reset( fClock, nRate );
	vrc6.Reset( fClock, nRate );
	vrc7.Reset( fClock, nRate );
	mmc5.Reset( fClock, nRate );
	fds.Reset ( fClock, nRate );
	n106.Reset( fClock, nRate );
	fme7.Reset( fClock, nRate );

	SoundSetup();
}

void	APU::SelectExSound( BYTE data )
{
	exsound_select = data;
}

BYTE	APU::Read( WORD addr )
{
	return	internal.SyncRead( addr );
}

void	APU::Write( WORD addr, BYTE data )
{
	// $4018はVirtuaNES固有ポート
	if( addr >= 0x4000 && addr <= 0x401F ) {
		internal.SyncWrite( addr, data );
		SetQueue( nes->cpu->GetTotalCycles(), addr, data );
	}
}

BYTE	APU::ExRead( WORD addr )
{
BYTE	data = 0;

	if( exsound_select & 0x10 ) {
		if( addr == 0x4800 ) {
			SetExQueue( nes->cpu->GetTotalCycles(), 0, 0 );
		}
	}
	if( exsound_select & 0x04 ) {
		if( addr >= 0x4040 && addr < 0x4100 ) {
			data = fds.SyncRead( addr );
		}
	}
	if( exsound_select & 0x08 ) {
		if( addr >= 0x5000 && addr <= 0x5015 ) {
			data = mmc5.SyncRead( addr );
		}
	}

	return	data;
}

void	APU::ExWrite( WORD addr, BYTE data )
{
	SetExQueue( nes->cpu->GetTotalCycles(), addr, data );

	if( exsound_select & 0x04 ) {
		if( addr >= 0x4040 && addr < 0x4100 ) {
			fds.SyncWrite( addr, data );
		}
	}

	if( exsound_select & 0x08 ) {
		if( addr >= 0x5000 && addr <= 0x5015 ) {
			mmc5.SyncWrite( addr, data );
		}
	}
}

void	APU::Sync()
{
}

void	APU::SyncDPCM( INT cycles )
{
	internal.Sync( cycles );

	if( exsound_select & 0x04 ) {
		fds.Sync( cycles );
	}
	if( exsound_select & 0x08 ) {
		mmc5.Sync( cycles );
	}
}

void	APU::WriteProcess( WORD addr, BYTE data )
{
	// $4018はVirtuaNES固有ポート
	if( addr >= 0x4000 && addr <= 0x401F ) {
		internal.Write( addr, data );
	}
}

void	APU::WriteExProcess( WORD addr, BYTE data )
{
	if( exsound_select & 0x01 ) {
		vrc6.Write( addr, data );
	}
	if( exsound_select & 0x02 ) {
		vrc7.Write( addr, data );
	}
	if( exsound_select & 0x04 ) {
		fds.Write( addr, data );
	}
	if( exsound_select & 0x08 ) {
		mmc5.Write( addr, data );
	}
	if( exsound_select & 0x10 ) {
		if( addr == 0x0000 ) {
			BYTE	dummy = n106.Read( addr );
		} else {
			n106.Write( addr, data );
		}
	}
	if( exsound_select & 0x20 ) {
		fme7.Write( addr, data );
	}
}

void	APU::Process( LPBYTE lpBuffer, DWORD dwSize )
{
INT	nBits = Config.sound.nBits;
DWORD	dwLength = dwSize / (nBits/8);
INT	output;
QUEUEDATA q;
DWORD	writetime;

LPSHORT	pSoundBuf = m_SoundBuffer;
INT	nCcount = 0;

INT	nFilterType = Config.sound.nFilterType;

	if( !Config.sound.bEnable ) {
		::FillMemory( lpBuffer, dwSize, (BYTE)(Config.sound.nRate==8?128:0) );
		return;
	}

	// Volume setup
	//  0:Master
	//  1:Rectangle 1
	//  2:Rectangle 2
	//  3:Triangle
	//  4:Noise
	//  5:DPCM
	//  6:VRC6
	//  7:VRC7
	//  8:FDS
	//  9:MMC5
	// 10:N106
	// 11:FME7
	INT	vol[24];
	BOOL*	bMute = m_bMute;
	SHORT*	nVolume = Config.sound.nVolume;

	INT	nMasterVolume = bMute[0]?nVolume[0]:0;

	// Internal
	vol[ 0] = bMute[1]?(RECTANGLE_VOL*nVolume[1]*nMasterVolume)/(100*100):0;
	vol[ 1] = bMute[2]?(RECTANGLE_VOL*nVolume[2]*nMasterVolume)/(100*100):0;
	vol[ 2] = bMute[3]?(TRIANGLE_VOL *nVolume[3]*nMasterVolume)/(100*100):0;
	vol[ 3] = bMute[4]?(NOISE_VOL    *nVolume[4]*nMasterVolume)/(100*100):0;
	vol[ 4] = bMute[5]?(DPCM_VOL     *nVolume[5]*nMasterVolume)/(100*100):0;

	// VRC6
	vol[ 5] = bMute[6]?(VRC6_VOL*nVolume[6]*nMasterVolume)/(100*100):0;
	vol[ 6] = bMute[7]?(VRC6_VOL*nVolume[6]*nMasterVolume)/(100*100):0;
	vol[ 7] = bMute[8]?(VRC6_VOL*nVolume[6]*nMasterVolume)/(100*100):0;

	// VRC7
	vol[ 8] = bMute[6]?(VRC7_VOL*nVolume[7]*nMasterVolume)/(100*100):0;

	// FDS
	vol[ 9] = bMute[6]?(FDS_VOL*nVolume[8]*nMasterVolume)/(100*100):0;

	// MMC5
	vol[10] = bMute[6]?(MMC5_VOL*nVolume[9]*nMasterVolume)/(100*100):0;
	vol[11] = bMute[7]?(MMC5_VOL*nVolume[9]*nMasterVolume)/(100*100):0;
	vol[12] = bMute[8]?(MMC5_VOL*nVolume[9]*nMasterVolume)/(100*100):0;

	// N106
	vol[13] = bMute[ 6]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[14] = bMute[ 7]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[15] = bMute[ 8]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[16] = bMute[ 9]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[17] = bMute[10]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[18] = bMute[11]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[19] = bMute[12]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;
	vol[20] = bMute[13]?(N106_VOL*nVolume[10]*nMasterVolume)/(100*100):0;

	// FME7
	vol[21] = bMute[6]?(FME7_VOL*nVolume[11]*nMasterVolume)/(100*100):0;
	vol[22] = bMute[7]?(FME7_VOL*nVolume[11]*nMasterVolume)/(100*100):0;
	vol[23] = bMute[8]?(FME7_VOL*nVolume[11]*nMasterVolume)/(100*100):0;

//	double	cycle_rate = ((double)FRAME_CYCLES*60.0/12.0)/(double)Config.sound.nRate;
	double	cycle_rate = ((double)nes->nescfg->FrameCycles*60.0/12.0)/(double)Config.sound.nRate;

	// CPUサイクル数がループしてしまった時の対策処理
	if( elapsed_time > nes->cpu->GetTotalCycles() ) {
		QueueFlush();
	}

	while( dwLength-- ) {
		writetime = (DWORD)elapsed_time;

		while( GetQueue( writetime, q ) ) {
			WriteProcess( q.addr, q.data );
		}

		while( GetExQueue( writetime, q ) ) {
			WriteExProcess( q.addr, q.data );
		}

		// 0-4:internal 5-7:VRC6 8:VRC7 9:FDS 10-12:MMC5 13-20:N106 21-23:FME7
		output = 0;
		output += internal.Process( 0 )*vol[0];
		output += internal.Process( 1 )*vol[1];
		output += internal.Process( 2 )*vol[2];
		output += internal.Process( 3 )*vol[3];
		output += internal.Process( 4 )*vol[4];

		if( exsound_select & 0x01 ) {
			output += vrc6.Process( 0 )*vol[5];
			output += vrc6.Process( 1 )*vol[6];
			output += vrc6.Process( 2 )*vol[7];
		}
		if( exsound_select & 0x02 ) {
			output += vrc7.Process( 0 )*vol[8];
		}
		if( exsound_select & 0x04 ) {
			output += fds.Process( 0 )*vol[9];
		}
		if( exsound_select & 0x08 ) {
			output += mmc5.Process( 0 )*vol[10];
			output += mmc5.Process( 1 )*vol[11];
			output += mmc5.Process( 2 )*vol[12];
		}
		if( exsound_select & 0x10 ) {
			output += n106.Process( 0 )*vol[13];
			output += n106.Process( 1 )*vol[14];
			output += n106.Process( 2 )*vol[15];
			output += n106.Process( 3 )*vol[16];
			output += n106.Process( 4 )*vol[17];
			output += n106.Process( 5 )*vol[18];
			output += n106.Process( 6 )*vol[19];
			output += n106.Process( 7 )*vol[20];
		}
		if( exsound_select & 0x20 ) {
			fme7.Process( 3 );	// Envelope & Noise
			output += fme7.Process( 0 )*vol[21];
			output += fme7.Process( 1 )*vol[22];
			output += fme7.Process( 2 )*vol[23];
		}

		output >>= 8;

		if( nFilterType == 1 ) {
			//ローパスフィルターTYPE 1(Simple)
			output = (lowpass_filter[0]+output)/2;
			lowpass_filter[0] = output;
		} else if( nFilterType == 2 ) {
			//ローパスフィルターTYPE 2(Weighted type 1)
			output = (lowpass_filter[1]+lowpass_filter[0]+output)/3;
			lowpass_filter[1] = lowpass_filter[0];
			lowpass_filter[0] = output;
		} else if( nFilterType == 3 ) {
			//ローパスフィルターTYPE 3(Weighted type 2)
			output = (lowpass_filter[2]+lowpass_filter[1]+lowpass_filter[0]+output)/4;
			lowpass_filter[2] = lowpass_filter[1];
			lowpass_filter[1] = lowpass_filter[0];
			lowpass_filter[0] = output;
		} else if( nFilterType == 4 ) {
			//ローパスフィルターTYPE 4(Weighted type 3)
			output = (lowpass_filter[1]+lowpass_filter[0]*2+output)/4;
			lowpass_filter[1] = lowpass_filter[0];
			lowpass_filter[0] = output;
		}

#if	0
		// DC成分のカット
		{
		static double ave = 0.0, max=0.0, min=0.0;
		double delta;
		delta = (max-min)/32768.0;
		max -= delta;
		min += delta;
		if( output > max ) max = output;
		if( output < min ) min = output;
		ave -= ave/1024.0;
		ave += (max+min)/2048.0;
		output -= (INT)ave;
		}
#endif
#if	1
		// DC成分のカット(HPF TEST)
		{
//		static	double	cutoff = (2.0*3.141592653579*40.0/44100.0);
		static	double	cutofftemp = (2.0*3.141592653579*40.0);
		double	cutoff = cutofftemp/(double)Config.sound.nRate;
		static	double	tmp = 0.0;
		double	in, out;

		in = (double)output;
		out = (in - tmp);
		tmp = tmp + cutoff * out;

		output = (INT)out;
		}
#endif
#if	0
		// スパイクノイズの除去(AGC TEST)
		{
		INT	diff = abs(output-last_data);
		if( diff > 0x4000 ) {
			output /= 4;
		} else 
		if( diff > 0x3000 ) {
			output /= 3;
		} else
		if( diff > 0x2000 ) {
			output /= 2;
		}
		last_data = output;
		}
#endif
		// Limit
		if( output > 0x7FFF ) {
			output = 0x7FFF;
		} else if( output < -0x8000 ) {
			output = -0x8000;
		}

		if( nBits != 8 ) {
			*(SHORT*)lpBuffer = (SHORT)output;
			lpBuffer += sizeof(SHORT);
		} else {
			*lpBuffer++ = (output>>8)^0x80;
		}

		if( nCcount < 0x0100 )
			pSoundBuf[nCcount++] = (SHORT)output;

//		elapsedtime += cycle_rate;
		elapsed_time += cycle_rate;
	}

#if	1
	if( elapsed_time > ((nes->nescfg->FrameCycles/24)+nes->cpu->GetTotalCycles()) ) {
		elapsed_time = nes->cpu->GetTotalCycles();
	}
	if( (elapsed_time+(nes->nescfg->FrameCycles/6)) < nes->cpu->GetTotalCycles() ) {
		elapsed_time = nes->cpu->GetTotalCycles();
	}
#else
	elapsed_time = nes->cpu->GetTotalCycles();
#endif
}

// チャンネルの周波数取得サブルーチン(NSF用)
INT	APU::GetChannelFrequency( INT no )
{
	if( !m_bMute[0] )
		return	0;

	// Internal
	if( no < 5 ) {
		return	m_bMute[no+1]?internal.GetFreq( no ):0;
	}
	// VRC6
	if( (exsound_select & 0x01) && no >= 0x0100 && no < 0x0103 ) {
		return	m_bMute[6+(no&0x03)]?vrc6.GetFreq( no & 0x03 ):0;
	}
	// FDS
	if( (exsound_select & 0x04) && no == 0x300 ) {
		return	m_bMute[6]?fds.GetFreq( 0 ):0;
	}
	// MMC5
	if( (exsound_select & 0x08) && no >= 0x0400 && no < 0x0402 ) {
		return	m_bMute[6+(no&0x03)]?mmc5.GetFreq( no & 0x03 ):0;
	}
	// N106
	if( (exsound_select & 0x10) && no >= 0x0500 && no < 0x0508 ) {
		return	m_bMute[6+(no&0x07)]?n106.GetFreq( no & 0x07 ):0;
	}
	// FME7
	if( (exsound_select & 0x20) && no >= 0x0600 && no < 0x0603 ) {
		return	m_bMute[6+(no&0x03)]?fme7.GetFreq( no & 0x03 ):0;
	}
	// VRC7
	if( (exsound_select & 0x02) && no >= 0x0700 && no < 0x0709 ) {
		return	m_bMute[6]?vrc7.GetFreq(no&0x0F):0;
	}
	return	0;
}

// State Save/Load
void	APU::SaveState( LPBYTE p )
{
#ifdef	_DEBUG
LPBYTE	pold = p;
#endif

	// 時間軸を同期させる為Flushする
	QueueFlush();

	internal.SaveState( p );
	p += (internal.GetStateSize()+15)&(~0x0F);	// Padding

	// VRC6
	if( exsound_select & 0x01 ) {
		vrc6.SaveState( p );
		p += (vrc6.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// VRC7 (not support)
	if( exsound_select & 0x02 ) {
		vrc7.SaveState( p );
		p += (vrc7.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// FDS
	if( exsound_select & 0x04 ) {
		fds.SaveState( p );
		p += (fds.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// MMC5
	if( exsound_select & 0x08 ) {
		mmc5.SaveState( p );
		p += (mmc5.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// N106
	if( exsound_select & 0x10 ) {
		n106.SaveState( p );
		p += (n106.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// FME7
	if( exsound_select & 0x20 ) {
		fme7.SaveState( p );
		p += (fme7.GetStateSize()+15)&(~0x0F);	// Padding
	}

#ifdef	_DEBUG
DEBUGOUT( "SAVE APU SIZE:%d\n", p-pold );
#endif
}

void	APU::LoadState( LPBYTE p )
{
	// 時間軸を同期させる為に消す
	QueueClear();

	internal.LoadState( p );
	p += (internal.GetStateSize()+15)&(~0x0F);	// Padding

	// VRC6
	if( exsound_select & 0x01 ) {
		vrc6.LoadState( p );
		p += (vrc6.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// VRC7 (not support)
	if( exsound_select & 0x02 ) {
		vrc7.LoadState( p );
		p += (vrc7.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// FDS
	if( exsound_select & 0x04 ) {
		fds.LoadState( p );
		p += (fds.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// MMC5
	if( exsound_select & 0x08 ) {
		mmc5.LoadState( p );
		p += (mmc5.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// N106
	if( exsound_select & 0x10 ) {
		n106.LoadState( p );
		p += (n106.GetStateSize()+15)&(~0x0F);	// Padding
	}
	// FME7
	if( exsound_select & 0x20 ) {
		fme7.LoadState( p );
		p += (fme7.GetStateSize()+15)&(~0x0F);	// Padding
	}
}
