/*
 *	framadp.c
 *
 *	2-bit ADPCM Decoder
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2006 Naoyuki Sawa
 *
 *	* Wed Dec 20 00:13:10 JST 2006 Naoyuki Sawa
 *	- 作成開始。
 *	* Wed Dec 20 17:31:02 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	- ASM版のadpcm_mix1()関数は、まだまだ最適化不足です。
 *	  インターロックの回避と遅延スロットを活用して、もっと高速化できます。
 *	  今後、実際のアプリケーションで性能を試しながら、高速化する予定です。
 *	* Thu Dec 21 11:18:32 JST 2006 Naoyuki Sawa
 *	- ADPCMCHANNEL構造体を扱い易く並び替えて、ASM版adpcm_mix1()を最適化しました。
 *	* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
 *	- P/ECE上のデコーダ実装を簡単にするため、係数テーブルを変更しました。
 *	  エンコーダ(dppcmcnv.exe)にも、同じ変更を行いました。
 *	  詳しくは、clip/keep/2-bit ADPCMの参考資料.7z/Adpcm2bit/main.cを参照してください。
 */
#include "clip.h"

#ifdef PIECE
#define ADPCM_ASM
#endif /*PIECE*/

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

//static const short adpcm_tbl[4/*ADPCM値*/][2] = {
//	{  128, 233 },	/* + 0,4 */
//	{  384, 375 },	/* + 4,4 */
//	{ -128, 233 },	/* + 8,4 */
//	{ -384, 375 },	/* +12,4 */
//};			/* =16   */
//↓* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
#define A	233	/* =(    1.1^-1)*256 */
#define B	375	/* =(    1.1^ 4)*256 */
#define C	141	/* =(0.5/1.1^-1)*256 */
#define D	263	/* =(1.5/1.1^ 4)*256 */
static const short adpcm_tbl[4/*ADPCM値*/][2] = {
#ifndef ADPCM_ASM
	{ A,  C },
	{ B,  D },
	{ A, -C },
	{ B, -D },
#else /*ADPCM_ASM*/
	/* ASM版では、ワード単位でいちどに取得して、下位ハーフ、上位ハーフの順に利用します。
	 * 上位ハーフを下位ハーフへ移動する際、"xsra %rd,16"では2命令に展開されますが、
	 * 以下のように、予め上位ハーフをSwapしておけば、"swap %rd,%rs"1命令で移動できます。
	 */
	{ A, SWAPH( C) },
	{ B, SWAPH( D) },
	{ A, SWAPH(-C) },
	{ B, SWAPH(-D) },
#endif /*ADPCM_ASM*/
};
#undef A
#undef B
#undef C
#undef D

/*--------------------------------------------------------------------------*/

#ifndef ADPCM_ASM
void
adpcm_mix1(ADPCMCHANNEL* pch, int mixbuf[/*ADPCMBUFLEN*/])
{
	int n;
	int adpcm;
	int loop;
	//
	int pcm                  = pch->pcm;
	int data                 = pch->data;
	int delta                = pch->delta;
	int progress             = pch->progress;
	int rate                 = pch->rate;
	int len                  = pch->len;
	const unsigned char* ptr = pch->ptr;

	n = ADPCMBUFLEN;
	do {
		progress -= rate;
		if(progress < 0) {
			progress += SPEAKER_FREQUENCY;
			if(data < (1<<2)) {		/* ASM版では、adpcm_tblへのオフセットとして×4しておきます。 */
				data = *ptr++ | (1<<8);	/* C版とは、dataが保持する値が異なることに注意してください。 */
			}
			adpcm = data & 3;
			data >>= 2;
			//pcm  += (delta * adpcm_tbl[adpcm][0]) >> 8;
			//delta = (delta * adpcm_tbl[adpcm][1]) >> 8;
			//↓* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
			delta = (delta * adpcm_tbl[adpcm][0]) >> 8;
			pcm  += (delta * adpcm_tbl[adpcm][1]) >> 8;
		}
		*mixbuf++ += pcm;
		if(!--len) {
			loop = pch->loop - 1;
			if(loop >= 0) {
				pch->loop = loop;
				if(!loop) break;
			}
			pcm      = 0;
			data     = 0;
			delta    = 16;
			progress = 0;
			len      = pch->init_len;
			ptr      = pch->init_ptr;
		}
	} while(--n);

	pch->pcm      = pcm;
	pch->data     = data;
	pch->delta    = delta;
	pch->progress = progress;
	pch->rate     = rate;
	pch->len      = len;
	pch->ptr      = ptr;
}
#else /*ADPCM_ASM*/
//asm("
//	.code
//	.align 1
//	.global adpcm_mix1
//adpcm_mix1:
//	; %r12 = pch
//	; %r13 = mixbuf
//	pushn	%r3
//	xld.w	%r0, [%r12+ 4]		; %r0  = pcm      = pch->pcm
//	xld.h	%r1, [%r12+ 8]		; %r1  = data     = pch->data
//	xld.h	%r2, [%r12+10]		; %r2  = delta    = pch->delta
//	xld.h	%r3, [%r12+12]		; %r3  = progress = pch->progress
//	xld.h	%r4, [%r12+14]		; %r4  = rate     = pch->rate
//	xld.w	%r5, [%r12+16]		; %r5  = len      = pch->len
//	xld.w	%r6, [%r12+20]		; %r6  = ptr      = pch->ptr
//	xld.w	%r7,  320		; %r7  = ADPCMBUFLEN
//	xld.w	%r10, 16000		; %r10 = SPEAKER_FREQUENCY
//	xld.w	%r11, 256		; %r11 = (1<<8)
//	xld.w	%r14, adpcm_tbl		; %r14 = adpcm_tbl
//adpcm_mix1_L10:				; do {
//	sub	%r3, %r4		;   progress -= rate 
//	xjrge	adpcm_mix1_L30		;   if(progress < 0) {
//	add	%r3, %r10		;     progress += SPEAKER_FREQUENCY
//	xcmp	%r1, 16			;     if(data < (1<<(2+2))) {
//	xjrge	adpcm_mix1_L20
//	ld.ub	%r1, [%r6]+		;       data = (*ptr++ | (1<<8)) << 2
//	or	%r1, %r11
//	xsla	%r1, 2
//adpcm_mix1_L20:				;     }
//	xand	%r15, %r1, 12		;     %r15 = adpcm = data & (3<<2)
//	xsra	%r1, 2			;     data >>= 2
//	add	%r15, %r14		;     %r15 = adpcm_tbl[adpcm]
//	ld.h	%r9, [%r15]+		;     pcm  += (delta*adpcm_tbl[adpcm][0]) >> 8
//	mlt.h	%r9, %r2
//	ld.w	%r9, %alr
//	xsra	%r9, 8
//	add	%r0, %r9
//	ld.h	%r9, [%r15]		;     delta = (delta*adpcm_tbl[adpcm][1]) >> 8
//	mlt.h	%r9, %r2
//	ld.w	%r2, %alr
//	xsra	%r2, 8
//adpcm_mix1_L30:				;   }
//	ld.w	%r15, [%r13]		;   *mixbuf++ = pcm
//	add	%r15, %r0
//	ld.w	[%r13]+, %r15
//	xsub	%r5, %r5, 1		;   if(!--len) {
//	xjrne	adpcm_mix1_L50
//	xld.w	%r15, [%r12+0]		;     %r15 = loop = pch->loop - 1
//	xsub	%r15, %r15, 1
//	xjrlt	adpcm_mix1_L40		;     if(loop >= 0) {
//	xld.w	[%r12+0], %r15		;       pch->loop = loop
//	xjreq	adpcm_mix1_L60		;       if(!loop) break
//adpcm_mix1_L40:				;     }
//	xld.w	%r0, 0			;     pcm      = 0
//	xld.w	%r1, 0			;     data     = 0
//	xld.w	%r2, 16			;     delta    = 16
//	xld.w	%r3, 0			;     progress = 0
//	xld.w	%r5, [%r12+24]		;     len      = pch->init_len
//	xld.w	%r6, [%r12+28]		;     ptr      = pch->init_ptr
//adpcm_mix1_L50:				;   }
//	xsub	%r7, %r7, 1		; } while(--n)
//	xjrne	adpcm_mix1_L10
//adpcm_mix1_L60:
//	xld.w	[%r12+ 4], %r0		; pch->pcm      = pcm
//	xld.h	[%r12+ 8], %r1		; pch->data     = data
//	xld.h	[%r12+10], %r2		; pch->delta    = delta
//	xld.h	[%r12+12], %r3		; pch->progress = progress
//	xld.h	[%r12+14], %r4		; pch->rate     = rate
//	xld.w	[%r12+16], %r5		; pch->len      = len
//	xld.w	[%r12+20], %r6		; pch->ptr      = ptr
//	popn	%r3
//	ret
//");
//↓* Thu Dec 21 11:18:32 JST 2006 Naoyuki Sawa
//↓- ADPCMCHANNEL構造体を扱い易く並び替えて、ASM版adpcm_mix1()を最適化しました。
//↓- 容量の少ない高速RAMに置くために、高速化一辺倒でなく、省メモリにも重点を置いています。
//↓  高速化一辺倒の実装も可能でしたが、サイズとのバランスを考えて、この実装に落ち着きました。
asm("
	.code
	.align 1
	.global adpcm_mix1
adpcm_mix1:
	; %r12 = pch
	; %r13 = mixbuf
;//{{-----------------------------------;
;//	pushn	%r0
;//→* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
;//→- 係数テーブルを変更し、最適化してレジスタを節約できたので、不要になりました。
;//}}-----------------------------------;
	xld.w	%r4,  320		; %r4  = ADPCMBUFLEN
	ld.w	%r5,  [%r12]+		; %r5  = pcm      = pch->pcm     , %r12 = &pch->data
	ld.h	%r6,  [%r12]+		; %r6  = data     = pch->data    , %r12 = &pch->delta
	ld.h	%r7,  [%r12]+		; %r7  = delta    = pch->delta   , %r12 = &pch->progress
	ld.h	%r10, [%r12]+		; %r10 = progress = pch->progress, %r12 = &pch->rate
	ld.h	%r11, [%r12]+		; %r11 = rate     = pch->rate    , %r12 = &pch->len
	ld.w	%r14, [%r12]+		; %r14 = len      = pch->len     , %r12 = &pch->ptr
	ld.w	%r15, [%r12]+		; %r15 = ptr      = pch->ptr     , %r12 = &pch->loop
adpcm_mix1_L10:				; do {
	sub	%r10, %r11		;   progress -= rate 
	xjrge	adpcm_mix1_L30		;   if(progress < 0) {
	xadd	%r10, %r10, 16000	;     progress += SPEAKER_FREQUENCY
	xcmp	%r6, 16			;     if(data < (1<<(2+2))) {
	xjrge	adpcm_mix1_L20
	ld.ub	%r6, [%r15]+		;       data = (*ptr++ | (1<<8)) << 2
	xoor	%r6, %r6, 256
	xsla	%r6, 2
adpcm_mix1_L20:				;     }
	xand	%r9, %r6, 12		;     %r9 = adpcm = data & (3<<2)
	xsra	%r6, 2			;     data >>= 2
	ext	adpcm_tbl@ah		;     %r9 = adpcm_tbl[adpcm]
	ext	adpcm_tbl@al
;//{{-----------------------------------;
;//	ld.w	%r9, [%r9]		;     %r9[0:15] = adpcm_tbl[adpcm][0], %r9[31:16] = adpcm_tbl[adpcm][1]
;//	mlt.h	%r9, %r7		;     pcm  += (delta*adpcm_tbl[adpcm][0]) >> 8
;//	ld.w	%r0, %alr		;     ※ここでもう一つレジスタをやりくりできれば
;//	xsra	%r0, 8			;     ※先頭のpush %r0と末尾のpop %r0を無くせるのですが…
;//	add	%r5, %r0		;     ※いろいろ試してみたけれど、断念しました。
;//	xsra	%r9, 16			;     %r9[0:15] = adpcm_tbl[adpcm][1]
;//	mlt.h	%r9, %r7		;     delta = (delta*adpcm_tbl[adpcm][1]) >> 8
;//	ld.w	%r7, %alr
;//	xsra	%r7, 8
;//↓* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
	ld.w	%r9, [%r9]		;     %r9[0:15] = adpcm_tbl[adpcm][0], %r9[31:16] = SWAPH(adpcm_tbl[adpcm][1])
	mlt.h	%r9, %r7		;     delta = (delta*adpcm_tbl[adpcm][0]) >> 8
	ld.w	%r7, %alr
	xsra	%r7, 8
	swap	%r9, %r9		;     %r9[0:15] = adpcm_tbl[adpcm][1]
	mlt.h	%r9, %r7		;     pcm  += (delta*adpcm_tbl[adpcm][1]) >> 8
	ld.w	%r9, %alr
	xsra	%r9, 8
	add	%r5, %r9
;//}}-----------------------------------;
adpcm_mix1_L30:				;   }
	ld.w	%r9, [%r13]		;   *mixbuf++ = pcm
	add	%r9, %r5
	ld.w	[%r13]+, %r9
	xsub	%r14, %r14, 1		;   if(!--len) {
	xjrne	adpcm_mix1_L50
	xld.w	%r9, [%r12+0]		;     %r9 = loop = pch->loop - 1
	xsub	%r9, %r9, 1
	xjrlt	adpcm_mix1_L40		;     if(loop >= 0) {
	xld.w	[%r12+0], %r9		;       pch->loop = loop
	xjreq	adpcm_mix1_L60		;       if(!loop) break
adpcm_mix1_L40:				;     }
	xld.w	%r5,  0			;     pcm      = 0
	xld.w	%r6,  0			;     data     = 0
	xld.w	%r7,  16		;     delta    = 16
	xld.w	%r10, 0			;     progress = 0
	xld.w	%r14, [%r12+4]		;     len      = pch->init_len
	xld.w	%r15, [%r12+8]		;     ptr      = pch->init_ptr
adpcm_mix1_L50:				;   }
	xsub	%r4, %r4, 1		; } while(--n)
	xjrne	adpcm_mix1_L10
adpcm_mix1_L60:
	xsub	%r12, %r12, 20		;                           %r12 = &pch->pcm
	ld.w	[%r12]+, %r5		; pch->pcm      = pcm     , %r12 = &pch->data
	ld.h	[%r12]+, %r6		; pch->data     = data    , %r12 = &pch->delta
	ld.h	[%r12]+, %r7		; pch->delta    = delta   , %r12 = &pch->progress
	ld.h	[%r12]+, %r10		; pch->progress = progress, %r12 = &pch->rate
	ld.h	[%r12]+, %r11		; pch->rate     = rate    , %r12 = &pch->len
	ld.w	[%r12]+, %r14		; pch->len      = len     , %r12 = &pch->ptr
	ld.w	[%r12] , %r15		; pch->ptr      = ptr
;//{{-----------------------------------;
;//	popn	%r0
;//→* Fri Dec 22 00:19:41 JST 2006 Naoyuki Sawa
;//→- 係数テーブルを変更し、最適化してレジスタを節約できたので、不要になりました。
;//}}-----------------------------------;
	ret
");
#endif /*ADPCM_ASM*/

/*--------------------------------------------------------------------------*/

#ifndef ADPCM_ASM
void
adpcm_mix2(short wbuff[/*ADPCMBUFLEN*/], int mixbuf[/*ADPCMBUFLEN*/])
{
	int n;
	int output;

	n = ADPCMBUFLEN;
	do {
		output = *mixbuf++;
		if(output > SHRT_MAX) output = SHRT_MAX;
		if(output < SHRT_MIN) output = SHRT_MIN;
		*wbuff++ = output;
	} while(--n);
}
#else /*ADPCM_ASM*/
asm("
	.code
	.align 1
	.global adpcm_mix2
adpcm_mix2:
	; %r12 = wbuff
	; %r13 = mixbuf
	xld.w %r4, 320			; ADPCMBUFLEN=320
	xld.w %r5, 32767		; SHRT_MAX= 32767
	not %r6, %r5			; SHRT_MIN=-32768
adpcm_mix2_L10:
	ld.w %r7, [%r13]+
	cmp %r7, %r5
	xjrle.d adpcm_mix2_L20
	cmp %r7, %r6			; *delay*
	ld.w %r7, %r5
adpcm_mix2_L20:
	xjrge.d adpcm_mix2_L30
	sub %r4, 1			; *delay*
	ld.w %r7, %r6
adpcm_mix2_L30:
	ld.h [%r12]+, %r7
	xjrne adpcm_mix2_L10
	ret
");
#endif /*ADPCM_ASM*/

