/*	
 *	framapu.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
 *	- 作成開始。
 *	* Tue Mar 01 05:20:00 JST 2005 Naoyuki Sawa
 *	- コメントを修正しました。
 *	  プログラムの内容には変化ありません。
 */
#include "clip.h"

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

#ifndef APU_ASM

void
apu_square_mix(APUSQUARE* square, short wbuff[/*64*/])
{
	APULENGTH* length = &square->length;
	APUENVELOPE* envelope = &square->envelope;
	APUSWEEP* sweep = &square->sweep;
	//
	int i;
	int volume;
	int wavelength;
	int waveindex;
	int wavecount;
	int duty;
	int output;

	/* レングスカウンタが0ならば、発声しません。 */
	if(!length->count) {
		return;
	}

	/* ボリュームが0ならば、発声しません。 */
	volume = envelope->volume;
	if(!volume) {
		return;
	}

	/* 波長が8以下または0x7ff以上ならば、発声しません。 */
	wavelength = sweep->wavelength;
	if(wavelength <= 8 || wavelength >= 0x7ff) {
		return;
	}
	wavelength = (wavelength + 1) * SPEAKER_FREQUENCY;
	// min(square->wavelength) = 9 ですから、
	//   (wavelength + 1) * SPEAKER_FREQUENCY = 160000
	//   1.79MHz / 160000 = 11.2
	// 1サンプル当りの最大更新回数は12回以下です。
	// P/ECEでも間に合うと思うので、リミッタは設けません。

	duty = square->duty;

	waveindex = square->waveindex; /* 取り出し */
	wavecount = square->wavecount; /* 取り出し */

	output = (waveindex < duty) ? volume : -volume;

	i = 64;
	do {
		wavecount -= APU_CLOCK;
		if(wavecount < 0) {
			do {
				waveindex++;
			} while((wavecount += wavelength) < 0);
			waveindex &= 15;
			output = (waveindex < duty) ? volume : -volume;
		}
		*wbuff++ += output;
	} while(--i);

	square->waveindex = waveindex; /* 書き戻し */
	square->wavecount = wavecount; /* 書き戻し */
}

void
apu_triangle_mix(APUTRIANGLE* triangle, short wbuff[/*64*/])
{
	APULENGTH* length = &triangle->length;
	APULINEAR* linear = &triangle->linear;
	//
	int i;
	int wavelength;
	int waveindex;
	int wavecount;
	int output;

	/* レングスカウンタが0ならば、発声しません。 */
	if(!length->count) {
		return;
	}

	/* リニアカウンタが0ならば、発声しません。 */
	if(!linear->count) {
		return;
	}

	/* 波長が8以下または0x7ff以上ならば、発声しません。 */
	wavelength = triangle->wavelength;
	if(wavelength <= 8 || wavelength >= 0x7ff) { /* 仕様 */
		return;
	}
	wavelength = (wavelength + 1) * SPEAKER_FREQUENCY;
	// min(triangle->wavelength) = 9 ですから、
	//   (wavelength + 1) * SPEAKER_FREQUENCY = 160000
	//   1.79MHz / 160000 = 11.2
	// 1サンプル当りの最大更新回数は12回以下です。
	// P/ECEでも間に合うと思うので、リミッタは設けません。

	waveindex = triangle->waveindex; /* 取り出し */
	wavecount = triangle->wavecount; /* 取り出し */

	output = apu_triangle_waveform_table[waveindex];

	i = 64;
	do {
		wavecount -= APU_CLOCK;
		if(wavecount < 0) {
			do {
				waveindex++;
			} while((wavecount += wavelength) < 0);
			waveindex &= 31;
			output = apu_triangle_waveform_table[waveindex];
		}
		*wbuff++ += output;
	} while(--i);

	triangle->waveindex = waveindex; /* 書き戻し */
	triangle->wavecount = wavecount; /* 書き戻し */
}

void
apu_noise_mix(APUNOISE* noise, short wbuff[/*64*/])
{
	APULENGTH* length = &noise->length;
	APUENVELOPE* envelope = &noise->envelope;
	//
	int i;
	int v;
	int volume;
	int wavelength;
	int wavecount;
	int mode;
	int lfsr;
	int output;

	/* レングスカウンタが0ならば、発声しません。 */
	if(!length->count) {
		return;
	}

	/* ボリュームが0ならば、発声しません。 */
	volume = envelope->volume;
	if(!volume) {
		return;
	}

	/* 波長が0ならば、発声しません。($400E未設定の場合) */
	wavelength = noise->wavelength;
	if(!wavelength) {
		return;
	}
	wavelength = wavelength * SPEAKER_FREQUENCY;
	// min(noise->wavelength) = 4 ですから、
	//   wavelength * SPEAKER_FREQUENCY = 64000
	//   1.79MHz / 64000 = 28.0
	// 1サンプル当りの最大更新回数は29回以下です。
	// これはかなり厳しいです...リミッタ必要かも？

	mode = noise->mode;

	lfsr      = noise->lfsr;      /* 取り出し */
	wavecount = noise->wavecount; /* 取り出し */

	output = !(lfsr & 1) ? volume : -volume;

	i = 64;
	do {
		wavecount -= APU_CLOCK;
		if(wavecount < 0) {
			do {
				v = ((lfsr>>0) ^ (lfsr>>mode)) & 1;
				lfsr = (lfsr >> 1) | (v << 14);
			} while((wavecount += wavelength) < 0);
			output = !(lfsr & 1) ? volume : -volume;
		}
		*wbuff++ += output;
	} while(--i);

	noise->lfsr      = lfsr;      /* 書き戻し */
	noise->wavecount = wavecount; /* 書き戻し */
}

void
apu_dmc_mix(APUDMC* dmc, short wbuff[/*64*/]) /* ボンバーマンが使ってます */
{
	const unsigned char* memory = dmc->memory;
	//
	int i;
	int wavelength;
	int output;
	int address;
	int length;
	int sample;
	int wavecount;

	/* 発声中フラグがクリアされた状態で呼び出されることはないはずです。 */
	ASSERT(dmc->flags & (1<<7));

	/* 波長が0ならば、発声しません。($4010未設定の場合) */
	wavelength = dmc->wavelength;
	if(!wavelength) {
		goto STOP;
	}
	wavelength = wavelength * SPEAKER_FREQUENCY;
	// min(dmc->wavelength) = 54 ですから、
	//   wavelength * SPEAKER_FREQUENCY = 864000
	//   1.79MHz / 864000 = 2.1
	// 1サンプル当りの最大更新回数は3回以下です。
	// P/ECEでも間に合うと思うので、リミッタは設けません。

	output    = dmc->output;    /* 取り出し */
	address   = dmc->address;   /* 取り出し */
	length    = dmc->length;    /* 取り出し */
	sample    = dmc->sample;    /* 取り出し */
	wavecount = dmc->wavecount; /* 取り出し */

	i = 64;
	do {
		wavecount -= APU_CLOCK;
		if(wavecount < 0) {
			do {
				/* 新しいサンプルバイトをDMA転送で取ってくる処理。 */
				if(sample <= 1) { /* サンプルバッファが空 */
					length--;
					if(!length) { /* サンプルデータ無し */
						if(!(dmc->flags & (1<<0))) { /* ループ無し */
							goto STOP;
						}
						/* ループ処理。(apu_write()内のデルタ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; -> 進行タイミングは継続するので0にしちゃダメ
						continue/*whileへ*/; /* ループ直後は初期値をそのまま出力(たぶん…) */
					}
					sample = memory[address++] | 0x100/*サンプルバッファ空を検出するため*/;
					address = (unsigned short)address | 0x8000;
				}

				/* サンプルバッファの最下位ビットを出力へ反映する処理。 */
				if(!(sample & 1)) {
					/* サンプルの最下位ビットが0ならば、出力-2。 */
					if(output >= -64 + 2) {
						output -= 2;
					} else {
						output = -64;
					}
				} else {
					/* サンプルの最下位ビットが1ならば、出力+2。 */
					if(output <= 63 - 2) {
						output += 2;
					} else {
						output = 63;
					}
				}
				sample >>= 1; /* 消費した最下位ビットをシフトアウト */
			} while((wavecount += wavelength) < 0);
		}
		*wbuff++ += output;
	} while(--i);

	dmc->output    = output;    /* 書き戻し */
	dmc->address   = address;   /* 書き戻し */
	dmc->length    = length;    /* 書き戻し */
	dmc->sample    = sample;    /* 書き戻し */
	dmc->wavecount = wavecount; /* 書き戻し */

	return;
STOP:
	dmc->flags &= ~(1<<7); /* 発声完了 */
	return;
}

#endif /*APU_ASM*/

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

/*   SQUARE1  符号付き5bit
 * + SQUARE2  符号付き5bit
 * + TRIANGLE 符号付き5bit
 * + NOISE    符号付き5bit
 * + DMC      符号付き7bit
 * =          符号付き8bit
 */
#define APU_VOLUME_SHIFT ((16-8)+1/*調整可*/)

#ifndef APU_ASM

void
apu_volume_shift(short wbuff[/*APUBUFLEN*/])
{
	int i;
	int v;

	i = APUBUFLEN;
	do {
		v = *wbuff;
		v <<= APU_VOLUME_SHIFT;
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*wbuff++ = v;
	} while(--i);
}

#else /*APU_ASM*/

asm("
	.code
	.align 1
	.global apu_volume_shift
apu_volume_shift:
	; %r12 = wbuff
	xld.w %r13, 320			; APUBUFLEN = 320
	xld.w %r4, 32767
	not %r5, %r4			; -32768
apu_volume_shift_DO:
	ld.h %r6, [%r12]
	xsll %r6, 9			; APU_VOLUME_SHIFT = (16-8)+1 = 9
	cmp %r6, %r4
	jrle.d 3
	cmp %r6, %r5			; *delay*
	ld.w %r6, %r4			; (skip?)
	jrge.d 3
	sub %r13, 1			; *delay*
	ld.w %r6, %r5			; (skip?)
	ld.h [%r12]+, %r6
	xjrne apu_volume_shift_DO
	ret
");

#endif /*APU_ASM*/
