/*	
 *	framp62.c
 *
 *	P/ECE HuC6280-PSG Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Mon Feb 23 04:28:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

void
huc6280psg_mix(HUC6280PSG* psg, short wbuff[/*count*/], int count)
{
	DDA clock_progress;
	int cycle;
	int sample;

	clock_progress = psg->clock_progress; /* 高速化のため、一時取り出し */
	while(count > 0) {
		/* 経過サイクル数を求めます。 */
		cycle = clock_progress.u;
		clock_progress.d += clock_progress.n;
		if(clock_progress.d >= 0) {
			clock_progress.d -= SPEAKER_FREQUENCY;
			cycle++;
		}

		/* 1サンプルミキシングします。 */
		sample = huc6280psg_process(psg, cycle);
		sample >>= (23 - 16) - 2/*調整可(増やすと音量大きくなります)*/;

		/* クリッピングして格納します。 */
		if(sample < -0x7fff) sample = -0x7fff;
		if(sample >  0x7fff) sample =  0x7fff;
		*wbuff++ = sample;

		/* 残りサンプル数を減らします。 */
		count--;
	}
	psg->clock_progress = clock_progress; /* 書き戻し。忘れずないで!! */
}

/****************************************************************************
 *	内部関数
 ****************************************************************************/

/* チャネル出力:符号付き20bit × 6チャネル ＝ 符号付き22.6bit */
int
huc6280psg_process(HUC6280PSG* psg, int cycle)
{
	return huc6280psg_channel_process(psg, &psg->channel[0], cycle)
	     + huc6280psg_channel_process(psg, &psg->channel[1], cycle)
	     + huc6280psg_channel_process(psg, &psg->channel[2], cycle)
	     + huc6280psg_channel_process(psg, &psg->channel[3], cycle)
	     + huc6280psg_channel_process(psg, &psg->channel[4], cycle)
	     + huc6280psg_channel_process(psg, &psg->channel[5], cycle);
}

#ifndef PIECE

/* 波形データ:符号付き5bit × チャネルボリューム:符号無し15bit ＝ 符号付き20bit */
int
huc6280psg_channel_process(HUC6280PSG* psg, HUC6280PSGCHANNEL* channel, int cycle)
{
	char sample;

	if(!channel->enable) return 0;

	/* enable=0の代わりにvolume=0でチャネルOffを行うHESファイルがあります。
	 * その際、periodも非常に小さな値(1)に設定されていることがあるようです。
	 * ("Aero Blasters.hes","Spin Pair.hes"等の一部のトラック)
	 * periodが小さすぎると処理が間に合わなくなるので、volume=0を調べて弾くことにします。
	 */
	if(!channel->volume) return 0;

	if(!channel->dda) {
		if(!channel->noise) { /* Tone mode */
			channel->tone_progress -= cycle;
			while(channel->tone_progress <= 0) {
				channel->tone_progress += channel->tone_period;
				channel->tone_index++;
			}
			sample = channel->wave[channel->tone_index & 0x1f];

		} else { /* Noise mode */
			channel->noise_progress -= cycle;
			while(channel->noise_progress <= 0) {
				channel->noise_progress += channel->noise_period;
				/* 実機の内部処理の仕様がわからないので、仮に[9bit tap=0x11]のM系列乱数を使うことにします。 */
				channel->noise_lfsr = (channel->noise_lfsr >> 1) |
						    (((channel->noise_lfsr >> 0) ^ (channel->noise_lfsr >> 4)) & 1) << 8;
			}
			sample = channel->noise_lfsr & 1 ? 15 : -16;
		}

	} else { /* Direct D/A mode */
		sample = channel->wave[0];
	}

	return sample * channel->volume; /* アセンブラ化時は、short×shortで高速化せよ */
}

#else /*PIECE*/

/* * 高速化のため、C言語版とアセンブラ版では僅かに出力が異なります。
 *   C言語版とアセンブラ版で処理が異なっている点は、次のとおりです。
 *
 * - 進行カウンタのしきいが異なります。
 *
 *   [C言語版]
 *	経過サイクル数を減算して、「0以下」になったときループ処理に入り、「0超過」になるまで周期値を加算します。
 *
 *   [アセンブラ版]
 *	経過サイクル数を減算して、「0未満」になったときループ処理に入り、「0以上」になるまで周期値を加算します。
 *
 *   この違いにより、出力波形の位相が最大1サンプル分(=1/SPEAKER_FREQUENCY[秒])ずれる可能性があります。
 *
 * - ノイズジェネレータの振幅が異なります。
 *
 *   [C言語版]
 *	ノイズジェネレータの出力サンプルは、-16〜+15です。
 *   [アセンブラ版]
 *	ノイズジェネレータの出力サンプルは、-16〜+16です。
 *
 *   この違いにより、ノイズボリュームが33/32倍(=約3%増)になります。
 */
asm("
	.code
	.align 1
	.global huc6280psg_channel_process
huc6280psg_channel_process:
	xld.ub %r10, [%r13+6]				; %r10 = channel->enable					| + 6, 1: unsigned char enable
	xcmp %r10, 0					; if(!channel->enable) return 0 !INTERLOCK!
	xjreq huc6280psg_channel_process_disable
	;
	xld.h %r10, [%r13+10]				; %r10 = channel->volume					| +10, 2: short volume
	xcmp %r10, 0					; if(!channel->volume) return 0 !INTERLOCK!
	xjreq huc6280psg_channel_process_disable
	;
	xbtst [%r13+7], 0				; if(channel->dda) goto huc6280psg_channel_process_dda		| + 7, 1: unsigned char dda
	xjrne huc6280psg_channel_process_dda
	;
	xbtst [%r13+8], 0				; if(channel->noise) goto huc6280psg_channel_process_noise	| + 8, 1: unsigned char noise
	xjrne huc6280psg_channel_process_noise
	;
	;/*===== Tone mode =====*/
	xld.w %r4, [%r13+16]				; %r4 = channel->tone_progress					| +16, 4: int tone_progress
	xld.uh %r5, [%r13+28]				; %r5 = channel->tone_index *anti-interlock*			| +28, 2: unsigned short tone_index
	sub %r4, %r14					; channel->tone_progress -= cycle
	xjruge huc6280psg_channel_process_tone_break	; if(!%psr(C)) goto huc6280psg_channel_process_tone_break
	;
	xld.w %r6, [%r13+12]				; %r6 = channel->tone_period					| +12, 4: int tone_period
huc6280psg_channel_process_tone_loop:
	add %r4, %r6					; channel->tone_progress += channel->tone_period
	xjruge.d huc6280psg_channel_process_tone_loop	; if(!%psr(C)) goto huc6280psg_channel_process_tone_loop
	add %r5, 1					; channel->tone_index++ *delay*
	xld.h [%r13+28], %r5				; (store channel->tone_index)					| +28, 2: unsigned short tone_index
	;
huc6280psg_channel_process_tone_break:
	xld.w [%r13+16], %r4				; (store channel->tone_progress)				| +16, 4: int tone_progress
	;
	xand %r5, %r5, 0x1f				; %r11 = channel->wave[channel->tone_index & 0x1f]
	xadd %r5, %r5, 32				;								| +32,32: char wave[32]
	add %r5, %r13
	xld.b %r11, [%r5]
	;
	mlt.h %r10, %r11				; %alr = volume * sample !INTERLOCK!
	ret.d
	ld.w %r10, %alr					; %r10 = output *delay*(undoc'd)
	;
	;/*===== Noise mode =====*/
huc6280psg_channel_process_noise:
	xld.w %r4, [%r13+24]				; %r4 = channel->noise_progress					| +24, 4: int noise_progress
	xld.uh %r5, [%r13+30]				; %r5 = channel->noise_lfsr *anti-interlock*			| +30, 2: unsigned short noise_lfsr
	sub %r4, %r14					; channel->noise_progress -= cycle
	xjruge huc6280psg_channel_process_noise_break	; if(!%psr(C)) goto huc6280psg_channel_process_noise_break
	;
	xld.w %r6, [%r13+20]				; %r6 = channel->noise_period					| +20, 4: int noise_period
huc6280psg_channel_process_noise_loop:
	ld.w %r7, %r5					; lfsr = lfsr>>1 | ((lfsr^(lfsr>>4))&1)<<8
	xsrl %r7, 4
	xor %r7, %r5
	xand %r7, %r7, 1
	xsll %r7, 8
	xsrl %r5, 1
	add %r4, %r6					; channel->noise_progress += channel->noise_period
	xjruge.d huc6280psg_channel_process_noise_loop	; if(!%psr(C)) goto huc6280psg_channel_process_noise_loop
	or %r5, %r7					; *delay*
	xld.h [%r13+30], %r5				; (store channel->noise_lfsr)					| +30, 2: unsigned short noise_lfsr
	;
huc6280psg_channel_process_noise_break:
	xld.w [%r13+24], %r4				; (store channel->noise_progress)				| +24, 4: int noise_progress
	;
	xand %r5, %r5, 1				; %r5 =  1 or   0
	xsll %r5, 5					; %r5 = 32 or   0
	xsub %r5, %r5, 16				; %r5 = 16 or -16
	mlt.h %r10, %r5					; %alr = volume * sample
	ret.d
	ld.w %r10, %alr					; %r10 = output *delay*(undoc'd)
	;
	;/*===== Direct D/A mode =====*/
huc6280psg_channel_process_dda:
	xld.b %r11, [%r13+32]				; %r11 = channel->wave[0]					| +32,32: char wave[32]
	;
	mlt.h %r10, %r11				; %alr = volume * sample !INTERLOCK!
	ret.d
	ld.w %r10, %alr					; %r10 = output *delay*(undoc'd)
	;
	;/*===== Disabled channel =====*/
huc6280psg_channel_process_disable:
	ret						; 既に %r10=0 になっています。
");

#endif /*PIECE*/

