/*	
 *	frams3m.c
 *
 *	P/ECE S3M Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Tue Nov 14 20:03:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/* * エラーコードの値は、行番号を負値にしたものです。
 *   プログラムを修正すると、エラーコードも変わる可能\性があります。
 *   エラーコードの値は、デバッグのためだけに利用してください。
 */
#define ERRNO	(-__LINE__)

/****************************************************************************
 *	P/ECE S3M Driver
 ****************************************************************************/

#ifdef PIECE
#define S3M_ASM
#endif /*PIECE*/

int
s3m_run(S3MDRIVER* driver)
{
	int flags;
	int note;
	int octave;
	int period;
	int instrument;
	int volume;
	int special_command;
	int command_info;
	int i_channel;
	S3MCHANNEL* channel;
	S3MSAMPLE* sample;

	if(driver->stat) goto L_EXIT;

	/* 1[Tick]未満の時間経過をカウントアップします。
	 * Tempoの設定値が125の場合に、1[Tick]=1/50[Sec]となります。
	 */
	driver->progress += driver->tempo;
	while(driver->progress >= 125 * 5/*1/5[Tick]単位で呼ばれる。s3m_stream_callback()参照*/) {
		driver->progress -= 125 * 5/*1/5[Tick]単位で呼ばれる。s3m_stream_callback()参照*/;

		/* 1[Tick]経過しました。
		 * 経過Tick数がSpeedの設定値に達したら、1[Row]進みます。
		 */
		driver->tick++;
		while(driver->tick >= driver->speed) {
			driver->tick -= driver->speed;

			/* Position jumpが要求されていたら... */
			if(driver->position_jump != -1) {

				/* Orderを設定します。
				 * * 不正なOrderへのPosition jumpは、Pattern取得時にSong終端として検出されます。
				 */
				driver->order = driver->position_jump;
				driver->row = 0; /* 必要!! */

				/* Patternを取得します。 */
				if(s3m_init_pattern(driver) != 0) {
					driver->stat = ERRNO; /* Song終端またはPattern不正 */
					goto L_EXIT;
				}

				/* Position jumpを解除します。 */
				driver->position_jump = -1;
			}

			/* Pattern breakが要求されているか、またはPatternが終了したら... */
			if((driver->pattern_break != -1) ||
			   (driver->row >= 64)) {

				/* 次のOrderの先頭のRowへ進めます。
				 * * Song終端は、Pattern取得時に検出されます。
				 * * S3Mには、MODやXMにあったような、Song終端でのループ制御がありません。
				 *   ループするSongには、明示的なPosition jumpが設定必要です。
				 */
				driver->order++;
				driver->row = 0;

				/* Patternを取得します。 */
				if(s3m_init_pattern(driver) != 0) {
					driver->stat = ERRNO; /* Song終端またはPattern不正 */
					goto L_EXIT;
				}

				/* Pattern breakが要求されていたら... */
				if(driver->pattern_break != -1) {

					/* Pattern breakによって指定されたRowまで進めます。 */
					if(s3m_seek_pattern(driver, driver->pattern_break) != 0) {
						driver->stat = ERRNO; /* Pattern break不正 */
						goto L_EXIT;
					}

					/* Pattern breakを解除します。 */
					driver->pattern_break = -1;
				}
			}

			/* 次回のために、Rowを進めておきます。 */
			driver->row++;

			/* まず、確実にSpecial commandを解除します。 */
			for(i_channel = 0, channel = driver->channels;
			    i_channel < 32;
			    i_channel++, channel++) {
				channel->special_command = 0; /* 解除 */
				/* Command infoは継続する可能\性があるので、クリアしてはダメ!! */
			}

			/* 1[Row]分のPattern dataを処理します。 */
			for(;;) {
				flags = *driver->pattern_position++;
				if(!flags) break; /* end of row */
				if(flags & 0x20) {
					note = *driver->pattern_position++;
					instrument = *driver->pattern_position++;
				} else {
					note = 0;
					instrument = 0;
				}
				if(flags & 0x40) {
					volume = *driver->pattern_position++;
				} else {
					volume = -1; /* Volume=0は有効値なので、-1としておきます */
				}
				if(flags & 0x80) {
					special_command = *driver->pattern_position++;
					command_info = *driver->pattern_position++;
				} else {
					special_command = 0;
					command_info = 0;
				}

				/* * 旧いS3Mファイルでは稀に、無効Channelに対する発声要求が入ってるみたいです。(2ND_PM.S3M)
				 *   エラー扱いすると2ND_PM.S3Mが演奏できなくなるため、無視することにしました。
				 */
				i_channel = flags & 0x1f;
				channel = &driver->channels[i_channel];
				if(channel->pending_sample) { /* Channel有効? */

					/* Instrumentを実行します。 */
					if(instrument) {
						if((instrument >= 1) &&
						   (instrument <= driver->insnum)) {
							sample = &driver->samples[instrument - 1/*忘れないで!!*/];
							if(sample->sampledata) { /* Sample有効? */
								channel->pending_sample = sample;
								channel->vol = sample->vol; /* VolumeはKey onよりも先に適用されます!! */
							}
						}
					}

					/* Noteを実行します。 */
					if(note) {
						switch(note) {
						case 255: /* empty note */
							/** no job **/
							break;
						case 254: /* key off */
							channel->playing_sample = NULL;
							break;
						default: /* key on */
							octave = note >> 4;
							note &= 15;
							if(note < 12) { /* 0=C,1=C#,..,10=A#,11=B */
								sample = channel->pending_sample;
								if(sample->sampledata) { /* Sample有効? */
								      //period = ((8363 << 4) * (s3m_note_frequency_table[note] >> octave)) / sample->c2spd;
								      //↓精度Upのため式を変形
									period = ((8363 << 4) * (s3m_note_frequency_table[note]) >> octave) / sample->c2spd;
									if((special_command ==  7) ||	/* Gxy Tone portamento */
									   (special_command == 12)) {	/* Lxy Volslide+Toneporta */
										channel->tone_portamento = period;
										/* Tone portamentoと併用して「発声中のInstrumentと異なるInstrumentを設定する」と、
										 * 発声トリガ扱いとなるようです。(ModplugTrackerで確認) この振る舞いは未実装です。
										 */
									} else {
										channel->playing_sample = sample;
										channel->period = period;
										channel->progress = 0;
										channel->sample_position = sample->sampledata;
										/* VolumeはInstrument実行のところで適用されています!! */
									}
								}
							}
							break;
						}
					}

					/* Volumeを実行します。
					 *   0x00..0x40: Set volume
					 *   0x80..0xc0: Set panning (未対応)
					 */
					if((volume >= 0x00) &&
					   (volume <= 0x40)) {
						channel->vol = volume;
					}

					/* Special commandを格納します。 */
					if(special_command) {
						channel->special_command = special_command;
						if(command_info) {
							channel->command_info = command_info;
						} else {
							/* Command infoが0の場合、一部のSpecial commandは前回値を引き継ぎます。
							 * 以下のswitch〜caseで、コメントアウトされて★い　る★Special commandは、前回値を継続します。
							 *                       コメントアウトされて★いない★Special commandは、0を格納します。
							 * どのSpecial commandが前回値を引き継ぐかDocumentに明記されていないかったので、独自判断です。
							 */
							switch(special_command) {
							case  1: /* Axy Set speed */
							case  2: /* Bxy Position jump */
							case  3: /* Cxy Pattern break */
							//case  4: /* Dxy Volume slide */
							//case  5: /* Exy Portamento down */
							//case  6: /* Fxy Portamento up */
							//case  7: /* Gxy Tone portamento */
							//case  8: /* Hxy Vibrato */
							//case  9: /* Ixy Tremor */
							//case 10: /* Jxy Arpeggio */
							//case 11: /* Kxy Volslide+Vibrato */
							//case 12: /* Lxy Volslide+Toneporta */
							//case 13: /* Mxy Set channel volume */
							//case 14: /* Nxy Channel volslide */
							//case 15: /* Oxy Set offset */
							//case 16: /* Pxy Panning slide */
							//case 17: /* Qxy Retrigger note */
							//case 18: /* Rxy Tremolo */
							//case 19: /* Sxy Extended S3M commands */ /* 未対応 */
							case 20: /* Txy Set tempo */
							//case 21: /* Uxy Fine vibrato */
							case 22: /* Vxy Set global volume */
							//case 23: /* Wxy Global volume slide */
							//case 24: /* Xxy Set panning */
							//case 25: /* Yxy Panbrello */
							//case 26: /* Zxy Midi macro */
								channel->command_info = command_info;
								break;
							}
						}
					}
				}
			}
		}

		/* 各チャネルについて... */
		for(i_channel = 0, channel = driver->channels;
		    i_channel < 32;
		    i_channel++, channel++) {

			/* 無効Channelならば飛ばします。 */
			if(!channel->pending_sample) continue;

			/* Special commandが有効ならば、実行します。 */
			special_command = channel->special_command; /* 取り出し */
			if(special_command) {
				command_info = channel->command_info;
				switch(special_command) {
				case  1: /* Axy Set speed */
					driver->speed = command_info;
					if(driver->speed < 1) {
						driver->stat = ERRNO; /* 演奏停止 (独自解釈、ModplugTrackerでは単に無視されます) */
						goto L_EXIT;
					}
					special_command = 0; /* 解除 */
					break;
				case  2: /* Bxy Position jump */
					driver->position_jump = command_info;
					special_command = 0; /* 解除 */
					break;
				case  3: /* Cxy Pattern break */
					driver->pattern_break = command_info;
					special_command = 0; /* 解除 */
					break;
VOLUME_SLIDE:			case  4: /* Dxy Volume slide */
					if(driver->tick) { /* Rowの最初のFrameでは変化しない */
						       if((command_info & 0xf0) == 0x00) { /* D0x Volume slide down */
							channel->vol -= command_info & 15;
							if(channel->vol < 0) {
								channel->vol = 0;
								special_command = 0; /* 解除 */
							}
						} else if((command_info & 0x0f) == 0x00) { /* Dx0 Volume slide up */
							channel->vol += command_info >> 4;
							if(channel->vol > 64) {
								channel->vol = 64;
								special_command = 0; /* 解除 */
							}
						} else if((command_info & 0xf0) == 0xf0) { /* DFx Fine volume slide down */
							channel->vol -= command_info & 15;
							if(channel->vol < 0) {
								channel->vol = 0;
							}
							special_command = 0; /* 解除 */
						} else if((command_info & 0x0f) == 0x0f) { /* DxF Fine volume slide up */
							channel->vol += command_info >> 4;
							if(channel->vol > 64) {
								channel->vol = 64;
							}
							special_command = 0; /* 解除 */
						}
					}
					break;
				case  5: /* Exy Portamento down */
					if(driver->tick) { /* Rowの最初のFrameでは変化しない */
						switch(command_info & 0xf0) {
						case 0xe0: /* EEx Extra fine portamento down */
							channel->period += command_info;
							if(channel->period > S3M_PERIOD_MAX) {
								channel->period = S3M_PERIOD_MAX;
							}
							special_command = 0; /* 解除 */
							break;
						case 0xf0: /* EFx Fine portamento down */
							channel->period += command_info << 2;
							if(channel->period > S3M_PERIOD_MAX) {
								channel->period = S3M_PERIOD_MAX;
							}
							special_command = 0; /* 解除 */
							break;
						default:   /* Exy Portamento down */
							channel->period += command_info << 2;
							if(channel->period > S3M_PERIOD_MAX) {
								channel->period = S3M_PERIOD_MAX;
								special_command = 0; /* 解除 */
							}
							break;
						}
					}
					break;
				case  6: /* Fxy Portamento up */
					if(driver->tick) { /* Rowの最初のFrameでは変化しない */
						switch(command_info & 0xf0) {
						case 0xe0: /* FEx Extra fine portamento up */
							channel->period -= command_info;
							if(channel->period < S3M_PERIOD_MIN) {
								channel->period = S3M_PERIOD_MIN;
							}
							special_command = 0; /* 解除 */
							break;
						case 0xf0: /* FFx Fine portamento up */
							channel->period -= command_info << 2;
							if(channel->period < S3M_PERIOD_MIN) {
								channel->period = S3M_PERIOD_MIN;
							}
							special_command = 0; /* 解除 */
							break;
						default:   /* Fxy Portamento up */
							channel->period -= command_info << 2;
							if(channel->period < S3M_PERIOD_MIN) {
								channel->period = S3M_PERIOD_MIN;
								special_command = 0; /* 解除 */
							}
							break;
						}
					}
					break;
				case  7: /* Gxy Tone portamento */
					/* Rowの最初のFrameから変化するか否か不明。たぶん変化する？ 独自判断 */
					       if(channel->period < channel->tone_portamento) {
						channel->period += command_info << 2;
						if(channel->period >= channel->tone_portamento) {
							channel->period = channel->tone_portamento;
							special_command = 0; /* 解除 */
						}
					} else if(channel->period > channel->tone_portamento) {
						channel->period -= command_info << 2;
						if(channel->period <= channel->tone_portamento) {
							channel->period = channel->tone_portamento;
							special_command = 0; /* 解除 */
						}
					}
					break;
				//case  8: /* Hxy Vibrato */
				//case  9: /* Ixy Tremor */
				//case 10: /* Jxy Arpeggio */
				case 11: /* Kxy Volslide+Vibrato */
					/* Vibratoは未対応 */
					goto VOLUME_SLIDE;
					break;
				case 12: /* Lxy Volslide+Toneporta */
					/* Tone portamentoは未対応 */
					goto VOLUME_SLIDE;
					break;
				//case 13: /* Mxy Set channel volume */
				//case 14: /* Nxy Channel volslide */
				//case 15: /* Oxy Set offset */
				//case 16: /* Pxy Panning slide */
				//case 17: /* Qxy Retrigger note */
				//case 18: /* Rxy Tremolo */
				//case 19: /* Sxy Extended S3M commands */
				case 20: /* Txy Set tempo */
					driver->tempo = command_info;
					if(driver->tempo < 1) {
						driver->stat = ERRNO; /* 演奏停止 (独自解釈、ModplugTrackerでは単に無視されます) */
						goto L_EXIT;
					}
					special_command = 0; /* 解除 */
					break;
				//case 21: /* Uxy Fine vibrato */
				case 22: /* Vxy Set global volume */
					driver->g_v = command_info;
					special_command = 0; /* 解除 */
					break;
				case 23: /* Wxy Global volume slide */
					if(driver->tick) { /* Rowの最初のFrameでは変化しない */
						       if((command_info & 0xf0) == 0x00) { /* W0x Global volume slide down */
							driver->g_v -= command_info & 15;
							if(driver->g_v < 0) {
								driver->g_v = 0;
								special_command = 0; /* 解除 */
							}
						} else if((command_info & 0x0f) == 0x00) { /* Wx0 Global volume slide up */
							driver->g_v += command_info >> 4;
							if(driver->g_v > 64) {
								driver->g_v = 64;
								special_command = 0; /* 解除 */
							}
						} else if((command_info & 0xf0) == 0xf0) { /* WFx Fine global volume slide down */
							driver->g_v -= command_info & 15;
							if(driver->g_v < 0) {
								driver->g_v = 0;
							}
							special_command = 0; /* 解除 */
						} else if((command_info & 0x0f) == 0x0f) { /* WxF Fine global volume slide up */
							driver->g_v += command_info >> 4;
							if(driver->g_v > 64) {
								driver->g_v = 64;
							}
							special_command = 0; /* 解除 */
						}
					}
					break;
				//case 24: /* Xxy Set panning */
				//case 25: /* Yxy Panbrello */
				//case 26: /* Zxy Midi macro */
				}
				channel->special_command = special_command; /* 書き戻し */
			}
		}
	}

L_EXIT:
	return driver->stat;
}

int
s3m_mix(S3MDRIVER* driver, short wbuff[/*S3MBUFLEN_5*/])
{
	static int mixbuf[S3MBUFLEN_5]; /* ミキシングバッファ、ＳＴＡＴＩＣです！ */
	//
	int i_channel;
	int frequency;
	int volume;
	S3MCHANNEL* channel;

	if(driver->stat) goto L_EXIT;

	/* まず、ミキシングバッファをクリアします。 */
	memset(mixbuf, 0, sizeof mixbuf);

	/* 各チャネルについて... */
	for(i_channel = 0, channel = driver->channels;
	    i_channel < 32;
	    i_channel++, channel++) {

		/* Silentならば飛ばします。
		 * 無効Channelは必ずSilentを維持しているので、無効Channelか否かの判断は不要です。
		 */
		if(!channel->playing_sample) continue;

		/* ボリュームを求めます。 */
		volume = driver->g_v *	/*   0..64 ( 6bit) */
			 channel->vol;	/* x 0..64 ( 6bit) */
					/* =       (12bit) */
		if(!volume) continue; /* 無音 */

		/* 再生周波数を求めます。 */
		frequency = 14317056 / channel->period;

		/* 1/5[Tick]分ミキシングします。 */
		s3m_mix1(channel, volume, frequency, mixbuf);
	}

	/* ミキシングバッファをクリッピングして、出力バッファへ書き込みます。 */
	s3m_mix2(wbuff, mixbuf);

L_EXIT:
	return driver->stat;
}

#ifndef S3M_ASM
void
s3m_mix1(S3MCHANNEL* channel, int volume, int frequency, int* mixbuf)
{
	int n;
	int progress;
	const unsigned char* sample_position;
	const unsigned char* loop_begin;
	const unsigned char* loop_end;

	/* 高速化のため、頻繁に使うフィールドを取り出しておきます。 */
	progress = channel->progress;
	sample_position = channel->sample_position;

	/* これらのフィールドは変化しないので、書き戻し不要です。 */
	loop_begin = channel->playing_sample->loop_begin;
	loop_end = channel->playing_sample->loop_end;

	n = S3MBUFLEN_5;
	do {
		progress += frequency;
		while(progress >= SPEAKER_FREQUENCY) {
			progress -= SPEAKER_FREQUENCY;
			sample_position++;
			if(sample_position >= loop_end) {
				if(!loop_begin) { /* Loop off */
					channel->playing_sample = NULL;
					return;
				}
				sample_position = loop_begin;
			}
		}
		*mixbuf++ += (*sample_position - 128) * volume;
	} while(--n);

	/* 取り出したフィールドを書き戻します。 */
	channel->progress = progress;
	channel->sample_position = sample_position;
}
#else /*S3M_ASM*/
asm("
	.code
	.align 1
	.global s3m_mix1
s3m_mix1:
	; %r12 = channel
	; %r13 = volume
	; %r14 = frequency
	; %r15 = mixbuf
	pushn %r2
	xld.w %r4, [%r12+16]		; channel->progress
	xld.w %r5, [%r12+20]		; channel->sample_position

	xld.w %r10, [%r12+4]		; channel->playing_sample
	xld.w %r6, [%r10+4]		; playing_sample->loop_begin
	xld.w %r7, [%r10+8]		; playing_sample->loop_end
	xld.w %r0, 16000		; SPEAKER_FREQUENCY
	xld.w %r1, 128			; 128

	xld.w %r2, 64			; S3MBUFLEN_5
					; do {
	add %r4, %r14			;   progress += frequency (二回目以降はループ末尾のDelaySlotで行う)
s3m_mix1_L10:				;   
	cmp %r4, %r0			;   while(progress >= SPEAKER_FREQUENCY) {
	xjrlt s3m_mix1_L20

	sub %r4, %r0			;     progress -= SPEAKER_FREQUENCY
	xadd %r5, %r5, 1		;     sample_position++
	cmp %r5, %r7			;     if(sample_position < loop_end) {
	xjrult s3m_mix1_L10		;       continue
					;     }
	xcmp %r6, 0			;     if(loop_begin) {
	xjrne.d s3m_mix1_L10		;       sample_position = loop_begin
	ld.w %r5, %r6			;	continue (*delay* loop_begin==NULL時はダミー処理となります)
					;     }
	xld.w [%r12+4], %r6		;     channel->playing_sample = NULL
	xjp s3m_mix1_EXIT		;     return

s3m_mix1_L20:
	ld.ub %r10, [%r5]		;   *sample_position
	ld.w %r11, [%r15]		;   *mixbuf (*anti-interlock*)
	sub %r10, %r1			;   *sample_position - 128
	mlt.h %r10, %r13		;   (*sample_position - 128) * volume (8bit×12bit)
	ld.w %r10, %alr			;   
	add %r10, %r11			;   (*sample_position - 128) * volume + *mixbuf
	ld.w [%r15]+, %r10		;   
	xsub %r2, %r2, 1		; } while(--n)
	xjrne.d s3m_mix1_L10		; 
	add %r4, %r14			; progress += frequency (*delay* ループ先頭の処理を先取り)
	sub %r4, %r14			; DelaySlotは無条件で実行されるため、最後の一回は足し過ぎなので戻す

	xld.w [%r12+16], %r4		; channel->progress
	xld.w [%r12+20], %r5		; channel->sample_position
s3m_mix1_EXIT:
	popn %r2
	ret
");
#endif /*S3M_ASM*/

#ifndef S3M_ASM
void
s3m_mix2(short* wbuff, const int* mixbuf)
{
	int n;
	int output;

	n = S3MBUFLEN_5;
	do {
		/*   volume (12bit)
		 * x sample ( 8bit)
		 * x 32[ch] ( 5bit)
		 * =        (25bit)
		 */
		output = *mixbuf++ >> ((25 - 16) - 4/*調整分*/);
		if(output >  32767) output =  32767;
		if(output < -32768) output = -32768;
		*wbuff++ = output;
	} while(--n);
}
#else /*S3M_ASM*/
asm("
	.code
	.align 1
	.global s3m_mix2
s3m_mix2:
	; %r12 = wbuff
	; %r13 = mixbuf
	xld.w %r4, 64			; S3MBUFLEN_5
	xld.w %r5, 32767
	not %r6, %r5			; -32768
s3m_mix2_L10:
	ld.w %r7, [%r13]+
	xsra %r7, 5			; ((25 - 16) - 4/*調整分*/)
	cmp %r7, %r5
	xjrle.d s3m_mix2_L20
	cmp %r7, %r6			; *delay*
	ld.w %r7, %r5
s3m_mix2_L20:
	xjrge.d s3m_mix2_L30
	sub %r4, 1			; *delay*
	ld.w %r7, %r6
s3m_mix2_L30:
	ld.h [%r12]+, %r7
	xjrne s3m_mix2_L10
	ret
");
#endif /*S3M_ASM*/
