/*
 *	cliptaps.c
 *
 *	TAPシーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2017 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モジュールに準じています。
 *	* 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モジュールを参照して下さい。
 *	* Thu Feb 09 23:42:37 JST 2017 Naoyuki Sawa
 *	- ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。
 *	  それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
 *	- pTBL_BaseChフィールドを追加した理由は、以下の通りです。
 *	  これまでは、シーケンスデータの中で論理チャネル番号を直接指定していたため、シーケンスデータを論理チャネル構成と独立して作成する事が難しい問題が有りました。
 *	  シーケンスチャネルと論理チャネルの結び付きが強過ぎて、アプリケーションプログラムがシーケンスチャネル番号を指定出来るとは言え、シーケンスデータが使用している論理チャネル番号によって、シーケンスチャネルも事実上固定となってしまっていました。
 *	  また、論理チャネルの構成が変わると、シーケンスデータ内の論理チャネル番号も全て修正しなければいけないという問題が有りました。
 *	- 今回、pTBL_BaseChフィールドを追加した事に伴い、シーケンスデータを実際に再生するシーケンスチャネルによって、シーケンスデータで指定された論理チャネル番号に対して、オフセットを掛けられるようにしました。
 *	  例えば、シーケンスデータ内では全て'論理チャネル番号0ベース'で作成しておき、各シーケンスチャネルが実際にどの範囲の論理チャネルを使用するかは、pTBL_BaseChフィールドが差すベース論理チャネル番号テーブルにて指定する事が出来るようになりました。
 *	  これによって、シーケンスデータが作り易くなり、また、シーケンスデータを使う上での柔軟性も改善しました。
 *	- 見方を逆にすると、既存のアプリケーションは、'全ての要素が0のベース論理チャネル番号テーブル'を使って動作していたと見なす事が出来ます。
 *	  既存のアプリケーションを少ない手間で移行するには、'全ての要素が0のベース論理チャネル番号テーブル'を、pTBL_BaseChフィールドに指定して下さい。
 *	  また、これまで通りシーケンスデータの中で論理チャネル番号を直接指定したい場合も、'全ての要素が0のベース論理チャネル番号テーブル'を、pTBL_BaseChフィールドに指定して下さい。
 *	* Sat Feb 11 21:09:58 JST 2017 Naoyuki Sawa
 *	- ループ制御を追加しました。
 *	  詳細は、'/clip/tool/dTapSeqC/winapp.h'の同日のコメントを参照して下さい。
 *	* Mon Feb 13 22:57:25 JST 2017 Naoyuki Sawa
 *	- 'play.w'命令を追加しました。
 *	  詳細は、'/clip/tool/dTapSeqC/winapp.h'の同日のコメントを参照して下さい。
 *	* Sun Apr 02 22:50:54 JST 2017 Naoyuki Sawa
 *	- 再生準備通知を追加しました。
 *	  詳細は、TapLogCh_Exec()内のコメントを参照して下さい。
 */
#include "clip.h"
//#define PRODUCT_SPECIFIC_WORKAROUND//このシンボルを定義すると、製品固有の調整を行う。
/*****************************************************************************
 *	ローカル関数宣言
 *****************************************************************************/
static void TapSeqInfo_Verify(const ST_TapSeqInfo* pInfo);
static void TapSeq_fnPlay(ST_TapSeq* pTapSeq, int iLogCh);
static void TapSeq_fnStop(ST_TapSeq* pTapSeq, int iLogCh);
static void TapSeq_fnLoop(ST_TapSeq* pTapSeq, int iLogCh);
static void TapSeq_fnEnd(ST_TapSeq* pTapSeq, int iLogCh);
static void TapSeq_fnCtrl(ST_TapSeq* pTapSeq, int iLogCh);
static void TapSeq_fnPrep(ST_TapSeq* pTapSeq, int iLogCh);			//{{2017/04/02追加:再生準備通知を追加しました。}}
static int TapSeq_fnGetPhrTime(ST_TapSeq* pTapSeq, int iPhr);
static int TapSeq_fnGetPhrNext(ST_TapSeq* pTapSeq, int iPhr);
static int TapSeq_GetPhrType(ST_TapSeq* pTapSeq, int iPhr);
static void TapSeq_AcqLogCh(ST_TapSeq* pTapSeq, int iLogCh);
static void TapLogCh_PlaySubr(ST_TapSeq* pTapSeq, int iLogCh, int iPhr);
static void TapLogCh_LoopSubr(ST_TapSeq* pTapSeq, int iLogCh);
static void TapLogCh_StopSubr(ST_TapSeq* pTapSeq, int iLogCh);
static void TapLogCh_Exec(ST_TapSeq* pTapSeq, int iLogCh, int iElapTime);
static void TapSeqCh_Exec(ST_TapSeq* pTapSeq, int iSeqCh, int iElapTime);
static ST_TapEvtBuf* TapSeq_GetEvtBuf(ST_TapSeq* pTapSeq);
static void TapSeq_AddEvt(ST_TapSeq* pTapSeq, int iEvt, int iLogCh, int iPhr);
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//シーケンサを動的に作成する。
ST_TapSeq* TapSeq_New(const ST_TapSeqInfo* pInfo) {
	ST_TapSeq* pTapSeq;
	int nBytes;
	//初期化情報を検査する。
	TapSeqInfo_Verify(pInfo);
	//メモリを確保する。
	nBytes = sizeof(ST_TapSeq) +					//シーケンサ
		(sizeof(ST_TapDevCh) * ((pInfo->nDevCh+3)&~3)) +	//デバイスチャネル
		(sizeof(ST_TapLogCh) *   pInfo->nLogCh       ) +	//論理チャネル
		(sizeof(ST_TapSeqCh) *   pInfo->nSeqCh       );		//シーケンサチャネル
	if(pInfo->nEvt) {
		nBytes += sizeof(ST_TapEvtBuf) +			//イベントバッファ
			 (sizeof(ST_TapEvt) * (pInfo->nEvt + 1));	//イベント
	}
	pTapSeq = malloc(nBytes);
	if(!pTapSeq) { DIE(); }
	//シーケンサを静的に初期化する。
	TapSeq_Init(pTapSeq, pInfo);
	return pTapSeq;
}
/*---------------------------------------------------------------------------*/
//シーケンサを静的に初期化する。
void TapSeq_Init(ST_TapSeq* pTapSeq, const ST_TapSeqInfo* pInfo) {
	int iDevCh, iLogCh, iSeqCh;
	//初期化情報を検査する。
	TapSeqInfo_Verify(pInfo);
	//初期化情報を格納する。
	pTapSeq->pInfo = pInfo;
	//デバイスチャネルをクリアする。
	for(iDevCh = 0; iDevCh < pTapSeq->pInfo->nDevCh; iDevCh++) {
		ST_TapDevCh* pDevCh = TapSeq_GetDevCh(pTapSeq, iDevCh);
		pDevCh->iLogCh = -1;	//このデバイスチャネルを制御している論理チャネル番号	UINT8_MAX=無し(停止中を意味する)
	}
	//論理チャネルをクリアする。
	for(iLogCh = 0; iLogCh < pTapSeq->pInfo->nLogCh; iLogCh++) {
		ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
		pLogCh->iPhr   = -1;	//フレーズ番号						UINT16_MAX=無し(停止中を意味する)
		pLogCh->iDevCh = -1;	//この論理チャネルが制御しているデバイスチャネル番号	UINT8_MAX=無し(停止中,又は,再生開始時にデバイスチャネルを獲得出来なかった,又は,再生開始後にデバイスチャネルを横取りされた事を意味する)
		pLogCh->iSeqCh = -1;	//この論理チャネルを制御しているシーケンサチャネル番号	UINT8_MAX=無し(シーケンサチャネルに制御されていない事を意味する。停止中を意味するのではない事に注意せよ)
	}
	//シーケンサチャネルをクリアする。
	for(iSeqCh = 0; iSeqCh < pTapSeq->pInfo->nSeqCh; iSeqCh++) {
		ST_TapSeqCh* pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
		pSeqCh->pData  = NULL;	//シーケンスデータ					NULL=無し(停止中を意味する)
	}
	//イベントバッファをクリアする。
	if(pInfo->nEvt) {
		ST_TapEvtBuf* pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
		pEvtBuf->iAddPos = 0;	//要素を書き込む位置
		pEvtBuf->iGetPos = 0;	//要素を読み出す位置
		pEvtBuf->nOvrCnt = 0;	//オーバーランが発生した回数
	}
}
/*---------------------------------------------------------------------------*/
//動的に作成したシーケンサを開放する。
void TapSeq_Free(ST_TapSeq* pTapSeq) {
	free(pTapSeq);
}
/*---------------------------------------------------------------------------*/
//シーケンサを実行する。
// - 普通は割り込み処理から呼び出すと思うが、アプリケーションが任意のコンテキストで呼び出す可能性も有るので、排他制御が必要です。
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;
	//論理チャネルを実行する。
	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()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
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;
	//この論理チャネルを制御しているシーケンサチャネル番号を、確実にクリアする。
	// - アプリケーションから直接呼び出された場合は、(pLogCh->iSeqCh=-1)のままとなる。
	// - シーケンサ処理から呼び出された場合は、この関数が処理を返した後に、シーケンサ処理の中でpLogCh->iSeqChを設定する。
	pLogCh->iSeqCh = -1;
	//ループフレーズ引き継ぎ時間を、確実にクリアする。
	// - 現在の再生を継続する場合も、先頭から再生開始する場合も、どちらにも必要な処理なので、ここで行うのが良い。
	pLogCh->iCont  = 0;
	//再生開始するフレーズのフレーズチェインの最後のフレーズの、フレーズ種別を取得する。
	iPhrType = TapSeq_GetPhrType(pTapSeq, iPhr);
	//再生開始するフレーズのフレーズチェインの最後のフレーズが、ループフレーズならば…
	// - 通常フレーズ(-1),又は,完全フレーズ(-2)ならば、下記の判断を行わず、常に、現在の再生を停止し先頭から再生開始する。
	if(iPhrType >= 0) {
		//論理チャネルが再生中ならば…
		if(pLogCh->iPhr != UINT16_MAX) {
			//再生中のフレーズの最後のフレーズもループフレーズであり,かつ,再生開始するフレーズと同じフレーズチェインならば、再生開始を行わずに現在の再生を継続する。
			//再生中のフレーズの最後のフレーズがループフレーズ以外か,又は,再生開始するフレーズと別のフレーズチェインならば、現在の再生を停止し先頭から再生開始する。
			if(TapSeq_GetPhrType(pTapSeq, pLogCh->iPhr) == iPhrType) {
				//論理チャネルの減衰量とパンを変更する。
				// - 再生開始を行わずに現在の再生を継続する時も、減衰量とパンは新しい値を適用する必要が有る。
				TapLogCh_Ctrl(pTapSeq, iLogCh, iAtt, iPan);
				goto L_RETURN;	//ここまで	ここはENTER_CS～LEAVE_CS区間なので、単純にreturnしてはいけない。
			}
		}
		//再生開始するフレーズのフレーズチェインの最後のフレーズがループフレーズなので、ループ回数を無限ループに置き換える。
		iLoop = 0;
	}
	//論理チャネルを確実に再生停止する。
	TapLogCh_Stop(pTapSeq, iLogCh);
	//ループ回数を格納する。
	pLogCh->iLoop = iLoop;
	//減衰量を格納する。
	pLogCh->iAtt  = iAtt;
	//パンを格納する。
	pLogCh->iPan  = iPan;
	//待ち時間をクリアする。
	// - この後、再生開始処理のサブルーチンの中で、フレーズ時間を加算する。
	pLogCh->iWait = 0;
	//デバイスチャネルを獲得する。
	// - デバイスチャネルの獲得に失敗する場合も有る。
	//   デバイスチャネルの獲得に失敗した場合も、以下の処理を行う。
	TapSeq_AcqLogCh(pTapSeq, iLogCh);
	//再生開始処理のサブルーチンを実行する。
	TapLogCh_PlaySubr(pTapSeq, iLogCh, iPhr);
L_RETURN:	//┐
LEAVE_CS;	//┴逆にしないよう注意せよ。
}
/*---------------------------------------------------------------------------*/
//論理チャネルを再生停止する。
// - TapLogCh_Play(),又は,TapSeqCh_Stop(),又は,TapSeqCh_Exec()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
void TapLogCh_Stop(ST_TapSeq* pTapSeq, int iLogCh) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
ENTER_CS;
	//この論理チャネルを制御しているシーケンサチャネル番号を、確実にクリアする。
	pLogCh->iSeqCh = -1;
	//論理チャネルが停止中ならば、何もせずに帰る。
	if(pLogCh->iPhr == UINT16_MAX) { goto L_RETURN; }	//ここまで	ここはENTER_CS～LEAVE_CS区間なので、単純にreturnしてはいけない。
	//再生中のフレーズのフレーズチェインの最後のフレーズが、完全フレーズ以外ならば…
	// - 完全フレーズならば、論理チャネルはシーケンサチャネルの制御を外れ、自然停止まで継続する。
	if(TapSeq_GetPhrType(pTapSeq, pLogCh->iPhr) != -2/*完全フレーズ*/) {
		//再生停止通知を行う。
		// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
		TapSeq_fnStop(pTapSeq, iLogCh);
		//再生停止処理のサブルーチンを実行する。
		TapLogCh_StopSubr(pTapSeq, iLogCh);
	}
L_RETURN:	//┐
LEAVE_CS;	//┴逆にしないよう注意せよ。
}
/*---------------------------------------------------------------------------*/
//論理チャネルの減衰量とパンを変更する。
// - TapLogCh_Play(),又は,TapSeqCh_Exec()から呼び出された場合は排他制御が不要ですが、アプリケーションが直接呼び出す可能性も有るので、排他制御が必要です。
void TapLogCh_Ctrl(ST_TapSeq* pTapSeq, int iLogCh, int iAtt, int iPan) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
ENTER_CS;
	//論理チャネルが停止中ならば、何もせずに帰る。
	if(pLogCh->iPhr == UINT16_MAX) { goto L_RETURN; }	//ここまで	ここはENTER_CS～LEAVE_CS区間なので、単純にreturnしてはいけない。
	//減衰量とパンが変化しなければ、何もせずに帰る。
	if((pLogCh->iAtt == iAtt) && (pLogCh->iPan == iPan)) { goto L_RETURN; }	//ここまで	ここはENTER_CS～LEAVE_CS区間なので、単純にreturnしてはいけない。
	//減衰量を格納する。
	pLogCh->iAtt = iAtt;
	//パンを格納する。
	pLogCh->iPan = iPan;
	//コントロール通知を行う。
	// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
	TapSeq_fnCtrl(pTapSeq, iLogCh);
L_RETURN:	//┐
LEAVE_CS;	//┴逆にしないよう注意せよ。
}
/*---------------------------------------------------------------------------*/
//シーケンサチャネルを再生開始する。
// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
void TapSeqCh_Play(ST_TapSeq* pTapSeq, int iSeqCh, const void* pData) {
	ST_TapSeqCh* pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
ENTER_CS;
	//シーケンサチャネルを確実に再生停止する。
	TapSeqCh_Stop(pTapSeq, iSeqCh);
	//シーケンスデータを格納する。
	pSeqCh->pData = pData;
	//待ち時間をクリアする。
	pSeqCh->iWait = 0;
	//シーケンスデータの先頭に「待ち時間無し」の処理が有れば、即実行する。
	TapSeqCh_Exec(pTapSeq, iSeqCh, 0);
LEAVE_CS;
}
/*---------------------------------------------------------------------------*/
//シーケンサチャネルを再生停止する。
// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
void TapSeqCh_Stop(ST_TapSeq* pTapSeq, int iSeqCh) {
	ST_TapSeqCh* pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
	int iLogCh;
ENTER_CS;
	//シーケンスデータをクリアする。
	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 = -1;
			//論理チャネルが再生中ならば…
			// - 必ず再生中であるはずだが、念のために確認する事にした。
			if(pLogCh->iPhr != UINT16_MAX) {
				//アプリケーション定義のループフレーズ引き継ぎ時間が0でなく,かつ,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズならば…
				if(pTapSeq->pInfo->iCont && (TapSeq_GetPhrType(pTapSeq, pLogCh->iPhr) >= 0)) {
					//ループフレーズ引き継ぎ時間がまだ開始していなければ、ループフレーズ引き継ぎ時間を開始する。
					// - ループ引き継ぎ時間内に再度この処理が実行された場合は、ループ引き継ぎ時間を再開しない。
					if(!pLogCh->iCont) { pLogCh->iCont = pTapSeq->pInfo->iCont; }
				//アプリケーション定義のループフレーズ引き継ぎ時間が0か,又は,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズでなければ…
				} else {
					//論理チャネルを再生停止する。
					TapLogCh_Stop(pTapSeq, iLogCh);
				}
			}
		}
	}
LEAVE_CS;
}
/*---------------------------------------------------------------------------*/
//デバイスチャネルを取得する。
ST_TapDevCh* TapSeq_GetDevCh(ST_TapSeq* pTapSeq, int iDevCh) {
	if((unsigned)iDevCh >= (unsigned)pTapSeq->pInfo->nDevCh) { DIE(); }	//デバイスチャネル番号が範囲外ならばエラー
	return (ST_TapDevCh*)(pTapSeq + 1) + iDevCh;
}
/*---------------------------------------------------------------------------*/
//論理チャネルを取得する。
ST_TapLogCh* TapSeq_GetLogCh(ST_TapSeq* pTapSeq, int iLogCh) {
	if((unsigned)iLogCh >= (unsigned)pTapSeq->pInfo->nLogCh) { DIE(); }	//論理チャネル番号が範囲外ならばエラー
	return (ST_TapLogCh*)(TapSeq_GetDevCh(pTapSeq, 0) + ((pTapSeq->pInfo->nDevCh+3)&~3)) + iLogCh;
}
/*---------------------------------------------------------------------------*/
//シーケンサチャネルを取得する。
ST_TapSeqCh* TapSeq_GetSeqCh(ST_TapSeq* pTapSeq, int iSeqCh) {
	if((unsigned)iSeqCh >= (unsigned)pTapSeq->pInfo->nSeqCh) { DIE(); }	//シーケンサチャネル番号が範囲外ならばエラー
	return (ST_TapSeqCh*)(TapSeq_GetLogCh(pTapSeq, 0) + pTapSeq->pInfo->nLogCh) + iSeqCh;
}
/*---------------------------------------------------------------------------*/
//イベントを取得する。
ST_TapEvt* TapSeq_EvtGet(ST_TapSeq* pTapSeq) {
	ST_TapEvtBuf* pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
//{{2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
	ST_TapEvt* TBL_Buf = (ST_TapEvt*)(pEvtBuf + 1);
//}}2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//{{cliprinb.cのRinBuf_Get()と同様の処理です。
	ST_TapEvt* pBuf;
ENTER_CS;
	{
		//今回の要素を読み出す位置を取得する。
		int iGetPos = pEvtBuf->iGetPos;
		//バッファが空でなければ…
		if(iGetPos != pEvtBuf->iAddPos) {
			//今回の要素を読み出す位置へのポインタを求める。
//{{2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//			pBuf = &pEvtBuf->TBL_Buf[iGetPos];
//↓2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
			pBuf = &TBL_Buf[iGetPos];
//}}2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
			//次回の要素を読み出す位置を進める。
			if(++iGetPos > pTapSeq->pInfo->nEvt) { iGetPos = 0; }
			//次回の要素を読み出す位置を格納する。
			pEvtBuf->iGetPos = iGetPos;
		//バッファが空ならば…
		} else {
			//NULLポインタを返す。
			pBuf = NULL;
		}
	}
LEAVE_CS;
	return pBuf;
//}}cliprinb.cのRinBuf_Get()と同様の処理です。
}
/*---------------------------------------------------------------------------*/
//イベントのオーバーランが発生した回数を取得する。
int TapSeq_EvtOvr(ST_TapSeq* pTapSeq) {
	ST_TapEvtBuf* pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
//{{cliprinb.cのRinBuf_Ovr()と同様の処理です。
	int nOvrCnt;
ENTER_CS;
	{
		//オーバーランが発生した回数を取得する。
		nOvrCnt = pEvtBuf->nOvrCnt;
		//オーバーランが発生した回数を確実にクリアする。
		pEvtBuf->nOvrCnt = 0;
	}
LEAVE_CS;
	return nOvrCnt;
//}}cliprinb.cのRinBuf_Ovr()と同様の処理です。
}
/*****************************************************************************
 *	ローカル関数宣言
 *****************************************************************************/
//初期化情報を検査する。
static void TapSeqInfo_Verify(const ST_TapSeqInfo* pInfo) {
//{{2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
////{{2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	const uint8_t** TBL_TapSet = (const uint8_t**)(pInfo + 1);
////}}2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	int iDevCh, iLogCh;
//↓2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
	int iDevCh, iLogCh, iSeqCh;
//}}2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
	if(!pInfo->nPhr				||	//フレーズ数				1～UINT16_MAX
	   !pInfo->nDevCh			||	//デバイスチャネル数			1～UINT8_MAX
	   !pInfo->nLogCh			||	//論理チャネル数			1～UINT8_MAX
	   !pInfo->nSeqCh			||	//シーケンサチャネル数			1～UINT8_MAX
	// !pInfo->iCont			||	//ループフレーズ引き継ぎ時間[ms]	0～UINT8_MAX	検査不要
	// !pInfo->iPrep			||	//再生準備時間[ms]			0～UINT16_MAX	検査不要	//{{2017/04/02追加:再生準備通知を追加しました。}}
	  (!pInfo->nEvt ^ !pInfo->iEvtMsk)	||	//イベント記録数,イベントマスク		0～UINT8_MAX	(nEvt=0)ならば(iEvtMsk=0)でなければならない。(nEvt!=0)ならば(iEvtMsk!=0)でなければならない。
	   !pInfo->fnPlay			||	//再生開始通知
	   !pInfo->fnStop			||	//再生停止通知
	   !pInfo->fnLoop			||	//自然ループ通知
	   !pInfo->fnEnd			||	//自然停止通知
	   !pInfo->fnNext			||	//自然遷移通知				//{{2016/08/02追加:自然遷移通知を追加しました。}}
	  (!pInfo->fnPrep && pInfo->iPrep)	||	//再生準備通知				//{{2017/04/02追加:再生準備通知を追加しました。}}
	   !pInfo->fnCtrl			||	//コントロール通知
	   !pInfo->fnGetPhrTime			||	//フレーズ時間[ms]を取得する。
	   !pInfo->fnGetPhrNext) { DIE(); }		//フレーズチェインの次のフレーズ番号を取得する。
	for(iLogCh = 0; iLogCh < pInfo->nLogCh; iLogCh++) {
//{{2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//		const uint8_t* pTapSet = pInfo->TBL_TapSet[iLogCh];	//TAPセット
//↓2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//		const uint8_t* pTapSet = TBL_TapSet[iLogCh];	//TAPセット
//↓2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
		const uint8_t* pTapSet = pInfo->pTBL_TapSet[iLogCh];	//TAPセット
//}}2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
		uint8_t mask[256/8];
		memset(mask, 0, sizeof mask);
		while((iDevCh = *pTapSet++) != UINT8_MAX) {	//空のTAPセット(先頭がUINT8_MAX)も可とする。フェーダーとして利用する論理チャネル等の、デバイスチャネルの獲得が不要である場合に、空のTAPセットを設定せよ。
			if((unsigned)iDevCh >= (unsigned)pInfo->nDevCh) { DIE(); }	//デバイスチャネル番号が範囲外ならばエラー
			if(mask[iDevCh/8] &  (1<<(iDevCh&7))) { DIE(); }		//一つの論理チャネルのTAPセット内で、同じデバイスチャネル番号を複数回指定してはいけない。
			   mask[iDevCh/8] |= (1<<(iDevCh&7));
		}
	}
//{{2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
	for(iSeqCh = 0; iSeqCh < pInfo->nSeqCh; iSeqCh++) {
		iLogCh = pInfo->pTBL_BaseCh[iSeqCh];
		if((unsigned)iLogCh >= (unsigned)pInfo->nLogCh) { DIE(); }	//ベース論理チャネル番号が範囲外ならばエラー
	}
//}}2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
}
/*---------------------------------------------------------------------------*/
//再生開始通知を行う。
static void TapSeq_fnPlay(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_Play, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnPlay)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnPlay)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
/*---------------------------------------------------------------------------*/
//再生停止通知を行う。
static void TapSeq_fnStop(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_Stop, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnStop)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnStop)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
/*---------------------------------------------------------------------------*/
//自然ループ通知を行う。
static void TapSeq_fnLoop(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_Loop, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnLoop)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnLoop)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
/*---------------------------------------------------------------------------*/
//自然停止通知を行う。
static void TapSeq_fnEnd(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_End, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnEnd)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnEnd)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
/*---------------------------------------------------------------------------*/
//{{2016/08/02追加:自然遷移通知を追加しました。
//自然遷移通知を行う。
static void TapSeq_fnNext(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_Next, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnNext)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnNext)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
//}}2016/08/02追加:自然遷移通知を追加しました。
/*---------------------------------------------------------------------------*/
//コントロール通知を行う。
static void TapSeq_fnCtrl(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	TapSeq_AddEvt(pTapSeq, TapEvt_Ctrl, iLogCh, pLogCh->iPhr);		//イベントバッファが存在しなければ何もしない。イベントマスクで指定されていなければ何もしない。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	(*pTapSeq->pInfo->fnCtrl)(iLogCh, pLogCh);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	(*pTapSeq->pInfo->fnCtrl)(pTapSeq, iLogCh, pLogCh);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
}
/*---------------------------------------------------------------------------*/
//{{2017/04/02追加:再生準備通知を追加しました。
//再生準備通知を行う。
static void TapSeq_fnPrep(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//(再生準備通知は、イベントに記録する必要は有りません。)
	(*pTapSeq->pInfo->fnPrep)(pTapSeq, iLogCh, pLogCh);
}
//}}2017/04/02追加:再生準備通知を追加しました。
/*---------------------------------------------------------------------------*/
//フレーズ時間[ms]を取得する。
static int TapSeq_fnGetPhrTime(ST_TapSeq* pTapSeq, int iPhr) {
	int iTime;
	if((unsigned)iPhr >= (unsigned)pTapSeq->pInfo->nPhr) { DIE(); }		//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	iTime = (*pTapSeq->pInfo->fnGetPhrTime)(iPhr);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	iTime = (*pTapSeq->pInfo->fnGetPhrTime)(pTapSeq, iPhr);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	if(iTime < 0) { DIE(); }						//(0～INT_MAX)の範囲外ならばエラー		ここで停止した場合はアプリケーション定義のコールバック関数のバグである可能性が高い。
	return iTime;
}
/*---------------------------------------------------------------------------*/
//フレーズチェインの次のフレーズ番号を取得する。
static int TapSeq_fnGetPhrNext(ST_TapSeq* pTapSeq, int iPhr) {
	if((unsigned)iPhr >= (unsigned)pTapSeq->pInfo->nPhr) { DIE(); }		//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
//{{2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
//	iPhr = (*pTapSeq->pInfo->fnGetPhrNext)(iPhr);
//↓2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	iPhr = (*pTapSeq->pInfo->fnGetPhrNext)(pTapSeq, iPhr);
//}}2016/10/13変更:fnPlay,fnStop,fnLoop,fnEnd,fnNext,fnCtrl,fnGetPhrTime,fnGetPhrNextの引数に、pTapSeqを追加しました。
	if((iPhr < -2) || (iPhr >= pTapSeq->pInfo->nPhr)) { DIE(); }		//-2未満,又は,フレーズ番号が範囲外ならばエラー	ここで停止した場合はアプリケーション定義のコールバック関数のバグである可能性が高い。
	return iPhr;
}
/*---------------------------------------------------------------------------*/
//フレーズチェインの最後のフレーズの、フレーズ種別を取得する。
static int TapSeq_GetPhrType(ST_TapSeq* pTapSeq, int iPhr) {
	if((unsigned)iPhr >= (unsigned)pTapSeq->pInfo->nPhr) { DIE(); }		//フレーズ番号が範囲外ならばエラー		ここで停止した場合は当モジュールのバグである可能性が高い。
	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区間から呼び出されるので、当関数内では排他制御が不要です。
static void TapSeq_AcqLogCh(ST_TapSeq* pTapSeq, int iLogCh) {
	ST_TapLogCh* pLogCh;
	ST_TapDevCh* pDevCh;
//{{2017/02/09削除:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
////{{2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	const uint8_t** TBL_TapSet;
////}}2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//}}2017/02/09削除:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
	const uint8_t* pTapSet;
//{{2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
//	int iDevCh, iDevChSel, iLogChMax;
//↓2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
	int iDevCh, iDevChSel, iLogChMin;
//}}2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
	//論理チャネルを取得する。
	pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//論理チャネルが、既にデバイスチャネルを制御している事は、起こり得ないはずである。
	if(pLogCh->iDevCh != UINT8_MAX) { DIE(); }	//ここでエラー停止した場合、当関数を呼び出す前にTapLogCh_Stop()を呼び出す事を、忘れている可能性が高い。
	//TAPセット(この論理チャネルで使用するデバイスチャネル番号のリスト)を取得する。
//{{2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	pTapSet = pTapSeq->pInfo->TBL_TapSet[iLogCh];	//TAPセット
//↓2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//	TBL_TapSet = (const uint8_t**)(pTapSeq->pInfo + 1);
//	pTapSet = TBL_TapSet[iLogCh];	//TAPセット
//↓2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
	pTapSet = pTapSeq->pInfo->pTBL_TapSet[iLogCh];	//TAPセット
//}}2017/02/09変更:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
//{{2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
//	//デバイスチャネル番号が未決定(-1)，最大の論理チャネル番号をこの論理チャネル番号としておく。
//	iDevChSel = -1, iLogChMax = iLogCh;
//↓2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
	//デバイスチャネル番号が未決定(-1)，最小の論理チャネル番号をこの論理チャネル番号としておく。
	iDevChSel = -1, iLogChMin = iLogCh;
//}}2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
	//TAPセットの終端(UINT8_MAX)に到達するまで…
	while((iDevCh = *pTapSet++) != UINT8_MAX) {	//空のTAPセット(先頭がUINT8_MAX)も可とする。フェーダーとして利用する論理チャネル等の、デバイスチャネルの獲得が不要である場合に、空のTAPセットを設定せよ。
		//デバイスチャネルを取得する。
		pDevCh = TapSeq_GetDevCh(pTapSeq, iDevCh);
		//デバイスチャネルが、既にこの論理チャネルに制御されている事は、起こり得ないはずである。
		if(pDevCh->iLogCh == iLogCh) { DIE(); }
//{{2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
//		//デバイスチャネルを制御している論理チャネルが、この論理チャネルよりも大きいか,又は,停止中(UINT8_MAX)ならば、デバイスチャネルを獲得出来る。
//		//停止中のデバイスチャネルが有ればそれを獲得し、停止中のデバイスチャネルが無ければ、最大の論理チャネル番号に制御されているデバイスチャネルを獲得する。
//		//この論理チャネルよりも大きい論理チャネル番号に制御されているデバイスチャネルも無ければ、獲得に失敗する。
//		if(pDevCh->iLogCh > iLogChMax) {
//			iDevChSel = iDevCh;
//			iLogChMax = pDevCh->iLogCh;
//		}
//↓2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
		//デバイスチャネルを制御している論理チャネルが、この論理チャネルよりも小さいか,又は,停止中(UINT8_MAX)ならば、デバイスチャネルを獲得出来る。
		//停止中のデバイスチャネルが有ればそれを獲得し、停止中のデバイスチャネルが無ければ、最小の論理チャネル番号に制御されているデバイスチャネルを獲得する。
		//この論理チャネルよりも小さい論理チャネル番号に制御されているデバイスチャネルも無ければ、獲得に失敗する。
		if(pDevCh->iLogCh == UINT8_MAX) {
			iDevChSel = iDevCh;
			break;	//ここまで
		}
		if(pDevCh->iLogCh < iLogChMin) {
			iDevChSel = iDevCh;
			iLogChMin = pDevCh->iLogCh;
		}
//}}2016/02/16変更:TapSeq_AcqLogCh()にて、デバイスチャネルを横取りする時の、論理チャネル番号の優先度の大小を逆にしました。
	}
	//デバイスチャネルを獲得出来たら…
	if(iDevChSel != -1) {
		//この論理チャネルが制御しているデバイスチャネル番号を格納する。
		pLogCh->iDevCh = iDevChSel;
		//デバイスチャネルを取得する。
		pDevCh = TapSeq_GetDevCh(pTapSeq, iDevChSel);
		//このデバイスチャネルが、他の論理チャネルに制御されてたら…
		if(pDevCh->iLogCh != UINT8_MAX) {
			//このデバイスチャネルを制御している論理チャネルを取得する。
			pLogCh = TapSeq_GetLogCh(pTapSeq, pDevCh->iLogCh);
			//その論理チャネルが制御しているデバイスチャネルが、このデバイスチャネルである事を確認する。
			if(pLogCh->iDevCh != iDevChSel) { DIE(); }
			//その論理チャネルが制御しているデバイスチャネルを、クリアする。
			pLogCh->iDevCh = -1;
		}
		//このデバイスチャネルを制御している論理チャネル番号を格納する。
		pDevCh->iLogCh = iLogCh;
	}
}
/*---------------------------------------------------------------------------*/
//再生開始処理のサブルーチン。
// - TapLogCh_Play()のENTER_CS～LEAVE_CS区間,又は,TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
static void TapLogCh_PlaySubr(ST_TapSeq* pTapSeq, int iLogCh, int iPhr) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//フレーズ番号を格納する。
	pLogCh->iPhr   = iPhr;
	//フレーズ時間を取得し、待ち時間に加算する。
	pLogCh->iWait += TapSeq_fnGetPhrTime(pTapSeq, pLogCh->iPhr);
	//再生開始通知を行う。
	// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
	TapSeq_fnPlay(pTapSeq, iLogCh);
}
/*---------------------------------------------------------------------------*/
//{{2016/08/02追加:自然遷移通知を追加しました。
//自然遷移処理のサブルーチン。
// - TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
static void TapLogCh_NextSubr(ST_TapSeq* pTapSeq, int iLogCh, int iPhr) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//フレーズ番号を格納する。
	pLogCh->iPhr   = iPhr;
	//フレーズ時間を取得し、待ち時間に加算する。
	pLogCh->iWait += TapSeq_fnGetPhrTime(pTapSeq, pLogCh->iPhr);
	//自然遷移通知を行う。
	// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
	TapSeq_fnNext(pTapSeq, iLogCh);
}
//}}2016/08/02追加:自然遷移通知を追加しました。
/*---------------------------------------------------------------------------*/
//自然ループ処理のサブルーチン。
// - TapLogCh_Exec()から呼び出されるので、当関数内では排他制御が不要です。
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()から呼び出されるので、当関数内では排他制御が不要です。
static void TapLogCh_StopSubr(ST_TapSeq* pTapSeq, int iLogCh) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//論理チャネルを停止中とする。
	pLogCh->iPhr = -1;
	//この論理チャネルが制御しているデバイスチャネルが有れば…
	if(pLogCh->iDevCh != UINT8_MAX) {
		//デバイスチャネルを取得する。
		ST_TapDevCh* pDevCh = TapSeq_GetDevCh(pTapSeq, pLogCh->iDevCh);
		//このデバイスチャネルを制御している論理チャネルが、この論理チャネルである事を確認する。
		if(pDevCh->iLogCh != iLogCh) { DIE(); }
		//デバイスチャネルを停止中とする。
		pDevCh->iLogCh = -1;
		//この論理チャネルが制御しているデバイスチャネルを、クリアする。
		pLogCh->iDevCh = -1;
	}
}
/*---------------------------------------------------------------------------*/
//論理チャネルを実行する。
// - TapSeq_Exec()のENTER_CS～LEAVE_CS区間から呼び出されるので、当関数内では排他制御が不要です。
static void TapLogCh_Exec(ST_TapSeq* pTapSeq, int iLogCh, int iElapTime) {
	//論理チャネルを取得する。
	ST_TapLogCh* pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
	//論理チャネルが停止中ならば、何もせずに帰る。
	if(pLogCh->iPhr == UINT16_MAX) { return; }	//ここまで
	//ループフレーズ引き継ぎ時間が開始してしたら…
	if(pLogCh->iCont) {
		//ループフレーズ引き継ぎ時間を減らす。
		UVAR_SUB(pLogCh->iCont, iElapTime);
		//ループフレーズ引き継ぎ時間が0になったら…
		if(!pLogCh->iCont) {
			//再生停止通知を行う。
			// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
			TapSeq_fnStop(pTapSeq, iLogCh);
			//再生停止処理のサブルーチンを実行する。
			TapLogCh_StopSubr(pTapSeq, iLogCh);
			return;	//ここまで
		}
	}
//{{2017/04/02変更:再生準備通知を追加しました。
//	//待ち時間を減らす。
//	VAR_SUB(pLogCh->iWait, iElapTime);
//↓2017/04/02変更:再生準備通知を追加しました。
	{
		//再生準備開始を判断するために、待ち時間を減らす前の待ち時間を覚えておく。
		int save_iWait = pLogCh->iWait;
		//待ち時間を減らす。
		VAR_SUB(pLogCh->iWait, iElapTime);
		//待ち時間が0以下になった場合は、再生準備通知を行わない。
		// - 待ち時間が0以下になった場合は、次のフレーズが有ればこの下で再生開始するので、再生準備を行っても無駄だからです。
		if(pLogCh->iWait > 0) {
			//待ち時間を減らす前の待ち時間が再生準備時間以上で、待ち時間を減らした後の待ち時間が再生準備時間未満ならば…
			if((   save_iWait >= pTapSeq->pInfo->iPrep) &&
			   (pLogCh->iWait <  pTapSeq->pInfo->iPrep)) {	//再生準備を行わない場合は(pTapSeq->pInfo->iPrep==0)なので、この条件が成立する事は無い。
				//再生準備通知を行う。
				// - 実際に再生準備通知を行うかどうかは、アプリケーション定義のコールバック関数にて判断して下さい。
				//   例えば、以下のように処理して下さい。
				//   □DxlibHelper.cppのTapSeq_fnPrep()より引用:
				//   │//再生準備処理は、論理チャネルがデバイスチャネルを獲得していなくても行う。
				//   │{
				//   │    //次のフレーズを取得する。
				//   │    int iPhr = TapSeq_fnGetPhrNext(pTapSeq, pLogCh->iPhr);
				//   │    //現在のフレーズが、通常フレーズ(-1),又は,完全フレーズ(-2)ならば…
				//   │    if(iPhr < 0) {
				//   │        //最後のループでなければ(=無限ループ,又は,ループ回数が残っていたら)…
				//   │        if(pLogCh->iLoop != 1) {
				//   │            //現在のフレーズの再生準備を行う。
				//   │            prep_phr(pLogCh->iPhr);
				//   │        }
				//   │    //現在のフレーズが、ループフレーズ,又は,中間フレーズならば…
				//   │    } else {
				//   │        //次のフレーズの再生準備を行う。
				//   │        prep_phr(iPhr);
				//   │    }
				//   │}
				// - 上記はあくまで一例であり、どのように処理するのが適切かは、デバイス事に検討して実装して下さい。
				//   例えば、P/ECEやXMP-1のようなレイテンシの小さいデバイスならば、再生準備を行う必要は有りません。
				//   再生準備を行う必要が無いデバイスならば、(ST_TapSeqInfo.iPrep=0)にしてシーケンサを初期化すれば、再生準備通知は行われません。
				// - レイテンシの大きいデバイスならば、再生準備を行うのが望ましいですが、行わなくても動作はします。
				//   再生準備は、音が途切れたりするのを低減するためだけであり、必須ではありません。
				TapSeq_fnPrep(pTapSeq, iLogCh);
			}
		}
	}
//}}2017/04/02変更:再生準備通知を追加しました。
	//待ち時間が0以下である間、繰り返す。
	while(pLogCh->iWait <= 0) {
		//次のフレーズを取得する。
		int iPhr = TapSeq_fnGetPhrNext(pTapSeq, pLogCh->iPhr);
	    //{{2016/01/11修正:完全フレーズの自然停止処理のバグを修正しました。通常フレーズと同様に自然停止すべきところ、中間フレーズと同様に次のフレーズを再生していました。TapLogCh_Exec()の処理で(-2)を間違って次のフレーズ番号と見なしてしまっていたバグです。
	    //	//現在のフレーズが、通常フレーズだったならば…
	    //	if(iPhr == -1) {
	    //↓2016/01/11修正:完全フレーズの自然停止処理のバグを修正しました。通常フレーズと同様に自然停止すべきところ、中間フレーズと同様に次のフレーズを再生していました。TapLogCh_Exec()の処理で(-2)を間違って次のフレーズ番号と見なしてしまっていたバグです。
		//現在のフレーズが、通常フレーズ(-1),又は,完全フレーズ(-2)だったならば…
		if(iPhr < 0) {
	    //}}2016/01/11修正:完全フレーズの自然停止処理のバグを修正しました。通常フレーズと同様に自然停止すべきところ、中間フレーズと同様に次のフレーズを再生していました。TapLogCh_Exec()の処理で(-2)を間違って次のフレーズ番号と見なしてしまっていたバグです。
			//無限ループでなく,かつ,ループ回数を減らして0になったら…
			if(pLogCh->iLoop && !--pLogCh->iLoop) {
				//自然停止通知を行う。
				// - この論理チャネルが制御しているデバイスチャネル番号が無い場合も、同じ処理で良い。
				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 {
//{{2016/08/02変更:自然遷移通知を追加しました。
//			//再生開始処理のサブルーチンを実行する。
//			TapLogCh_PlaySubr(pTapSeq, iLogCh, iPhr);
//↓2016/08/02変更:自然遷移通知を追加しました。
			//自然遷移処理のサブルーチンを実行する。
			TapLogCh_NextSubr(pTapSeq, iLogCh, iPhr);
//}}2016/08/02変更:自然遷移通知を追加しました。
		}
	}
}
/*---------------------------------------------------------------------------*/
//シーケンサチャネルを実行する。
// - TapSeq_Exec(),又は,TapSeqCh_Play()のENTER_CS～LEAVE_CS区間から呼び出されるので、当関数内では排他制御が不要です。
static void TapSeqCh_Exec(ST_TapSeq* pTapSeq, int iSeqCh, int iElapTime) {
	ST_TapSeqCh* pSeqCh = TapSeq_GetSeqCh(pTapSeq, iSeqCh);
	ST_TapLogCh* pLogCh;
//{{2017/02/11変更:ループ制御を追加しました。
//	int iData, iTime, iLogCh, iPhr, iLoop, iAtt, iPan;
//↓2017/02/11変更:ループ制御を追加しました。
	int iData, iTime, iDist, iLogCh, iPhr, iLoop, iAtt, iPan;
	const uint8_t* pData;
//}}2017/02/11変更:ループ制御を追加しました。
	//シーケンサチャネルが停止中ならば、何もせずに帰る。
	if(!pSeqCh->pData) { return; }	//ここまで
	//待ち時間を減らす。
	VAR_SUB(pSeqCh->iWait, iElapTime);
	//待ち時間が0以下である間、繰り返す。
	while(pSeqCh->iWait <= 0) {
		switch((iData = *pSeqCh->pData++)) {
		default:DIE();
		//シーケンスデータの終端
		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++) + 1);
			} while(--iData);
			//待ち時間を増やす。
			VAR_ADD(pSeqCh->iWait, iTime);
			break;
		//コントロールチェンジ
		case 0x05://iLogCh iAtt
		case 0x06://iLogCh      iPan
		case 0x07://iLogCh iAtt iPan
			//論理チャネル番号を取得する。
			iLogCh = *pSeqCh->pData++;
//{{2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//ベース論理チャネル番号を加算する。
			iLogCh += pTapSeq->pInfo->pTBL_BaseCh[iSeqCh];
//}}2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//論理チャネルを取得する。
			pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//減衰量,パンを取得する。
			iAtt   = pLogCh->iAtt; if(iData & 1) { iAtt =         *pSeqCh->pData++; }	//既定値:現在値
			iPan   = pLogCh->iPan; if(iData & 2) { iPan = (int8_t)*pSeqCh->pData++; }	//既定値:現在値
			//コントロールチェンジを実行する。
			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
//{{2017/02/13追加:'play.w'命令を追加しました。
		//論理チャネル再生開始(ウェイト付き)
		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
//}}2017/02/13追加:'play.w'命令を追加しました。
			//論理チャネル番号を取得する。
			iLogCh = *pSeqCh->pData++;
//{{2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//ベース論理チャネル番号を加算する。
			iLogCh += pTapSeq->pInfo->pTBL_BaseCh[iSeqCh];
//}}2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//論理チャネルを取得する。
			pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
			//フレーズ番号を取得する。
			iPhr   = *pSeqCh->pData++;
			iPhr <<= 8;
			iPhr  |= *pSeqCh->pData++;
			//減衰量,パン,ループ回数を取得する。
			iLoop  = 1; if(iData & 1) { iLoop =         *pSeqCh->pData++; }			//既定値:ループ回数=1
			iAtt   = 0; if(iData & 2) { iAtt  =         *pSeqCh->pData++; }			//既定値:減衰量=0(減衰無し)
			iPan   = 0; if(iData & 4) { iPan  = (int8_t)*pSeqCh->pData++; }			//既定値:パン=0(中央)
			//論理チャネルを再生開始する。
			TapLogCh_Play(pTapSeq, iLogCh, iPhr, iLoop, iAtt, iPan);
			//この論理チャネルを制御しているシーケンサチャネル番号を設定する。
			pLogCh->iSeqCh = iSeqCh;
//{{2017/02/13追加:'play.w'命令を追加しました。
			//論理チャネル再生開始(ウェイト付き)ならば…
			if(iData & 0x10) {
				//フレーズ時間を取得する。
				iTime = TapSeq_fnGetPhrTime(pTapSeq, iPhr);
				//待ち時間を増やす。
				VAR_ADD(pSeqCh->iWait, iTime);
			}
//}}2017/02/13追加:'play.w'命令を追加しました。
			break;
		//論理チャネル再生停止
		case 0x10://iLogCh
			//論理チャネル番号を取得する。
			iLogCh = *pSeqCh->pData++;
//{{2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//ベース論理チャネル番号を加算する。
			iLogCh += pTapSeq->pInfo->pTBL_BaseCh[iSeqCh];
//}}2017/02/09追加:ST_TapSeqInfo構造体に、pTBL_BaseChフィールドを追加しました。それに伴って統一のために、TBL_TapSetフィールドも配列でなく配列へのポインタに変更して、pTBL_TapSetフィールドとしました。
			//論理チャネルを取得する。
			pLogCh = TapSeq_GetLogCh(pTapSeq, iLogCh);
#ifdef  PRODUCT_SPECIFIC_WORKAROUND
			//論理チャネルが再生中,かつ,再生中のフレーズのフレーズチェインの最後のフレーズがループフレーズならば、論理チャネル再生停止コマンドを無視する。
			if((pLogCh->iPhr != UINT16_MAX) && (TapSeq_GetPhrType(pTapSeq, pLogCh->iPhr) >= 0)) { break; }
#endif//PRODUCT_SPECIFIC_WORKAROUND
			//論理チャネルを再生停止する。
			TapLogCh_Stop(pTapSeq, iLogCh);
			break;
//{{2017/02/11追加:ループ制御を追加しました。
		//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++) + 1);
			} while(--iData);
		    //}}1～4バイトジャンプのデコード方法は、既存の1～4バイトウェイトのデコード方法と同じです。
			//ジャンプ命令のコマンド番号が格納されているアドレスからの逆方向相対位置へジャンプする。
			pSeqCh->pData = pData - iDist;
			break;
//}}2017/02/11追加:ループ制御を追加しました。
		}
	}
}
/*---------------------------------------------------------------------------*/
//イベントバッファを取得する。
static ST_TapEvtBuf* TapSeq_GetEvtBuf(ST_TapSeq* pTapSeq) {
	if(!pTapSeq->pInfo->nEvt) { DIE(); }					//イベントバッファが存在しなければエラー
	return (ST_TapEvtBuf*)(TapSeq_GetSeqCh(pTapSeq, 0) + pTapSeq->pInfo->nSeqCh);
}
/*---------------------------------------------------------------------------*/
//イベントを追加する。
static void TapSeq_AddEvt(ST_TapSeq* pTapSeq, int iEvt, int iLogCh, int iPhr) {
	if(pTapSeq->pInfo->nEvt &&						//イベントバッファが存在しなければ何もしない。
	  (pTapSeq->pInfo->iEvtMsk & (1<<iEvt))) {				//イベントマスクで指定されていなければ何もしない。
		ST_TapEvtBuf* pEvtBuf = TapSeq_GetEvtBuf(pTapSeq);
//{{2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
		ST_TapEvt* TBL_Buf = (ST_TapEvt*)(pEvtBuf + 1);
//}}2016/08/19追加:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//{{cliprinb.cのRinBuf_Add()と同様の処理です。
ENTER_CS;
		{
			//今回の要素を書き込む位置を取得する。
			int iAddPos = pEvtBuf->iAddPos;
			//今回の要素を書き込む位置へのポインタを求める。
//{{2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
//			ST_TapEvt* pBuf = &pEvtBuf->TBL_Buf[iAddPos];
//↓2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
			ST_TapEvt* pBuf = &TBL_Buf[iAddPos];
//}}2016/08/19変更:ST_TapEvtBuf.TBL_Buf[0]フィールド,及び,ST_TapSeqInfo.TBL_TapSet[0]フィールドを廃止して、処理で明示的にアドレスを求めるようにしました。フィールドの宣言を廃止しただけで、メモリレイアウトは変わっていません。
			//次回の要素を書き込む位置を進める。
			if(++iAddPos > pTapSeq->pInfo->nEvt) { iAddPos = 0; }
			//次回の要素を書き込む位置が、要素を読み出す位置に一致しなければ…
			// - 即ち、今回の要素を書き込む位置が、現在アプリケーションが参照中(かも知れない)の位置(=前回TapSeq_EvtGet()が返した位置)よりも前ならば…
			if(iAddPos != pEvtBuf->iGetPos) {
				//次回の要素を書き込む位置を格納する。
				pEvtBuf->iAddPos = iAddPos;
				//今回の要素を書き込む。
				pBuf->iEvt   = iEvt;
				pBuf->iLogCh = iLogCh;
				pBuf->iPhr   = iPhr;
			//次回の要素を書き込む位置が、要素を読み出す位置に一致したら…
			// - 即ち、今回要素を書き込む位置が、現在アプリケーションが参照中(かも知れない)の位置(=前回TapSeq_EvtGet()が返した位置)ならば…
			} else {
				//今回の要素を書き込む事は出来ない。オーバーランが発生した回数を増やす。
				if(pEvtBuf->nOvrCnt < UINT16_MAX) { pEvtBuf->nOvrCnt++; }
			}
		}
LEAVE_CS;
//}}cliprinb.cのRinBuf_Add()と同様の処理です。
	}
}
/****************************************************************************
 *	dTapSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_TAPDEF_BIN
void TapSeqCh_PlayNo(ST_TapSeq* pTapSeq, int iTapSeqCh, const void* pTapDefBin, int iTapSeqNo) {
	const int* const TBL_TapSeqNo = pTapDefBin;						//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
	const int nTapSeqNo = TBL_TapSeqNo[0];							//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
	const unsigned char* const TBL_TapSeq = (const unsigned char*)&TBL_TapSeqNo[nTapSeqNo];	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
	if((iTapSeqNo <= 0) || (iTapSeqNo >= nTapSeqNo)) { DIE(); }
	TapSeqCh_Play(pTapSeq, iTapSeqCh, &TBL_TapSeq[TBL_TapSeqNo[iTapSeqNo]]);
}
#endif//USE_TAPDEF_BIN
/*****************************************************************************
 *	
 *****************************************************************************/
//■ﾒﾓ:YMZ871のﾙｰﾌﾟ処理について
//YMZ871のﾙｰﾌﾟ処理は､ﾏﾆｭｱﾙと実機の挙動が違う｡
//①ﾏﾆｭｱﾙの説明を読むと､以下のような処理であると読み取れるのだが:
//│//参照資料:｢YMZ871ｱﾌﾟﾘｹｰｼｮﾝﾏﾆｭｱﾙ｣(5MZ871A40.pdf)｢3.4.1.3.ﾙｰﾌﾟ処理｣
//│for(;;) {
//│  1回再生;
//│  if(LOOP_x != 255) {
//│    if(!LOOP_x) { break; }
//│    LOOP_x--;
//│  }
//│}
//②実際の挙動から推測すると､実際の内部処理は以下のようになっていると思う:
//│内部ｶｳﾝﾀ = 0;
//│for(;;) {
//│  1回再生;
//│  if(内部ｶｳﾝﾀ < 255) { 内部ｶｳﾝﾀ++; }
//│  if(内部ｶｳﾝﾀ > LOOP_x) { break; }
//│}
//もし①ならば､LOOP_xが変化する瞬間にPVUP_x=1を実行すると競合状態が生じてしまう｡
//それでは問題が有るので､内部処理が①ではなく②である方が､理にかなっている｡
//途中でLOOP_xを書き換えてPVUP_x=1を実行すると､②に沿った挙動になる事も確認した｡
//従って､上記の推測②は合っていると思う｡
//■ﾒﾓ:上記を踏まえての注意点
//ｱﾌﾟﾘｹｰｼｮﾝ定義のfnCtrlが､PVUP_x=1を行う際､LOOP_xにpLogCh->iLoopを設定してはいけない｡
//pLogCh->iLoopは上記②で言うところの(LOOP_x-内部ｶｳﾝﾀ)相当であり､LOOP_xとは違う値だ｡
//もし､LOOP_xにpLogCh->iLoopを設定してPVUP_x=1を行うと､残り回数が余計に減ってしまう｡
//従って､ｱﾌﾟﾘｹｰｼｮﾝ側でfnPlay時のpLogCh->iLoopを記憶しておく必要が有る｡
//PNRCL=1しても構わないが､ﾘｺｰﾙは煩わしいので､ｱﾌﾟﾘｹｰｼｮﾝ側で記憶しておく方が良いだろう｡
//尚､上記の情報は､論理ﾁｬﾈﾙ毎に記憶しておく必要は無く､ﾃﾞﾊﾞｲｽﾁｬﾈﾙ毎で構わない｡
//PVUP_x=1を行う時にも､再生開始時と同じLOOP_xを設定するためだけの目的だから｡
