/*	
 *	clipapu.c
 *
 *	P/ECE APU (RICOH RP2A03) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sun Feb 06 18:39:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

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

APU apu;

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

/* 矩形波デューティ設定値 -> デューティサイクル変換テーブル */
const unsigned char apu_square_duty_table[4] = { 2,4,8,12 };

/* 三角波波形インデクス -> 波形出力値変換テーブル */
const char apu_triangle_waveform_table[32] = {
	+ 1,+ 3,+ 5,+ 7,+ 9,+11,+13,+15,
	+15,+13,+11,+ 9,+ 7,+ 5,+ 3,+ 1,
	- 1,- 3,- 5,- 7,- 9,-11,-13,-15,
	-15,-13,-11,- 9,- 7,- 5,- 3,- 1,
};

/* ノイズ波長設定値 -> 波長変換テーブル */
const short apu_noise_wavelength_table[16] = {
	0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0a0,
	0x0ca,0x0fe,0x17c,0x1fc,0x2fa,0x3f8,0x7f2,0xfe4,
};

/* デルタPCM波長設定値 -> 波長変換テーブル */
const short apu_dmc_wavelength_table[16] = {
	0x1ac,0x17c,0x154,0x140,0x11e,0x0fe,0x0e2,0x0d6,
	0x0be,0x0a0,0x08e,0x080,0x06a,0x054,0x048,0x036,
};

/* レングスカウンタ設定値 -> フレーム数変換テーブル */
const unsigned char apu_length_count_table[32] = {
	0x05,0x7f, /* $00,$01 */
	0x0a,0x01, /* $02,$03 */
	0x14,0x02, /* $04,$05 */
	0x28,0x03, /* $06,$07 */
	0x50,0x04, /* $08,$09 */
	0x1e,0x05, /* $0A,$0B */
	0x07,0x06, /* $0C,$0D */
	0x0e,0x07, /* $0E,$0F */
	0x06,0x08, /* $10,$11 */
	0x0c,0x09, /* $12,$13 */
	0x18,0x0a, /* $14,$15 */
	0x30,0x0b, /* $16,$17 */
	0x60,0x0c, /* $18,$19 */
	0x24,0x0d, /* $1A,$1B */
	0x08,0x0e, /* $1C,$1D */
	0x10,0x0f, /* $1E,$1F */
};

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

void
apu_reset(const void* memory)
{
	APU* p_apu = &apu;

	/* まず、クリアします。 */
	memset(p_apu, 0, sizeof(APU));

	/* サンプルデータメモリの先頭アドレスを格納します。 */
	p_apu->dmc.memory = memory;

	/* デルタPCMチャネルを初期化します。
	 * * APUリセット後、$4010..$4013設定前にデルタPCMチャネルを発声開始すると、
	 *   address,length,wavelengthが不正な値のまま発声してしまいます。
	 *   そのような状況を防ぐために、$4010..$4013にダミー設定を行っておきます。
	 */
	apu_write(0x4010, 0);
	apu_write(0x4011, 0);
	apu_write(0x4012, 0);
	apu_write(0x4013, 0);
}

void
apu_write(int addr, int data)
{
	APU* p_apu            = &apu;
	unsigned char* regs   = p_apu->regs;
	APUSQUARE* square   /*= p_apu->square*/;
	APUTRIANGLE* triangle = &p_apu->triangle;
	APUNOISE* noise       = &p_apu->noise;
	APUDMC* dmc           = &p_apu->dmc;
	//
	APULENGTH* length;
	APUENVELOPE* envelope;
	APUSWEEP* sweep;
	APULINEAR* linear;
	//
	int base;

	addr &= 0x1f;		/* $4000..$401F -> $00..$1F */
	base = addr & ~3;	/* $00..$03 -> $00, $04..$07 -> $04, ... */

	/* まず、レジスタ値を格納しておきます。 */
	regs[addr] = data;

	switch(addr) {

	/****************************
	 *  矩形波エンベロープ設定  *
	 ****************************/
	case 0x00: /* $4000 */
	case 0x04: /* $4004 */
		square = &p_apu->square[addr >> 2];
		/* エンベロープ設定。 */
		envelope = &square->envelope;
		envelope->flags = 0;
		if(!(regs[base+0] & (1<<4))) { /* エンベロープ有効 */
			envelope->flags |= (1<<0);
			if(regs[base+0] & (1<<5)) { /* ループ有り */
				envelope->flags |= (1<<1);
			}
			envelope->period = (regs[base+0] & 0x0f) + 1;
			envelope->count = envelope->period;
			envelope->volume = 15;
		} else { /* エンベロープ無効 */
			envelope->volume = regs[base+0] & 0x0f;
		}

		break;

	/************************
	 *  矩形波スイープ設定  *
	 ************************/
	case 0x01: /* $4001 */
	case 0x05: /* $4005 */
		square = &p_apu->square[addr >> 2];
		/* スイープ設定。 */
		sweep = &square->sweep;
		sweep->flags = 0;
		if(regs[base+1] & (1<<7)) { /* スイープ有効 */
			sweep->flags |= (1<<0);
			if(regs[base+1] & (1<<3)) { /* 波長減少 */
				sweep->flags |= (1<<1);
			}
			sweep->shift = regs[base+1] & 0x07;
			sweep->period = ((regs[base+1] >> 4) & 0x07) + 1;
			sweep->count = sweep->period;
		}

		break;

	/********************
	 *  矩形波発声開始  *
	 ********************/
	case 0x03: /* $4003 */
	case 0x07: /* $4007 */
		square = &p_apu->square[addr >> 2];
		/* レングスカウンタ設定。 */
		length = &square->length;
		length->flags = 0;
		if(!(regs[base+0] & (1<<5))) { /* レングスカウンタ有効 */
			length->flags |= (1<<0);
			length->count = apu_length_count_table[regs[base+3] >> 3];
		} else { /* レングスカウンタ無効 */
			length->count = -1;
		}

		/* 波形ジェネレータ設定。 */
		square->duty = apu_square_duty_table[regs[base+0] >> 6];
		square->waveindex = 0;
		square->wavecount = 0;

		/* FALLTHRU */;

	/********************
	 *  矩形波波長設定  *
	 ********************/
	case 0x02: /* $4002 */
	case 0x06: /* $4006 */
		square = &p_apu->square[addr >> 2];
		/* 波長設定。 */
		sweep = &square->sweep;
		sweep->wavelength = regs[base+2] | (regs[base+3] & 0x07) << 8;

		break;

	/*******************
	 *  三角波発声開始 *
	 *******************/
	case 0x0b: /* $400B */
		/* レングスカウンタ設定。 */
		length = &triangle->length;
		length->flags = 0;
		if(!(regs[base+0] & (1<<7))) { /* レングスカウンタ有効 */
			length->flags |= (1<<0);
			length->count = apu_length_count_table[regs[base+3] >> 3];
		} else { /* レングスカウンタ無効 */
			length->count = -1;
		}

		/* リニアカウンタ設定。 */
		linear = &triangle->linear;
		linear->flags = 0;
		if(!(regs[base+0] & (1<<7))) { /* リニアカウンタ有効 */
			linear->flags |= (1<<0);
			linear->count = regs[base+0] & 0x7f;
		} else { /* リニアカウンタ無効 */
			linear->count = -1;
		}

		/* 波形ジェネレータ設定。 */
		triangle->waveindex = 0;
		triangle->wavecount = 0;

		/* FALLTHRU */

	/*******************
	 *  三角波波長設定 *
	 *******************/
	case 0x0a: /* $400A */
		/* 波長設定。 */
		triangle->wavelength = regs[base+2] | (regs[base+3] & 0x07) << 8;

		break;

	/****************************
	 *  ノイズエンベロープ設定  *
	 ****************************/
	case 0x0c: /* $400C */
		/* エンベロープ設定。 */
		envelope = &noise->envelope;
		envelope->flags = 0;
		if(!(regs[base+0] & (1<<4))) { /* エンベロープ有効 */
			envelope->flags |= (1<<0);
			if(regs[base+0] & (1<<5)) { /* ループ有り */
				envelope->flags |= (1<<1);
			}
			envelope->period = (regs[base+0] & 0x0f) + 1;
			envelope->count = envelope->period;
			envelope->volume = 15;
		} else { /* エンベロープ無効 */
			envelope->volume = regs[base+0] & 0x0f;
		}

		break;

	/********************
	 *  ノイズ発声開始  *
	 ********************/
	case 0x0f: /* $400F */
		/* レングスカウンタ設定。 */
		length = &noise->length;
		length->flags = 0;
		if(!(regs[base+0] & (1<<5))) { /* レングスカウンタ有効 */
			length->flags |= (1<<0);
			length->count = apu_length_count_table[regs[base+3] >> 3];
		} else { /* レングスカウンタ無効 */
			length->count = -1;
		}

		/* 波形ジェネレータ設定。 */
		noise->lfsr = 1 << 14;
		noise->wavecount = 0;

		break; /* FALLTHRU不要!! */

	/****************************
	 *  ノイズモード・波長設定  *
	 ****************************/
	case 0x0e: /* $400E */
		/* モード設定。 */
		noise->mode = !(regs[base+2] & 0x80) ? 1/*LongMode*/ : 6/*ShortMode*/;

		/* 波長設定。 */
		noise->wavelength = apu_noise_wavelength_table[regs[base+2] & 0x0f];

		break;

	/*******************************************
	 *  デルタPCM IRQクリア、モード・波長設定  *
	 *******************************************/
	case 0x10: /* $4010 */
		/* デルタPCM IRQクリア。
		 * デルタPCM IRQは、D7=0の値を$4010に書き込んで、手動でクリアする仕様です。
		 * Frame IRQと異なり、ステータスの読み出しでは、自動的にクリアされません。
		 */
		if(!(regs[base+0] & (1<<7))) { /* 0 = disable, 1 = enable (Frame IRQとは逆です!!) */
			p_apu->irq &= ~(1<<7); /* デルタPCM IRQクリア*/
		}

		/* モード設定。 */
		dmc->flags = 0;
		if(regs[base+0] & (1<<6)) { /* ループ有り */
			dmc->flags |= (1<<0);
		}

		/* 波長設定。 */
		dmc->wavelength = apu_dmc_wavelength_table[regs[base+0] & 0x0f];

		break;

	/*****************************
	 *  デルタPCM出力初期値設定  *
	 *****************************/
	case 0x11: /* $4011 */
		/* 出力初期値設定。 */
		dmc->init_output = (regs[base+1] & 0x7f) - 64; /* 0..127 -> -64..63 */

		break;

	/*****************************************
	 *  デルタPCMサンプルアドレス初期値設定  *
	 *****************************************/
	case 0x12: /* $4012 */
		/* サンプルアドレス初期値設定。 */
		dmc->init_address = 0xc000 + regs[base+2] * 0x40; /* $C000..$FFC0 */

		break;

	/*****************************************
	 *  デルタPCMサンプルデータ長初期値設定  *
	 *****************************************/
	case 0x13: /* $4013 */
		/* サンプルデータ長初期値設定。 */
		dmc->init_length = regs[base+3] * 0x10 + 1; /* $001..$FF1 */

		break;

	/***************************************************
	 *  各チャネルの有効・無効設定、デルタPCM発声開始  *
	 ***************************************************/
	case 0x15: /* $4015 */
		/* デルタPCM発声開始。 */
		if(regs[0x15] & (1<<4)) { /* デルタPCMチャネル有効 */
			dmc->flags    |= (1<<7); /* 発声中 */
			dmc->output    = dmc->init_output;
			dmc->address   = dmc->init_address;
			dmc->length    = dmc->init_length;
			dmc->sample    = 0; /* サンプルバッファ空 */
			dmc->wavecount = 0;
		}

		break;
	}
}

int
apu_read(int addr)
{
	APU* p_apu            = &apu;
	unsigned char* regs   = p_apu->regs;
	APUSQUARE* square     = p_apu->square;
	APUTRIANGLE* triangle = &p_apu->triangle;
	APUNOISE* noise       = &p_apu->noise;
	APUDMC* dmc           = &p_apu->dmc;
	//
	int result = p_apu->irq & (regs[0x15] & 0x1f);

	/* ステータスの読み出しで、Frame IRQをクリアします。
	 * デルタPCM IRQはクリアしません。
	 */
	p_apu->irq &= ~(1<<6); /* Frame IRQクリア */

	/* 停止しているチャネルのbitを下げます。
	 * レングスカウンタ以外の要因による停止は反映しません。
	 */
	if(!square[0].length.count) {
		result &= ~(1<<0); /* 矩形波チャネル1停止 */
	}
	if(!square[1].length.count) {
		result &= ~(1<<1); /* 矩形波チャネル2停止 */
	}
	if(!triangle->length.count) {
		result &= ~(1<<2); /* 三角波チャネル停止 */
	}
	if(!noise->length.count) {
		result &= ~(1<<3); /* ノイズチャネル停止 */
	}
	if(!(dmc->flags & (1<<7))) { /* デルタPCMだけ、発声中フラグを見ます */
		result &= ~(1<<4); /* デルタPCMチャネル停止 */
	}

	return result;
}

void
apu_mix(short wbuff[/*APUBUFLEN*/])
{
	APU* p_apu            = &apu;
	unsigned char* regs   = p_apu->regs;
	APUSQUARE* square     = p_apu->square;
	APUTRIANGLE* triangle = &p_apu->triangle;
	APUNOISE* noise       = &p_apu->noise;
	APUDMC* dmc           = &p_apu->dmc;
	//
	int fc_250_240;
	int framecount;
	//
	int i;

	memset(wbuff, 0, sizeof(short) * APUBUFLEN);

	fc_250_240 = p_apu->fc_250_240; /* 取り出し */
	framecount = p_apu->framecount; /* 取り出し */
	i = APUBUFLEN / 64;
	do {
		/* 250Hz->240Hz変換。 */
		fc_250_240 += 24;
		if(fc_250_240 >= 25) {
			fc_250_240 -= 25;

			if(framecount < 4) { /* 240Hz: {3,2,1,0} */
				/* エンベロープ処理。 */
				apu_do_envelope(&square[0].envelope);
				apu_do_envelope(&square[1].envelope);
				apu_do_envelope(&noise->envelope);
				/* リニアカウンタ処理。 */
				apu_do_linear(&triangle->linear);
			}
			if(framecount & 1) { /* 120Hz: {3,1} */
				/* スイープ処理。 */
				apu_do_sweep(&square[0].sweep);
				apu_do_sweep(&square[1].sweep);
			}
			if(!framecount) { /* 60Hz: {0} */
				/* レングスカウンタ処理。 */
				apu_do_length(&square[0].length);
				apu_do_length(&square[1].length);
				apu_do_length(&triangle->length);
				apu_do_length(&noise->length);
				/* Frame IRQ生成。 */
				if(!(regs[0x17] & (1<<7))) { /* 4 step mode */
					framecount = 4;
					if(!(regs[0x17] & (1<<6))) { /* 0 = enable, 1 = disable (デルタPCM IRQとは逆です!!) */
						p_apu->irq |= (1<<6); /* Frame IRQ */
					}
				} else { /* 5 step mode */
					framecount = 5;
					/* no Frame IRQ */
				}
			}
			framecount--;
		}

		/* ミキシング処理。 */
		if(regs[0x15] & (1<<0)) { /* 矩形波チャネル1有効 */
			apu_square_mix(&square[0], wbuff);
		}
		if(regs[0x15] & (1<<1)) { /* 矩形波チャネル2有効 */
			apu_square_mix(&square[1], wbuff);
		}
		if(regs[0x15] & (1<<2)) { /* 三角波チャネル有効 */
			apu_triangle_mix(triangle, wbuff);
		}
		if(regs[0x15] & (1<<3)) { /* ノイズチャネル有効 */
			apu_noise_mix(noise, wbuff);
		}
		if(regs[0x15] & (1<<4)) { /* デルタPCMチャネル有効 */
			if(dmc->flags & (1<<7)) { /* 発声中? */
				apu_dmc_mix(dmc, wbuff);
				if(!(dmc->flags & (1<<7))) { /* 発生完了? */
					if(regs[0x10] & (1<<7)) { /* 0 = disable, 1 = enable (Frame IRQとは逆です!!) */
						p_apu->irq |= (1<<7); /* デルタPCM IRQ */
					}
				}
			}
		}
		wbuff += 64;
	} while(--i);
	p_apu->fc_250_240 = fc_250_240; /* 書き戻し */
	p_apu->framecount = framecount; /* 書き戻し */

	wbuff -= APUBUFLEN;
	apu_volume_shift(wbuff);
}

void
apu_do_length(APULENGTH* length)
{
	if(length->flags & (1<<0)) {
		if(length->count) {
			length->count--;
			if(!length->count) {
				length->flags &= ~(1<<0);
			}
		}
	}
}

void
apu_do_linear(APULINEAR* linear)
{
	if(linear->flags & (1<<0)) {
		linear->count--;
		if(!linear->count) {
			linear->flags &= ~(1<<0);
		}
	}
}

void
apu_do_envelope(APUENVELOPE* envelope)
{
	if(envelope->flags & (1<<0)) {
		envelope->count--;
		if(!envelope->count) {
			envelope->count = envelope->period;
			envelope->volume--;
			if(!envelope->volume) {
				if(envelope->flags & (1<<1)) {
					envelope->volume = 15;
				} else {
					envelope->flags &= ~(1<<0);
				}
			}
		}
	}
}

void
apu_do_sweep(APUSWEEP* sweep)
{
	if(sweep->flags & (1<<0)) {
		sweep->count--;
		if(!sweep->count) {
			sweep->count = sweep->period;
			if(!(sweep->flags & (1<<1))) {
				sweep->wavelength += sweep->wavelength >> sweep->shift;
				if(sweep->wavelength >= 0x7ff) {
					sweep->wavelength = 0x7ff;
					sweep->flags &= ~(1<<0);
				}
			} else {
				sweep->wavelength -= sweep->wavelength >> sweep->shift;
				if(sweep->wavelength <= 8) {
					sweep->wavelength = 8;
					sweep->flags &= ~(1<<0);
				}
			}
		}
	}
}
