/*	
 *	clipit.c
 *
 *	P/ECE IT Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Thu Nov 18 18:59:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

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

/****************************************************************************
 *	P/ECE IT Driver
 ****************************************************************************/

const int
it_pitch_table[12] = {     /* 小数部bit数↓ */
	(int)(1.0000000000000000 * (1 << 10) + 0.5),	/* C  = 2^( 0/12) */
	(int)(1.0594630943592953 * (1 << 10) + 0.5),	/* C# = 2^( 1/12) */
	(int)(1.1224620483093730 * (1 << 10) + 0.5),	/* D  = 2^( 2/12) */
	(int)(1.1892071150027210 * (1 << 10) + 0.5),	/* D# = 2^( 3/12) */
	(int)(1.2599210498948732 * (1 << 10) + 0.5),	/* E  = 2^( 4/12) */
	(int)(1.3348398541700344 * (1 << 10) + 0.5),	/* F  = 2^( 5/12) */
	(int)(1.4142135623730951 * (1 << 10) + 0.5),	/* F# = 2^( 6/12) */
	(int)(1.4983070768766815 * (1 << 10) + 0.5),	/* G  = 2^( 7/12) */
	(int)(1.5874010519681994 * (1 << 10) + 0.5),	/* G# = 2^( 8/12) */
	(int)(1.6817928305074290 * (1 << 10) + 0.5),	/* A  = 2^( 9/12) */
	(int)(1.7817974362806785 * (1 << 10) + 0.5),	/* A# = 2^(10/12) */
	(int)(1.8877486253633868 * (1 << 10) + 0.5),	/* B  = 2^(11/12) */
};

static int
_it_init(ITDRIVER* driver, const void* data)
{
#define module_header     (header._module_header)
#define instrument_header (header._instrument_header)
#define sample_header     (header._sample_header)
#define pattern_header    (header._pattern_header)

	/* ITファイル構\造の複数バイトフィールドは整列が保証されないので、いったんローカル変数に読み込みます。
	 * 各構\造体は500B程度の大きさを持ち、個別に定義するとスタック消費量が大きすぎるため、共用体とします。
	 * それでも500B〜1KBぐらい消費すると思います。スタックサイズを多めに設定するよう、注意してください。
	 */
	union {
		ITMODULEHEADER     _module_header;
		ITINSTRUMENTHEADER _instrument_header;
		ITSAMPLEHEADER     _sample_header;
		ITPATTERNHEADER    _pattern_header;
	} header;
	//
	const unsigned char* ptr;
	const unsigned char* offset_of_instruments;
	const unsigned char* offset_of_samples;
	const unsigned char* offset_of_patterns;
	int note;
	int offset;
	int i_instrument;
	int i_sample;
	int i_point;
	int i_pattern;
	int i_channel;
	ITINSTRUMENT* instrument;
	ITSAMPLE* sample;
	ITENVELOPENODEPOINT* point;
	ITPATTERN* pattern;
	ITCHANNEL* channel;

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

	/***************************
	 *  Impulse Header Layout  *
	 ***************************/

	/* ITMODULEHEADERを読み込みます。 */
	ptr = (const unsigned char*)((int)data + 0);
	memcpy(&module_header, ptr, sizeof(ITMODULEHEADER));

	/* 固定フィールドを検査します。 */
	if(memcmp(module_header.impm, "IMPM", 4) != 0) {
		return ERRNO;
	}
	if(module_header.cmwt < 0x200) {
		return ERRNO; /* 1.xxは未対応 */
	}

	/* Ordnum、Insnum、Smpnum、Patnumを取得します。 */
	driver->ordnum = module_header.ordnum;
	if(driver->ordnum < 1) {
		return ERRNO; /* Ordnum不正 */
	}
	driver->insnum = module_header.insnum;
	if(module_header.flags & 4) { /* Instrument mode */
		if(driver->insnum < 1) {
			return ERRNO; /* Insnum不正 */
		}
	}
	driver->smpnum = module_header.smpnum;
	if(driver->smpnum < 1) {
		return ERRNO; /* smpnum不正 */
	}
	driver->patnum = module_header.patnum;
	if(driver->patnum < 1) {
		return ERRNO; /* patnum不正 */
	}

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

	/* Orders、Offset of instruments、Offset of samples、Offset of patternsを取得します。 */
	ptr += sizeof(ITMODULEHEADER);
	driver->orders = ptr;
	ptr += driver->ordnum * sizeof(unsigned char);
	offset_of_instruments = ptr;
	ptr += driver->insnum * sizeof(int);
	offset_of_samples = ptr;
	ptr += driver->smpnum * sizeof(int);
	offset_of_patterns = ptr;

	/* これ以降module_headerは上書きされます。(共用体のため)
	 * module_headerのフィールドにアクセスしてはいけません。
	 */

	/*******************************
	 *  Impulse Instrument Format  *
	 *******************************/

	if(module_header.flags & 4) { /* Instrument mode */

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

		/* 各Instrumentについて... */
		for(i_instrument = 0, instrument = driver->instruments;
		    i_instrument < driver->insnum;
		    i_instrument++, instrument++) {

			/* ITINSTRUMENTHEADER構\造体を読み込みます。 */
			ptr = offset_of_instruments + i_instrument * sizeof(int);
			offset = LEWORD(ptr);
			ptr = (const unsigned char*)((int)data + offset);
			memcpy(&instrument_header, ptr, sizeof(ITINSTRUMENTHEADER));

			/* 固定フィールドを検査します。 */
			if(memcmp(instrument_header.impi, "IMPI", 4) != 0) {
				return ERRNO;
			}
			if(instrument_header._00h != 0x00) {
				return ERRNO;
			}

			/* Fadeout、Global volumeを取得します。 */
			instrument->fadeout = instrument_header.fadeout;
			instrument->gbv = instrument_header.gbv;

			/* Note-Sample/Keyboard Tableを取得します。 */
			ptr = instrument_header.note_sample_keyboard_table;
			for(note = 0; note < 120; note++) {
				instrument->note_sample_keyboard_table[note].note = *ptr++;
				instrument->note_sample_keyboard_table[note].sample = *ptr++;
			}

			/* Volume envelopeを取得します。 */
			if(instrument_header.volume_envelope.flg & 1/*Envelope on*/) {

				/* ITENVELOPE構\造体を確保します。 */
				instrument->volume_envelope = calloc(1, sizeof(ITENVELOPE));
				if(!instrument->volume_envelope) {
					return ERRNO;
				}

				/* ITENVELOPENODEPOINT構\造体配列を確保します。 */
				if((instrument_header.volume_envelope.num < 2) ||
				   (instrument_header.volume_envelope.num > 25)) {
					return ERRNO; /* Number of node points不正 */
				}
				instrument->volume_envelope->node_points = calloc(instrument_header.volume_envelope.num, sizeof(ITENVELOPENODEPOINT));
				if(!instrument->volume_envelope->node_points) {
					return ERRNO;
				}
				instrument->volume_envelope->node_points_end = instrument->volume_envelope->node_points + instrument_header.volume_envelope.num;

				/* Node pointsを読み込みます。 */
				ptr = instrument_header.volume_envelope.node_points;
				for(i_point = 0, point = instrument->volume_envelope->node_points;
				    i_point < instrument_header.volume_envelope.num;
				    i_point++, point++) {
					point->y    = LEBYTE(ptr); ptr += 1;
					point->tick = LEHALF(ptr); ptr += 2;
					if(i_point) {
						if(point->tick < (point - 1)->tick) {
							return ERRNO; /* Tick逆行 (==は可) */
						}
					}
				}

				/* Loop begin、Loop endを取得します。
				 * * IT形式では、終端の表\記は(終端+1)とするのがセオリーなのですが、Envelopeだけ(終端)そのものを示しています。
				 *   他との整合性のため、読み込み時に(終端+1)としておくことにします。
				 */
				if(instrument_header.volume_envelope.flg & 2/*Loop on*/) {
					if(instrument_header.volume_envelope.lpb >= instrument_header.volume_envelope.num) {
						return ERRNO; /* Loop begin不正 (0..(num-1)必須) */
					}
					if(instrument_header.volume_envelope.lpe >= instrument_header.volume_envelope.num) {
						return ERRNO; /* Loop end不正 (0..(num-1)必須) */
					}
					if(instrument_header.volume_envelope.lpb > instrument_header.volume_envelope.lpe) {
						return ERRNO; /* Loop begin/end不正 ((begin)<=(end)必須) */
					}
					instrument->volume_envelope->loop_begin = instrument->volume_envelope->node_points + instrument_header.volume_envelope.lpb;
					instrument->volume_envelope->loop_end   = instrument->volume_envelope->node_points + instrument_header.volume_envelope.lpe + 1/*終端+1*/;
				}

				/* Sustain loop begin、Loop endを取得します。
				 * * IT形式では、終端の表\記は(終端+1)とするのがセオリーなのですが、Envelopeだけ(終端)そのものを示しています。
				 *   他との整合性のため、読み込み時に(終端+1)としておくことにします。
				 */
				if(instrument_header.volume_envelope.flg & 4/*Sustain loop on*/) {
					if(instrument_header.volume_envelope.slb >= instrument_header.volume_envelope.num) {
						return ERRNO; /* Sustain loop begin不正 (0..(num-1)必須) */
					}
					if(instrument_header.volume_envelope.sle >= instrument_header.volume_envelope.num) {
						return ERRNO; /* Sustain loop end不正 (0..(num-1)必須) */
					}
					if(instrument_header.volume_envelope.slb > instrument_header.volume_envelope.sle) {
						return ERRNO; /* Sustain loop begin/end不正 ((begin)<=(end)必須) */
					}
					instrument->volume_envelope->susloop_begin = instrument->volume_envelope->node_points + instrument_header.volume_envelope.slb;
					instrument->volume_envelope->susloop_end   = instrument->volume_envelope->node_points + instrument_header.volume_envelope.sle + 1/*終端+1*/;
				}
			}
		}

	} else { /* Sample mode */

		/* Sampleに一対一の仮想Instrumentを作成します。 */
		driver->insnum = driver->smpnum;

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

		/* 各Instrumentについて... */
		for(i_instrument = 0, instrument = driver->instruments;
		    i_instrument < driver->insnum;
		    i_instrument++, instrument++) {

			/* Global volumeを設定します。 */
			instrument->gbv = 128;

			/* Note-Sample/Keyboard Tableを作成します。 */
			for(note = 0; note < 120; note++) {
				instrument->note_sample_keyboard_table[note].note = note;
				instrument->note_sample_keyboard_table[note].sample = i_instrument + 1; /* Sample番号は1ベース */
			}
		}
	}

	/***************************
	 *  Impulse sample format  *
	 ***************************/

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

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

		/* ITSAMPLEHEADER構\造体を読み込みます。 */
		ptr = offset_of_samples + i_sample * sizeof(int);
		offset = LEWORD(ptr);
		ptr = (const unsigned char*)((int)data + offset);
		memcpy(&sample_header, ptr, sizeof(ITSAMPLEHEADER));

		/* コメント用の無効Sampleならばスキップします。 */
		if(sample_header.length < 1) {
			continue;
		}

		/* 固定フィールドを検査します。 */
		if(memcmp(sample_header.imps, "IMPS", 4) != 0) {
			return ERRNO;
		}
		if(sample_header._00h != 0x00) {
			return ERRNO;
		}
		if(!(sample_header.cvt & 1)) {
			return ERRNO; /* Unsigned sampleは未対応 */
		}

		/* Global volume、Flags、Default volume、C5speedを取得します。 */
		sample->gvl = sample_header.gvl;
		sample->flg = sample_header.flg;
		if(sample->flg & (4/*Stereo samples*/ | 8/*Compressed samples*/)) {
			return ERRNO; /* Stereo samples、Compressed samplesは未対応 */
		}
		sample->vol = sample_header.vol;
		sample->c5speed = sample_header.c5speed;
		if((sample->c5speed < 0) ||
		   (sample->c5speed > 9999999)) {
			return ERRNO; /* C5speed不正 */
		}

		/* Sample data区間を算出します。 */
		sample->sample_data = (const unsigned char*)((int)data + sample_header.sample_pointer);
		if(sample->flg & 2/*16 bit*/) {
			sample->sample_end = sample->sample_data + sample_header.length * 2;
		} else { /*8 bit*/
			sample->sample_end = sample->sample_data + sample_header.length * 1;
		}

		/* Loop区間を算出します。 */
		if(sample->flg & 16/*Use loop*/) {
			if((sample_header.loop_begin < 0) ||
			   (sample_header.loop_begin >= sample_header.length)) {
				return ERRNO; /* Loop begin不正 (0..(length-1)必須) */
			}
			if((sample_header.loop_end <= 0) ||
			   (sample_header.loop_end > sample_header.length)) {
				return ERRNO; /* Loop end不正 (1..(length)必須) */
			}
			if(sample_header.loop_begin >= sample_header.loop_end) {
				return ERRNO; /* Loop begin/end不正 ((begin)<(end)必須) */
			}
			if(sample->flg & 2/*16 bit*/) {
				sample->loop_begin = sample->sample_data + sample_header.loop_begin * 2;
				sample->loop_end   = sample->sample_data + sample_header.loop_end   * 2;
			} else { /*8 bit*/
				sample->loop_begin = sample->sample_data + sample_header.loop_begin * 1;
				sample->loop_end   = sample->sample_data + sample_header.loop_end   * 1;
			}
		}

		/* Sustain loop区間を算出します。 */
		if(sample->flg & 32/*Use sustain loop*/) {
			if((sample_header.susloop_begin < 0) ||
			   (sample_header.susloop_begin >= sample_header.length)) {
				return ERRNO; /* Sustain loop begin不正 (0..(length-1)必須) */
			}
			if((sample_header.susloop_end <= 0) ||
			   (sample_header.susloop_end > sample_header.length)) {
				return ERRNO; /* Sustain loop end不正 (1..(length)必須) */
			}
			if(sample_header.susloop_begin >= sample_header.susloop_end) {
				return ERRNO; /* Sustain loop begin/end不正 ((begin)<(end)必須) */
			}
			if(sample->flg & 2/*16 bit*/) {
				sample->susloop_begin = sample->sample_data + sample_header.susloop_begin * 2;
				sample->susloop_end   = sample->sample_data + sample_header.susloop_end   * 2;
			} else { /*8 bit*/
				sample->susloop_begin = sample->sample_data + sample_header.susloop_begin * 1;
				sample->susloop_end   = sample->sample_data + sample_header.susloop_end   * 1;
			}
		}
	}

	/****************************
	 *  Impulse Pattern Format  *
	 ****************************/

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

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

		/* ITPATTERNHEADER構\造体を読み込みます。 */
		ptr = offset_of_patterns + i_pattern * sizeof(int);
		offset = LEWORD(ptr);
		if(!offset) {
			/* 欠番PatternはOffset=0で保存されるようです。(NO-TMATF.IT)
			 * ロード時にはエラー扱いせず、スキップすることにしました。
			 * 演奏時にはrows==0で判断し、254(Skip)と同じ扱いとします。(独自判断)
			 */
			continue;
		}
		ptr = (const unsigned char*)((int)data + offset);
		memcpy(&pattern_header, ptr, sizeof(ITPATTERNHEADER));

		/*{{この後ptrを使うので破壊しないこと!! */

		/* Rowsを取得します。 */
		pattern->rows = pattern_header.rows;
		if((pattern->rows < 32) ||
		   (pattern->rows > 200)) {
			return ERRNO; /* Rows不正 (32..200必須) */
		}

		/*}}ここまでにptrを破壊していないこと!! */

		/* Packed dataを取得します。
		 * コピー先のローカル変数ではなく、コピー元のPacked dataを参照します。
		 */
		pattern->packed_data = (const unsigned char*)((int)ptr + sizeof(ITPATTERNHEADER));
	}

	/************************
	 *  演奏開始の初期設定  *
	 ************************/

	/* もういちどITMODULEHEADERを読み込みます。
	 * (必要!! module_headerは共用体なので、最初に読み込んだ内容は破壊されています。)
	 */
	ptr = (const unsigned char*)((int)data + 0);
	memcpy(&module_header, ptr, sizeof(ITMODULEHEADER));

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

		/* 無効Channelならばスキップします。 */
		if(module_header.chnl_pan[i_channel] & 128) {
			continue;
		}

		/* Silent状態にします。(有効Channel) */
		channel->stat = ITCHANNEL_SILENT;

		/* Channel volumeを取得します。 */
		channel->chnl_vol = module_header.chnl_vol[i_channel];

		/* Instrument無しで初回発声要求された場合に備えて、Instrument 0を設定しておきます。 */
		channel->pending_instrument = &driver->instruments[0];

		/* Volume/PanningとCommandを解除しておきます。 */
		channel->volume_panning = -1;
		channel->command = -1;
	}

	/* 最初の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でない */
			if((i_pattern < 0) ||
			   (i_pattern > driver->patnum - 1)) {
				return ERRNO; /* Pattern不正 */
			}
			driver->pattern = &driver->patterns[i_pattern];
			if(driver->pattern->rows) { /* 欠番でない */
				driver->pattern_position = driver->pattern->packed_data;
				break;
			}
		}
		driver->order++;
	}

	return 0;

#undef module_header
#undef instrument_header
#undef sample_header
#undef pattern_header
}

int
it_init(ITDRIVER* driver, const void* data)
{
	int retval;

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

	return retval;
}

void
it_free(ITDRIVER* driver)
{
	int i_instrument;
	ITINSTRUMENT* instrument;

	/* ITINSTRUMENT構\造体、ITENVELOPE構\造体、ITENVELOPENODEPOINT構\造体を開放します。 */
	if(driver->instruments) {
		for(i_instrument = 0, instrument = driver->instruments;
		    i_instrument < driver->insnum;
		    i_instrument++, instrument++) {
			if(instrument->volume_envelope) {
				if(instrument->volume_envelope->node_points) {
					free(instrument->volume_envelope->node_points);
				}
				free(instrument->volume_envelope);
			}
		}
		free(driver->instruments);
	}

	/* ITSAMPLE構\造体を開放します。 */
	if(driver->samples) {
		free(driver->samples);
	}

	/* ITPATTERN構\造体を開放します。 */
	if(driver->patterns) {
		free(driver->patterns);
	}

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

int
it_stream_callback(short* wbuff, int param)
{
	ITDRIVER* driver = (ITDRIVER*)param;
	//
	int retval;

	retval = it_run(driver);
	if(retval == 0) {
		retval = it_mix(driver, wbuff);
	}

	return retval;
}

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

static ITDRIVER driver;

int
it_play(const void* data)
{
	int retval;

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

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

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

	return 0;
}

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

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

