;	
;	framdms2.c
;
;	P/ECE DMG-Sound Emulator (高速版)
;
;	CLiP - Common Library for P/ECE
;	Copyright (C) 2001-2005 Naoyuki Sawa
;
;	* Mon Feb 28 20:00:00 JST 2005 Naoyuki Sawa
;	- 作成開始。
;

	.code
	.align 1

;****************************************************************************
;	
;****************************************************************************

;-------------------------------------------------------------------------------------------------------------------------------------------------------------- 
					; /* Ch1,2: 矩形波 */
					; typedef struct _DMGSOUND2SQUARE {
					; 	/*** Length ***/
#define SQUARE_SOUND_LENGTH	 0	; 	int sound_length;		// + 0,4: 残り発声時間を(1/256[sec])単位で示します。
				  	; 					//          -1ならば、明示的に停止するまで発声を続けます。
				  	; 					//           0ならば、停止中であることを示します。
					; 
					; 	/*** Envelope ***/
#define SQUARE_ENVELOPE_TIME	 4	; 	unsigned char envelope_time;	// + 4,1: エンベロープ実行間隔を(1/256[sec])単位で示します。
				  	; 					//          0ならば、エンベロープを行いません。
#define SQUARE_ENVELOPE_TYPE	 5	; 	unsigned char envelope_type;	// + 5,1: エンベロープ方向を示します。
				  	; 					//          0ならば、音量を減少します。(音が小さくなる)
				  	; 					//          1ならば、音量を増加します。(音が大きくなる)
#define SQUARE_ENVELOPE_VOLUME	 6	; 	unsigned char envelope_volume;	// + 6,1: 現在のエンベロープ音量を示します。(0..15)
				  	; 					//          0で消音、15で最大音量となります。
#define SQUARE_ENVELOPE_COUNT	 7	; 	unsigned char envelope_count;	// + 7,1: 次回エンベロープ実行までのダウンカウンタです。
					; 
					; 	/*** Sweep (Ch1のみ) ***/
#define SQUARE_SWEEP_TIME	 8	; 	unsigned char sweep_time;	// + 8,1: スイープ実行間隔を(1/256[sec])単位で示します。
				  	; 					//          0ならば、スイープを行いません。
				  	; 					//          Ch2はスイープ機能が無いので、0固定とします。
#define SQUARE_SWEEP_TYPE	 9	; 	unsigned char sweep_type;	// + 9,1: スイープ方向を示します。
				  	; 					//          0ならば、周波数を増加します。(音が高くなる)
				  	; 					//          1ならば、周波数を減少します。(音が低くなる)
#define SQUARE_SWEEP_SHIFT	10	; 	unsigned char sweep_shift;	// +10,1: 周波数変化量のシフト数を示します。
				  	; 					//          スイープを一回実行することによる周波数変化は、次の通りです。
				  	; 					//          frequency(t) = frequency(t-1) ± frequency(t-1)>>sweep_shift
#define SQUARE_SWEEP_COUNT	11	; 	unsigned char sweep_count;	// +11,1: 次回スイープ実行までのダウンカウンタです。
					; 
					; 	/*** Wave ***/
#define SQUARE_WAVE_DUTY	12	; 	unsigned char wave_duty;	// +12,1: 矩形波のデューティ比を示します。
				  	; 					//                    01234567
				  	; 					//          1: 12.5% (_-------)
				  	; 					//          2: 25  % (__------)
				  	; 					//          4: 50  % (____----)
				  	; 					//          6: 75  % (______--)
#define SQUARE_WAVE_INDEX	13	; 	unsigned char wave_index;	// +13,1: 波形サンプル単位の進行カウンタです。(0..7)
				  	; 					//          (wave_index＜wave_duty)ならば、(-envelope_volume)を出力します。
				  	; 					//          (wave_index≧wave_duty)ならば、(+envelope_volume)を出力します。
#define SQUARE_WAVE_FREQUENCY	14	; 	short wave_frequency;		// +14,2: 周波数を示します。(0x000..0x7ff)
				  	; 					//          矩形波1波形単位の再生周波数は、
				  	; 					//            Frequency = 4194304/(32*(2048-x))[Hz] (仕様)
				  	; 					//          矩形波1波形分のサイクル数を、マスタークロックの倍数で表すと、
				  	; 					//            Period    = (2048-x)*32
				  	; 					//          矩形波1波形が8サンプルと見なすと、1サンプル分のサイクル数は、
				  	; 					//            Period    = (2048-x)*32/8
				  	; 					//                      = (2048-x)*4
#define SQUARE_WAVE_COUNT	16	; 	int wave_count;			// +16,4: マスタークロック単位の進行カウンタです。
					; 
					; 	/*** Speaker ***/
#define SQUARE_SPEAKER_VOLUME	20	; 	unsigned char speaker_volume;	// +20,1: 出力先スピーカー(左、右、または両方)の合計ボリューム (0..14)
					; } DMGSOUND2SQUARE;
;-------------------------------------------------------------------------------------------------------------------------------------------------------------- 
					; /* Ch3: 波形メモリ */
					; typedef struct _DMGSOUND2WAVE {
					; 	/*** Length ***/
#define WAVE_SOUND_LENGTH	 0	; 	int sound_length;		// + 0,4: 残り発声時間を(1/256[sec])単位で示します。
				  	; 					//          -1ならば、明示的に停止するまで発声を続けます。
				  	; 					//           0ならば、停止中であることを示します。
					; 
					; 	/*** Wave ***/
#define WAVE_WAVE_SHIFT		 4	; 	unsigned char wave_shift;	// + 4,1: 波形データの出力レベルを示します。
				  	; 					//          0: 消音
				  	; 					//          1: そのまま出力
				  	; 					//          2: 1bit右シフトして出力
				  	; 					//          3: 2bit右シフトして出力
#define WAVE_WAVE_INDEX		 5	; 	unsigned char wave_index;	// + 5,1: 波形サンプル単位の進行カウンタです。(0..31)
				  	; 					//          (wave_pattern[wave_index]>>(wave_shift-1))を出力します。
#define WAVE_WAVE_FREQUENCY	 6	; 	short wave_frequency;		// + 6,2: 周波数を示します。(0x000..0x7ff)
				  	; 					//          矩形パターン1波形単位の再生周波数は、
				  	; 					//            Frequency = 4194304/(64*(2048-x))[Hz] (仕様)
				  	; 					//          波形パターン1波形分のサイクル数を、マスタークロックの倍数で表すと、
				  	; 					//            Period    = (2048-x)*64
				  	; 					//          波形パターン1波形が32サンプルと見なすと、1サンプル分のサイクル数は、
				  	; 					//            Period    = (2048-x)*64/32
				  	; 					//                      = (2048-x)*2
#define WAVE_WAVE_COUNT		 8	; 	int wave_count;			// + 8,4: マスタークロック単位の進行カウンタです。
					; 
					; 	/*** Speaker ***/
#define WAVE_SPEAKER_VOLUME	12	; 	unsigned char speaker_volume;	// +12,1: 出力先スピーカー(左、右、または両方)の合計ボリューム (0..14)
					; } DMGSOUND2WAVE;
;-------------------------------------------------------------------------------------------------------------------------------------------------------------- 
					; /* Ch4: ノイズ */
					; typedef struct _DMGSOUND2NOISE {
					; 	/*** Length ***/
#define NOISE_SOUND_LENGTH	 0	; 	int sound_length;		// + 0,4: 残り発声時間を(1/256[sec])単位で示します。
				  	; 					//          -1ならば、明示的に停止するまで発声を続けます。
				  	; 					//           0ならば、停止中であることを示します。
					; 
					; 	/*** Envelope ***/
#define NOISE_ENVELOPE_TIME	 4	; 	unsigned char envelope_time;	// + 4,1: エンベロープ実行間隔を(1/256[sec])単位で示します。
				  	; 					//          0ならば、エンベロープを行いません。
#define NOISE_ENVELOPE_TYPE	 5	; 	unsigned char envelope_type;	// + 5,1: エンベロープ方向を示します。
				  	; 					//          0ならば、音量を減少します。(音が小さくなる)
				  	; 					//          1ならば、音量を増加します。(音が大きくなる)
#define NOISE_ENVELOPE_VOLUME	 6	; 	unsigned char envelope_volume;	// + 6,1: 現在のエンベロープ音量を示します。(0..15)
				  	; 					//          0で消音、15で最大音量となります。
#define NOISE_ENVELOPE_COUNT	 7	; 	unsigned char envelope_count;	// + 7,1: 次回エンベロープ実行までのダウンカウンタです。
					; 
					; 	/*** LFSR ***/
#define NOISE_LFSR_TYPE		 8	; 	unsigned char lfsr_type;	// + 8,1: 1 = 15 steps (long mode), 6 = 7 steps (short mode)
				  	; 					//          正しい仕様がわからないので、ファミコンAPU互換で実装してみました。
					; 					// + 9,1: (パディング)
#define NOISE_LFSR		10	; 	unsigned short lfsr;		// +10,2: LFSR (Linear Feedback Shift Register)
#define NOISE_LFSR_FREQUENCY	12	; 	int lfsr_frequency;		// +12,4: マスタークロックサイクル単位のLFSRシフト周期を示します。
				  	; 					//          {1,2,4,6,8,10,12,14}<<{3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18}
#define NOISE_LFSR_COUNT	16	; 	int lfsr_count;			// +16,4: マスタークロック単位の進行カウンタです。
					; 
					; 	/*** Speaker ***/
#define NOISE_SPEAKER_VOLUME	20	; 	unsigned char speaker_volume;	// +20,1: 出力先スピーカー(左、右、または両方)の合計ボリューム (0..14)
					; } DMGSOUND2NOISE;
;-------------------------------------------------------------------------------------------------------------------------------------------------------------- 

#define SPEAKER_FREQUENCY	16000			; -> clipmisc.h
#define DMGSOUND2_CLOCK		(1<<22)			; -> clipdms2.h
#define DMGSOUND2_PERIOD_MIN	(DMGSOUND2_CLOCK/16)	; -> clipdms2.c
#define DMGSOUND2_VOLUME_SHIFT	((16-11)+1)		; -> clipdms2.c

;****************************************************************************
;	
;****************************************************************************

	.global	dmgsound2_square_mix
dmgsound2_square_mix:
	xld.w	%r9, [%r12+SQUARE_SOUND_LENGTH]		; if(!ch->sound_length) goto EXIT
	xcmp	%r9, 0
	xjreq	dmgsound2_square_mix_EXIT
	;
	xld.ub	%r4, [%r12+SQUARE_ENVELOPE_VOLUME]	; %r4 = volume = ch->envelope_volume * ch->speaker_volume
	xld.ub	%r9, [%r12+SQUARE_SPEAKER_VOLUME]
	mlt.h	%r4, %r9
	ld.w	%r4, %alr
	xcmp	%r4, 0					; if(!volume) goto EXIT
	xjreq	dmgsound2_square_mix_EXIT
	;
	xld.w	%r5, 2048				; %r5 = period = (2048 - ch->wave_frequency) * 4 * SPEAKER_FREQUENCY
	xld.h	%r9, [%r12+SQUARE_WAVE_FREQUENCY]
	sub	%r5, %r9
	xld.w	%r9, 4*SPEAKER_FREQUENCY
	mltu.h	%r5, %r9				; (4*SPEAKER_FREQUENCY=64000なので、mlt.hではなくmltu.hを使う必要があります)
	ld.w	%r5, %alr
	xcmp	%r5, DMGSOUND2_PERIOD_MIN		; if(period < DMGSOUND2_PERIOD_MIN) period = DMGSOUND2_PERIOD_MIN
	xjrge	dmgsound2_square_mix_L10
	xld.w	%r5, DMGSOUND2_PERIOD_MIN
dmgsound2_square_mix_L10:
	;
	xld.ub	%r6, [%r12+SQUARE_WAVE_DUTY]		; %r6 = wave_duty = ch->wave_duty
	;
	xld.ub	%r14, [%r12+SQUARE_WAVE_INDEX]		; %r14 = wave_index = ch->wave_index
	xld.w	%r15, [%r12+SQUARE_WAVE_COUNT]		; %r15 = wave_count = ch->wave_count
	;
	cmp	%r14, %r6				; output = (wave_index < wave_duty) ? -volume : +volume
	jrge.d	4
	ld.w	%r7, %r4				; *delay*
	not	%r7, %r4				; (skip?)
	add	%r7, 1					; (skip?)
	;
	xld.w	%r10, DMGSOUND2_CLOCK			; %r10 = DMGSOUND2_CLOCK
	xld.w	%r11, 64				; %r11 = i = 64
dmgsound2_square_mix_DO:
	sub	%r15, %r10				; wave_count -= DMGSOUND2_CLOCK
	xjruge	dmgsound2_square_mix_NO_ADVANCE		; if(NonCarry) goto NO_ADVANCE
	;
dmgsound2_square_mix_ADVANCE:
	add	%r15, %r5				; wave_count += period
	xjruge.d	dmgsound2_square_mix_ADVANCE	; if(NonCarry) goto ADVANCE
	add	%r14, 1					; wave_index++ *delay*
	;
	xand	%r14, %r14, 7				; wave_index &= 7
	;
	cmp	%r14, %r6				; output = (wave_index < wave_duty) ? -volume : +volume
	jrge.d	4
	ld.w	%r7, %r4				; *delay*
	not	%r7, %r4				; (skip?)
	add	%r7, 1					; (skip?)
	;
dmgsound2_square_mix_NO_ADVANCE:
	ld.h	%r9, [%r13]				; *wbuff++ += output
	add	%r9, %r7
	ld.h	[%r13]+, %r9
	;
	xsub	%r11, %r11, 1				; if(--i) goto DO
	xjrne	dmgsound2_square_mix_DO
	;
	xld.b	[%r12+SQUARE_WAVE_INDEX], %r14		; ch->wave_index = wave_index
	xld.w	[%r12+SQUARE_WAVE_COUNT], %r15		; ch->wave_count = wave_count
	;
dmgsound2_square_mix_EXIT:
	ret

;****************************************************************************
;	
;****************************************************************************

	.global	dmgsound2_wave_mix
dmgsound2_wave_mix:
	xld.w	%r9, [%r12+WAVE_SOUND_LENGTH]		; if(!ch->sound_length) goto EXIT
	xcmp	%r9, 0
	xjreq	dmgsound2_wave_mix_EXIT
	;
	xld.ub	%r4, [%r12+WAVE_SPEAKER_VOLUME]		; %r4 = volume = ch->speaker_volume
	xcmp	%r4, 0					; if(!volume) goto EXIT
	xjreq	dmgsound2_wave_mix_EXIT
	;
	xld.w	%r5, 2048				; %r5 = period = (2048 - ch->wave_frequency) * 2 * SPEAKER_FREQUENCY
	xld.h	%r9, [%r12+WAVE_WAVE_FREQUENCY]
	sub	%r5, %r9
	xld.w	%r9, 2*SPEAKER_FREQUENCY
	mlt.h	%r5, %r9				; (2*SPEAKER_FREQUENCY=32000なので、mlt.hでもmltu.hでも構いません)
	ld.w	%r5, %alr
	xcmp	%r5, DMGSOUND2_PERIOD_MIN		; if(period < DMGSOUND2_PERIOD_MIN) period = DMGSOUND2_PERIOD_MIN
	xjrge	dmgsound2_wave_mix_L10
	xld.w	%r5, DMGSOUND2_PERIOD_MIN
dmgsound2_wave_mix_L10:
	;
	xld.ub	%r6, [%r12+WAVE_WAVE_SHIFT]		; %r6 = wave_shift = ch->wave_shift
	xsub	%r6, %r6, 1				; if((--wave_shift) < 0) goto EXIT
	xjrlt	dmgsound2_wave_mix_EXIT
	;
	xld.ub	%r14, [%r12+WAVE_WAVE_INDEX]		; %r14 = wave_index = ch->wave_index
	xld.w	%r15, [%r12+WAVE_WAVE_COUNT]		; %r15 = wave_count = ch->wave_count
	;
	ext	dmgsound2_wave_pattern@ah		; output = (dmgsound2_wave_pattern[wave_index] >> wave_shift) * volume
	ext	dmgsound2_wave_pattern@al
	ld.b	%r7, [%r14]
	sra	%r7, %r6				; (0≦wave_shift≦2なので、xsraを使う必要はありません。sraで充分です)
	mlt.h	%r7, %r4
	ld.w	%r7, %alr
	;
	xld.w	%r10, DMGSOUND2_CLOCK			; %r10 = DMGSOUND2_CLOCK
	xld.w	%r11, 64				; %r11 = i = 64
dmgsound2_wave_mix_DO:
	sub	%r15, %r10				; wave_count -= DMGSOUND2_CLOCK
	xjruge	dmgsound2_wave_mix_NO_ADVANCE		; if(NonCarry) goto NO_ADVANCE
	;
dmgsound2_wave_mix_ADVANCE:
	add	%r15, %r5				; wave_count += period
	xjruge.d	dmgsound2_wave_mix_ADVANCE	; if(NonCarry) goto ADVANCE
	add	%r14, 1					; wave_index++ *delay*
	;
	xand	%r14, %r14, 31				; wave_index &= 31
	;
	ext	dmgsound2_wave_pattern@ah		; output = (dmgsound2_wave_pattern[wave_index] >> wave_shift) * volume
	ext	dmgsound2_wave_pattern@al
	ld.b	%r7, [%r14]
	sra	%r7, %r6				; (0≦wave_shift≦2なので、xsraを使う必要はありません。sraで充分です)
	mlt.h	%r7, %r4
	ld.w	%r7, %alr
	;
dmgsound2_wave_mix_NO_ADVANCE:
	ld.h	%r9, [%r13]				; *wbuff++ += output
	add	%r9, %r7
	ld.h	[%r13]+, %r9
	;
	xsub	%r11, %r11, 1				; if(--i) goto DO
	xjrne	dmgsound2_wave_mix_DO
	;
	xld.b	[%r12+WAVE_WAVE_INDEX], %r14		; ch->wave_index = wave_index
	xld.w	[%r12+WAVE_WAVE_COUNT], %r15		; ch->wave_count = wave_count
	;
dmgsound2_wave_mix_EXIT:
	ret

;****************************************************************************
;	
;****************************************************************************

	.global	dmgsound2_noise_mix
dmgsound2_noise_mix:
	xld.w	%r9, [%r12+NOISE_SOUND_LENGTH]		; if(!ch->sound_length) goto EXIT
	xcmp	%r9, 0
	xjreq	dmgsound2_noise_mix_EXIT
	;
	xld.ub	%r4, [%r12+NOISE_ENVELOPE_VOLUME]	; %r4 = volume = ch->envelope_volume * ch->speaker_volume
	xld.ub	%r9, [%r12+NOISE_SPEAKER_VOLUME]
	mlt.h	%r4, %r9
	ld.w	%r4, %alr
	xcmp	%r4, 0					; if(!volume) goto EXIT
	xjreq	dmgsound2_noise_mix_EXIT
	;
	xld.w	%r5, [%r12+NOISE_LFSR_FREQUENCY]	; %r5 = period = ch->lfsr_frequency * (SPEAKER_FREQUENCY>>7)
	xld.w	%r9, (SPEAKER_FREQUENCY>>7)
	mlt.h	%r5, %r9				; ((SPEAKER_FREQUENCY>>7)=125なので、mlt.hでもmltu.hでも構いません)
	ld.w	%r5, %alr
	xcmp	%r5, (DMGSOUND2_PERIOD_MIN>>7)		; if(period < (DMGSOUND2_PERIOD_MIN>>7)) period = (DMGSOUND2_PERIOD_MIN>>7)
	xjrge	dmgsound2_noise_mix_L10
	xld.w	%r5, (DMGSOUND2_PERIOD_MIN>>7)
dmgsound2_noise_mix_L10:
	;
	xld.ub	%r6, [%r12+NOISE_LFSR_TYPE]		; %r6 = lfsr_type = ch->lfsr_type
	;
	xld.uh	%r14, [%r12+NOISE_LFSR      ]		; %r14 = lfsr       = ch->lfsr
	xld.w	%r15, [%r12+NOISE_LFSR_COUNT]		; %r15 = wave_count = ch->wave_count
	;
	xand	%r9, %r14, 1				; output = !(lfsr & 1) ? -volume : +volume
	jrne.d	4
	ld.w	%r7, %r4				; *delay*
	not	%r7, %r4				; (skip?)
	add	%r7, 1					; (skip?)
	;
	xld.w	%r10, (DMGSOUND2_CLOCK>>7)		; %r10 = (DMGSOUND2_CLOCK>>7)
	xld.w	%r11, 64				; %r11 = i = 64
dmgsound2_noise_mix_DO:
	sub	%r15, %r10				; wave_count -= (DMGSOUND2_CLOCK>>7)
	xjruge	dmgsound2_noise_mix_NO_ADVANCE		; if(NonCarry) goto NO_ADVANCE
	;
dmgsound2_noise_mix_ADVANCE:
	ld.w	%r9, %r14				; %r9 = v = ((lfsr>>0) ^ (lfsr>>mode)) & 1
	srl	%r9, %r6				; ★ "xsrl %r9,%r6"としてはいけません!!
	xor	%r9, %r14				; ★ xsrlが展開され、%r9が破壊されてしまいます。
	and	%r9, 1					; ★ %r6=mode=1or6なので、xsrlは必要ありません。
	;
	xsrl	%r14, 1
	xsll	%r9, 14					; lfsr = (lfsr >> 1) | (v << 14)
	;or	%r14, %r9				; ◎ -> 4行下へ移動しました。
	;
	add	%r15, %r5				; wavecount += wavelength
	xjruge.d	dmgsound2_noise_mix_ADVANCE	; if(NonCarry) goto ADVANCE
	or	%r14, %r9				; ◎ <- 4行上から移しました。 *delay*
	;
	xand	%r9, %r14, 1				; output = !(lfsr & 1) ? -volume : +volume
	jrne.d	4
	ld.w	%r7, %r4				; *delay*
	not	%r7, %r4				; (skip?)
	add	%r7, 1					; (skip?)
	;
dmgsound2_noise_mix_NO_ADVANCE:
	ld.h	%r9, [%r13]				; *wbuff++ += output
	add	%r9, %r7
	ld.h	[%r13]+, %r9
	;
	xsub	%r11, %r11, 1				; if(--i) goto DO
	xjrne	dmgsound2_noise_mix_DO
	;
	xld.h	[%r12+NOISE_LFSR      ], %r14		; ch->lfsr       = lfsr
	xld.w	[%r12+NOISE_LFSR_COUNT], %r15		; ch->wave_count = wave_count
	;
dmgsound2_noise_mix_EXIT:
	ret

;****************************************************************************
;	
;****************************************************************************

	.global dmgsound2_volume_shift
dmgsound2_volume_shift:
	; %r12 = wbuff
	xld.w %r13, 320			; DMGSOUND2BUFLEN = 320
	xld.w %r4, 32767
	not %r5, %r4			; -32768
dmgsound2_volume_shift_DO:
	ld.h %r6, [%r12]
	xsll %r6, DMGSOUND2_VOLUME_SHIFT
	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 dmgsound2_volume_shift_DO
	ret

;****************************************************************************
;	
;****************************************************************************

