/*	
 *	clipmod.c
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2002 Naoyuki Sawa
 *
 *	* Sat Nov 16 10:13:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/*****************************************************************************
 *	P/ECE MOD Driver （このブロックのコードは、Windowsでも使えます）
 *****************************************************************************/

#define MODBUFLEN 320 /* = 16000/50 … 1[tick](=1/50[sec])当たりのサンプル数 */

/* P/ECEで利用する場合は、バイトスワップマクロはclipmisc.hで定義済みです。 */
#ifdef WIN32
#define SWAPH(a) \
	(((a) >> 8 & 0x00ff) | \
	 ((a) << 8 & 0xff00))
#define SWAPW(a) \
	(((a) >> 24 & 0x000000ff) | \
	 ((a) >>  8 & 0x0000ff00) | \
	 ((a) <<  8 & 0x00ff0000) | \
	 ((a) << 24 & 0xff000000))
#endif /*WIN32*/

/*--------------- MODファイル形式 ---------------*/

/* MODファイルヘッダ内のサンプル情報 */
typedef struct _MODSAMPLE {
	char name[22];			/* + 0,22 */
	unsigned short length;		/* +22, 2 */
	unsigned char finetune;		/* +24, 1 */
	unsigned char volume;		/* +25, 1 */
	unsigned short repofs;		/* +26, 2 */
	unsigned short replen;		/* +28, 2 */
					/* =30    */
} MODSAMPLE;

/* パターン内のロー */
typedef struct _MODROW {
	unsigned int note[4];		/* + 0,16 */
					/* =16   */
} MODROW;

/* MODファイルヘッダ内のパターン */
typedef struct _MODPATTERN {
	MODROW row[64];			/* +   0,1024 */
					/* =1024      */
} MODPATTERN;

/* MODファイルの固定部分（ヘッダ） */
typedef struct _MODHEADER {
	char title[20];			/* +   0, 20 */
	MODSAMPLE sample[31];		/* +  20,930 */
	unsigned char seqlen;		/* + 950,  1 */
	unsigned char seqrep;		/* + 951,  1 */
	unsigned char sequence[128];	/* + 952,128 */
	char tag[4];			/* +1080,  4 */
					/* =1084     */
	/* MODPATTERN pattern[1〜64]                 */
	/* unsigned char smpdat[1〜31][2B〜128KB]    */
} MODHEADER;

/*--------------- ドライバ内部形式 ---------------*/

typedef struct _MODCHANNEL {
	int smpno;	/* サンプル番号                */
	int period;	/* ピリオド値                  */
	int volume;	/* ボリューム                  */
	int effect;	/* エフェクト                  */
	int offset;	/* サンプルオフセット(24.8bit) */
	int loop;	/* ループ中？                  */
} MODCHANNEL;

typedef struct _MODDRIVER {
	MODHEADER* header;		/* ファイルヘッダアドレス */
	MODPATTERN* pattern[64];	/* パターンアドレス       */
	unsigned char* smpdat[31];	/* サンプルデータアドレス */
	MODCHANNEL channel[4];		/* チャネル               */
	int speed;			/* スピード設定値         */
	int tick;			/* チック(カウントアップ) */
	int seqno;			/* シーケンス番号         */
	int rowno;			/* ロー番号               */
} MODDRIVER;

/*--------------- MODドライバ関数 ---------------*/

/* mod_init()/mod_tick()は、MODサンプル展開だけを行う低レベル関数です。
 * * P/ECEで使う場合は、高レベルなmod_play()/mod_stop()を使ってください。
 *   mod_init()/mod_tick()を直接使う必要はありません。
 * * Windowsで利用する場合は、mod_init()/mod_tick()とWindowsサウンドAPIを
 *   組み合わせて使ってください。
 */
int mod_init(MODDRIVER* driver, const void* data);
int mod_tick(MODDRIVER* driver, short wbuff[/*MODBUFLEN*/]);

static int mod_seq(MODDRIVER* driver);
static int mod_mix(MODDRIVER* driver, short wbuff[MODBUFLEN]);

int
mod_init(MODDRIVER* driver, const void* data)
{
	MODHEADER* header = (MODHEADER*)data;
	int i, patno_max;
	MODSAMPLE* sample;
	MODPATTERN* pattern;
	unsigned char* smpdat;

	/* MODデータのタグ(シグネチャ)を確認します。
	 * * 現在の実装では、いちばん標準的な「M.K.」以外はエラーと見なしています。
	 *   「M.K.」以外の4チャネル形式(「M!K!」「FLT4」「4CHN」)は、詳細が不明なので保留としました。
	 */
	if(strncmp(header->tag, "M.K.", 4) != 0) return -1;

	/* まずゼロクリア。 */
	memset(driver, 0, sizeof(MODDRIVER));

	/* ヘッダアドレスを格納します。 */
	driver->header = header;

	/* 最大パターン番号を調べます。 */
	patno_max = -1;
	for(i = 0; i < header->seqlen; i++) {
		if(header->sequence[i] > patno_max) {
			patno_max = header->sequence[i];
		}
	}

	/* パターンアドレスを格納します。 */
	pattern = (MODPATTERN*)(header + 1);
	for(i = 0; i <= patno_max; i++) {
		driver->pattern[i] = pattern;
		pattern++;
	}

	/* サンプルデータアドレスを格納します。 */
	smpdat = (unsigned char*)pattern;
	for(i = 0, sample = header->sample; i < 31; i++, sample++) {
		driver->smpdat[i] = smpdat;
		smpdat += SWAPH(sample->length) * 2;
	}

	/* 初期値設定。 */
	driver->speed = 6;
	driver->tick = driver->speed - 1; /* 初回タイムアウトするように */

	return 0;
}

int
mod_tick(MODDRIVER* driver, short wbuff[MODBUFLEN])
{
	int retval;

	/* シーケンス処理。 */
	retval = mod_seq(driver);
	if(retval != 0) return retval;

	/* ミキシング処理。 */
	retval = mod_mix(driver, wbuff);
	if(retval != 0) return retval;

	return 0;
}

static int
mod_seq(MODDRIVER* driver)
{
	MODHEADER* header = driver->header;
	int chno, *pnote, note, smpno, period, effect;
	MODSAMPLE* sample;
	MODROW* row;
	MODCHANNEL* channel;

	/* チックを進めます。 */
	driver->tick++;
	if(driver->tick >= driver->speed) {
		driver->tick = 0;

		/* ローを取得します。
		 * * ローやシーケンス番号は、エフェクトによって変更される可能性があるので、
		 *   ローからシーケンス番号への繰上げや終了判定は、ロー取得直前に行います。
		 */
		if(driver->rowno >= 64) {
			driver->rowno = 0;
			driver->seqno++;
		}
		if(driver->seqno >= header->seqlen) {
			if(header->seqrep < 127) { /* イコール127の場合も演奏終了です！ */
				driver->seqno = header->seqrep;
			} else {
				return 1; /* シーケンス終了 */
			}
		}
		row = &driver->pattern[header->sequence[driver->seqno]]->row[driver->rowno];
		driver->rowno++; /* 次回のためにローを進めておきます */

		/* 各チャネルのノートを処理します。 */
		for(chno = 0, channel = driver->channel, pnote = row->note; chno < 4; chno++, channel++, pnote++) {
			note = SWAPW(*pnote);

			/* サンプル番号を変更します。 */
			smpno = (note >> 24 & 0xf0) | (note >> 12 & 0xf);
			if(smpno) {
				smpno--; /* ノート上のサンプル番号は#1ベースなので、#0ベースに補正 */
				sample = &header->sample[smpno];
				channel->smpno = smpno;
				channel->volume = sample->volume;
			}

			/* ピリオド値を変更します。 */
			period = note >> 16 & 0xfff;
			if(period) {
				channel->period = period; /* 発声開始 */
				channel->offset = 0;
				channel->loop = 0;
			}

			/* エフェクトを格納します。 */
			effect = note & 0xfff;
			channel->effect = effect;
		}
	}

	/* 各チャネルのエフェクトを処理します。 */
	for(chno = 0, channel = driver->channel; chno < 4; chno++, channel++) {
		effect = channel->effect;
		if(effect) {
			switch(effect & 0xf00) {
			/*---------- 音程に関するエフェクト ----------*/
			case 0x100: /* Slide Up */
				channel->period -= effect & 0xff;
				if(channel->period < 0) channel->period = 0;
				/* エフェクト継続 */
				break;
			case 0x200: /* Slide Down */
				channel->period += effect & 0xff;
				if(channel->period > 0xfff) channel->period = 0xfff;
				/* エフェクト継続 */
				break;
			/*---------- 音量に関するエフェクト ----------*/
			case 0x500: /* Tone Portamento + Volume Slide */
			case 0x600: /* Vibrato + Volume Slide */
			case 0xa00: /* Volume Slide */
				/* トーンポルタメント・ビブラートは未対応です。 */
				if(effect & 0xf) {
					channel->volume -= effect & 0xf;
					if(channel->volume < 0) channel->volume = 0;
				} else {
					channel->volume += effect >> 4 & 0xf;
					if(channel->volume > 64) channel->volume = 64;
				}
				/* エフェクト継続 */
				break;
			case 0xc00: /* Set Volume */
				channel->volume = effect & 0xff;
				channel->effect = 0; /* エフェクト解除 */
				break;
			/*---------- 演奏位置に関するエフェクト ----------*/
			case 0x900: /* Set Sample Offset */
				channel->offset = (effect & 0xff) << (8/*MOD仕様*/ + 8/*固定小数*/);
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xb00: /* Position Jump */
				driver->seqno = effect & 0xff;
				driver->rowno = 0;
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xd00: /* Pattern Break */
				driver->seqno++;
				driver->rowno = effect & 0xff;
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xf00: /* Set Speed */
				if((effect & 0xff) <= 31) {
					driver->speed = effect & 0xff;
				} else {
					/* BPM(テンポ)設定は未対応です。 */
					/**/#ifdef WIN32
					/**/fprintf(stderr, "IGN-BPM\n");
					/**/#endif /*WIN32*/
				}
				channel->effect = 0; /* エフェクト解除 */
				break;
			/*{{未対応エフェクト*/
			//case 0x000: /* Arpeggio */
			//case 0x300: /* Tone Portamento */
			//case 0x400: /* Vibrato */
			//case 0x700: /* Tremolo */
			//case 0x800: /* Set Panning Position */
			/**/#ifdef WIN32
			/**/default:
			/**/	fprintf(stderr, "IGN-EFF: %03x\n", effect);
			/**/	break;
			/**/#endif /*WIN32*/
			/*}}未対応エフェクト*/
			case 0xe00: /* Extended Effects */
				switch(effect & 0xf0) {
				/*---------- 音程に関する拡張エフェクト ----------*/
				case 0x10: /* Fine Slide Up */
					channel->period -= effect & 0xf;
					if(channel->period < 0) channel->period = 0;
					channel->effect = 0; /* エフェクト解除 */
					break;
				case 0x20: /* Fine Slide Down */
					channel->period += effect & 0xf;
					if(channel->period > 0xfff) channel->period = 0xfff;
					channel->effect = 0; /* エフェクト解除 */
					break;
				/*---------- 音量に関する拡張エフェクト ----------*/
				case 0xa0: /* Fine Volume Slide Up */
					channel->volume += effect & 0xf;
					if(channel->volume > 64) channel->volume = 64;
					channel->effect = 0; /* エフェクト解除 */
					break;
				case 0xb0: /* Fine Volume Slide Down */
					channel->volume -= effect & 0xf;
					if(channel->volume < 0) channel->volume = 0;
					channel->effect = 0; /* エフェクト解除 */
					break;
				/*{{未対応拡張エフェクト*/
				//case 0x00: /* Set Filter */
				//case 0x30: /* Glissando Control */
				//case 0x40: /* Set Vibrato Waveform */
				//case 0x50: /* Set Fine Tune */
				//case 0x60: /* Set/Jump to Loop */
				//case 0x70: /* Set Tremolo Waveform */
				//case 0x80: /* NOT USED */
				//case 0x90: /* Retrig Note */
				//case 0xc0: /* Note Cut */
				//case 0xd0: /* Note Delay */
				//case 0xe0: /* Pattern Delay */
				//case 0xf0: /* Invert Loop */
				/**/#ifdef WIN32
				/**/default:
				/**/	fprintf(stderr, "IGN-EFF: %03x\n", effect);
				/**/	break;
				/**/#endif /*WIN32*/
				/*}}未対応拡張エフェクト*/
				}
				break;
			}
		}
	}

	return 0;
}

static int
mod_mix(MODDRIVER* driver, short wbuff[MODBUFLEN])
{
	MODHEADER* header = driver->header;
	int i, chno, period, length, repofs, replen, volume, offset, step;
	short* dst;
	char* smpdat;
	MODSAMPLE* sample;
	MODCHANNEL* channel;

	/* まずミキシングバッファをクリアします。 */
	//memset(wbuff, 0, sizeof wbuff); ←こうしないよう注意！
	memset(wbuff, 0, sizeof(short) * MODBUFLEN);

	/* 発生中の各チャネルについて... */
	for(chno = 0, channel = driver->channel; chno < 4; chno++, channel++) {
		if(!(period = channel->period)) continue;

		/* サンプル情報などを取り出しておきます。 */
		sample = &header->sample[channel->smpno];
		smpdat = driver->smpdat[channel->smpno];
		length = SWAPH(sample->length) * 2 << 8; /* 24.8bit */
		repofs = SWAPH(sample->repofs) * 2 << 8; /* 24.8bit */
		replen = SWAPH(sample->replen) * 2 << 8; /* 24.8bit */
		volume = channel->volume;
		offset = channel->offset;

		/* ピリオド値からサンプルオフセット増分を求めます。
		 * * 定数の由来：
		 *	AmigaのPAL基本クロック(?) = 7093789.2[Hz] ⇒ 二分周してサウンド回路に供給？
		 *	P/ECEのサウンド出力周波数 =   16000  [Hz]
		 * * サンプルオフセット増分：
		 *	再生レート    = 7093789.2 / (period * 2)
		 *	増分[24.8bit] = (再生レート / 16000) * 256
		 *	              = ((7093789.2 / (period * 2)) / 16000) * 256
		 *	              = (((7093789.2 / 2) / period) / 16000) * 256
		 *	              = ((7093789.2 / (2 * 16000)) / period) * 256
		 *	              = ((7093789.2 * 256) / (2 * 16000)) / period
		 *	              = 56750.3136 / period
		 */
		step = 56750 / period;

		for(i = 0, dst = wbuff; i < MODBUFLEN; i++, dst++) {
LOOP:
			/* サンプルオフセットをサンプルデータ内にクリピングします。 */
			if(!channel->loop) { /* 一周目 */
				if(offset >= length) {
					if(!replen) { /* ループなし */
						channel->period = 0; /* 発声停止 */
						goto NO_LOOP;
					}
					offset -= length - repofs;
					channel->loop = 1;
					goto LOOP;
				}
			} else { /* 二周目以降 */
				if(offset >= repofs + replen) {
					offset -= replen;
					goto LOOP;
				}
			}

			/* ミキシングバッファにサンプル値×ボリュームを加算します。 */
			*dst += smpdat[offset >> 8] * volume;

			/* サンプルオフセットを進めます。 */
			offset += step;
		}
NO_LOOP:
		/* サンプルオフセットを書き戻します。 */
		channel->offset = offset;
	}

	/* * 4チャネル分を合成した振幅は、ちょうど符号付き16ビットの振幅に一致します。
	 *	±128(最大サンプル値) × 64(最大ボリューム) × 4(チャネル数) ＝ ±32768
	 *   音量増幅の必要はありませんが、2倍(左1bitシフト)までなら増幅してもいい感じです。
	 *   処理速度との兼ね合いで、お好みによって、どうぞ。
	 * * シフト数を変数にして実行時に変更できるようにするのは、やめた方がいいです。
	 *   S1C33 CPU のシフト命令は1命令で8ビットまでしかシフトできないので、
	 *   シフト数を変数にすると、xslaになって、8命令(!)に展開されてしまいます。
	 */
	for(i = 0, dst = wbuff; i < MODBUFLEN; i++, dst++) {
		volume = *dst << 1/*調整可*/;
		if(volume < -32767) {
			volume = -32767;
		} else if(volume > 32767) {
			volume = 32767;
		}
		*dst = volume;
	}

	return 0;
}

/*****************************************************************************
 *	P/ECE MOD Stream （このブロックのコードは、P/ECE専用です）
 *****************************************************************************/

static int mod_stream_callback(short* wbuff, int param);

int
mod_play(const void* data)
{
	static MODDRIVER driver; /* ＳＴＡＴＩＣです！ */

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

	/* MODドライバを初期化します。 */
	if(mod_init(&driver, data) != 0) return -1;

	/* ストリーム再生を開始します。 */
	stream_play(MODBUFLEN, mod_stream_callback, (int)&driver);

	return 0;
}

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

	/* MODドライバのクリーンアップは不要です。 */
}

static int
mod_stream_callback(short* wbuff, int param)
{
	return mod_tick((MODDRIVER*)param, wbuff);
}
