/*	
 *	framsn7.c
 *
 *	P/ECE SN76489/SN76496(DCSG) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Mon Jan 24 18:30:00 JST 2005 Naoyuki Sawa
 *	- 高速化のために大幅な改造を行いました。
 *	  VGM Player全体で2.5倍程度速くなったので、単体ではもっと速くなっていると思います。
 *	- 動作実績のある旧コードは keep/SN76489改造前保存-20050124.zip に保存してあります。
 */
#include "clip.h"

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

/* 安全のため、1サンプル当りの出力極性反転回数、LFSRシフト回数を制限します。
 * 極端に周期が短いと、1サンプル当りの反転処理回数が多くなりすぎて、
 * サウンド処理が割り込みに間に合わず、ハングアップする危険があるからです。
 * 1サンプル当りの反転回数を (2^SN76489_SAFETY_SHIFT) 以下に制限します。
 * すなわち 最高周波数＝16000[Hz]×(2^SN76489_SAFETY_SHIFT) となります。
 */
#define SN76489_SAFETY_SHIFT	4/*調整可*/

/* 各チャネルの出力は-15..15(符号付き5bit)です。
 * 全4チャネルなので、符号付き5bit×4=符号付き7bitとなります。
 * 符号付き16bitへスケーリングするために、(16-7)bit左シフトします。
 * 音量増幅のために、+1bit左シフトします。(調整可)
 */
#define SN76489_VOLUME_SHIFT	((16 - 7) + 1/*調整可*/)

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

void
sn76489_tone_mix(SN76489* sn, SN76489TONE* tone, short wbuff[/*count*/], int count)
{
	int clock;
	int volume;
	int period;
	int progress;

	ASSERT(count > 0);

	/* ボリュームを取得します。 */
	volume = tone->volume;
	if(!volume) {
		return;
	}

	/* 現在の出力極性を反映します。 */
	if(tone->polarity) {
		volume = -volume;
	}

	/* 周期を取得します。 */
	period = tone->period;

	/* 周期は入力クロック×16単位です。 */
	period *= SPEAKER_FREQUENCY * 16;

	/* 入力クロックを取得します。 */
	clock = sn->clock;

	/* 安全のため、1サンプル当りの出力極性反転回数を制限します。 */
	if(period < (clock >> SN76489_SAFETY_SHIFT)) {
		period = clock >> SN76489_SAFETY_SHIFT;
	}

	/* 高速化のため、進行カウンタを取り出しておきます。 */
	progress = tone->progress;

	/* 出力波形合成ループ。 */
	do {
		if((progress += clock) >= 0) {
			do {
				volume = -volume;
			} while((progress -= period) >= 0);
		}
		*wbuff++ += volume;
	} while(--count);

	/* 進行カウンタを書き戻します。 */
	tone->progress = progress;

	/* 最後の出力極性を書き戻します。 */
	tone->polarity = volume < 0;
}

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

void
sn76489_noise_mix(SN76489* sn, SN76489NOISE* noise, short wbuff[/*count*/], int count)
{
	int clock;
	int volume;
	int period;
	int progress;
	int lfsr;
	int fbtype;
	int parity;

	ASSERT(count > 0);

	/* ボリュームを取得します。 */
	volume = noise->volume;
	if(!volume) {
		return;
	}

	/* 周期を取得します。 */
	period = noise->period;
	if(!period) {
		period = sn->tone[2].period; /* Tone2の周期を使用 */
	}

	/* 周期は入力クロック×16単位です。 */
	period *= SPEAKER_FREQUENCY * 16;

	/* 入力クロックを取得します。 */
	clock = sn->clock;

	/* 安全のため、1サンプル当りのLFSRシフト回数を制限します。 */
	if(period < (clock >> SN76489_SAFETY_SHIFT)) {
		period = clock >> SN76489_SAFETY_SHIFT;
	}

	/* フィードバックタイプを取得します。 */
	fbtype = noise->fbtype;

	/* 高速化のため、進行カウンタとLFSRを取り出しておきます。 */
	progress = noise->progress;
	lfsr = noise->lfsr;

	/* 出力波形合成ループ。 */
	do {
		if((progress += clock) >= 0) {
			do {
				/* タップビットの奇数パリティを求めます。
				 *   0=周期ノイズ     (タップビット=0x1)
				 *   1=ホワイトノイズ (タップビット=0x9)
				 */
				parity = lfsr;
				if(fbtype) {
					parity ^= lfsr >> 3;
				}
				parity &= 1;
				/* LFSRをシフト、フィードバックします。 */
				lfsr = (lfsr >> 1) | (parity << 15);
			} while((progress -= period) >= 0);
		}
		*wbuff++ += (lfsr & 1) ? -volume : volume;
	} while(--count);

	/* 進行カウンタとLFSRを書き戻します。 */
	noise->progress = progress;
	noise->lfsr = lfsr;
}

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

#ifndef PIECE

void
sn76489_volume_shift(short wbuff[/*count*/], int count)
{
	int v;

	ASSERT(count > 0);

	do {
		v = *wbuff;
		v <<= SN76489_VOLUME_SHIFT;
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*wbuff++ = v;
	} while(--count);
}

#else /*PIECE*/

asm("
	.code
	.align 1
	.global sn76489_volume_shift
sn76489_volume_shift:
	; %r12 = wbuff
	; %r13 = count
	xld.w %r4, 32767
	not %r5, %r4			; -32768
sn76489_volume_shift_DO:
	ld.h %r6, [%r12]
	xsll %r6, 10			; SN76489_VOLUME_SHIFT = (16-7)+1 = 10
	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 sn76489_volume_shift_DO
	ret
");

#endif /*PIECE*/
