//////////////////////////////////////////////////////////////////////////
//                                                                      //
//      APU Internal                                                    //
//                                                           Norix      //
//                                               written     2002/06/27 //
//                                               last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#include "DebugOut.h"
#include "Pathlib.h"
#include "Config.h"

#include "APU_INTERNAL.h"

#include "state.h"
#include "rom.h"

// Dummy
#define	APU_CLOCK	1789772.5f

// Volume shift
#define	RECTANGLE_VOL_SHIFT	8
#define	TRIANGLE_VOL_SHIFT	9
#define	NOISE_VOL_SHIFT		8
#define	DPCM_VOL_SHIFT		8

INT	APU_INTERNAL::vbl_length[32] = {
    5, 127,   10,   1,   19,   2,   40,   3,
   80,   4,   30,   5,    7,   6,   13,   7,
    6,   8,   12,   9,   24,  10,   48,  11,
   96,  12,   36,  13,    8,  14,   16,  15
};

INT	APU_INTERNAL::freq_limit[8] = {
	0x03FF, 0x0555, 0x0666, 0x071C, 0x0787, 0x07C1, 0x07E0, 0x07F0
};
INT	APU_INTERNAL::duty_lut[4] = {
	 2,  4,  8, 12
};

INT	APU_INTERNAL::noise_freq[16] = {
	  4,    8,   16,   32,   64,   96,  128,  160,
	202,  254,  380,  508,  762, 1016, 2034, 4068
};

// DMC 転送クロック数テーブル
INT	APU_INTERNAL::dpcm_cycles[16] = {
	428, 380, 340, 320, 286, 254, 226, 214,
	190, 160, 142, 128, 106,  85,  72,  54
};

//INT	APU_INTERNAL::vol_effect[16] = {
//	100,  94,  88,  83,  78,  74,  71,  67,
//	 64,  61,  59,  56,  54,  52,  50,  48
//};

APU_INTERNAL::APU_INTERNAL()
{
	nes = NULL;

	ZEROMEMORY( &ch0, sizeof(ch0) );
	ZEROMEMORY( &ch1, sizeof(ch1) );
	ZEROMEMORY( &ch2, sizeof(ch2) );
	ZEROMEMORY( &ch3, sizeof(ch3) );
	ZEROMEMORY( &ch4, sizeof(ch4) );

	FrameIRQ = 0xC0;
	FrameCycle = 0;
	FrameIRQoccur = 0;
	FrameCount = 0;
	FrameType  = 0;

	reg4015 = sync_reg4015 = 0;

	cpu_clock = APU_CLOCK;
	sampling_rate = 22050;

	// 仮設定
	cycle_rate = (INT)(cpu_clock*65536.0f/22050.0f);

}

APU_INTERNAL::~APU_INTERNAL()
{
}

void	APU_INTERNAL::Reset( FLOAT fClock, INT nRate )
{
	ZEROMEMORY( &ch0, sizeof(ch0) );
	ZEROMEMORY( &ch1, sizeof(ch1) );
	ZEROMEMORY( &ch2, sizeof(ch2) );
	ZEROMEMORY( &ch3, sizeof(ch3) );
//	ZEROMEMORY( &ch4, sizeof(ch4) );

	ZEROMEMORY( bToneTableEnable, sizeof(bToneTableEnable) );
	ZEROMEMORY( ToneTable, sizeof(ToneTable) );
	ZEROMEMORY( ChannelTone, sizeof(ChannelTone) );

	reg4015 = sync_reg4015 = 0;

	// Sweep complement
	ch0.complement = 0x00;
	ch1.complement = 0xFF;

	// Noise shift register
	ch3.shift_reg = 0x4000;

	Setup( fClock, nRate );

	// $4011は初期化しない
	WORD	addr;
	for( addr = 0x4000; addr <= 0x4010; addr++ ) {
		Write( addr, 0x00 );
		SyncWrite( addr, 0x00 );
	}
//	Write( 0x4001, 0x08 );	// Reset時はincモードになる?
//	Write( 0x4005, 0x08 );	// Reset時はincモードになる?
	Write( 0x4012, 0x00 );
	Write( 0x4013, 0x00 );
	Write( 0x4015, 0x00 );
	SyncWrite( 0x4012, 0x00 );
	SyncWrite( 0x4013, 0x00 );
	SyncWrite( 0x4015, 0x00 );

	// $4017は書き込みで初期化しない(初期モードが0であるのを期待したソフトがある為)
	FrameIRQ = 0xC0;
	FrameCycle = 0;
	FrameIRQoccur = 0;
	FrameCount = 0;
	FrameType  = 0;

	// ToneLoad
	ToneTableLoad();
}

void	APU_INTERNAL::Setup( FLOAT fClock, INT nRate )
{
	cpu_clock = fClock;
	sampling_rate = nRate;

	cycle_rate = (INT)(fClock*65536.0f/(float)nRate);
}

//
// Wavetable loader
//
void	APU_INTERNAL::ToneTableLoad()
{
FILE*	fp = NULL;
CHAR	buf[512];

	string	tempstr;
	tempstr = CPathlib::MakePathExt( nes->rom->GetRomPath(), nes->rom->GetRomName(), "vtd" );
	DEBUGOUT( "Path: %s\n", tempstr.c_str() );
	if( !(fp = ::fopen( tempstr.c_str(), "r" )) ) {
		// デフォルトファイル名で読んで見る
		tempstr = CPathlib::MakePathExt( nes->rom->GetRomPath(), "Default", "vtd" );
		DEBUGOUT( "Path: %s\n", tempstr.c_str() );
		if( !(fp = ::fopen( tempstr.c_str(), "r" )) ) {
			DEBUGOUT( "File not found.\n" );
			return;
		}
	}

	DEBUGOUT( "Find.\n" );

	// 定義ファイルを読み込む
	while( ::fgets( buf, 512, fp ) != NULL ) {
		if( buf[0] == ';' || ::strlen(buf) <= 0 )
			continue;

		CHAR	c = ::toupper( buf[0] );

		if( c == '@' ) {
		// 音色読み込み
		CHAR*	pbuf = &buf[1];
		CHAR*	p;
		INT	no, val;

			// 音色ナンバー取得
			no = ::strtol( pbuf, &p, 10 );
			if( pbuf == p )
				continue;
			if( no < 0 || no > TONEDATA_MAX-1 )
				continue;

			// '='を見つける
			p = ::strchr( pbuf, '=' );
			if( p == NULL )
				continue;
			pbuf = p+1;	// 次

			// 音色データを取得
			for( INT i = 0; i < TONEDATA_LEN; i++ ) {
				val = ::strtol( pbuf, &p, 10 );
				if( pbuf == p )	// 取得失敗？
					break;
				if( *p == ',' )	// カンマを飛ばす…
					pbuf = p+1;
				else
					pbuf = p;

				ToneTable[no][i] = val;
			}
			if( i >= TONEDATA_MAX )
				bToneTableEnable[no] = TRUE;
		} else
		if( c == 'A' || c == 'B' ) {
		// 各チャンネル音色定義
		CHAR*	pbuf = &buf[1];
		CHAR*	p;
		INT	no, val;

			// 内部音色ナンバー取得
			no = ::strtol( pbuf, &p, 10 );
			if( pbuf == p )
				continue;
			pbuf = p;
			if( no < 0 || no > TONE_MAX-1 )
				continue;

			// '='を見つける
			p = ::strchr( pbuf, '=' );
			if( p == NULL )
				continue;
			pbuf = p+1;	// 次

			// 音色ナンバー取得
			val = ::strtol( pbuf, &p, 10 );
			if( pbuf == p )
				continue;
			pbuf = p;

			if( val > TONEDATA_MAX-1 )
				continue;

			if( val >= 0 && bToneTableEnable[val] ) {
				if( c == 'A' ) {
					ChannelTone[0][no] = val+1;
				} else {
					ChannelTone[1][no] = val+1;
				}
			} else {
				if( c == 'A' ) {
					ChannelTone[0][no] = 0;
				} else {
					ChannelTone[1][no] = 0;
				}
			}
		} else
		if( c == 'C' ) {
		// 各チャンネル音色定義
		CHAR*	pbuf = &buf[1];
		CHAR*	p;
		INT	val;

			// '='を見つける
			p = ::strchr( pbuf, '=' );
			if( p == NULL )
				continue;
			pbuf = p+1;	// 次

			// 音色ナンバー取得
			val = ::strtol( pbuf, &p, 10 );
			if( pbuf == p )
				continue;
			pbuf = p;

			if( val > TONEDATA_MAX-1 )
				continue;

			if( val >= 0 && bToneTableEnable[val] ) {
				ChannelTone[2][0] = val+1;
			} else {
				ChannelTone[2][0] = 0;
			}
		}
	}

	FCLOSE( fp );
}

INT	APU_INTERNAL::Process( INT channel )
{
	switch( channel ) {
		case	0:
			return	RenderRectangle( ch0 );
		case	1:
			return	RenderRectangle( ch1 );
		case	2:
			return	RenderTriangle();
		case	3:
			return	RenderNoise();
		case	4:
			return	RenderDPCM();
		default:
			return	0;
	}

	return	0;
}

void	APU_INTERNAL::Write( WORD addr, BYTE data )
{
	switch( addr ) {
		// CH0,1 rectangle
		case	0x4000:	case	0x4001:
		case	0x4002:	case	0x4003:
		case	0x4004:	case	0x4005:
		case	0x4006:	case	0x4007:
			WriteRectangle( (addr<0x4004)?0:1, addr, data );
			break;

		// CH2 triangle
		case	0x4008:	case	0x4009:
		case	0x400A:	case	0x400B:
			WriteTriangle( addr, data );
			break;

		// CH3 noise
		case	0x400C:	case	0x400D:
		case	0x400E:	case	0x400F:
			WriteNoise( addr, data );
			break;

		// CH4 DPCM
		case	0x4010:	case	0x4011:
		case	0x4012:	case	0x4013:
			WriteDPCM( addr, data );
			break;

		case	0x4015:
			reg4015 = data;

			if( !(data&(1<<0)) ) {
				ch0.enable    = 0;
				ch0.len_count = 0;
			}
			if( !(data&(1<<1)) ) {
				ch1.enable    = 0;
				ch1.len_count = 0;
			}
			if( !(data&(1<<2)) ) {
				ch2.enable       = 0;
				ch2.len_count    = 0;
				ch2.lin_count    = 0;
			}
			if( !(data&(1<<3)) ) {
				ch3.enable    = 0;
				ch3.len_count = 0;
			}
			if( !(data&(1<<4)) ) {
				ch4.enable    = 0;
				ch4.dmalength = 0;
			} else {
				ch4.enable = 0xFF;
				if( !ch4.dmalength ) {
					ch4.address   = ch4.cache_addr;
					ch4.dmalength = ch4.cache_dmalength;
				}
			}
			break;

		case	0x4017:
			break;

		// VirtuaNES固有ポート
		case	0x4018:
			UpdateRectangle( ch0, (INT)data );
			UpdateRectangle( ch1, (INT)data );
			UpdateTriangle ( (INT)data );
			UpdateNoise    ( (INT)data );
			break;

		default:
			break;
	}
}

BYTE	APU_INTERNAL::Read( WORD addr )
{
BYTE	data = addr>>8;

	if( addr == 0x4015 ) {
		data = 0;
		if( ch0.enable && ch0.len_count > 0 ) data |= (1<<0);
		if( ch1.enable && ch1.len_count > 0 ) data |= (1<<1);
		if( ch2.enable ) {
			if( !ch2.holdnote ) {
				if( ch2.len_count > 0 ) data |= (1<<2);
			} else {
				if( ch2.lin_count > 0 ) data |= (1<<2);
			}
		}
		if( ch3.enable && ch3.len_count > 0 ) data |= (1<<3);
	}
	return	data;
}

void	APU_INTERNAL::SyncWrite( WORD addr, BYTE data )
{
//DEBUGOUT( "$%04X=$%02X\n", addr, data );

	switch( addr ) {
		// CH0,1 rectangle
		case	0x4000:	case	0x4001:
		case	0x4002:	case	0x4003:
		case	0x4004:	case	0x4005:
		case	0x4006:	case	0x4007:
			SyncWriteRectangle( (addr<0x4004)?0:1, addr, data );
			break;

		// CH2 triangle
		case	0x4008:	case	0x4009:
		case	0x400A:	case	0x400B:
			SyncWriteTriangle( addr, data );
			break;

		// CH3 noise
		case	0x400C:	case	0x400D:
		case	0x400E:	case	0x400F:
			SyncWriteNoise( addr, data );
			break;

		// CH4 DPCM
		case	0x4010:	case	0x4011:
		case	0x4012:	case	0x4013:
			SyncWriteDPCM( addr, data );
			break;

		case	0x4015:
			sync_reg4015 = data;

			if( !(data&(1<<0)) ) {
				ch0.sync_enable    = 0;
				ch0.sync_len_count = 0;
			}
			if( !(data&(1<<1)) ) {
				ch1.sync_enable    = 0;
				ch1.sync_len_count = 0;
			}
			if( !(data&(1<<2)) ) {
				ch2.sync_enable       = 0;
				ch2.sync_len_count    = 0;
				ch2.sync_lin_count    = 0;
			}
			if( !(data&(1<<3)) ) {
				ch3.sync_enable    = 0;
				ch3.sync_len_count = 0;
			}
			if( !(data&(1<<4)) ) {
				ch4.sync_enable     = 0;
				ch4.sync_dmalength  = 0;
				ch4.sync_irq_enable = 0;

				nes->cpu->ClrIRQ( IRQ_DPCM );
			} else {
				ch4.sync_enable = 0xFF;
				if( !ch4.sync_dmalength ) {
					ch4.sync_cycles    = ch4.sync_cache_cycles;
					ch4.sync_dmalength = ch4.sync_cache_dmalength;
				}
			}
			break;

		case	0x4017:
			SyncWrite4017( data );
			break;

		// VirtuaNES固有ポート
		case	0x4018:
			SyncUpdateRectangle( ch0, (INT)data );
			SyncUpdateRectangle( ch1, (INT)data );
			SyncUpdateTriangle ( (INT)data );
			SyncUpdateNoise    ( (INT)data );
			break;

		default:
			break;
	}
}

// $4017 Write
void	APU_INTERNAL::SyncWrite4017( BYTE data )
{
	FrameCycle = 0;
	FrameIRQ = data;
	FrameIRQoccur = 0;

	nes->cpu->ClrIRQ( IRQ_FRAMEIRQ );

	if( !(data&0x80) ) {
		FrameType  = 0;
		FrameCount = 0;
	} else {
		FrameCount = 0;
		FrameType  = 1;

		// Counters Update
		nes->Write( 0x4018, 0 );
	}
}

BYTE	APU_INTERNAL::SyncRead( WORD addr )
{
BYTE	data = addr>>8;

	if( addr == 0x4015 ) {
		data = 0;
		if( ch0.sync_enable && ch0.sync_len_count > 0 ) data |= (1<<0);
		if( ch1.sync_enable && ch1.sync_len_count > 0 ) data |= (1<<1);
		if( ch2.sync_enable ) {
			if( !ch2.sync_holdnote ) {
				if( ch2.sync_len_count > 0 ) data |= (1<<2);
			} else {
				if( ch2.sync_lin_count > 0 ) data |= (1<<2);
			}
		}
		if( ch3.sync_enable && ch3.sync_len_count > 0 ) data |= (1<<3);
		if( ch4.sync_enable && ch4.sync_dmalength )     data |= (1<<4);
		if( FrameIRQoccur )                             data |= (1<<6);
		if( ch4.sync_irq_enable )                       data |= (1<<7);
		FrameIRQoccur = 0;

		nes->cpu->ClrIRQ( IRQ_FRAMEIRQ );
	}
	if( addr == 0x4017 ) {
		if( FrameIRQoccur ) {
			data = 0;
		} else {
			data |= (1<<6);
		}
	}
	return	data;
}

BOOL	APU_INTERNAL::Sync( INT cycles )
{
	FrameCycle += cycles;
	if( FrameCycle >= 7457 ) {
		FrameCycle -= 7457;

		if( FrameType == 0 ) {
			// 0,1,2,3
			if( FrameCount < 4 ) {
				// Counters Update
				nes->Write( 0x4018, (BYTE)FrameCount&3 );
			}
			if( ++FrameCount > 3 ) {
				FrameCount = 0;

				if( !(FrameIRQ&0xC0) && nes->GetFrameIRQmode() ) {
					FrameIRQoccur = 0xFF;

					nes->cpu->SetIRQ( IRQ_FRAMEIRQ );
				}
			}
		} else {
			// 0,1,2
			if( FrameCount < 3 ) {
				// Counters Update
				nes->Write( 0x4018, (BYTE)(FrameCount+1)&3 );
			}
			if( ++FrameCount > 4 ) {
				FrameCount = 0;
				// Counters Update
				nes->Write( 0x4018, 0 );
			}
		}
	}

	return	FrameIRQoccur | SyncUpdateDPCM( cycles );
}

INT	APU_INTERNAL::GetFreq( INT channel )
{
INT	freq = 0;

	// Rectangle
	if( channel == 0 || channel == 1 ) {
		RECTANGLE* ch;
		if( channel == 0 ) ch = &ch0;
		else		   ch = &ch1;
		if( !ch->enable || ch->len_count <= 0 )
			return	0;
		if( (ch->freq < 8) || (!ch->swp_inc && ch->freq > ch->freqlimit) )
			return	0;

		if( !ch->volume )
			return	0;

//		freq = (((INT)ch->reg[3]&0x07)<<8)+(INT)ch->reg[2]+1;
		freq = (INT)(16.0f*cpu_clock/(FLOAT)(ch->freq+1));
		return	freq;
	}

	// Triangle
	if( channel == 2 ) {
		if( !ch2.enable || ch2.len_count <= 0 )
			return	0;
		if( ch2.lin_count <= 0 || ch2.freq < INT2FIX(8) )
			return	0;
		freq = (((INT)ch2.reg[3]&0x07)<<8)+(INT)ch2.reg[2]+1;
		freq = (INT)(8.0f*cpu_clock/(FLOAT)freq);
		return	freq;
	}

	// Noise
	if( channel == 3 ) {
		if( !ch3.enable || ch3.len_count <= 0 )
			return	0;
		if( ch3.env_fixed ) {
			if( !ch3.volume )
				return	0;
		} else {
			if( !ch3.env_vol )
				return	0;
		}
		return	1;
	}

	// DPCM
	if( channel == 4 ) {
		if( ch4.enable && ch4.dmalength )
			return	1;
	}

	return	0;
}

// Write Rectangle
void	APU_INTERNAL::WriteRectangle( INT no, WORD addr, BYTE data )
{
	RECTANGLE& ch = (no==0)?ch0:ch1;

	ch.reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch.holdnote  = data&0x20;
			ch.volume    = data&0x0F;
			ch.env_fixed = data&0x10;
			ch.env_decay = (data&0x0F)+1;
			ch.duty      = duty_lut[data>>6];
			break;
		case	1:
			ch.swp_on    = data&0x80;
			ch.swp_inc   = data&0x08;
			ch.swp_shift = data&0x07;
			ch.swp_decay = ((data>>4)&0x07)+1;
			ch.freqlimit = freq_limit[data&0x07];
			break;
		case	2:
			ch.freq = (ch.freq&(~0xFF))+data;
			break;
		case	3: // Master
			ch.freq      = ((data&0x07)<<8)+(ch.freq&0xFF);
			ch.len_count = vbl_length[data>>3]*2;
			ch.env_vol   = 0x0F;
			ch.env_count = ch.env_decay+1;
			ch.adder     = 0;

			if( reg4015&(1<<no) )
				ch.enable    = 0xFF;
			break;
	}
}

// Update Rectangle
void	APU_INTERNAL::UpdateRectangle( RECTANGLE& ch, INT type )
{
	if( !ch.enable || ch.len_count <= 0 )
		return;

	// Update Length/Sweep
	if( type&1 ) {
		// Update Length
		if( ch.len_count && !ch.holdnote ) {
			// Holdnote
			if( ch.len_count ) {
				ch.len_count--;
			}
		}

		// Update Sweep
		if( ch.swp_on && ch.swp_shift ) {
			if( ch.swp_count ) {
				ch.swp_count--;
			}
			if( ch.swp_count == 0 ) {
				ch.swp_count = ch.swp_decay;
				if( ch.swp_inc ) {
				// Sweep increment(to higher frequency)
					if( !ch.complement )
						ch.freq += ~(ch.freq >> ch.swp_shift); // CH 0
					else
						ch.freq -=  (ch.freq >> ch.swp_shift); // CH 1
				} else {
				// Sweep decrement(to lower frequency)
					ch.freq += (ch.freq >> ch.swp_shift);
				}
			}
		}
	}

	// Update Envelope
	if( ch.env_count ) {
		ch.env_count--;
	}
	if( ch.env_count == 0 ) {
		ch.env_count = ch.env_decay;

		// Holdnote
		if( ch.holdnote ) {
			ch.env_vol = (ch.env_vol-1)&0x0F;
		} else if( ch.env_vol ) {
			ch.env_vol--;
		}
	}

	if( !ch.env_fixed ) {
		ch.nowvolume = ch.env_vol<<RECTANGLE_VOL_SHIFT;
	}
}

// Sync Write Rectangle
void	APU_INTERNAL::SyncWriteRectangle( INT no, WORD addr, BYTE data )
{
	RECTANGLE& ch = (no==0)?ch0:ch1;

	ch.sync_reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch.sync_holdnote = data&0x20;
			break;
		case	1:
		case	2:
			break;
		case	3: // Master
			ch.sync_len_count = vbl_length[data>>3]*2;
			if( sync_reg4015&(1<<no) )
				ch.sync_enable = 0xFF;
			break;
	}
}

// Sync Update Rectangle
void	APU_INTERNAL::SyncUpdateRectangle( RECTANGLE& ch, INT type )
{
	if( !ch.sync_enable || ch.sync_len_count <= 0 )
		return;

	// Update Length
	if( ch.sync_len_count && !ch.sync_holdnote ) {
		if( (type&1) && ch.sync_len_count ) {
			ch.sync_len_count--;
		}
	}
}

// Render Rectangle
INT	APU_INTERNAL::RenderRectangle( RECTANGLE& ch )
{
	if( !ch.enable || ch.len_count <= 0 )
		return	0;

	// Channel disable?
	if( (ch.freq < 8) || (!ch.swp_inc && ch.freq > ch.freqlimit) ) {
		return	0;
	}

	if( ch.env_fixed ) {
		ch.nowvolume = ch.volume<<RECTANGLE_VOL_SHIFT;
	}
	INT	volume = ch.nowvolume;

	if( !(Config.sound.bChangeTone && ChannelTone[(!ch.complement)?0:1][ch.reg[0]>>6]) ) {
		// 補間処理
		double	total;
		double	sample_weight = ch.phaseacc;
		if( sample_weight > cycle_rate ) {
			sample_weight = cycle_rate;
		}
		total = (ch.adder < ch.duty)?sample_weight:-sample_weight;

		INT	freq = INT2FIX( ch.freq+1 );
		ch.phaseacc -= cycle_rate;
		while( ch.phaseacc < 0 ) {
			ch.phaseacc += freq;
			ch.adder = (ch.adder+1)&0x0F;

			sample_weight = freq;
			if( ch.phaseacc > 0 ) {
				sample_weight -= ch.phaseacc;
			}
			total += (ch.adder < ch.duty)?sample_weight:-sample_weight;
		}
		return	(INT)floor( volume*total/cycle_rate + 0.5 );
	} else {
		INT*	pTone = ToneTable[ChannelTone[(!ch.complement)?0:1][ch.reg[0]>>6]-1];

		// 更新無し
		ch.phaseacc -= cycle_rate*2;
		if( ch.phaseacc >= 0 ) {
			return	pTone[ch.adder&0x1F]*volume/((1<<RECTANGLE_VOL_SHIFT)/2);
		}

		// 1ステップだけ更新
		INT	freq = INT2FIX( ch.freq+1 );
		if( freq > cycle_rate*2 ) {
			ch.phaseacc += freq;
			ch.adder = (ch.adder+1)&0x1F;
			return	pTone[ch.adder&0x1F]*volume/((1<<RECTANGLE_VOL_SHIFT)/2);
		}

		// 加重平均
		INT	num_times, total;
		num_times = total = 0;
		while( ch.phaseacc < 0 ) {
			ch.phaseacc += freq;
			ch.adder = (ch.adder+1)&0x1F;
			total += pTone[ch.adder&0x1F]*volume/((1<<RECTANGLE_VOL_SHIFT)/2);
			num_times++;
		}
		return	total/num_times;
	}
}

/////////////

// Write Triangle
void	APU_INTERNAL::WriteTriangle( WORD addr, BYTE data )
{
	ch2.reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			if( ch2.counter_start & 0x80 ) {
				ch2.lin_count = data&0x7F;
				ch2.holdnote = data&0x80;
			}
			if( !(data&0x7F) ) {
				ch2.lin_count = 0;
			}
			ch2.counter_start = data&0x80;
			break;
		case	1: // Unused
			break;
		case	2:
			ch2.freq = INT2FIX( ((((INT)ch2.reg[3]&0x07)<<8)+(INT)data+1) );
			break;
		case	3: // Master
			ch2.freq      = INT2FIX( ((((INT)data&0x07)<<8)+(INT)ch2.reg[2]+1) );
			ch2.len_count = vbl_length[data>>3]*2;
			ch2.lin_count = ch2.reg[0]&0x7F;
			ch2.holdnote  = ch2.reg[0]&0x80;
			ch2.counter_start = 0x80;

			if( reg4015&(1<<2) )
				ch2.enable = 0xFF;
			break;
	}
}

// Update Triangle
void	APU_INTERNAL::UpdateTriangle( INT type )
{
	if( !ch2.enable )
		return;

	// Update Length/Linear
	if( !ch2.holdnote ) {
		if( (type&1) && ch2.len_count ) {
			ch2.len_count--;
		}
		ch2.counter_start = 0;

		if( ch2.lin_count ) {
			ch2.lin_count--;
		}
		if( (ch2.len_count <= 0) || (ch2.lin_count <= 0) ) {
			ch2.lin_count = 0;
		}
	}
}

// Sync Write Triangle
void	APU_INTERNAL::SyncWriteTriangle( WORD addr, BYTE data )
{
	ch2.sync_reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			if( ch2.sync_counter_start & 0x80 ) {
				ch2.sync_lin_count = data&0x7F;
				ch2.sync_holdnote = data&0x80;
			}
			if( !(data&0x7F) ) {
				ch2.sync_lin_count = 0;
			}
			ch2.sync_counter_start = data&0x80;
			break;
		case	1:
			break;
		case	2:
			break;
		case	3: // Master
			ch2.sync_len_count = vbl_length[ch2.sync_reg[3]>>3]*2;
			ch2.sync_lin_count = ch2.sync_reg[0]&0x7F;
			ch2.sync_holdnote = ch2.sync_reg[0]&0x80;
			ch2.sync_counter_start = 0x80;

			if( sync_reg4015&(1<<2) )
				ch2.sync_enable = 0xFF;
			break;
	}
}

// Sync Update Triangle
void	APU_INTERNAL::SyncUpdateTriangle( INT type )
{
	if( !ch2.sync_enable )
		return;

	// Update Length/Linear
	if( !ch2.sync_holdnote ) {
		if( (type&1) && ch2.sync_len_count ) {
			ch2.sync_len_count--;
		}
		ch2.sync_counter_start = 0;

		if( ch2.sync_lin_count ) {
			ch2.sync_lin_count--;
		}
	}
}

// Render Triangle
INT	APU_INTERNAL::RenderTriangle()
{
	INT	vol;
	if( Config.sound.bDisableVolumeEffect ) {
		vol = 256;
	} else {
		vol = 256-(INT)((ch4.reg[1]&0x01)+ch4.dpcm_value*2);
	}

	if( !ch2.enable || (ch2.len_count <= 0) || (ch2.lin_count <= 0) ) {
		return	ch2.nowvolume*vol/256;
	}

	if( ch2.freq < INT2FIX(8) ) {
		return	ch2.nowvolume*vol/256;
	}

//	if( !ch2.holdnote ) {
//		ch2.counter_start = 0xFF;
//	}

	if( !(Config.sound.bChangeTone && ChannelTone[2][0]) ) {
		ch2.phaseacc -= cycle_rate;
		if( ch2.phaseacc >= 0 ) {
			return	ch2.nowvolume*vol/256;
		}

		if( ch2.freq > cycle_rate ) {
			ch2.phaseacc += ch2.freq;
			ch2.adder = (ch2.adder+1)&0x1F;

			if( ch2.adder < 0x10 ) {
				ch2.nowvolume = (ch2.adder&0x0F)<<TRIANGLE_VOL_SHIFT;
			} else {
				ch2.nowvolume = (0x0F-(ch2.adder&0x0F))<<TRIANGLE_VOL_SHIFT;
			}

			return	ch2.nowvolume*vol/256;
		}

		// 加重平均
		INT	num_times, total;
		num_times = total = 0;
		while( ch2.phaseacc < 0 ) {
			ch2.phaseacc += ch2.freq;
			ch2.adder = (ch2.adder+1)&0x1F;

			if( ch2.adder < 0x10 ) {
				ch2.nowvolume = (ch2.adder&0x0F)<<TRIANGLE_VOL_SHIFT;
			} else {
				ch2.nowvolume = (0x0F-(ch2.adder&0x0F))<<TRIANGLE_VOL_SHIFT;
			}

			total += ch2.nowvolume;
			num_times++;
		}

		return	(total/num_times)*vol/256;
	} else {
		INT*	pTone = ToneTable[ChannelTone[2][0]-1];

		ch2.phaseacc -= cycle_rate;
		if( ch2.phaseacc >= 0 ) {
			return	ch2.nowvolume*vol/256;
		}

		if( ch2.freq > cycle_rate ) {
			ch2.phaseacc += ch2.freq;
			ch2.adder = (ch2.adder+1)&0x1F;
			ch2.nowvolume = pTone[ch2.adder&0x1F]*0x0F;
			return	ch2.nowvolume*vol/256;
		}

		// 加重平均
		INT	num_times, total;
		num_times = total = 0;
		while( ch2.phaseacc < 0 ) {
			ch2.phaseacc += ch2.freq;
			ch2.adder = (ch2.adder+1)&0x1F;
			total += pTone[ch2.adder&0x1F]*0x0F;
			num_times++;
		}

		return	(total/num_times)*vol/256;
	}
}

//////////////

// Write Noise
void	APU_INTERNAL::WriteNoise( WORD addr, BYTE data )
{
	ch3.reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch3.holdnote  = data&0x20;
			ch3.volume    = data&0x0F;
			ch3.env_fixed = data&0x10;
			ch3.env_decay = (data&0x0F)+1;
			break;
		case	1: // Unused
			break;
		case	2:
			ch3.freq    = INT2FIX(noise_freq[data&0x0F]);
			ch3.xor_tap = (data&0x80)?0x40:0x02;
			break;
		case	3: // Master
			ch3.len_count = vbl_length[data>>3]*2;
			ch3.env_vol   = 0x0F;
			ch3.env_count = ch3.env_decay+1;

			if( reg4015&(1<<3) )
				ch3.enable    = 0xFF;
			break;
	}
}

// Update Noise
void	APU_INTERNAL::UpdateNoise( INT type )
{
	if( !ch3.enable || ch3.len_count <= 0 )
		return;

	// Update Length
	if( !ch3.holdnote ) {
		// Holdnote
		if( (type&1) && ch3.len_count ) {
			ch3.len_count--;
		}
	}

	// Update Envelope
	if( ch3.env_count ) {
		ch3.env_count--;
	}
	if( ch3.env_count == 0 ) {
		ch3.env_count = ch3.env_decay;

		// Holdnote
		if( ch3.holdnote ) {
			ch3.env_vol = (ch3.env_vol-1)&0x0F;
		} else if( ch3.env_vol ) {
			ch3.env_vol--;
		}
	}

	if( !ch3.env_fixed ) {
		ch3.nowvolume = ch3.env_vol<<RECTANGLE_VOL_SHIFT;
	}
}

// Sync Write Noise
void	APU_INTERNAL::SyncWriteNoise( WORD addr, BYTE data )
{
	ch3.sync_reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch3.sync_holdnote = data&0x20;
			break;
		case	1:
			break;
		case	2:
			break;
		case	3: // Master
			ch3.sync_len_count = vbl_length[data>>3]*2;
			if( sync_reg4015&(1<<3) )
				ch3.sync_enable = 0xFF;
			break;
	}
}

// Sync Update Noise
void	APU_INTERNAL::SyncUpdateNoise( INT type )
{
	if( !ch3.sync_enable || ch3.sync_len_count <= 0 )
		return;

	// Update Length
	if( ch3.sync_len_count && !ch3.sync_holdnote ) {
		if( (type&1) && ch3.sync_len_count ) {
			ch3.sync_len_count--;
		}
	}
}

// Noise ShiftRegister
BYTE	APU_INTERNAL::NoiseShiftreg( BYTE xor_tap )
{
int	bit0, bit14;

	bit0 = ch3.shift_reg & 1;
	if( ch3.shift_reg & xor_tap ) bit14 = bit0^1;
	else			      bit14 = bit0^0;
	ch3.shift_reg >>= 1;
	ch3.shift_reg |= (bit14<<14);
	return	(bit0^1);
}

// Render Noise
INT	APU_INTERNAL::RenderNoise()
{
	if( !ch3.enable || ch3.len_count <= 0 )
		return	0;

	if( ch3.env_fixed ) {
		ch3.nowvolume = ch3.volume<<RECTANGLE_VOL_SHIFT;
	}

	INT	vol = 256-(INT)((ch4.reg[1]&0x01)+ch4.dpcm_value*2);

	ch3.phaseacc -= cycle_rate;
	if( ch3.phaseacc >= 0 )
		return	ch3.output*vol/256;

	if( ch3.freq > cycle_rate ) {
		ch3.phaseacc += ch3.freq;
		if( NoiseShiftreg(ch3.xor_tap) )
			ch3.output = ch3.nowvolume;
		else
			ch3.output = -ch3.nowvolume;

		return	ch3.output*vol/256;
	}

	INT	num_times, total;
	num_times = total = 0;
	while( ch3.phaseacc < 0 ) {
		ch3.phaseacc += ch3.freq;
		if( NoiseShiftreg(ch3.xor_tap) )
			ch3.output = ch3.nowvolume;
		else
			ch3.output = -ch3.nowvolume;

		total += ch3.output;
		num_times++;
	}

	return	(total/num_times)*vol/256;
}

//////////

void	APU_INTERNAL::WriteDPCM( WORD addr, BYTE data )
{
	ch4.reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch4.freq    = INT2FIX( dpcm_cycles[data&0x0F] );
			ch4.looping = data&0x40;
			break;
		case	1:
			ch4.dpcm_value = (data&0x7F)>>1;
			break;
		case	2:
			ch4.cache_addr = 0xC000+(WORD)(data<<6);
			break;
		case	3:
			ch4.cache_dmalength = ((data<<4)+1)<<3;
			break;
	}
}

INT	APU_INTERNAL::RenderDPCM()
{
	if( ch4.dmalength ) {
		ch4.phaseacc -= cycle_rate;

		while( ch4.phaseacc < 0 ) {
			ch4.phaseacc += ch4.freq;
			if( !(ch4.dmalength&7) ) {
				ch4.cur_byte = nes->Read( ch4.address );
				if( 0xFFFF == ch4.address )
					ch4.address = 0x8000;
				else
					ch4.address++;
			}

			if( !(--ch4.dmalength) ) {
				if( ch4.looping ) {
					ch4.address = ch4.cache_addr;
					ch4.dmalength = ch4.cache_dmalength;
				} else {
					ch4.enable = 0;
					break;
				}
			}
			// positive delta
			if( ch4.cur_byte&(1<<((ch4.dmalength&7)^7)) ) {
				if( ch4.dpcm_value < 0x3F )
					ch4.dpcm_value += 1;
			} else {
			// negative delta
				if( ch4.dpcm_value > 1 )
					ch4.dpcm_value -= 1;
			}
		}
	}

#if	1
	// インチキ臭いプチノイズカット(TEST)
	ch4.dpcm_output_real = (INT)((ch4.reg[1]&0x01)+ch4.dpcm_value*2)-0x40;
	if( abs(ch4.dpcm_output_real-ch4.dpcm_output_fake) <= 8 ) {
		ch4.dpcm_output_fake = ch4.dpcm_output_real;
		ch4.output = (INT)ch4.dpcm_output_real<<DPCM_VOL_SHIFT;
	} else {
		if( ch4.dpcm_output_real > ch4.dpcm_output_fake )
			ch4.dpcm_output_fake += 8;
		else
			ch4.dpcm_output_fake -= 8;
		ch4.output = (INT)ch4.dpcm_output_fake<<DPCM_VOL_SHIFT;
	}
#else
	ch4.output = (((INT)ch4.reg[1]&0x01)+(INT)ch4.dpcm_value*2)<<DPCM_VOL_SHIFT;
//	ch4.output = ((((INT)ch4.reg[1]&0x01)+(INT)ch4.dpcm_value*2)-0x40)<<DPCM_VOL_SHIFT;
#endif
	return	ch4.output;
}

void	APU_INTERNAL::SyncWriteDPCM( WORD addr, BYTE data )
{
	ch4.reg[addr&3] = data;
	switch( addr&3 ) {
		case	0:
			ch4.sync_cache_cycles = dpcm_cycles[data&0x0F];
			ch4.sync_cycles       = 0;
			ch4.sync_looping      = data&0x40;
			ch4.sync_irq_gen      = data&0x80;
			if( !ch4.sync_irq_gen ) {
				ch4.sync_irq_enable = 0;
				nes->cpu->ClrIRQ( IRQ_DPCM );
			}
			break;
		case	1:
			break;
		case	2:
			break;
		case	3:
			ch4.sync_cache_dmalength = ((data<<4)+1)<<3;
			break;
	}
}

BOOL	APU_INTERNAL::SyncUpdateDPCM( INT cycles )
{
BOOL	bIRQ = FALSE;

	if( ch4.sync_enable ) {
		ch4.sync_cycles -= cycles;
		while( ch4.sync_cycles < 0 ) {
			ch4.sync_cycles += ch4.sync_cache_cycles;
			if( ch4.sync_dmalength ) {
				if( !(--ch4.sync_dmalength) ) {
					if( ch4.sync_looping ) {
						ch4.sync_dmalength = ch4.sync_cache_dmalength;
					} else if( ch4.sync_irq_gen ) {
						ch4.sync_irq_enable = 0xFF;
						nes->cpu->SetIRQ( IRQ_DPCM );
					}
				}
			}
		}
	}
	if( ch4.sync_irq_enable ) {
		bIRQ = TRUE;
	}

	return	bIRQ;
}

INT	APU_INTERNAL::GetStateSize()
{
	return	4*sizeof(BYTE) + 3*sizeof(INT)
		+ sizeof(ch0) + sizeof(ch1)
		+ sizeof(ch2) + sizeof(ch3)
		+ sizeof(ch4);
}

void	APU_INTERNAL::SaveState( LPBYTE p )
{
	SETBYTE( p, reg4015 );
	SETBYTE( p, sync_reg4015 );

	SETINT( p, FrameCycle );
	SETINT( p, FrameCount );
	SETINT( p, FrameType );
	SETBYTE( p, FrameIRQ );
	SETBYTE( p, FrameIRQoccur );

	SETBLOCK( p, &ch0, sizeof(ch0) );
	SETBLOCK( p, &ch1, sizeof(ch1) );
	SETBLOCK( p, &ch2, sizeof(ch2) );
	SETBLOCK( p, &ch3, sizeof(ch3) );
	SETBLOCK( p, &ch4, sizeof(ch4) );
}

void	APU_INTERNAL::LoadState( LPBYTE p )
{
	GETBYTE( p, reg4015 );
	GETBYTE( p, sync_reg4015 );

	GETINT( p, FrameCycle );
	GETINT( p, FrameCount );
	GETINT( p, FrameType );
	GETBYTE( p, FrameIRQ );
	GETBYTE( p, FrameIRQoccur );

	GETBLOCK( p, &ch0, sizeof(ch0) );
	GETBLOCK( p, &ch1, sizeof(ch1) );
	GETBLOCK( p, &ch2, sizeof(ch2) );
	GETBLOCK( p, &ch3, sizeof(ch3) );
//	p += sizeof(ch3);
	GETBLOCK( p, &ch4, sizeof(ch4) );
}

