/*
 *	cliptaps.h
 *
 *	TAPシーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Sun Sep 13 20:50:05 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Mon Nov 16 21:09:12 JST 2015 Naoyuki Sawa
 *	- ST_TapSeqInfo.nEvt,iEvtMsk、及び、TapSeq_EvtGet(),TapSeq_EvtOvr()を追加しました。
 *	  割り込み内で発生する可能性の有る各種イベントを、割り込み外で取得して処理するための仕組みです。
 *	  リングバッファの実装は、cliprinb.cモジュールに準じています。
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 'extern "C" {'〜'}'で囲みました。.cppを含むWin32プロジェクトからもインクルード出来るようにするためです。
 *	* Mon Jan 11 21:31:05 JST 2016 Naoyuki Sawa
 *	- 完全フレーズの自然停止処理のバグを修正しました。
 *	  通常フレーズと同様に自然停止すべきところ、中間フレーズと同様に次のフレーズを再生していました。
 *	  TapLogCh_Exec()の処理で(-2)を間違って次のフレーズ番号と見なしてしまっていたバグです。
 *	* Tue Feb 16 21:10:19 JST 2016 Naoyuki Sawa
 *	- TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
 *	  変更前は、論理チャネル番号が大きいほど優先度が低く、デバイスチャネルが横取りされ易いとしていました。
 *	  変更後は、論理チャネル番号が小さいほど優先度が低く、デバイスチャネルが横取りされ易いようにしました。
 *	  変更した理由は、プログラムの作成途中で論理チャネルの設計を変更した時に、後から追加した論理チャネル(=論理チャネル番号が大きい)ほど、通常の演出音を横取りして再生したい(=優先度が高い)ケースが多いからです。
 *	- TapSeq_AcqLogCh()にて、停止中のデバイスチャネルを見つけたら、残りのTAPセットを走査せずに、即、デバイスチャネルの獲得処理へ移行するように変更しました。
 *	  これまでは、停止中のデバイスチャネルを見つけても、残りのTAPセットの走査を継続しており、無駄でした。
 *	* Tue Aug 02 21:11:08 JST 2016 Naoyuki Sawa
 *	- 自然遷移通知を追加しました。
 *	  これまでは、最初のフレーズを再生開始する時も、次のフレーズへ移行する時も、共通で、再生開始通知(fnPlay)を呼び出していました。
 *	  今後は、最初のフレーズを再生開始する時は再生開始通知(fnPlay)、次のフレーズへ移行する時は自然遷移通知(fnNext)を呼び出すようにしました。
 *	- 変更した理由は、デバイスがシームレス連続再生機能を持っている場合(ADX2等)に、次のフレーズへ移行する時は明示的な再生開始処理を行わないようにする事も出来るように、アプリケーションが両者を区別出来るようにするためです。
 *	  デバイスのシームレス連続再生機能を利用する場合は、アプリケーション定義の(*fnNext)()関数では、何も行わないようにして下さい。
 *	  また、デバイスに設定するシームレス連続再生のプレイバックリストと、当モジュールで管理するフレーズチェインを、同じ順序で作成して下さい。
 *	  デバイスが実際に再生しているフレーズと、当モジュールが認識している'現在再生中のフレーズ'を一致させるためです。
 *	- デバイスがシームレス連続再生機能を持っていない場合(P/ECEやDxLib等)は、これまで通り、自然遷移時にも明示的な再生処理を行って下さい。
 *	  具体的には、アプリケーション定義の(*fnNext)()関数にて(*fnPlay)()と同じ処理を行うか、又は、同じ関数ポインタを設定して下さい。
 *	* Fri Aug 19 21:29:24 JST 2016 Naoyuki Sawa
 *	- ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。
 *	  フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
 *	- 上記のフィールドを廃止した理由は、要素数[0]のフィールドに対応していないコンパイラと、コードを共通化するためです。
 *	  明示的にアドレスを求める処理がさほど複雑な訳でも無いので、#ifdefで条件コンパイルするよりもコードを共通化する方が判り易いと思いました。
 *	* Thu Oct 13 21:52:21 JST 2016 Naoyuki Sawa
 *	- fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
 *	  稀ではあると思いますが、TAPシーケンサの複数インスタンスを作成し、コールバック関数を共有した場合に、どのインスタンスから呼び出されたかを判断するためです。
 *	  使用例は、clipxmp1.cモジュールを参照して下さい。
 */
#ifndef __CLIP_TAPS_H__
#define __CLIP_TAPS_H__
#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus
/*****************************************************************************
 *	定数、構造体
 *****************************************************************************/
//デバイスチャネル
typedef struct _ST_TapDevCh {
	uint8_t				iLogCh;					//+ 0,  1 このデバイスチャネルを制御している論理チャネル番号	UINT8_MAX=無し(停止中を意味する)
} ST_TapDevCh;									//= 1
/*---------------------------------------------------------------------------*/
//論理チャネル
typedef struct _ST_TapLogCh {
	uint16_t			iPhr;					//+ 0,  2 フレーズ番号						UINT16_MAX=無し(停止中を意味する)
	uint8_t				iLoop;					//+ 2,  1 ループ回数						0=無限ループ,1〜UINT8_MAX=残りループ回数
	uint8_t				iAtt;					//+ 3,  1 減衰量						0=減衰無し,1〜UINT8_MAX=減衰有り(アプリケーションが任意に解釈する)
	 int8_t				iPan;					//+ 4,  1 パン							0=パン無し,INT8_MIN〜-1=左パン(アプリケーションが任意に解釈する),1〜INT8_MAX=右パン(アプリケーションが任意に解釈する)
	uint8_t				iDevCh;					//+ 5,  1 この論理チャネルが制御しているデバイスチャネル番号	UINT8_MAX=無し(停止中,又は,再生開始時にデバイスチャネルを獲得出来なかった,又は,再生開始後にデバイスチャネルを横取りされた事を意味する)
	uint8_t				iSeqCh;					//+ 6,  1 この論理チャネルを制御しているシーケンサチャネル番号	UINT8_MAX=無し(シーケンサチャネルに制御されていない事を意味する。停止中を意味するのではない事に注意せよ)
	uint8_t				iCont;					//+ 7,  1 ループフレーズ引き継ぎ時間[ms]
	 int32_t			iWait;					//+ 8,  4 待ち時間[ms]
} ST_TapLogCh;									//=12
/*---------------------------------------------------------------------------*/
//シーケンサチャネル
typedef struct _ST_TapSeqCh {
	const uint8_t*			pData;					//+ 0,  4 シーケンスデータ					NULL=無し(停止中を意味する)
	 int32_t			iWait;					//+ 4,  4 待ち時間[ms]
} ST_TapSeqCh;									//= 8
/*---------------------------------------------------------------------------*/
//イベントバッファ
#define TapEvt_Play			0					//再生開始通知
#define TapEvt_Stop			1					//再生停止通知
#define TapEvt_Loop			2					//自然ループ通知
#define TapEvt_End			3					//自然停止通知	
#define TapEvt_Next			4					//自然遷移通知							//{{2016/08/02追加:自然遷移通知を追加しました。}}
#define TapEvt_Ctrl			5					//コントロール通知
typedef struct _ST_TapEvt {
	uint8_t				iEvt;					//+ 0, 1							TapEvt_Play,TapEvt_Stop,TapEvt_Loop,TapEvt_End,TapEvt_Next,TapEvt_Ctrl																																		//{{2016/08/02コメント変更:自然遷移通知を追加しました。}}
	uint8_t				iLogCh;					//+ 1, 1							0〜(nLogCh-1)
	uint16_t			iPhr;					//+ 2, 2							0〜(nPhr-1)
} ST_TapEvt;									//= 4
typedef struct _ST_TapEvtBuf {
	uint8_t				iAddPos;				//+ 0, 1 要素を書き込む位置					0〜nEvt
	uint8_t				iGetPos;				//+ 1, 1 要素を読み出す位置					0〜nEvt
	uint16_t			nOvrCnt;				//+ 2, 2 オーバーランが発生した回数				0〜UINT16_MAX	オーバーランが発生した場合、既にバッファ内に有る要素は維持し、書き込もうとした要素が失われる。
//{{2016/08/19削除:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	ST_TapEvt			TBL_Buf[0/*(nEvt+1)*/];			//+ 4,(4*(nEvt+1))
//}}2016/08/19削除:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
} ST_TapEvtBuf;									//= 4+(4*(nEvt+1))
/*---------------------------------------------------------------------------*/
//シーケンサ
typedef struct _ST_TapSeq {
	const struct _ST_TapSeqInfo*	pInfo;					//+ 0,  4 初期化情報
    //	ST_TapDevCh			TBL_DevCh[(nDevCh+3)&~3];		//+ 4, 1N デバイスチャネル
    //	ST_TapLogCh			TBL_LogCh[ nLogCh      ];		//+ ?,12N 論理チャネル
    //	ST_TapSeqCh			TBL_SeqCh[ nSeqCh      ];		//+ ?, 8N シーケンサチャネル
    //	ST_TapEvtBuf			    EvtBuf;				//+ ?,  * イベントバッファ					(nEvt=0)ならば存在しない。
    //	ST_TapEvt			TBL_Buf[(nEvt+1)];			//+ ?,  * イベント						(nEvt=0)ならば存在しない。
} ST_TapSeq;									//= ?
/*---------------------------------------------------------------------------*/
//初期化情報
typedef struct _ST_TapSeqInfo {
	uint16_t			nPhr;					//+ 0,  2 フレーズ数						1〜UINT16_MAX	このフィールドはエラー検出のためだけに使用している。エラー検出が不要ならば常にUINT16_MAXを設定して構わない。
	uint8_t				nDevCh;					//+ 2,  1 デバイスチャネル数					1〜UINT8_MAX
	uint8_t				nLogCh;					//+ 3,  1 論理チャネル数					1〜UINT8_MAX
	uint8_t				nSeqCh;					//+ 4,  1 シーケンサチャネル数					1〜UINT8_MAX
	uint8_t				iCont;					//+ 5,  1 ループフレーズ引き継ぎ時間[ms]			0〜UINT8_MAX	0ならばループフレーズ引き継ぎ処理を行わない。
	uint8_t				nEvt;					//+ 6,  1 イベント記録数					0〜UINT8_MAX	0ならばイベントを記録しない。			(ST_RinBuf.iMaxPos)に相当する値です。従って実際の配列要素数は(nEvt+1)確保する。
	uint8_t				iEvtMsk;				//+ 7,  1 イベントマスク					1<<TapEvt_Play,1<<TapEvt_Stop,1<<TapEvt_Loop,1<<TapEvt_End,1<<TapEvt_Next,1<<TapEvt_Ctrlの組み合わせ。ビットがセットされているイベントの種類だけを記録します。一般的には(~(1<<TapEvt_Ctrl))を指定するのが良いだろう。TapEvt_Ctrlは頻度が高すぎてバッファが溢れる恐れが有るし、TapEvt_Ctrlに対して割り込み外で行う処理も無いだろうから。		//{{2016/08/02コメント変更:自然遷移通知を追加しました。}}
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	void (*fnPlay)(int iLogCh, ST_TapLogCh* pLogCh);			//+ 8,  4 再生開始通知
//	void (*fnStop)(int iLogCh, ST_TapLogCh* pLogCh);			//+12,  4 再生停止通知
//	void (*fnLoop)(int iLogCh, ST_TapLogCh* pLogCh);			//+16,  4 自然ループ通知					fnPlayと同じコールバック関数を設定しても良い。
//	void (*fnEnd )(int iLogCh, ST_TapLogCh* pLogCh);			//+20,  4 自然停止通知						fnStopと同じコールバック関数を設定しても良い。
//	void (*fnNext)(int iLogCh, ST_TapLogCh* pLogCh);			//+24,  4 自然遷移通知						fnPlayと同じコールバック関数を設定しても良い。			//{{2016/08/02追加:自然遷移通知を追加しました。}}
//	void (*fnCtrl)(int iLogCh, ST_TapLogCh* pLogCh);			//+28,  4 コントロール通知
//	int  (*fnGetPhrTime)(int iPhr);						//+32,  4 フレーズ時間[ms]を取得する。				(0〜INT_MAX)を返せ。
//	int  (*fnGetPhrNext)(int iPhr);						//+36,  4 フレーズチェインの次のフレーズ番号を取得する。	通常フレーズならば-1を返せ。完全フレーズならば-2を返せ。中間フレーズならば0〜(nPhr-1),かつ,iPhr以外の値を返せ。ループフレーズならばiPhrを返せ。
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	void (*fnPlay)(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+ 8,  4 再生開始通知
	void (*fnStop)(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+12,  4 再生停止通知
	void (*fnLoop)(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+16,  4 自然ループ通知					fnPlayと同じコールバック関数を設定しても良い。
	void (*fnEnd )(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+20,  4 自然停止通知						fnStopと同じコールバック関数を設定しても良い。
	void (*fnNext)(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+24,  4 自然遷移通知						fnPlayと同じコールバック関数を設定しても良い。			//{{2016/08/02追加:自然遷移通知を追加しました。}}
	void (*fnCtrl)(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);	//+28,  4 コントロール通知
	int  (*fnGetPhrTime)(ST_TapSeq* pTapSeq, int iPhr);			//+32,  4 フレーズ時間[ms]を取得する。				(0〜INT_MAX)を返せ。
	int  (*fnGetPhrNext)(ST_TapSeq* pTapSeq, int iPhr);			//+36,  4 フレーズチェインの次のフレーズ番号を取得する。	通常フレーズならば-1を返せ。完全フレーズならば-2を返せ。中間フレーズならば0〜(nPhr-1),かつ,iPhr以外の値を返せ。ループフレーズならばiPhrを返せ。
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//{{2016/08/19削除:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	const uint8_t*		TBL_TapSet[0/*nLogCh*/];			//+40, 4N TAPセット
//}}2016/08/19削除:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
} ST_TapSeqInfo;								//= ?
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//■重要
//│アプリケーション定義のコールバック関数の中から、当モジュールの関数を呼び出してはいけない。
//│当モジュールの実装は、コールバック関数の呼び出し中に、当モジュールの内部状態が変化しない事を前提としている。
//│もし、コールバック関数の中から当モジュールの関数を呼び出すと、内部状態が変化して予測できない動作結果となる。
/*****************************************************************************
 *	グローバル関数宣言
 *****************************************************************************/
//シーケンサ
ST_TapSeq* TapSeq_New(const ST_TapSeqInfo* pInfo);
void TapSeq_Init(ST_TapSeq* pTapSeq, const ST_TapSeqInfo* pInfo);
void TapSeq_Free(ST_TapSeq* pTapSeq);
void TapSeq_Exec(ST_TapSeq* pTapSeq, int iElapTime);
//論理チャネル
void TapLogCh_Play(ST_TapSeq* pTapSeq, int iLogCh, int iPhr, int iLoop, int iAtt, int iPan);
void TapLogCh_Stop(ST_TapSeq* pTapSeq, int iLogCh);
void TapLogCh_Ctrl(ST_TapSeq* pTapSeq, int iLogCh, int iAtt, int iPan);
//シーケンサチャネル
void TapSeqCh_Play(ST_TapSeq* pTapSeq, int iSeqCh, const void* pData);
void TapSeqCh_Stop(ST_TapSeq* pTapSeq, int iSeqCh);
//情報
ST_TapDevCh* TapSeq_GetDevCh(ST_TapSeq* pTapSeq, int iDevCh);
ST_TapLogCh* TapSeq_GetLogCh(ST_TapSeq* pTapSeq, int iLogCh);
ST_TapSeqCh* TapSeq_GetSeqCh(ST_TapSeq* pTapSeq, int iSeqCh);
//イベント
ST_TapEvt* TapSeq_EvtGet(ST_TapSeq* pTapSeq);	//(nEvt=0)を指定した場合は呼び出してはいけない。
int TapSeq_EvtOvr(ST_TapSeq* pTapSeq);		//(nEvt=0)を指定した場合は呼び出してはいけない。
/****************************************************************************
 *	dTapSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#define USE_TAPDEF_BIN	//ユーティリティ関数が不要ならばこのシンボル定義をコメントアウトしてください。
#ifdef  USE_TAPDEF_BIN
void TapSeqCh_PlayNo(ST_TapSeq* pTapSeq, int iTapSeqCh, const void* pTapDefBin, int iTapSeqNo);
#endif//USE_TAPDEF_BIN
#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus
#endif//__CLIP_TAPS_H__
