/*	
 *	clips3m.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__)

/*
 *	-------	-------	-----------------------	-------	-----------------------
 *	Special					S3mod	
 *	Command	Name	Description		Support	Comment
 *	-------	-------	-----------------------	-------	-----------------------
 *	 1	Axy	Set speed		○	
 *	 2	Bxy	Position jump		○	
 *	 3	Cxy	Pattern break		○	
 *	 4	Dxy	Volume slide		○	
 *	 5	Exy	Portamento down		○	
 *	 6	Fxy	Portamento up		○	
 *	 7	Gxy	Tone portamento		○	
 *	 8	Hxy	Vibrato			○	
 *	 9	Ixy	Tremor				
 *	10	Jxy	Arpeggio		○	
 *	11	Kxy	Volslide+Vibrato	○	
 *	12	Lxy	Volslide+Toneporta	○	
 *	13	Mxy	Set channel volume		
 *	14	Nxy	Channel volslide		
 *	15	Oxy	Set offset		○	
 *	16	Pxy	Panning slide			
 *	17	Qxy	Retrigger note			
 *	18	Rxy	Tremolo				
 *	19	Sxy	Extended S3M commands		
 *	20	Txy	Set tempo			
 *	21	Uxy	Fine vibrato			
 *	22	Vxy	Set global volume		
 *	23	Wxy	Global volume slide		
 *	24	Xxy	Set panning			
 *	25	Yxy	Panbrello			
 *	26	Zxy	Midi macro			
 *	-------	-------	-----------------------	-------	-----------------------
 *	19	S0x	Set filter			Modplug Trackerは未対応
 *	19	S1x	Glissando control		
 *	19	S2x	Set finetune			MODとの互換性のためのみ
 *	19	S3x	Vibrato waveform		
 *	19	S4x	Tremolo waveform		
 *	19	S5x	Panbrello waveform		
 *	19	S6x	Fine pattern delay		
 *	19	S7x	Envelope and New Note		Modplug Trackerは未対応
 *			Action (NNA) control		
 *	19	S8x	Set panning			
 *	19	S9x	Sound control			
 *	19	SAx	Set high offset			
 *	19	SBx	Pattern loop			
 *	19	SCx	Note cut			
 *	19	SDx	Note delay			
 *	19	SEx	Pattern delay			
 *	19	SFx	Funkrepeat			Modplug Trackerは未対応
 *	-------	-------	-----------------------	-------	-----------------------
 */

/*
 *	Note無効・Inst有効での発声処理について
 *	======================================
 *
 *	S3Mで、Note無効・Inst有効での発声を行うと、InstのVolumeだけが適用されます。
 *	もしも次回に Note有効・Inst無効での発生を行うと、後出のInstが適用されます。
 *	Instを明示しない場合は、Volumeは現在の値を維持します。
 *	次の例を参照してください。
 *
 *		==Channel 1==
 *		C-5 01 -- ---	; Inst=01で発声開始、Vol=Vol(Inst 01)に変更
 *		--- 02 -- ---	; Inst=01で発声継続、Vol=Vol(Inst 02)に変更
 *		--- --v16 ---	; Inst=01で発声継続、Vol=16に変更
 *		C-5 -- -- ---	; Inst=02で発声開始、Vol=16を維持
 *
 *	以上の動作は「Modplug Tracker version 1.16.0204」で確認しました。
 */

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

/*
 *	NoteからFrequencyの算出方法
 *	===========================
 *
 *	Documentには、次のように書いてあります。
 *
 *	---------------------------------------------------------------------
 *
 *	                  8363 * 16 * ( period(NOTE) >> octave(NOTE) )
 *	note_st3period = ----------------------------------------------
 *	                       middle_c_finetunevalue(INSTRUMENT)
 *
 *	note_amigaperiod = st3_period / 4
 *
 *	note_herz = 14317056 / note_st3period
 *
 *	Note that ST3 uses period values that are 4 times larger than the
 *	amiga to allow for extra fine slides (which  are 4 times finer
 *	than normal fine slides).
 *
 *	---------------------------------------------------------------------
 *
 *	わかり易く書き換えると、次のようになります。
 *
 *	---------------------------------------------------------------------
 *
 *	                     (8363 << 4) * (s3m_note_frequency_table[Note & 15] >> (Note >> 4))
 *	S3MCHANNEL.period = --------------------------------------------------------------------
 *	                                      C2spd(S3MCHANNEL.playing_sample)
 *
 *	frequency = 14317056 / S3MCHANNEL.period
 *
 *	---------------------------------------------------------------------
 *
 *	ST3 periodとAmiga periodについて
 *	================================
 *
 *	S3MCHANNEL.periodは、ST3 periodです。
 *	ST3 periodは、Amiga periodの4倍の精度を持ちます。
 *
 *	・           Portamento up/downは、Amiga period単位で継続してかかります。
 *	・      Fine portamento up/downは、Amiga period単位で一度だけかかります。
 *	・Extra fine portamento up/downは、  ST3 period単位で一度だけかかります。
 *
 *	たとえば、
 *
 *	・E01(           Portamento down 1)は、S3MCHANNEL.periodを4づつ増加します。
 *	・EF1(      Find portamento down 1)は、S3MCHANNEL.periodを4だけ増加します。
 *	・EE1(Extra fine portamento down 1)は、S3MCHANNEL.periodを1だけ増加します。
 *
 *	PeriodはFrequencyの逆数なので、増減も逆であることに注意してください。
 */
const short s3m_note_frequency_table[12] = {
	1712,	/*  0: C  */
	1616,	/*  1: C# */
	1524,	/*  2: D  */
	1440,	/*  3: D# */
	1356,	/*  4: E  */
	1280,	/*  5: F  */
	1208,	/*  6: F# */
	1140,	/*  7: G  */
	1076,	/*  8: G# */
	1016,	/*  9: A  */
	 960,	/* 10: A# */
	 907,	/* 11: B  */
};

static int
_s3m_init(S3MDRIVER* driver, const void* data, int initial_row)
{
	const S3MMODULEHEADER* module_header;
	const S3MSAMPLEHEADER* sample_header;
	//int order;
	int i_sample;
	int i_pattern;
	int i_channel;
	//const unsigned short* parapointers_to_instruments;
	//const unsigned short* parapointers_to_patterns;
	//↓2004/11/17 Naoyuki Sawa 奇数Ordnum対応
	const unsigned char* parapointers_to_instruments;
	const unsigned char* parapointers_to_patterns;
	S3MSAMPLE* sample;
	S3MCHANNEL* channel;

	/* まず、S3MDRIVER構造体をクリアします。 */
	memset(driver, 0, sizeof(S3MDRIVER));

	/***********************
	 *  S3M Module header  *
	 ***********************/

	module_header = (const S3MMODULEHEADER*)data;

	/* 固定フィールドを検査します。 */
	if(module_header->_1ah != '\x1a') {
		return ERRNO; /* 固定フィールド不正 */
	}
	if(module_header->type != 16) {
		return ERRNO; /* ST3 module以外ありえない */
	}
	if(module_header->ffv != 2) {
		return ERRNO; /* standardのみ対応 */
	}
	if(memcmp(module_header->scrm, "SCRM", 4) != 0) {
		return ERRNO; /* シグネチャ不正 */
	}

	/* Ordnum、Insnum、Patnumを取得します。 */
	driver->ordnum = module_header->ordnum;
	//if((driver->ordnum < 1) ||
	//   (driver->ordnum & 1)) {
	//	return ERRNO; /* Ordnumが不正または奇数 */
	//}
	//↓仕様ではOrdnumが偶数でなければいけないのですが、現実に奇数のS3Mファイルが存在します。(PARTY92.S3M)
	//↓仕方がないので、奇数のOrdnumも許容することにします。Ordnumが奇数の場合、Ordersフィールドが奇数サイズになるため、
	//↓Ordersの後ろにあるParapointers to instrumentsとParapointers to patternsの各フィールドがHalfWord整列されません。
	//↓Parapointers to instrumentsとParapointers to patternsの各フィールドの読み込みは、LEHALFマクロを使ってください。
	if(driver->ordnum < 1) {
		return ERRNO; /* Ordnumが不正 */
	}
	driver->insnum = module_header->insnum;
	if(driver->insnum < 1) {
		return ERRNO; /* Insnumが不正 */
	}
	driver->patnum = module_header->patnum;
	if(driver->patnum < 1) {
		return ERRNO; /* Patnumが不正 */
	}

	/* Global volume、Initial speed、Initial tempoを取得します。 */
	driver->g_v = module_header->g_v;
	driver->speed = module_header->i_s;
	if(driver->speed < 1) {
		return ERRNO; /* Initial speed不正 */
	}
	driver->tempo = module_header->i_t;
	if(driver->tempo < 1) {
		return ERRNO; /* Initial tempo不正 */
	}

	/* Orders、Parapointers to instruments、Parapointers to patternsのアドレスを取得します。 */
	driver->orders              = (const unsigned char *)((int)module_header               + sizeof(S3MMODULEHEADER)                 );
      //parapointers_to_instruments = (const unsigned short*)((int)driver->orders              + sizeof(unsigned char  ) * driver->ordnum);
      //parapointers_to_patterns    = (const unsigned short*)((int)parapointers_to_instruments + sizeof(unsigned short ) * driver->insnum);
      //↓2004/11/17 Naoyuki Sawa 奇数Ordnum対応
	parapointers_to_instruments = (const unsigned char *)((int)driver->orders              + sizeof(unsigned char  ) * driver->ordnum);
	parapointers_to_patterns    = (const unsigned char *)((int)parapointers_to_instruments + sizeof(unsigned short ) * driver->insnum);

	/* Ordersを検査します。 */
	//for(order = 0; order < driver->ordnum; order++) {
	//	i_pattern = driver->orders[order];
	//	if((i_pattern == 254) ||
	//	   (i_pattern == 255)) {
	//		continue; /* 254(スキップ)と255(終了)は許可 */
	//	}
	//	if((i_pattern < 0) ||
	//	   (i_pattern > driver->patnum - 1)) {
	//		return ERRNO; /* Pattern番号不正 */
	//	}
	//}
	//→不正なPattern番号を含むS3Mファイルがときどき存在するようなので、検査しないことにしました。
	//  不正なPattern番号は演奏時に検出し、Song終了と見なします。

	/* S3MSAMPLE構造体配列を確保します。 */
	driver->samples = calloc(driver->insnum, sizeof(S3MSAMPLE));
	if(!driver->samples) {
		return ERRNO;
	}

	/* 各Sampleについて... */
	for(i_sample = 0, sample = driver->samples;
	    i_sample < driver->insnum;
	    i_sample++, sample++) {

		/* Digiplayer ST3/ samplefileformatのアドレスを求めます。 */
		//sample_header = (const S3MSAMPLEHEADER*)((int)module_header +
		//					 (parapointers_to_instruments[i_sample] << 4));
		//↓2004/11/17 Naoyuki Sawa 奇数Ordnum対応
		sample_header = (const S3MSAMPLEHEADER*)((int)module_header +
							 (LEHALF(&parapointers_to_instruments[sizeof(unsigned short) * i_sample]) << 4));

		/* Type=1(Sample)以外ならば、スキップします。 */
		if(sample_header->type != 1) {
			continue;
		}

		/* 固定フィールドを検査します。 */
		if(memcmp(sample_header->scrs, "SCRS", 4) != 0) {
			return ERRNO; /* シグネチャ不正 */
		}

		/* Sampledataアドレスを算出します。 */
		sample->sampledata = (const unsigned char *)((int)module_header +
							     (sample_header->memseg << 4));
		if(sample_header->length < 1) {
			return ERRNO; /* Length不正 */
		}
		if(!(sample_header->flags & 1)) { /* Loop off */
			sample->loop_end = sample->sampledata + sample_header->length;
		} else { /* Loop on */
			if((sample_header->loop_begin < 0) ||
			   (sample_header->loop_begin >= sample_header->length)) {
				return ERRNO; /* Loop begin不正 (1..(Length-1)必須) */
			}
			if((sample_header->loop_end <= 0) ||
			   (sample_header->loop_end > sample_header->length)) {
				return ERRNO; /* Loop end不正 (0..(Length)必須) */
			}
			if(sample_header->loop_begin >= sample_header->loop_end) {
				return ERRNO; /* Loop begin/end不正 ((Loop begin)<(Loop end)必須) */
			}
			sample->loop_begin = sample->sampledata + sample_header->loop_begin;
			sample->loop_end = sample->sampledata + sample_header->loop_end;
		}

		/* Vol、C2spdを取得します。 */
		sample->vol = sample_header->vol;
		sample->c2spd = sample_header->c2spd;
		if(sample->c2spd < 1) {
			return ERRNO; /* C2spd不正 (Period算出時のゼロ除算防止) */
		}
	}

	/* Patterns配列を確保します。 */
	driver->patterns = calloc(driver->patnum, sizeof(const unsigned char*));
	if(!driver->patterns) {
		return ERRNO;
	}

	/* 各Patternについて... */
	for(i_pattern = 0;
	    i_pattern < driver->patnum;
	    i_pattern++) {

		/* Packed patterndataのアドレスを格納します。
		 * Packed patterndataの先頭には、Length of packed patternフィールド(HalfWord)があります。
		 * Length of packed patternは不要なので、あらかじめスキップしておきます。
		 */
		//driver->patterns[i_pattern] = (const unsigned char*)((int)module_header +
		//						     (parapointers_to_patterns[i_pattern] << 4) +
		//						     sizeof(unsigned short)/*Length of packed pattern*/);
		//↓2004/11/17 Naoyuki Sawa 奇数Ordnum対応
		driver->patterns[i_pattern] = (const unsigned char*)((int)module_header +
								     (LEHALF(&parapointers_to_patterns[sizeof(unsigned short) * i_pattern]) << 4) +
								     sizeof(unsigned short)/*Length of packed pattern*/);
	}

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

		/* Channel setting = 0..15ならばSample channelです。
		 * Adlib channel(16..31)およびDisabled channel(255)はスキップします。
		 */
		if(module_header->channel_settings[i_channel] >= 16) {
			continue;
		}

		/* Sample無しで初回発声要求された場合に備えて、Sample0を設定しておきます。
		 * Pending sampleを設定することが、このChannelが有効であることを示します。
		 */
		channel->pending_sample = &driver->samples[0];
	}

	/* 最初のPatternを取得します。 */
	if(s3m_init_pattern(driver) != 0) {
		return ERRNO; /* Song終端またはPattern不正 */
	}

	/* 演奏開始Rowまで進めます。 */
	if(s3m_seek_pattern(driver, initial_row) != 0) {
		return ERRNO; /* 演奏開始Row不正 */
	}

	/* Position jump、Pattern breakをクリアします。 */
	driver->position_jump = -1;
	driver->pattern_break = -1;

	return 0;
}

int
s3m_init(S3MDRIVER* driver, const void* data, int initial_row)
{
	int retval;

	retval = _s3m_init(driver, data, initial_row);
	if(retval != 0) {
		s3m_free(driver); /* メモリ開放 */
	}

	return retval;
}

void
s3m_free(S3MDRIVER* driver)
{
	/* S3MSAMPLE構造体配列を開放します。 */
	free(driver->samples); /* free(NULL)は安全 */

	/* Patterns配列を開放します。 */
	free((/*警告抑制*/void*)driver->patterns); /* free(NULL)は安全 */

	/* s3m_free()が二回以上呼ばれた場合のために、S3MDRIVER構造体をクリアしておきます。 */
	memset(driver, 0, sizeof(S3MDRIVER));
}

int
s3m_init_pattern(S3MDRIVER* driver)
{
	int i_pattern;

	for(;;) {
		if((driver->order < 0) ||
		   (driver->order > driver->ordnum - 1)) {
			return ERRNO; /* Song終端 */
		}
		i_pattern = driver->orders[driver->order];
		if(i_pattern != 254/*Skip*/) {
			break;
		}
		driver->order++;
	}
	if((i_pattern < 0) ||
	   (i_pattern > driver->patnum - 1)) {
		return ERRNO; /* Pattern不正 */
	}
	driver->pattern_position = driver->patterns[i_pattern];

	return 0;
}

int
s3m_seek_pattern(S3MDRIVER* driver, int row)
{
	int flags;

	if((row < driver->row) || /* 後退不可 */
	   (row > 64 - 1)) {      /* Row不正 */
		return ERRNO;
	}
	while(driver->row < row) {
		for(;;) {
			flags = *driver->pattern_position++;
			if(!flags) break; /* end of row */
			if(flags & 0x20) driver->pattern_position += 2; /* note, instrument */
			if(flags & 0x40) driver->pattern_position += 1; /* volume */
			if(flags & 0x80) driver->pattern_position += 2; /* special_command, command_info */
		}
		driver->row++;
	}

	return 0;
}

int
s3m_stream_callback(short* wbuff, int param)
{
	S3MDRIVER* driver = (S3MDRIVER*)param;
	//
	int retval;
	int i;

	/* テンポずれ防止のため、1/5[Tick]づつミキシングします。 */
	for(i = 0; i < 5; i++) {
		retval = s3m_run(driver);
		if(retval) break;
		retval = s3m_mix(driver, wbuff);
		if(retval) break;
		wbuff += S3MBUFLEN_5;
	}

	return retval;
}

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

static S3MDRIVER driver;

int
s3m_play(const void* data, int initial_row)
{
	int retval;

	/* まず確実に停止します。 */
	s3m_stop();

	/* S3M Driverを初期化します。 */
	retval = s3m_init(&driver, data, initial_row);
	if(retval != 0) {
		return retval;
	}

	/* ストリーム再生を開始します。 */
	stream_play(S3MBUFLEN, s3m_stream_callback, (int)&driver, 1/*スタック切換使用*/);

	return 0;
}

void
s3m_stop()
{
	/* 確実にストリーム再生を停止します。 */
	stream_stop();

	/* 確実にS3M Driverを開放します。 */
	s3m_free(&driver);
}

int
s3m_stat()
{
	int result;

	/* ストリーム再生中(1)、停止中(0)。 */
	result = stream_stat();

	/* ストリーム再生中(1)かつエラー発生ならば、戻り値をエラーコード(<0)に。
	 * エラーが発生していなければ、戻り値は1のままとします。
	 */
	if(result) {
		if(result != 1) DIE(); /* stream_stat()の戻り値が想定外 */
		if(driver.stat) {
			if(driver.stat > 0) DIE(); /* driver.statのエラー要因が想定外 */
			result = driver.stat;
		}
	}

	return result;
}
