/*	
 *	clipxm.h
 *
 *	P/ECE XM Driver
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Tue Nov 9 19:00:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 'extern "C" {'〜'}'で囲みました。.cppを含むWin32プロジェクトからもインクルード出来るようにするためです。
 */
#ifndef __CLIP_XM_H__
#define __CLIP_XM_H__

#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus

/****************************************************************************
 *	XMファイル仕様
 ****************************************************************************/

/* バイトオーダーはリトルエンディアンです。 */

/* General header */
typedef struct _XMGENERALHEADER {
	unsigned char id_text[17];				/* +  0, 17: ID text: 'Extended Module: ' */
								/*           (※資料には'Extended module: 'と書いてありますが間違い。'm'ではなく'M'が正解のようです) */
	unsigned char module_name[20];				/* + 17, 20: Module name, padded with spaces */
	unsigned char _1a[1];					/* + 37,  1: $1a */
	unsigned char tracker_name[20];				/* + 38, 20: Tracker name */
	unsigned char version_number[2];			/* + 58,  2: Version number, hi-byte major and low-byte minor */
								/*           The current format is version $0103 */
								/*	     (※手元のファイルでは$0104でした。$0103未満の場合だけをエラー扱いとします) */
	unsigned char header_size[4];				/* + 60,  4: Header size */
								/*           (※Header sizeから末尾までのサイズ。Header size自身も含みます。通常は276(=336-60)です) */
	unsigned char song_length[2];				/* + 64,  2: Song length (in pattern order table) */
	unsigned char restart_position[2];			/* + 66,  2: Restart position */
	unsigned char number_of_channels[2];			/* + 68,  2: Number of channels (2,4,6,8,10,...,32) */
	unsigned char number_of_patterns[2];			/* + 70,  2: Number of patterns (max 256) */
	unsigned char number_of_instruments[2];			/* + 72,  2: Number of instruments (max 128) */
	unsigned char flags[2];					/* + 74,  2: Flags: bit 0: 0 = Amiga frequency table, 1 = Linear frequency table */
	unsigned char default_tempo[2];				/* + 76,  2: Default tempo */
								/*           (※用語が混乱している。MODのSpeedに相当。1[Row]当りのフレーム数。小さいほど早い。1-31) */
	unsigned char default_bpm[2];				/* + 78,  2: Default bpm */
								/*           (※用語が混乱している。MODのTempoに相当。1分当りの四分音符数。大きいほど早い。125前後) */
	unsigned char pattern_order_table[256];			/* + 80,256: Pattern order table */
} XMGENERALHEADER;						/* =336 */

/* Pattern header */
typedef struct _XMPATTERNHEADER {
	unsigned char pattern_header_length[4];			/* +  0,  4: Pattern header length */
	unsigned char packing_type[1];				/* +  4,  1: Packing type (always 0) */
								/*           (※一応、手元のファイルでも0ですが、本当に常に0か不安なので、検査しないことにしました) */
	unsigned char number_of_rows_in_pattern[2];		/* +  5,  2: Number of rows in pattern (1..256) */
	unsigned char packed_pattern_data_size[2];		/* +  7,  2: Packed pattern data size */
} XMPATTERNHEADER;						/* =  9 */

/* Instrument header */
typedef struct _XMINSTRUMENTHEADER {
	unsigned char instrument_size[4];			/* +  0,  4: Instrument size */
								/*           (※手元のファイルでは263でした。必ずしも29または252ではないようです) */
	unsigned char instrument_name[22];			/* +  4, 22: Instrument name */
	unsigned char instrument_type[1];			/* + 26,  1: Instrument type (always 0) */
								/*           (※手元のファイルでは115でした。検査しないほうが良さそうです) */
	unsigned char number_of_samples_in_instrument[2];	/* + 27,  2: Number of samples in instrument */
								/*           If the number of samples > 0, then the this will follow: */
	unsigned char sample_header_size[4];			/* + 29,  4: Sample header size */
	unsigned char sample_number_for_all_notes[96];		/* + 33, 96: Sample number for all notes */
	unsigned char points_for_volume_envelope[48];		/* +129, 48: Points for volume envelope */
	unsigned char points_for_panning_envelope[48];		/* +177, 48: Points for panning envelope */
								/*           Envelope points: x,y...x,y.... in couples (2 words/point => a maximum of 12 points). */
	unsigned char number_of_volume_points[1];		/* +225,  1: Number of volume points */
	unsigned char number_of_panning_points[1];		/* +226,  1: Number of panning points */
	unsigned char volume_sustain_point[1];			/* +227,  1: Volume sustain point */
								/*           (※Volume type On = 1 かつ Sustain = 1 の場合に有効です。                                                ) */
								/*	     (  NoteOn中にVolume sustain pointに達したら、Volume sustain pointへ移動します。                          ) */
								/*           (  Volume type On = 1 かつ Sustain = 0 の場合は、最後のpointにsustain pointが設定されているのと同じです。) */
	unsigned char volume_loop_start_point[1];		/* +228,  1: Volume loop start point */
	unsigned char volume_loop_end_point[1];			/* +229,  1: Volume loop end point */
								/*           (※Volume type On = 1 かつ Loop = 1 の場合に有効です。                                                       ) */
								/*           (  NoteOn/NoteOffにかかわらず、Volume loop end pointに達したら、Volume loop start pointへ移動します。        ) */
								/*           (  Volume type On = 1 かつ Loop = 0 の場合は、最後のpointにloop start/end pointが設定されているのと同じです。) */
	unsigned char panning_sustain_point[1];			/* +230,  1: Panning sustain point */
	unsigned char panning_loop_start_point[1];		/* +231,  1: Panning loop start point */
	unsigned char panning_loop_end_point[1];		/* +232,  1: Panning loop end point */
	unsigned char volume_type[1];				/* +233,  1: Volume type: bit 0: On; 1: Sustain; 2: Loop */
	unsigned char panning_type[1];				/* +234,  1: Panning type: bit 0: On; 1: Sustain; 2: Loop */
	unsigned char vibrato_type[1];				/* +235,  1: Vibrato type */
	unsigned char vibrato_sweep[1];				/* +236,  1: Vibrato sweep */
	unsigned char vibrato_depth[1];				/* +237,  1: Vibrato depth */
	unsigned char vibrato_rate[1];				/* +238,  1: Vibrato rate */
	unsigned char volume_fadeout[2];			/* +239,  2: Volume fadeout */
								/*           (※小数部16bitの固定小数で表されるFadeout volumeの、Tick毎の減衰量です。) */
								/*           (  NoteOn中は、Fadeout volume = (1<<16) を維持します。                  ) */
								/*           (  NoteOff中は、Tick毎に Fadeout volume -= Volume fadeout を行います。  ) */
	unsigned char reserved[11];				/* +241, 11: Reserved */
} XMINSTRUMENTHEADER;						/* =252 */

/* Sample header */
typedef struct _XMSAMPLEHEADER {
	unsigned char sample_length[4];				/* +  0,  4: Sample length */
								/*           (※8/16bitいずれの場合もバイト単位です。16bit時サンプル数は半分ということになります) */
	unsigned char sample_loop_start[4];			/* +  4,  4: Sample loop start */
								/*           (※8/16bitいずれの場合もバイト単位です。16bit時サンプル数は半分ということになります) */
	unsigned char sample_loop_length[4];			/* +  8,  4: Sample loop length */
								/*           Note: If the sample loop length is 0, the sample is *NOT* a looping one, */
								/*                 even if the "Forward loop" bit is set in the "Type" field. */
								/*           (※8/16bitいずれの場合もバイト単位です。16bit時サンプル数は半分ということになります) */
	unsigned char volume[1];				/* + 12,  1: Volume */
	unsigned char finetune[1];				/* + 13,  1: Finetune (signed byte -128..+127) */
	unsigned char type[1];					/* + 14,  1: Type: bit 0-1: 0 = No loop, 1 = Forward loop, 2 = Ping-pong loop; bit 4: 16-bit sample data */
	unsigned char panning[1];				/* + 15,  1: Panning (0-255) */
	unsigned char relative_note_number[1];			/* + 16,  1: Relative note number (signed byte) */
	unsigned char reserved[1];				/* + 17,  1: Reserved */
	unsigned char sample_name[22];				/* + 18, 22: Sample name */
} XMSAMPLEHEADER;						/* = 40 */

/****************************************************************************
 *	P/ECE XM Driver
 ****************************************************************************/

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

/* * PeriodとFrequencyについて
 *
 * -  XM仕様では、発声の音程を決めるためのFrequencyは、次のように定められています。
 *
 *		Period = 10*12*16*4 - Note*16*4 - Finetune/2
 *		Frequency = 8363 * 2^((6*12*16*4 - Period) / (12*16*4)) ★浮動小数演算です★
 *
 * - Finetuneは単に、Noteの1/256単位(厳密には1/255)の調整なので、あとで考えることにして、式から取り外します。
 *
 *		Period = 10*12*16*4 - Note*16*4
 *		Frequency = 8363 * 2^((6*12*16*4 - Period) / (12*16*4))
 *		          = 8363 * 2^((6*12*16*4 - (10*12*16*4 - Note*16*4)) / (float)(12*16*4))
 *		          = 8363 * 2^((6*12 - (10*12 - Note)) / 12)
 *		          = 8363 * 2^((Note - 48) / 12)
 *		          = 8363 * 2^(Note/12 - 4)
 *		          = 8363 * 2^(Note/12) * 2^(-4)
 *		          = 8363/16 * 2^(Note/12)
 *
 * - さて、Porta up/down effectはNoteの1/256単位でかかるので、Periodの変化を行いやすいように、
 *
 *		Period = Note << 8
 *
 *   と定めることにします。(厳密には1/255ですが簡単のために1/256とします)
 *   XM仕様では、Periodが大きくなると音が低くなり、Periodが小さくなると音が高くなるのですが、
 *   P/ECE XM Driver方式のPeriodは、式を変形しているため、±方向が逆であることに注意してください。
 *
 * - また、FinetuneはNoteの1/128の効果を持ちます。
 *   Finetuneを含めて、PeriodからFrequencyを求める方法は、次のようになります。
 *
 *		Frequency = 8363/16 * 2^((Period + Finetune*2)/(12*256))
 *
 * - 浮動小数点のべき乗計算はP/ECEには重過ぎるので、整数乗除算とテーブル参照に変形します。
 *
 *		Frequency = xm_linear_frequency_table[(Period + Finetune*2) % (12 * 256)]
 *						   << (Period + Finetune*2) / (12 * 256));
 *
 *   xm_linear_frequency_table[]は、8363/16*2^0.0〜8363/16*2^1.0 を、(12*256)ステップで計算したテーブルです。
 *
 * - 以上、詳細はxm_run()とxm_mix()の実装を参照してください。
 */

/* Note */
typedef struct _XMNOTE {
	int note;						/* Note (1-96, 1 = C-0) */
								/* (※0=No note, 1..96=C-0..B-7, 97=NoteOff, 98-=未使用(稀にあるみたいですが...) ) */
								/* (  ModPlugTrackerはオクターブ表記が一つ大きいので、1..96=C-1..B-8に相当します。) */
								/* (  ModPlugTracker上でB#0以下やC-9以上を入力すると、No noteでセーブされます。   ) */
	int instrument;						/* Instrument (1-128) */
	int volume_column_byte;					/* Volume column byte */
	int effect_type;					/* Effect type */
	int effect_parameter;					/* Effect parameter */
} XMNOTE;

/* Pattern */
typedef struct _XMPATTERN {
	int number_of_rows_in_pattern;
	const unsigned char* packed_pattern_data;		/* Direct */
} XMPATTERN;

/* Sample */
typedef struct _XMSAMPLE {
	const unsigned char* sample_data;			/* Direct, No loop, Forward loop, Ping-pong loop */
	const unsigned char* sample_end;			/* Direct, No loop                               */
	const unsigned char* sample_loop_start;			/* Direct,          Forward loop, Ping-pong loop */
	const unsigned char* sample_loop_end;			/* Direct,          Forward loop, Ping-pong loop */
	int sample_loop_output;					/*                  Forward loop                 */
	int volume;
	int finetune;
	int type;
	int relative_note_number;
} XMSAMPLE;

/* Envelope point */
typedef struct XMENVELOPEPOINT {
	unsigned short x;					/* Tick */
	unsigned short y;					/* Volume */
} XMENVELOPEPOINT;

/* Instrument */
typedef struct _XMINSTRUMENT {
	int number_of_samples_in_instrument;
	XMSAMPLE* samples/*[number_of_samples_in_instrument]*/;				/* Allocate */
	const unsigned char*  sample_number_for_all_notes/*[96]*/;			/* Direct */
	XMENVELOPEPOINT* points_for_volume_envelope/*[number_of_volume_points]*/;	/* Allocate, Volume type On */
	XMENVELOPEPOINT* volume_sustain_point;						/*           Volume type On, Sustain */
	XMENVELOPEPOINT* volume_loop_start_point;					/*           Volume type On, Loop */
	XMENVELOPEPOINT* volume_loop_end_point;						/*           Volume type On, Loop */
	int volume_fadeout;
} XMINSTRUMENT;

/* Channel */
typedef struct _XMCHANNEL {
/*0*/	int stat;						/* 0 = Silent, 1 = NoteOn, 2 = NoteOff */
/*1*/	XMINSTRUMENT* pending_instrument;			/* 設定されているInstrument */
/*2*/	XMINSTRUMENT* playing_instrument;			/* 発声中のInstrument */
/*3*/	XMSAMPLE* playing_sample;				/* 発声中のSample */
/*4*/	const unsigned char* sample_position;			/* Sample data走査位置 */
/*5*/	int sample_direction;					/* Sample data走査方向: 0 = 前進, 1 = 後退(Ping-pong loop) */
/*6*/	int sample_output;					/* Sample dataの累積値 (Wraparoundする変な仕様なので、D31:16は不定!!) */
/*7*/	int volume;						/* Sampleの規定値とEffectで変化します (0〜64) */
/*8*/	int period;						/* XM仕様とはPeriod,Frequencyの計算方法を変えていますが、結果のFrequencyは同じになります */
/*9*/	int finetune;						/* Sampleの規定値とEffectで変化します (±128で半音上下) */
/*A*/	int progress;						/* Sample data走査用の進行カウンタ */
/*B*/	XMENVELOPEPOINT *volume_envelope_position;		/* Points for volume envelope走査位置 */
/*C*/	int volume_envelope_progress;				/* Points for volume envelope走査用のTickカウンタ */
/*D*/	int envelope_volume;					/* Volume envelopeで変化します (0〜64) */
/*E*/	int fadeout_volume;					/* NoteOff後、Volume fadeoutで変化します (65536→0) */
/*F*/	int volume_column_byte;					/* Volume column byteを保持 */
/*G*/	int effect_type;					/* Effect typeを保持 */
/*H*/	int effect_parameter;					/* Effect parameterを保持 */
/*I*/	int tone_porta;						/* Tone portaの目標Period */
} XMCHANNEL;

/* XMCHANNEL.stat */
#define XMCHANNEL_SILENT	0
#define XMCHANNEL_NOTEON	1
#define XMCHANNEL_NOTEOFF	2

typedef struct _XMDRIVER {
	/***** 初期化後は変更しないフィールド *****/

	/* General header */
	int song_length;
	int restart_position;
	int number_of_channels;
	int number_of_patterns;
	int number_of_instruments;
	int flags;
	const unsigned char* pattern_order_table/*[song_length]*/;	/* Direct */
	/* Patterns */
	XMPATTERN* patterns/*[number_of_patterns]*/;			/* Allocate */
	/* Instruments */
	XMINSTRUMENT* instruments/*[number_of_instruments]*/;		/* Allocate */

	/***** 演奏中に随時変更するフィールド *****/

	XMCHANNEL* channels/*[number_of_channels]*/;	/* Allocate */
	int tempo;					/* Tempo設定値 */
	int bpm;					/* BPM設定値 */
	int row;					/* 現在のRow */
	int order;					/* Pattern order table走査位置(インデクス) */
	XMPATTERN* pattern;				/* 現在のPattern */
	const unsigned char* packed_pattern_position;	/* 現在のPatternのPacked pattern data走査位置 */
	int progress;					/* BPM進行用カウンタ */
	int tick;					/* Tempo進行用カウンタ */

	int stat;					/* 通常は0。演奏停止時に要因を格納。 */
	int global_volume;				/* Global volume設定値 (0〜64) */
	int position_jump;				/* 通常は-1。Position jump時にorderを設定。 */
	int pattern_break;				/* 通常は-1。Pattern break時にrowを設定。 */
} XMDRIVER;

extern const short xm_linear_frequency_table[12 * 256];

int xm_init(XMDRIVER* driver, const void* data);
void xm_free(XMDRIVER* driver);
int xm_run(XMDRIVER* driver);
int xm_mix(XMDRIVER* driver, short wbuff[/*XMBUFLEN*/]);
int xm_stream_callback(short* wbuff, int param);

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

/* XMファイルの再生を開始します。
 * [in]
 *	data		XMデータの先頭アドレス。
 * [out]
 *	戻り値		再生開始に成功したら0を返します。
 *			再生開始に失敗したら0以外の値を返します。
 * [note]
 *	* この関数は内部でstream_play()を呼び出しています。
 *	* stream_play()を呼ぶと、サウンド割り込みベクタがフックされます。
 *	  プログラム終了前に必ずxm_stop()を呼んで、ベクタを復元してください。
 */
int xm_play(const void* data);

/* XMファイルの再生を停止します。
 * [note]
 *	* 再生中でなければ、この関数は何もしません。
 */
void xm_stop();

#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus

#endif /*__CLIP_XM_H__*/
