//
//	cliptaps.cs
//
//	TAPシーケンサ
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Fri Mar 24 23:38:46 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Sun Apr 02 22:50:54 JST 2017 Naoyuki Sawa
//	- 再生準備通知を追加しました。
//	  詳細は、cliptaps.cの同日のコメントを参照して下さい。
//	* Mon Apr 03 23:36:16 JST 2017 Naoyuki Sawa
//	- clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//
#define USE_TAPDEF_BIN
using System;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	定数、構造体
		//*****************************************************************************
		//デバイスチャネル
		public class ST_TapDevCh {
			public byte			iLogCh;					//このデバイスチャネルを制御している論理チャネル番号	UINT8_MAX=無し(停止中を意味する)
		}
		//-----------------------------------------------------------------------------
		//論理チャネル
		public class ST_TapLogCh {
			public ushort			iPhr;					//フレーズ番号						UINT16_MAX=無し(停止中を意味する)
			public byte			iLoop;					//ループ回数						0=無限ループ,1～UINT8_MAX=残りループ回数
			public byte			iAtt;					//減衰量						0=減衰無し,1～UINT8_MAX=減衰有り(アプリケーションが任意に解釈する)
			public sbyte			iPan;					//パン							0=パン無し,INT8_MIN～-1=左パン(アプリケーションが任意に解釈する),1～INT8_MAX=右パン(アプリケーションが任意に解釈する)
			public byte			iDevCh;					//この論理チャネルが制御しているデバイスチャネル番号	UINT8_MAX=無し(停止中,又は,再生開始時にデバイスチャネルを獲得出来なかった,又は,再生開始後にデバイスチャネルを横取りされた事を意味する)
			public byte			iSeqCh;					//この論理チャネルを制御しているシーケンサチャネル番号	UINT8_MAX=無し(シーケンサチャネルに制御されていない事を意味する。停止中を意味するのではない事に注意せよ)
			public byte			iCont;					//ループフレーズ引き継ぎ時間[ms]
			public int			iWait;					//待ち時間[ms]
		}
		//-----------------------------------------------------------------------------
		//シーケンサチャネル
		public class ST_TapSeqCh {
			public BytePtr			pData;					//シーケンスデータ					null=無し(停止中を意味する)
			public int			iWait;					//待ち時間[ms]
		}
		//-----------------------------------------------------------------------------
		//イベントバッファ
		public const int TapEvt_Play		= 0;					//再生開始通知
		public const int TapEvt_Stop		= 1;					//再生停止通知
		public const int TapEvt_Loop		= 2;					//自然ループ通知
		public const int TapEvt_End		= 3;					//自然停止通知	
		public const int TapEvt_Next		= 4;					//自然遷移通知
		public const int TapEvt_Ctrl		= 5;					//コントロール通知
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public struct ST_TapEvt {
			public byte			iEvt;					//							TapEvt_Play,TapEvt_Stop,TapEvt_Loop,TapEvt_End,TapEvt_Next,TapEvt_Ctrl
			public byte			iLogCh;					//							0～(nLogCh-1)
			public ushort			iPhr;					//							0～(nPhr-1)
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		public class ST_TapEvtBuf {
			public byte			iAddPos;				//要素を書き込む位置					0～nEvt
			public byte			iGetPos;				//要素を読み出す位置					0～nEvt
			public ushort			nOvrCnt;				//オーバーランが発生した回数				0～UINT16_MAX	オーバーランが発生した場合、既にバッファ内に有る要素は維持し、書き込もうとした要素が失われる。
			public ST_TapEvt[]		TBL_Buf/*[(nEvt+1)]*/;			//
		}
		//-----------------------------------------------------------------------------
		//シーケンサ
		public class ST_TapSeq {
			public ST_TapSeqInfo		pInfo;					//初期化情報
			public ST_TapDevCh[]		TBL_DevCh/*[nDevCh]*/;			//デバイスチャネル					【C#版特有のコメント】C言語版ではアドレス計算のために4の倍数に切り上げていましたが、C#版ではアドレス計算が不要なので切り上げは行いません。
			public ST_TapLogCh[]		TBL_LogCh/*[nLogCh]*/;			//論理チャネル
			public ST_TapSeqCh[]		TBL_SeqCh/*[nSeqCh]*/;			//シーケンサチャネル
			public ST_TapEvtBuf		EvtBuf;					//イベントバッファ					(nEvt=0)ならば存在しない。
		}
		//-----------------------------------------------------------------------------
		//初期化情報
		public class ST_TapSeqInfo {
			public ushort								nPhr;							//フレーズ数						1～UINT16_MAX	このフィールドはエラー検出のためだけに使用している。エラー検出が不要ならば常にUINT16_MAXを設定して構わない。
			public byte								nDevCh;							//デバイスチャネル数					1～UINT8_MAX
			public /*byte*/int							nLogCh { get { return pTBL_TapSet.Length; } }		//論理チャネル数					1～UINT8_MAX
			public /*byte*/int							nSeqCh { get { return pTBL_BaseCh.Length; } }		//シーケンサチャネル数					1～UINT8_MAX
			public byte								iCont;							//ループフレーズ引き継ぎ時間[ms]			0～UINT8_MAX	0ならばループフレーズ引き継ぎ処理を行わない。
			public ushort								iPrep;							//再生準備時間[ms]					0～UINT16_MAX	0ならば再生準備を通知しない。			//{{2017/04/02追加:再生準備通知を追加しました。}}
			public byte								nEvt;							//イベント記録数					0～UINT8_MAX	0ならばイベントを記録しない。			(ST_RinBuf.iMaxPos)に相当する値です。従って実際の配列要素数は(nEvt+1)確保する。
			public byte								iEvtMsk;						//イベントマスク					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に対して割り込み外で行う処理も無いだろうから。
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnPlay;							//再生開始通知
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnStop;							//再生停止通知
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnLoop;							//自然ループ通知					fnPlayと同じコールバック関数を設定しても良い。
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnEnd;							//自然停止通知						fnStopと同じコールバック関数を設定しても良い。
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnNext;							//自然遷移通知						fnPlayと同じコールバック関数を設定しても良い。
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnCtrl;							//コントロール通知
			public Action<ST_TapSeq/*pTapSeq*/,int/*iLogCh*/,ST_TapLogCh/*pLogCh*/>	fnPrep;							//再生準備通知						(iPrep==0)ならば呼ばれないのでnullで構わない。			//{{2017/04/02追加:再生準備通知を追加しました。}}
			public Func<ST_TapSeq/*pTapSeq*/,int/*iPhr*/,int>			fnGetPhrTime;						//フレーズ時間[ms]を取得する。				(0～INT_MAX)を返せ。
			public Func<ST_TapSeq/*pTapSeq*/,int/*iPhr*/,int>			fnGetPhrNext;						//フレーズチェインの次のフレーズ番号を取得する。	通常フレーズならば-1を返せ。完全フレーズならば-2を返せ。中間フレーズならば0～(nPhr-1),かつ,iPhr以外の値を返せ。ループフレーズならばiPhrを返せ。
			public byte[][]								pTBL_TapSet/*[nLogCh][]*/;				//TAPセットテーブルへのポインタ
			public byte[]								pTBL_BaseCh/*[nSeqCh]*/;				//ベース論理チャネル番号テーブルへのポインタ		TapSeqCh_Exec()において、シーケンスデータから取得した論理チャネル番号に、このテーブルの値を加算します。
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//■重要
		//│アプリケーション定義のコールバック関数の中から、当モジュールの関数を呼び出してはいけない。
		//│当モジュールの実装は、コールバック関数の呼び出し中に、当モジュールの内部状態が変化しない事を前提としている。
		//│もし、コールバック関数の中から当モジュールの関数を呼び出すと、内部状態が変化して予測できない動作結果となる。
		//*****************************************************************************
		//	グローバル関数
		//*****************************************************************************
		//シーケンサを動的に作成する。
		public static ST_TapSeq TapSeq_New(ST_TapSeqInfo pInfo) {
			ST_TapSeq pTapSeq;
			//初期化情報を検査する。
			TapSeqInfo_Verify(pInfo);
			//メモリを確保する。
			pTapSeq = new ST_TapSeq();
			//シーケンサを静的に初期化する。
			TapSeq_Init(pTapSeq, pInfo);
			return pTapSeq;
		}
		//-----------------------------------------------------------------------------
		//シーケンサを静的に初期化する。
		public static void TapSeq_Init(ST_TapSeq pTapSeq, ST_TapSeqInfo pInfo) {
			int iDevCh, iLogCh, iSeqCh;
			//初期化情報を検査する。
			TapSeqInfo_Verify(pInfo);
			//初期化情報を格納する。
			pTapSeq.pInfo = pInfo;
			//デバイスチャネルを初期化する。
			pTapSeq.TBL_DevCh = new ST_TapDevCh[pTapSeq.pInfo.nDevCh];
			for(iDevCh = 0; iDevCh < pTapSeq.pInfo.nDevCh; iDevCh++) {
				ST_TapDevCh pDevCh = new ST_TapDevCh();
				pTapSeq.TBL_DevCh[iDevCh] = pDevCh;
				pDevCh.iLogCh = byte.MaxValue;				//このデバイスチャネルを制御している論理チャネル番号	UINT8_MAX=無し(停止中を意味する)
			}
			//論理チャネルを初期化する。
			pTapSeq.TBL_LogCh = new ST_TapLogCh[pTapSeq.pInfo.nLogCh];
			for(iLogCh = 0; iLogCh < pTapSeq.pInfo.nLogCh; iLogCh++) {
				ST_TapLogCh pLogCh = new ST_TapLogCh();
				pTapSeq.TBL_LogCh[iLogCh] = pLogCh;
				pLogCh.iPhr   = ushort.MaxValue;			//フレーズ番号						UINT16_MAX=無し(停止中を意味する)
				pLogCh.iDevCh = byte.MaxValue;				//この論理チャネルが制御しているデバイスチャネル番号	UINT8_MAX=無し(停止中,又は,再生開始時にデバイスチャネルを獲得出来なかった,又は,再生開始後にデバイスチャネルを横取りされた事を意味する)
				pLogCh.iSeqCh = byte.MaxValue;				//この論理チャネルを制御しているシーケンサチャネル番号	UINT8_MAX=無し(シーケンサチャネルに制御されていない事を意味する。停止中を意味するのではない事に注意せよ)
			}
			//シーケンサチャネルを初期化する。
			pTapSeq.TBL_SeqCh = new ST_TapSeqCh[pTapSeq.pInfo.nSeqCh];
			for(iSeqCh = 0; iSeqCh < pTapSeq.pInfo.nSeqCh; iSeqCh++) {
				ST_TapSeqCh pSeqCh = new ST_TapSeqCh();
				pTapSeq.TBL_SeqCh[iSeqCh] = pSeqCh;
			//不要	pSeqCh.pData = null;					//シーケンスデータ					null=無し(停止中を意味する)
			}
			//イベントバッファを初期化する。
			if(pInfo.nEvt != 0) {
				ST_TapEvtBuf pEvtBuf = new ST_TapEvtBuf();
				pTapSeq.EvtBuf = pEvtBuf;
				pEvtBuf.iAddPos = 0;					//要素を書き込む位置
				pEvtBuf.iGetPos = 0;					//要素を読み出す位置
				pEvtBuf.nOvrCnt = 0;					//オーバーランが発生した回数
				pEvtBuf.TBL_Buf = new ST_TapEvt[(pInfo.nEvt+1)];	//イベント
			} else {
				pTapSeq.EvtBuf = null;					//							(nEvt=0)ならば存在しない。
			}
		}
		//-----------------------------------------------------------------------------
		//シーケンサを実行する。
		// - 普通は割り込み処理から呼び出すと思うが、アプリケーションが任意のコンテキストで呼び出す可能性も有るので、排他制御が必要です。
		public static void TapSeq_Exec(ST_TapSeq pTapSeq, int iElapTime) {
			int iLogCh, iSeqCh;
			if(iElapTime <    0) { iElapTime =    0; }	//経過時間が0秒未満ならば、0秒に補正する。
			if(iElapTime > 1000) { iElapTime = 1000; }	//経過時間が1秒超過ならば、1秒に補正する。
/*ENTER_CS;*/		lock(pTapSeq) {
				//論理チャネルを実行する。
				for(iLogCh = 0; iLogCh < pTapSeq.pInfo.nLogCh; iLogCh++) {
					TapLogCh_Exec(pTapSeq, iLogCh, iElapTime);
				}
				//↑↓この処理順必須です。逆にするとシーケンサが再生開始した直後の論理チャネルの待ち時間を減らしてしまう恐れが有ります。
				//シーケンサチャネルを実行する。
				for(iSeqCh = 0; iSeqCh < pTapSeq.pInfo.nSeqCh; iSeqCh++) {
					TapSeqCh_Exec(pTapSeq, iSeqCh, iElapTime);
				}
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//論理チャネルを再生開始する。
		// - TapSeqCh_Exec()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
		public static void TapLogCh_Play(ST_TapSeq pTapSeq, int iLogCh, int iPhr, int iLoop, int iAtt, int iPan) {
			int iPhrType;
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
/*ENTER_CS;*/		lock(pTapSeq) {
				//この論理チャネルを制御しているシーケンサチャネル番号を、確実にクリアする。
				// - アプリケーションから直接呼び出された場合は、(pLogCh.iSeqCh=-1)のままとなる。
				// - シーケンサ処理から呼び出された場合は、この関数が処理を返した後に、シーケンサ処理の中でpLogCh.iSeqChを設定する。
				pLogCh.iSeqCh = byte.MaxValue;
				//ループフレーズ引き継ぎ時間を、確実にクリアする。
				// - 現在の再生を継続する場合も、先頭から再生開始する場合も、どちらにも必要な処理なので、ここで行うのが良い。
				pLogCh.iCont  = 0;
				//再生開始するフレーズのフレーズチェインの最後のフレーズの、フレーズ種別を取得する。
				iPhrType = TapSeq_GetPhrType(pTapSeq, iPhr);
				//再生開始するフレーズのフレーズチェインの最後のフレーズが、ループフレーズならば…
				// - 通常フレーズ(-1),又は,完全フレーズ(-2)ならば、下記の判断を行わず、常に、現在の再生を停止し先頭から再生開始する。
				if(iPhrType >= 0) {
					//論理チャネルが再生中ならば…
					if(pLogCh.iPhr != ushort.MaxValue) {
						//再生中のフレーズの最後のフレーズもループフレーズであり,かつ,再生開始するフレーズと同じフレーズチェインならば、再生開始を行わずに現在の再生を継続する。
						//再生中のフレーズの最後のフレーズがループフレーズ以外か,又は,再生開始するフレーズと別のフレーズチェインならば、現在の再生を停止し先頭から再生開始する。
						if(TapSeq_GetPhrType(pTapSeq, pLogCh.iPhr) == iPhrType) {
							//論理チャネルの減衰量とパンを変更する。
							// - 再生開始を行わずに現在の再生を継続する時も、減衰量とパンは新しい値を適用する必要が有る。
							TapLogCh_Ctrl(pTapSeq, iLogCh, iAtt, iPan);
							return;	//ここまで
						}
					}
					//再生開始するフレーズのフレーズチェインの最後のフレーズがループフレーズなので、ループ回数を無限ループに置き換える。
					iLoop = 0;
				}
				//論理チャネルを確実に再生停止する。
				TapLogCh_Stop(pTapSeq, iLogCh);
				//ループ回数を格納する。
				pLogCh.iLoop = (byte)iLoop;
				//減衰量を格納する。
				pLogCh.iAtt  = (byte)iAtt;
				//パンを格納する。
				pLogCh.iPan  = (sbyte)iPan;
				//待ち時間をクリアする。
				// - この後、再生開始処理のサブルーチンの中で、フレーズ時間を加算する。
				pLogCh.iWait = 0;
				//デバイスチャネルを獲得する。
				// - デバイスチャネルの獲得に失敗する場合も有る。
				//   デバイスチャネルの獲得に失敗した場合も、以下の処理を行う。
				TapSeq_AcqLogCh(pTapSeq, iLogCh);
				//再生開始処理のサブルーチンを実行する。
				TapLogCh_PlaySubr(pTapSeq, iLogCh, iPhr);
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//論理チャネルを再生停止する。
		// - TapLogCh_Play(),又は,TapSeqCh_Stop(),又は,TapSeqCh_Exec()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
		public static void TapLogCh_Stop(ST_TapSeq pTapSeq, int iLogCh) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
/*ENTER_CS;*/		lock(pTapSeq) {
				//この論理チャネルを制御しているシーケンサチャネル番号を、確実にクリアする。
				pLogCh.iSeqCh = byte.MaxValue;
				//論理チャネルが停止中ならば、何もせずに帰る。
				if(pLogCh.iPhr == ushort.MaxValue) { return; }	//ここまで
				//再生中のフレーズのフレーズチェインの最後のフレーズが、完全フレーズ以外ならば…
				// - 完全フレーズならば、論理チャネルはシーケンサチャネルの制御を外れ、自然停止まで継続する。
				if(TapSeq_GetPhrType(pTapSeq, pLogCh.iPhr) != -2/*完全フレーズ*/) {
					//再生停止通知を行う。
					// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
					TapSeq_fnStop(pTapSeq, iLogCh);
					//再生停止処理のサブルーチンを実行する。
					TapLogCh_StopSubr(pTapSeq, iLogCh);
				}
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//論理チャネルの減衰量とパンを変更する。
		// - TapLogCh_Play(),又は,TapSeqCh_Exec()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
		public static void TapLogCh_Ctrl(ST_TapSeq pTapSeq, int iLogCh, int iAtt, int iPan) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
/*ENTER_CS;*/		lock(pTapSeq) {
				//論理チャネルが停止中ならば、何もせずに帰る。
				if(pLogCh.iPhr == ushort.MaxValue) { return; }	//ここまで
				//減衰量とパンが変化しなければ、何もせずに帰る。
				if((pLogCh.iAtt == iAtt) && (pLogCh.iPan == iPan)) { return; }	//ここまで
				//減衰量を格納する。
				pLogCh.iAtt = (byte)iAtt;
				//パンを格納する。
				pLogCh.iPan = (sbyte)iPan;
				//コントロール通知を行う。
				// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
				TapSeq_fnCtrl(pTapSeq, iLogCh);
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//シーケンサチャネルを再生開始する。
		// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
		public static void TapSeqCh_Play(ST_TapSeq pTapSeq, int iSeqCh, VoidPtr pData) {
			ST_TapSeqCh pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
/*ENTER_CS;*/		lock(pTapSeq) {
				//シーケンサチャネルを確実に再生停止する。
				TapSeqCh_Stop(pTapSeq, iSeqCh);
				//シーケンスデータを格納する。
				pSeqCh.pData = pData;
				//待ち時間をクリアする。
				pSeqCh.iWait = 0;
				//シーケンスデータの先頭に「待ち時間無し」の処理が有れば、即実行する。
				TapSeqCh_Exec(pTapSeq, iSeqCh, 0);
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//シーケンサチャネルを再生停止する。
		// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
		public static void TapSeqCh_Stop(ST_TapSeq pTapSeq, int iSeqCh) {
			ST_TapSeqCh pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
			int iLogCh;
/*ENTER_CS;*/		lock(pTapSeq) {
				//シーケンスデータをクリアする。
				pSeqCh.pData = null;
				//各論理チャネルについて…
				for(iLogCh = 0; iLogCh < pTapSeq.pInfo.nLogCh; iLogCh++) {
					ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
					//このシーケンサチャネルが制御している論理チャネルならば…
					if(pLogCh.iSeqCh == iSeqCh) {
						//この論理チャネルを制御しているシーケンサチャネル番号をクリアする。
						// - この下の処理でTapLogCh_Stop()を呼び出す場合は、(pLogCh.iSeqCh=-1)を二度行う事になり、無駄だが害は無い。
						pLogCh.iSeqCh = byte.MaxValue;
						//論理チャネルが再生中ならば…
						// - 必ず再生中であるはずだが、念のために確認する事にした。
						if(pLogCh.iPhr != ushort.MaxValue) {
							//アプリケーション定義のループフレーズ引き継ぎ時間が0でなく,かつ,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズならば…
							if((pTapSeq.pInfo.iCont != 0) && (TapSeq_GetPhrType(pTapSeq, pLogCh.iPhr) >= 0)) {
								//ループフレーズ引き継ぎ時間がまだ開始していなければ、ループフレーズ引き継ぎ時間を開始する。
								// - ループ引き継ぎ時間内に再度この処理が実行された場合は、ループ引き継ぎ時間を再開しない。
								if(pLogCh.iCont == 0) { pLogCh.iCont = pTapSeq.pInfo.iCont; }
							//アプリケーション定義のループフレーズ引き継ぎ時間が0か,又は,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズでなければ…
							} else {
								//論理チャネルを再生停止する。
								TapLogCh_Stop(pTapSeq, iLogCh);
							}
						}
					}
				}
/*LEAVE_CS;*/		}
		}
		//-----------------------------------------------------------------------------
		//デバイスチャネルを取得する。
		public static ST_TapDevCh TapSeq_GetDevCh(ST_TapSeq pTapSeq, int iDevCh) {
			if((uint)iDevCh >= (uint)pTapSeq.pInfo.nDevCh) { throw new ApplicationException(); }	//デバイスチャネル番号が範囲外ならばエラー
			return pTapSeq.TBL_DevCh[iDevCh];
		}
		//-----------------------------------------------------------------------------
		//論理チャネルを取得する。
		public static ST_TapLogCh TapSeq_GetLogCh(ST_TapSeq pTapSeq, int iLogCh) {
			if((uint)iLogCh >= (uint)pTapSeq.pInfo.nLogCh) { throw new ApplicationException(); }	//論理チャネル番号が範囲外ならばエラー
			return pTapSeq.TBL_LogCh[iLogCh];
		}
		//-----------------------------------------------------------------------------
		//シーケンサチャネルを取得する。
		public static ST_TapSeqCh TapSeq_GetSeqCh(ST_TapSeq pTapSeq, int iSeqCh) {
			if((uint)iSeqCh >= (uint)pTapSeq.pInfo.nSeqCh) { throw new ApplicationException(); }	//シーケンサチャネル番号が範囲外ならばエラー
			return pTapSeq.TBL_SeqCh[iSeqCh];
		}
		//-----------------------------------------------------------------------------
		//イベントを取得する。
		public static bool TapSeq_EvtGet(ST_TapSeq pTapSeq, out ST_TapEvt pBuf) {
			ST_TapEvtBuf pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
			ST_TapEvt[] TBL_Buf = pEvtBuf.TBL_Buf;
//{{cliprinb.cのRinBuf_Get()と同様の処理です。
/*ENTER_CS;*/		lock(pTapSeq) {
				//今回の要素を読み出す位置を取得する。
				int iGetPos = pEvtBuf.iGetPos;
				//バッファが空でなければ…
				if(iGetPos != pEvtBuf.iAddPos) {
					//今回の要素を読み出す。
					pBuf = TBL_Buf[iGetPos];
					//次回の要素を読み出す位置を進める。
					if(++iGetPos > pTapSeq.pInfo.nEvt) { iGetPos = 0; }
					//次回の要素を読み出す位置を格納する。
					pEvtBuf.iGetPos = (byte)iGetPos;
					//trueを返す。
					return true;
				//バッファが空ならば…
				} else {
					pBuf = default(ST_TapEvt);	//エラー抑制
					//falseを返す。
					return false;
				}
/*LEAVE_CS;*/		}
//}}cliprinb.cのRinBuf_Get()と同様の処理です。
		}
		//-----------------------------------------------------------------------------
		//イベントのオーバーランが発生した回数を取得する。
		public static int TapSeq_EvtOvr(ST_TapSeq pTapSeq) {
			ST_TapEvtBuf pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
//{{cliprinb.cのRinBuf_Ovr()と同様の処理です。
			int nOvrCnt;
/*ENTER_CS;*/		lock(pTapSeq) {
				//オーバーランが発生した回数を取得する。
				nOvrCnt = pEvtBuf.nOvrCnt;
				//オーバーランが発生した回数を確実にクリアする。
				pEvtBuf.nOvrCnt = 0;
/*LEAVE_CS;*/		}
			return nOvrCnt;
//}}cliprinb.cのRinBuf_Ovr()と同様の処理です。
		}
		//*****************************************************************************
		//	ローカル関数宣言
		//*****************************************************************************
		//初期化情報を検査する。
		private static void TapSeqInfo_Verify(ST_TapSeqInfo pInfo) {
			int iLogCh, iSeqCh;
			if((pInfo.nPhr   == 0)					||	//フレーズ数				1～UINT16_MAX
			   (pInfo.nDevCh == 0)					||	//デバイスチャネル数			1～UINT8_MAX
			   (pInfo.nLogCh == 0)					||	//論理チャネル数			1～UINT8_MAX
			   (pInfo.nSeqCh == 0)					||	//シーケンサチャネル数			1～UINT8_MAX
			// (pInfo.iCont  == 0)					||	//ループフレーズ引き継ぎ時間[ms]	0～UINT8_MAX	検査不要
			// (pInfo.iPrep  == 0)					||	//再生準備時間[ms]			0～UINT16_MAX	検査不要	//{{2017/04/02追加:再生準備通知を追加しました。}}
			  ((pInfo.nEvt   == 0) ^ (pInfo.iEvtMsk == 0))		||	//イベント記録数,イベントマスク		0～UINT8_MAX	(nEvt=0)ならば(iEvtMsk=0)でなければならない。(nEvt!=0)ならば(iEvtMsk!=0)でなければならない。
			   (pInfo.fnPlay       == null)				||	//再生開始通知
			   (pInfo.fnStop       == null)				||	//再生停止通知
			   (pInfo.fnLoop       == null)				||	//自然ループ通知
			   (pInfo.fnEnd        == null)				||	//自然停止通知
			   (pInfo.fnNext       == null)				||	//自然遷移通知
			  ((pInfo.fnPrep       == null) && (pInfo.iPrep != 0))	||	//再生準備通知				//{{2017/04/02追加:再生準備通知を追加しました。}}
			   (pInfo.fnCtrl       == null)				||	//コントロール通知
			   (pInfo.fnGetPhrTime == null)				||	//フレーズ時間[ms]を取得する。
			   (pInfo.fnGetPhrNext == null)) { throw new ApplicationException(); }	//フレーズチェインの次のフレーズ番号を取得する。
			for(iLogCh = 0; iLogCh < pInfo.nLogCh; iLogCh++) {
				byte[] pTapSet = pInfo.pTBL_TapSet[iLogCh];	//TAPセット
				byte[] mask = new byte[256/8];
				foreach(int iDevCh in pTapSet) {	//空のTAPセット(長さ0の配列)も可とする。フェーダーとして利用する論理チャネル等の、デバイスチャネルの獲得が不要である場合に、空のTAPセットを設定せよ。	【C#版特有のコメント】C言語版ではTAPセットの終端をUINT8_MAXで示していましたが、C#版では終端のUINT8_MAXは不要です。
					if((uint)iDevCh >= (uint)pInfo.nDevCh) { throw new ApplicationException(); }	//デバイスチャネル番号が範囲外ならばエラー
					if((mask[iDevCh/8] &        (1<<(iDevCh&7))) != 0) { throw new ApplicationException(); }	//一つの論理チャネルのTAPセット内で、同じデバイスチャネル番号を複数回指定してはいけない。
					    mask[iDevCh/8] |= (byte)(1<<(iDevCh&7));
				}
			}
			for(iSeqCh = 0; iSeqCh < pInfo.nSeqCh; iSeqCh++) {
				iLogCh = pInfo.pTBL_BaseCh[iSeqCh];
				if((uint)iLogCh >= (uint)pInfo.nLogCh) { throw new ApplicationException(); }	//ベース論理チャネル番号が範囲外ならばエラー
			}
		}
		//-----------------------------------------------------------------------------
		//再生開始通知を行う。
		private static void TapSeq_fnPlay(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_Play, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnPlay.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//再生停止通知を行う。
		private static void TapSeq_fnStop(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_Stop, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnStop.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//自然ループ通知を行う。
		private static void TapSeq_fnLoop(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_Loop, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnLoop.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//自然停止通知を行う。
		private static void TapSeq_fnEnd(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_End, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnEnd.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//自然遷移通知を行う。
		private static void TapSeq_fnNext(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_Next, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnNext.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//コントロール通知を行う。
		private static void TapSeq_fnCtrl(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			TapSeq_AddEvt(pTapSeq, TapEvt_Ctrl, iLogCh, pLogCh.iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
			pTapSeq.pInfo.fnCtrl.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//-----------------------------------------------------------------------------
		//{{2017/04/02追加:再生準備通知を追加しました。
		//再生準備通知を行う。
		private static void TapSeq_fnPrep(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//(再生準備通知は、イベントに記録する必要は有りません。)
			pTapSeq.pInfo.fnPrep.Invoke(pTapSeq, iLogCh, pLogCh);
		}
		//}}2017/04/02追加:再生準備通知を追加しました。
		//-----------------------------------------------------------------------------
		//フレーズ時間[ms]を取得する。
		private static int TapSeq_fnGetPhrTime(ST_TapSeq pTapSeq, int iPhr) {
			int iTime;
			if((uint)iPhr >= (uint)pTapSeq.pInfo.nPhr) { throw new ApplicationException(); }	//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
			iTime = pTapSeq.pInfo.fnGetPhrTime.Invoke(pTapSeq, iPhr);
			if(iTime < 0) { throw new ApplicationException(); }					//(0～INT_MAX)の範囲外ならばエラー		ここで停止した場合はアプリケーション定義のコールバック関数のバグである可能性が高い。
			return iTime;
		}
		//-----------------------------------------------------------------------------
		//フレーズチェインの次のフレーズ番号を取得する。
		private static int TapSeq_fnGetPhrNext(ST_TapSeq pTapSeq, int iPhr) {
			if((uint)iPhr >= (uint)pTapSeq.pInfo.nPhr) { throw new ApplicationException(); }	//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
			iPhr = pTapSeq.pInfo.fnGetPhrNext.Invoke(pTapSeq, iPhr);
			if((iPhr < -2) || (iPhr >= pTapSeq.pInfo.nPhr)) { throw new ApplicationException(); }	//-2未満,又は,フレーズ番号が範囲外ならばエラー	ここで停止した場合はアプリケーション定義のコールバック関数のバグである可能性が高い。
			return iPhr;
		}
		//-----------------------------------------------------------------------------
		//フレーズチェインの最後のフレーズの、フレーズ種別を取得する。
		private static int TapSeq_GetPhrType(ST_TapSeq pTapSeq, int iPhr) {
			if((uint)iPhr >= (uint)pTapSeq.pInfo.nPhr) { throw new ApplicationException(); }	//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
			for(;;) {
				int iPhrNext = TapSeq_fnGetPhrNext(pTapSeq, iPhr);
				if(iPhrNext <     0) { return iPhrNext; }	//-1=通常フレーズ,又は,-2=完全フレーズ
				if(iPhrNext == iPhr) { return iPhr;     }	//0以上=ループフレーズ
				iPhr = iPhrNext;
			}
		}
		//-----------------------------------------------------------------------------
		//デバイスチャネルを獲得する。
		// - TapLogCh_Play()のENTER_CS～LEAVE_CS区間から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapSeq_AcqLogCh(ST_TapSeq pTapSeq, int iLogCh) {
			ST_TapLogCh pLogCh;
			ST_TapDevCh pDevCh;
			byte[] pTapSet;
			int iDevChSel, iLogChMin;
			//論理チャネルを取得する。
			pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//論理チャネルが、既にデバイスチャネルを制御している事は、起こり得ないはずである。
			if(pLogCh.iDevCh != byte.MaxValue) { throw new ApplicationException(); }	//ここでエラー停止した場合、当関数を呼び出す前にTapLogCh_Stop()を呼び出す事を、忘れている可能性が高い。
			//TAPセット(この論理チャネルで使用するデバイスチャネル番号のリスト)を取得する。
			pTapSet = pTapSeq.pInfo.pTBL_TapSet[iLogCh];	//TAPセット
			//デバイスチャネル番号が未決定(-1)，最小の論理チャネル番号をこの論理チャネル番号としておく。
			iDevChSel = -1;
			iLogChMin = iLogCh;
			//TAPセットの終端に到達するまで…
			foreach(int iDevCh in pTapSet) {	//空のTAPセット(長さ0の配列)も可とする。フェーダーとして利用する論理チャネル等の、デバイスチャネルの獲得が不要である場合に、空のTAPセットを設定せよ。	【C#版特有のコメント】C言語版ではTAPセットの終端をUINT8_MAXで示していましたが、C#版では終端のUINT8_MAXは不要です。
				//デバイスチャネルを取得する。
				pDevCh = TapSeq_GetDevCh(pTapSeq, iDevCh);
				//デバイスチャネルが、既にこの論理チャネルに制御されている事は、起こり得ないはずである。
				if(pDevCh.iLogCh == iLogCh) { throw new ApplicationException(); }
				//デバイスチャネルを制御している論理チャネルが、この論理チャネルよりも小さいか,又は,停止中(UINT8_MAX)ならば、デバイスチャネルを獲得出来る。
				//停止中のデバイスチャネルが有ればそれを獲得し、停止中のデバイスチャネルが無ければ、最小の論理チャネル番号に制御されているデバイスチャネルを獲得する。
				//この論理チャネルよりも小さい論理チャネル番号に制御されているデバイスチャネルも無ければ、獲得に失敗する。
				if(pDevCh.iLogCh == byte.MaxValue) {
					iDevChSel = iDevCh;
					break;	//ここまで
				}
				if(pDevCh.iLogCh < iLogChMin) {
					iDevChSel = iDevCh;
					iLogChMin = pDevCh.iLogCh;
				}
			}
			//デバイスチャネルを獲得出来たら…
			if(iDevChSel != -1) {
				//この論理チャネルが制御しているデバイスチャネル番号を格納する。
				pLogCh.iDevCh = (byte)iDevChSel;
				//デバイスチャネルを取得する。
				pDevCh = TapSeq_GetDevCh(pTapSeq, iDevChSel);
				//このデバイスチャネルが、他の論理チャネルに制御されてたら…
				if(pDevCh.iLogCh != byte.MaxValue) {
					//このデバイスチャネルを制御している論理チャネルを取得する。
					pLogCh = TapSeq_GetLogCh(pTapSeq, pDevCh.iLogCh);
					//その論理チャネルが制御しているデバイスチャネルが、このデバイスチャネルである事を確認する。
					if(pLogCh.iDevCh != iDevChSel) { throw new ApplicationException(); }
					//その論理チャネルが制御しているデバイスチャネルを、クリアする。
					pLogCh.iDevCh = byte.MaxValue;
				}
				//このデバイスチャネルを制御している論理チャネル番号を格納する。
				pDevCh.iLogCh = (byte)iLogCh;
			}
		}
		//-----------------------------------------------------------------------------
		//再生開始処理のサブルーチン。
		// - TapLogCh_Play()のENTER_CS～LEAVE_CS区間,又は,TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapLogCh_PlaySubr(ST_TapSeq pTapSeq, int iLogCh, int iPhr) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//フレーズ番号を格納する。
			pLogCh.iPhr   = (ushort)iPhr;
			//フレーズ時間を取得し、待ち時間に加算する。
			pLogCh.iWait += TapSeq_fnGetPhrTime(pTapSeq, pLogCh.iPhr);
			//再生開始通知を行う。
			// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
			TapSeq_fnPlay(pTapSeq, iLogCh);
		}
		//-----------------------------------------------------------------------------
		//自然遷移処理のサブルーチン。
		// - TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapLogCh_NextSubr(ST_TapSeq pTapSeq, int iLogCh, int iPhr) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//フレーズ番号を格納する。
			pLogCh.iPhr   = (ushort)iPhr;
			//フレーズ時間を取得し、待ち時間に加算する。
			pLogCh.iWait += TapSeq_fnGetPhrTime(pTapSeq, pLogCh.iPhr);
			//自然遷移通知を行う。
			// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
			TapSeq_fnNext(pTapSeq, iLogCh);
		}
		//-----------------------------------------------------------------------------
		//自然ループ処理のサブルーチン。
		// - TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapLogCh_LoopSubr(ST_TapSeq pTapSeq, int iLogCh) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//フレーズ時間を取得し、待ち時間に加算する。
			pLogCh.iWait += TapSeq_fnGetPhrTime(pTapSeq, pLogCh.iPhr);
			//自然ループ通知を行う。
			// - この論理チャネルが制御しているデバイスチャネルが無い場合も同じ処理で良い。
			TapSeq_fnLoop(pTapSeq, iLogCh);
		}
		//-----------------------------------------------------------------------------
		//再生停止処理のサブルーチン。
		// - TapLogCh_Stop()のENTER_CS～LEAVE_CS区間,又は,TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapLogCh_StopSubr(ST_TapSeq pTapSeq, int iLogCh) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//論理チャネルを停止中とする。
			pLogCh.iPhr = ushort.MaxValue;
			//この論理チャネルが制御しているデバイスチャネルが有れば…
			if(pLogCh.iDevCh != byte.MaxValue) {
				//デバイスチャネルを取得する。
				ST_TapDevCh pDevCh = TapSeq_GetDevCh(pTapSeq, pLogCh.iDevCh);
				//このデバイスチャネルを制御している論理チャネルが、この論理チャネルである事を確認する。
				if(pDevCh.iLogCh != iLogCh) { throw new ApplicationException(); }
				//デバイスチャネルを停止中とする。
				pDevCh.iLogCh = byte.MaxValue;
				//この論理チャネルが制御しているデバイスチャネルを、クリアする。
				pLogCh.iDevCh = byte.MaxValue;
			}
		}
		//-----------------------------------------------------------------------------
		//論理チャネルを実行する。
		// - TapSeq_Exec()のENTER_CS～LEAVE_CS区間から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapLogCh_Exec(ST_TapSeq pTapSeq, int iLogCh, int iElapTime) {
			//論理チャネルを取得する。
			ST_TapLogCh pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//論理チャネルが停止中ならば、何もせずに帰る。
			if(pLogCh.iPhr == ushort.MaxValue) { return; }	//ここまで
			//ループフレーズ引き継ぎ時間が開始してしたら…
			if(pLogCh.iCont != 0) {
				//ループフレーズ引き継ぎ時間を減らす。
//{{2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//				pLogCh.iCont = (byte)(((uint)pLogCh.iCont >= (uint)iElapTime) ? (pLogCh.iCont - iElapTime) : 0);
//↓2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
				UVAR_SUB(ref pLogCh.iCont, iElapTime);
//}}2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
				//ループフレーズ引き継ぎ時間が0になったら…
				if(pLogCh.iCont == 0) {
					//再生停止通知を行う。
					// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
					TapSeq_fnStop(pTapSeq, iLogCh);
					//再生停止処理のサブルーチンを実行する。
					TapLogCh_StopSubr(pTapSeq, iLogCh);
					return;	//ここまで
				}
			}
//{{2017/04/02変更:再生準備通知を追加しました。
//			//待ち時間を減らす。
//			pLogCh.iWait = ((uint)(pLogCh.iWait - int.MinValue) >= (uint)iElapTime) ? (pLogCh.iWait - iElapTime) : int.MinValue;
//↓2017/04/02変更:再生準備通知を追加しました。
			{
				//再生準備開始を判断するために、待ち時間を減らす前の待ち時間を覚えておく。
				int save_iWait = pLogCh.iWait;
				//待ち時間を減らす。
//{{2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//				pLogCh.iWait = ((uint)(pLogCh.iWait - int.MinValue) >= (uint)iElapTime) ? (pLogCh.iWait - iElapTime) : int.MinValue;
//↓2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
				VAR_SUB(ref pLogCh.iWait, iElapTime);
//}}2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
				//待ち時間が0以下になった場合は、再生準備通知を行わない。
				// - 待ち時間が0以下になった場合は、次のフレーズが有ればこの下で再生開始するので、再生準備を行っても無駄だからです。
				if(pLogCh.iWait > 0) {
					//待ち時間を減らす前の待ち時間が再生準備時間以上で、待ち時間を減らした後の待ち時間が再生準備時間未満ならば…
					if((  save_iWait >= pTapSeq.pInfo.iPrep) &&
					   (pLogCh.iWait <  pTapSeq.pInfo.iPrep)) {	//再生準備を行わない場合は(pTapSeq.pInfo.iPrep==0)なので、この条件が成立する事は無い。
						//再生準備通知を行う。
						TapSeq_fnPrep(pTapSeq, iLogCh);
					}
				}
			}
//}}2017/04/02変更:再生準備通知を追加しました。
			//待ち時間が0以下である間、繰り返す。
			while(pLogCh.iWait <= 0) {
				//次のフレーズを取得する。
				int iPhr = TapSeq_fnGetPhrNext(pTapSeq, pLogCh.iPhr);
				//現在のフレーズが、通常フレーズ(-1),又は,完全フレーズ(-2)だったならば…
				if(iPhr < 0) {
					//無限ループでなく,かつ,ループ回数を減らして0になったら…
					if((pLogCh.iLoop != 0) && (--pLogCh.iLoop == 0)) {
						//自然停止通知を行う。
						// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
						TapSeq_fnEnd(pTapSeq, iLogCh);
						//再生停止処理のサブルーチンを実行する。
						TapLogCh_StopSubr(pTapSeq, iLogCh);
						return;	//ここまで
					//無限ループか,又は,ループ回数を減らして0にならなければ…
					} else {
						//自然ループ処理のサブルーチンを実行する。
						TapLogCh_LoopSubr(pTapSeq, iLogCh);
					}
				//現在のフレーズが、ループフレーズだったならば…
				} else if(iPhr == pLogCh.iPhr) {
					//自然ループ処理のサブルーチンを実行する。
					TapLogCh_LoopSubr(pTapSeq, iLogCh);
				//現在のフレーズが、中間フレーズだったならば…
				} else {
					//自然遷移処理のサブルーチンを実行する。
					TapLogCh_NextSubr(pTapSeq, iLogCh, iPhr);
				}
			}
		}
		//-----------------------------------------------------------------------------
		//シーケンサチャネルを実行する。
		// - TapSeq_Exec(),又は,TapSeqCh_Play()のENTER_CS～LEAVE_CS区間から呼び出されるので、当関数内では排他制御が不要です。
		private static void TapSeqCh_Exec(ST_TapSeq pTapSeq, int iSeqCh, int iElapTime) {
			ST_TapSeqCh pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
			ST_TapLogCh pLogCh;
			int iData, iTime, iDist, iLogCh, iPhr, iLoop, iAtt, iPan;
			BytePtr pData;
			//シーケンサチャネルが停止中ならば、何もせずに帰る。
			if(!pSeqCh.pData) { return; }	//ここまで
			//待ち時間を減らす。
//{{2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//			pSeqCh.iWait = ((uint)(pSeqCh.iWait - int.MinValue) >= (uint)iElapTime) ? (pSeqCh.iWait - iElapTime) : int.MinValue;
//↓2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
			VAR_SUB(ref pSeqCh.iWait, iElapTime);
//}}2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
			//待ち時間が0以下である間、繰り返す。
			while(pSeqCh.iWait <= 0) {
				switch((iData = pSeqCh.pData.Read())) {
				default:throw new ApplicationException();
				//シーケンスデータの終端
				case 0x00://引数無し
					//シーケンスデータの終端でステイする。
					pSeqCh.pData--;
					return;	//ここまで
				//1～4バイトウェイト
				case 0x01://(iTime-0x00001)[ 7: 0]								//1バイトウェイトの範囲 = 0x00001+(0～0x000000FF) [ms]
				case 0x02://(iTime-0x00101)[15: 8] (iTime-0x00101)[ 7: 0]					//2バイトウェイトの範囲 = 0x00101+(0～0x0000FFFF) [ms]
				case 0x03://(iTime-0x10101)[23:16] (iTime-0x10101)[15: 8] (iTime-0x10101)[ 7:0]			//3バイトウェイトの範囲 = 0x10101+(0～0x00FFFFFF) [ms]
				case 0x04://(iTime        )[31:24] (iTime        )[23:16] (iTime        )[15:8] (iTime)[7:0]	//4バイトウェイトの範囲 =         (0～0xFFFFFFFF) [ms]
					//ウェイト時間を取得する。
					iTime  = 0;
					if(iData == 0x04) { iTime = -0x01010101; }	//4バイトウェイトならば4回ループする毎回の(+1)を相殺する。
					do {
						iTime <<= 8;
						iTime  += ((pSeqCh.pData.Read()) + 1);
					} while(--iData != 0);
					//待ち時間を増やす。
//{{2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//					pSeqCh.iWait = ((uint)(int.MaxValue - pSeqCh.iWait) >= (uint)iTime) ? (pSeqCh.iWait + iTime) : int.MaxValue;
//↓2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
					VAR_ADD(ref pSeqCh.iWait, iTime);
//}}2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
					break;
				//コントロールチェンジ
				case 0x05://iLogCh iAtt
				case 0x06://iLogCh      iPan
				case 0x07://iLogCh iAtt iPan
					//論理チャネル番号を取得する。
					iLogCh = pSeqCh.pData.Read();
					//ベース論理チャネル番号を加算する。
					iLogCh += pTapSeq.pInfo.pTBL_BaseCh[iSeqCh];
					//論理チャネルを取得する。
					pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
					//減衰量,パンを取得する。
					iAtt   = pLogCh.iAtt; if((iData & 1) != 0) { iAtt =       pSeqCh.pData.Read(); }	//既定値:現在値
					iPan   = pLogCh.iPan; if((iData & 2) != 0) { iPan = (byte)pSeqCh.pData.Read(); }	//既定値:現在値
					//コントロールチェンジを実行する。
					TapLogCh_Ctrl(pTapSeq, iLogCh, iAtt, pLogCh.iPan);
					break;
				//論理チャネル再生開始
				case 0x08://iLogCh iPhr[15:8] iPhr[7:0]                
				case 0x09://iLogCh iPhr[15:8] iPhr[7:0] iLoop          
				case 0x0A://iLogCh iPhr[15:8] iPhr[7:0]       iAtt     
				case 0x0B://iLogCh iPhr[15:8] iPhr[7:0] iLoop iAtt     
				case 0x0C://iLogCh iPhr[15:8] iPhr[7:0]            iPan
				case 0x0D://iLogCh iPhr[15:8] iPhr[7:0] iLoop      iPan
				case 0x0E://iLogCh iPhr[15:8] iPhr[7:0]       iAtt iPan
				case 0x0F://iLogCh iPhr[15:8] iPhr[7:0] iLoop iAtt iPan
				//論理チャネル再生開始(ウェイト付き)
				case 0x18://iLogCh iPhr[15:8] iPhr[7:0]                
				case 0x19://iLogCh iPhr[15:8] iPhr[7:0] iLoop          
				case 0x1A://iLogCh iPhr[15:8] iPhr[7:0]       iAtt     
				case 0x1B://iLogCh iPhr[15:8] iPhr[7:0] iLoop iAtt     
				case 0x1C://iLogCh iPhr[15:8] iPhr[7:0]            iPan
				case 0x1D://iLogCh iPhr[15:8] iPhr[7:0] iLoop      iPan
				case 0x1E://iLogCh iPhr[15:8] iPhr[7:0]       iAtt iPan
				case 0x1F://iLogCh iPhr[15:8] iPhr[7:0] iLoop iAtt iPan
					//論理チャネル番号を取得する。
					iLogCh = pSeqCh.pData.Read();
					//ベース論理チャネル番号を加算する。
					iLogCh += pTapSeq.pInfo.pTBL_BaseCh[iSeqCh];
					//論理チャネルを取得する。
					pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
					//フレーズ番号を取得する。
					iPhr   = pSeqCh.pData.Read();
					iPhr <<= 8;
					iPhr  |= pSeqCh.pData.Read();
					//減衰量,パン,ループ回数を取得する。
					iLoop  = 1; if((iData & 1) != 0) { iLoop =       pSeqCh.pData.Read(); }			//既定値:ループ回数=1
					iAtt   = 0; if((iData & 2) != 0) { iAtt  =       pSeqCh.pData.Read(); }			//既定値:減衰量=0(減衰無し)
					iPan   = 0; if((iData & 4) != 0) { iPan  = (byte)pSeqCh.pData.Read(); }			//既定値:パン=0(中央)
					//論理チャネルを再生開始する。
					TapLogCh_Play(pTapSeq, iLogCh, iPhr, iLoop, iAtt, iPan);
					//この論理チャネルを制御しているシーケンサチャネル番号を設定する。
					pLogCh.iSeqCh = (byte)iSeqCh;
					//論理チャネル再生開始(ウェイト付き)ならば…
					if((iData & 0x10) != 0) {
						//フレーズ時間を取得する。
						iTime = TapSeq_fnGetPhrTime(pTapSeq, iPhr);
						//待ち時間を増やす。
//{{2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
//						pSeqCh.iWait = ((uint)(int.MaxValue - pSeqCh.iWait) >= (uint)iTime) ? (pSeqCh.iWait + iTime) : int.MaxValue;
//↓2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
						VAR_ADD(ref pSeqCh.iWait, iTime);
//}}2017/04/03変更:clipmisc.csに「変数の最大値,又は,最小値へ飽和する飽和加減算マクロ」を追加した事に伴い、C#版独自の処理をやめてC言語版と同じ処理に変更しました。
					}
					break;
				//論理チャネル再生停止
				case 0x10://iLogCh
					//論理チャネル番号を取得する。
					iLogCh = pSeqCh.pData.Read();
					//ベース論理チャネル番号を加算する。
					iLogCh += pTapSeq.pInfo.pTBL_BaseCh[iSeqCh];
					//論理チャネルを取得する。
					pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
//{{【C#版特有のコメント】C#版では条件コンパイルにかかわらずこの処理は行わない事にしました。もし必要になった場合は復活させて下さい。
//#if     PRODUCT_SPECIFIC_WORKAROUND
//					//論理チャネルが再生中,かつ,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズならば、論理チャネル再生停止コマンドを無視する。
//					if((pLogCh.iPhr != UINT16_MAX) && (TapSeq_GetPhrType(pTapSeq, pLogCh.iPhr) >= 0)) { break; }
//#endif//PRODUCT_SPECIFIC_WORKAROUND
//}}【C#版特有のコメント】C#版では条件コンパイルにかかわらずこの処理は行わない事にしました。もし必要になった場合は復活させて下さい。
					//論理チャネルを再生停止する。
					TapLogCh_Stop(pTapSeq, iLogCh);
					break;
				//1～4バイトジャンプ
				case 0x11://(iDist-0x00001)[ 7: 0]								//1バイトジャンプの範囲 = 0x00001+(0～0x000000FF) [ms]
				case 0x12://(iDist-0x00101)[15: 8] (iDist-0x00101)[ 7: 0]					//2バイトジャンプの範囲 = 0x00101+(0～0x0000FFFF) [ms]
				case 0x13://(iDist-0x10101)[23:16] (iDist-0x10101)[15: 8] (iDist-0x10101)[ 7:0]			//3バイトジャンプの範囲 = 0x10101+(0～0x00FFFFFF) [ms]
				case 0x14://(iDist        )[31:24] (iDist        )[23:16] (iDist        )[15:8] (iDist)[7:0]	//4バイトジャンプの範囲 =         (0～0xFFFFFFFF) [ms]
					iData -= 0x10;			//1～4バイトジャンプのデコード方法を、既存の1～4バイトウェイトのデコード方法と同じにするために、コマンド番号を(0x11～0x14)⇒(0x01～0x04)に変換しておく。
					pData = pSeqCh.pData - 1;	//ジャンプ距離は、ジャンプ命令のコマンド番号が格納されているアドレスからの逆方向相対である。(pSeqCh.pData)は既にコマンド番号が格納されている次のアドレスへ進んでしまっているし、この下の処理で(pSeqCh.pData)が変化してしまうので、ここでコマンド番号が格納されているアドレスを記憶しておく。
				    //{{1～4バイトジャンプのデコード方法は、既存の1～4バイトウェイトのデコード方法と同じです。
					//ジャンプ距離を取得する。
					iDist  = 0;
					if(iData == 0x04) { iDist = -0x01010101; }	//4バイトジャンプならば4回ループする毎回の(+1)を相殺する。
					do {
						iDist <<= 8;
						iDist  += ((pSeqCh.pData.Read()) + 1);
					} while(--iData != 0);
				    //}}1～4バイトジャンプのデコード方法は、既存の1～4バイトウェイトのデコード方法と同じです。
					//ジャンプ命令のコマンド番号が格納されているアドレスからの逆方向相対位置へジャンプする。
					pSeqCh.pData = pData - iDist;
					break;
				}
			}
		}
		//-----------------------------------------------------------------------------
		//イベントバッファを取得する。
		private static ST_TapEvtBuf TapSeq_GetEvtBuf(ST_TapSeq pTapSeq) {
			if(pTapSeq.pInfo.nEvt == 0) { throw new ApplicationException(); }					//イベントバッファが存在しなければエラー
			return pTapSeq.EvtBuf;
		}
		//-----------------------------------------------------------------------------
		//イベントを追加する。
		private static void TapSeq_AddEvt(ST_TapSeq pTapSeq, int iEvt, int iLogCh, int iPhr) {
			if((pTapSeq.pInfo.nEvt != 0) &&						//イベントバッファが存在しなければ何もしない。
			  ((pTapSeq.pInfo.iEvtMsk & (1<<iEvt)) != 0)) {				//イベントマスクで指定されていなければ何もしない。
				ST_TapEvtBuf pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
				ST_TapEvt[] TBL_Buf = pEvtBuf.TBL_Buf;
//{{cliprinb.cのRinBuf_Add()と同様の処理です。
/*ENTER_CS;*/			lock(pTapSeq) {
					//今回の要素を書き込む位置を取得する。
					int iAddPos = pEvtBuf.iAddPos;
					//今回の要素を書き込む位置のインデクスを求める。
					int iBuf = iAddPos;
					//次回の要素を書き込む位置を進める。
					if(++iAddPos > pTapSeq.pInfo.nEvt) { iAddPos = 0; }
					//次回の要素を書き込む位置が、要素を読み出す位置に一致しなければ…
					// - 即ち、今回の要素を書き込む位置が、現在アプリケーションが参照中(かも知れない)の位置(=前回TapSeq_EvtGet()が返した位置)よりも前ならば…
					if(iAddPos != pEvtBuf.iGetPos) {
						//次回の要素を書き込む位置を格納する。
						pEvtBuf.iAddPos = (byte)iAddPos;
						//今回の要素を書き込む。
						TBL_Buf[iBuf].iEvt   = (byte)iEvt;
						TBL_Buf[iBuf].iLogCh = (byte)iLogCh;
						TBL_Buf[iBuf].iPhr   = (ushort)iPhr;
					//次回の要素を書き込む位置が、要素を読み出す位置に一致したら…
					// - 即ち、今回要素を書き込む位置が、現在アプリケーションが参照中(かも知れない)の位置(=前回TapSeq_EvtGet()が返した位置)ならば…
					} else {
						//今回の要素を書き込む事は出来ない。オーバーランが発生した回数を増やす。
						if(pEvtBuf.nOvrCnt < ushort.MaxValue) { pEvtBuf.nOvrCnt++; }
					}
/*LEAVE_CS;*/			}
//}}cliprinb.cのRinBuf_Add()と同様の処理です。
			}
		}
		//*****************************************************************************
		//	dTapSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
		//*****************************************************************************
#if     USE_TAPDEF_BIN
		public static void TapSeqCh_PlayNo(ST_TapSeq pTapSeq, int iTapSeqCh, VoidPtr pTapDefBin, int iTapSeqNo) {
			Int32Ptr TBL_TapSeqNo = pTapDefBin;				//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
			int nTapSeqNo = TBL_TapSeqNo[0];				//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
			BytePtr TBL_TapSeq = (BytePtr)(TBL_TapSeqNo + nTapSeqNo);	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
			if((iTapSeqNo <= 0) || (iTapSeqNo >= nTapSeqNo)) { throw new ApplicationException(); }
			TapSeqCh_Play(pTapSeq, iTapSeqCh, TBL_TapSeq + TBL_TapSeqNo[iTapSeqNo]);
		}
#endif//USE_TAPDEF_BIN
	}
}
