/*
 *	clipsnds.c
 *
 *	サウンドシーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Thu Oct 03 00:19:14 JST 2013 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Oct 05 15:05:45 JST 2013 Naoyuki Sawa
 *	- パターンコマンドに、4バイトウェイトを追加しました。
 *	  4バイトウェイトにはオフセットが無く全範囲をカバーするので、手作業でデータを作成する時は4バイトウェイトだけを使用すると直感的に作成できます。
 *	  ただし1～3バイトウェイトの方がメモリを節約できるので、ツールでデータを作成する時はウェイト時間によって1～4バイトウェイトを生成してください。
 *	* Fri Nov 01 17:54:30 JST 2013 Naoyuki Sawa
 *	- SndSeq_Play()が、パターンデータ先頭の0msタイミングに相当する処理を即実行するように、修正しました。
 *	* Sun Jun 01 22:15:15 JST 2014 Naoyuki Sawa
 *	- SndSeqCmd_Vol,SndSeqCmd_Panのマクロ定義が間違っていたので、修正しました。
 *	* Tue Jul 01 22:19:13 JST 2014 Naoyuki Sawa
 *	- イントロループ構成の再生制御を追加しました。
 *	* Mon Jul 07 01:54:09 JST 2014 Naoyuki Sawa
 *	- SndSeq_Stop()とSndSeq_StopAll()の引数に、bForce(強制停止)を追加しました。
 *	* Tue May 05 17:15:43 JST 2015 Naoyuki Sawa
 *	- SndSeq_PlayNo()を追加しました。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
static ST_SndSeq stSndSeq;
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static void SndTrk_Exec(int iTrk, int ElapsedTime);
static void SndSeqCh_Exec(int iSndSeqCh, int ElapsedTime);
/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/
/* サウンドシーケンサを初期化する。
 * [in]
 *	pSndSeqInfo	初期化情報へのポインタ
 * [note]
 *	- 本関数は、初期化情報をコピーせず、ポインタを格納することに注意せよ。
 *	  アプリケーションは、本関数が処理を返した後も、初期化情報を変更したり破棄してはいけない。
 */
void SndSeq_Init(const ST_SndSeqInfo* pSndSeqInfo) {
	/* サウンドシーケンサ全体をクリアする。 */
	memset(&stSndSeq, 0, sizeof stSndSeq);
	/* 初期化情報が正常であることを確認する。 */
	if(!pSndSeqInfo || !pSndSeqInfo->fnTime || !pSndSeqInfo->fnPlay || !pSndSeqInfo->fnStop || !pSndSeqInfo->fnVol || !pSndSeqInfo->fnPan || !pSndSeqInfo->fnLen || !pSndSeqInfo->fnNext || !pSndSeqInfo->fnPrev) { DIE(); }
	/* 初期化情報へのポインタを格納する。
	 * - コピーではなく、ポインタを格納することに注意せよ。アプリケーションは、本関数が処理を返した後も、初期化情報を変更したり破棄してはいけない。
	 */
	stSndSeq.pSndSeqInfo = pSndSeqInfo;
	/* トラック情報をクリアする。 */
	memset(stSndSeq.SndTrk, -1, sizeof stSndSeq.SndTrk); //無制御,無効値
	/* 前回時刻として、初期化完了時の時刻を格納する。
	 * - 初回実行時に長時間経過することを避けるため、極力、初期化完了の直前に時刻を取得するのが適切である。
	 *   より確実のためには、メインループ開始直前に一回、SndSeq_Exec()を呼び出しておくと良い。
	 */
	stSndSeq.Time = (*stSndSeq.pSndSeqInfo->fnTime)();
}
/*--------------------------------------------------------------------------*/
/* サウンドシーケンサを実行する。
 * [note]
 *	- 本関数は、アプリケーションのメインループ(割り込み外),又は,タイマ割り込み処理の、どちらから呼ぶようにしても良い。
 *	- 本関数の中で、経過時間を判断し、適切な個数のコマンドを実行する。
 *	  アプリケーションは、必ずしも一定間隔で本関数を呼び出す必要は無く、メインループ(ビジーループ)から呼び出して良い。
 */
void SndSeq_Exec() {
	int Time, ElapsedTime, iTrk, iSndSeqCh;
	/* 現在時刻を取得する。 */
	Time = (*stSndSeq.pSndSeqInfo->fnTime)();
	/* 経過時間を計算する。 */
	ElapsedTime = Time - stSndSeq.Time;
	/* 前回時刻として、現在時刻を格納する */
	stSndSeq.Time = Time;
	/* デバッグ時などに、経過時間が異常に長くなる可能性も有るので、全ての値を許容することにした。 */
	if(ElapsedTime <    0) { ElapsedTime =    0; }	/* 経過時間が0秒未満ならば、0秒に補正する。 */
	if(ElapsedTime > 1000) { ElapsedTime = 1000; }	/* 経過時間が1秒超過ならば、1秒に補正する。 */
	/* 各トラックについて… */
	for(iTrk = 0; iTrk <= SndTrk_Max; iTrk++) {
		/* トラックを実行する。 */
		SndTrk_Exec(iTrk, ElapsedTime);
	}
	//↑↓この処理順必須。逆にすると、再生開始した直後のトラックのイントロカウンタを減らしてしまう。
	/* 各サウンドシーケンサチャネルについて… */
	for(iSndSeqCh = 0; iSndSeqCh <= SndSeqCh_Max; iSndSeqCh++) {
		/* サウンドシーケンサチャネルを実行する。 */
		SndSeqCh_Exec(iSndSeqCh, ElapsedTime);
	}
}
/*--------------------------------------------------------------------------*/
/* トラックを、指定された経過時間の分、実行する。
 * [in]
 *	iTrk		トラック番号(0～SndTrk_Max)
 *	ElapsedTime	経過時間[ms]
 */
static void SndTrk_Exec(int iTrk, int ElapsedTime) {
	ST_SndTrk* pSndTrk;
	int iPhr;
	/* トラック番号が正常であることを確認する。 */
	if((unsigned)iTrk > SndTrk_Max) { DIE(); }
	/* トラック情報を取得する。 */
	pSndTrk = &stSndSeq.SndTrk[iTrk];
	/* 再生中のフレーズが、イントロフレーズならば… */
	if((pSndTrk->iPhr != -1) && (pSndTrk->Intr != -1)) {
		/* イントロカウンタから経過時間を減算し、0以下になったら… */
		if((pSndTrk->Intr -= ElapsedTime) <= 0) {
			/* ループフレーズを取得する。 */
			iPhr = (*stSndSeq.pSndSeqInfo->fnNext)(pSndTrk->iPhr);
			if(iPhr == -1) { DIE(); }
			/* トラック情報を更新する。 */
			pSndTrk->iPhr = iPhr; /* 有効値 */
			/* イントロカウンタを停止する。 */
			pSndTrk->Intr = -1;
			/* 再生処理を実行する。 */
			(*stSndSeq.pSndSeqInfo->fnPlay)(iTrk, iPhr, pSndTrk->iVol, pSndTrk->iPan);
		}
	}
}
/*--------------------------------------------------------------------------*/
/* サウンドシーケンサチャネルを、指定された経過時間の分、実行する。
 * [in]
 *	iSndSeqCh	サウンドシーケンサチャネル番号(0～SndSeqCh_Max)
 *	ElapsedTime	経過時間[ms]
 */
static void SndSeqCh_Exec(int iSndSeqCh, int ElapsedTime) {
	ST_SndSeqCh* pSndSeqCh;
	ST_SndTrk* pSndTrk;
	int iCmd, Wait, iTrk, iPhr, iVol, iPan, iNext, iPrev;
	/* サウンドシーケンサチャネル番号が正常であることを確認する。 */
	if((unsigned)iSndSeqCh > SndSeqCh_Max) { DIE(); }
	/* サウンドシーケンサチャネルを取得する。 */
	pSndSeqCh = &stSndSeq.SndSeqCh[iSndSeqCh];
	/* 停止中ならば、何もしない。 */
	if(!pSndSeqCh->pData) { return; }
	/* ウェイトカウンタから経過時間を減算する。0超過ならば何もしない。 */
	pSndSeqCh->Wait -= ElapsedTime;
	/* ウェイトカウンタが0以下である間、パターンコマンドを実行する。 */
	while(pSndSeqCh->Wait <= 0) {
		/* パターンコマンドを取得する。 */
		iCmd = *pSndSeqCh->pData++;
		/* パターンコマンドによって… */
		switch(iCmd) {
		/* パターン終了 */
		case 0x00: /* 引数無し */
			/* シーケンサ停止処理を実行する。 */
			SndSeq_Stop(iSndSeqCh, 0);
			return; /* ここまで */
		/* 1バイトウェイト */
		case 0x01: /* (ms-0x00001)[7:0] */
			/* ウェイト時間を取得する。 */							//1バイトウェイトの範囲 = 0x00001+(0～0x000000FF) [ms]
			Wait  = ((*pSndSeqCh->pData++) + 1);
			/* ウェイトカウンタにウェイト時間を加算する。 */
			pSndSeqCh->Wait += Wait;
			break;
		/* 2バイトウェイト */
		case 0x02: /* (ms-0x00101)[7:0] (ms-0x00101)[15:8] */					//2バイトウェイトの範囲 = 0x00101+(0～0x0000FFFF) [ms]
			/* ウェイト時間を取得する。 */
			Wait  = ((*pSndSeqCh->pData++) + 1);
			Wait += ((*pSndSeqCh->pData++) + 1) <<  8;
			/* ウェイトカウンタにウェイト時間を加算する。 */
			pSndSeqCh->Wait += Wait;
			break;
		/* 3バイトウェイト */
		case 0x03: /* (ms-0x10101)[7:0] (ms-0x10101)[15:8] (ms-0x10101)[23:16] */		//3バイトウェイトの範囲 = 0x10101+(0～0x00FFFFFF) [ms]
			/* ウェイト時間を取得する。 */
			Wait  = ((*pSndSeqCh->pData++) + 1);
			Wait += ((*pSndSeqCh->pData++) + 1) <<  8;
			Wait += ((*pSndSeqCh->pData++) + 1) << 16;
			/* ウェイトカウンタにウェイト時間を加算する。 */
			pSndSeqCh->Wait += Wait;
			break;
		/* 4バイトウェイト */
		case 0x04: /* (ms        )[7:0] (ms        )[15:8] (ms        )[23:16] (ms)[31:24] */	//4バイトウェイトの範囲 =         (0～0xFFFFFFFF) [ms]
			/* ウェイト時間を取得する。 */
			Wait  =  (*pSndSeqCh->pData++);
			Wait |=  (*pSndSeqCh->pData++)      <<  8;
			Wait |=  (*pSndSeqCh->pData++)      << 16;
			Wait |=  (*pSndSeqCh->pData++)      << 24;
			/* ウェイトカウンタにウェイト時間を加算する。 */
			pSndSeqCh->Wait += Wait;
			break;
		/* トラック停止 */
		case 0x10: /* trk */
			/* トラック番号を取得する。 */
			iTrk = *pSndSeqCh->pData++;
			/* トラック番号が正常であることを確認する。 */
			if((unsigned)iTrk > SndTrk_Max) { DIE(); }
			/* トラック情報を取得する。 */
			pSndTrk = &stSndSeq.SndTrk[iTrk];
			/* このサウンドシーケンサチャネルが制御しているトラックならば… */
			if(pSndTrk->iSndSeqCh == iSndSeqCh) {
				/* 再生中のフレーズが、通常フレーズならば… */
				if((*stSndSeq.pSndSeqInfo->fnNext)(pSndTrk->iPhr) == -1) {
					/* トラック情報をクリアする。 */
					memset(pSndTrk, -1, sizeof(ST_SndTrk)); /* 無制御,無効値 */
					/* トラック停止処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnStop)(iTrk);
				}
			}
			break;
		/* モノラル再生 */
		case 0x11: /* trk phrL phrH vol pan */
			/* トラック番号を取得する。 */
			iTrk  = *pSndSeqCh->pData++;
			/* フレーズ番号を取得する。 */
			iPhr  = *pSndSeqCh->pData++;
			iPhr |= *pSndSeqCh->pData++ << 8;
			/* ボリュームを取得する。 */
			iVol  = *pSndSeqCh->pData++;
			/* パンを取得する。 */
			iPan  = *pSndSeqCh->pData++;
			/* トラック番号,フレーズ番号,ボリューム,パンが正常であることを確認する。 */
			if(((unsigned)iTrk > SndTrk_Max) || ((unsigned)iPhr > SndPhr_Max) || ((unsigned)iVol > SndVol_Max) || ((unsigned)iPan > SndPan_Max)) { DIE(); }
			/* トラック情報を取得する。 */
			pSndTrk = &stSndSeq.SndTrk[iTrk];
			/* このサウンドシーケンサチャネルが制御しているトラックで,かつ,
			 * │新しいフレーズがイントロフレーズであると仮定し(①)、再生中のフレーズがイントロフレーズに一致する(②),又は,再生中のフレーズがループフレーズに一致する(③)
			 * │,又は,
			 * │新しいフレーズがループフレーズ  であると仮定し(④)、再生中のフレーズがイントロフレーズに一致する(⑤),又は,再生中のフレーズがループフレーズに一致する(⑥)
			 * ならば… */
			iNext = (*stSndSeq.pSndSeqInfo->fnNext)(iPhr);
			iPrev = (*stSndSeq.pSndSeqInfo->fnPrev)(iPhr);
			if((pSndTrk->iSndSeqCh == iSndSeqCh) &&
			   (((iNext != -1)/*①*/ && ((pSndTrk->iPhr == iNext)/*③*/ || (pSndTrk->iPhr == iPhr)/*②*/)) ||
			    ((iPrev != -1)/*④*/ && ((pSndTrk->iPhr == iPrev)/*⑤*/ || (pSndTrk->iPhr == iPhr)/*⑥*/)))) {
				/* 再生中のボリュームと新しいボリュームが違ったら… */
				if(pSndTrk->iVol != iVol) {
					/* トラック情報を更新する。 */
					pSndTrk->iVol = iVol; /* 有効値 */
					/* ボリューム更新処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnVol)(iTrk, iVol);
				}
				/* 再生中のパンと新しいパンが違ったら… */
				if(pSndTrk->iPan != iPan) {
					/* トラック情報を更新する。 */
					pSndTrk->iPan = iPan; /* 有効値 */
					/* パン更新処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnPan)(iTrk, iPan);
				}
			/* そうでなければ… */
			} else {
				/* ループフレーズからの再生開始が指示された場合、イントロフレーズからの再生開始に変換する。 */
				if((iPrev != -1) && (iPrev != iPhr)) {
					iPhr = iPrev;
					/* ループフレーズは、変換前のiPhrであるはずだが、確実のために再取得することにする。 */
					iNext = (*stSndSeq.pSndSeqInfo->fnNext)(iPhr);
				}
				/* トラック情報を格納する。 */
				pSndTrk->iSndSeqCh = iSndSeqCh;
				pSndTrk->iPhr = iPhr; /* 有効値 */
				pSndTrk->iVol = iVol; /* 有効値 */
				pSndTrk->iPan = iPan; /* 有効値 */
				/* イントロフレーズならば、イントロカウンタに再生時間を格納する。
				 * イントロフレーズ以外ならば、イントロカウンタに(-1)を格納する。 */
				pSndTrk->Intr = (iNext != -1) && (iNext != iPhr) ? (*stSndSeq.pSndSeqInfo->fnLen)(iPhr) : -1;
				/* 再生処理を実行する。 */
				(*stSndSeq.pSndSeqInfo->fnPlay)(iTrk, iPhr, iVol, iPan);
			}
			break;
		/* ステレオ再生 */
		case 0x12: /* trk phrL phrH vol */
			/* トラック番号を取得する。 */
			iTrk  = *pSndSeqCh->pData++;
			/* フレーズ番号を取得する。 */
			iPhr  = *pSndSeqCh->pData++;
			iPhr |= *pSndSeqCh->pData++ << 8;
			/* ボリュームを取得する。 */
			iVol  = *pSndSeqCh->pData++;
			/* トラック番号,フレーズ番号,ボリュームが正常であることを確認する。 */
			if(((unsigned)iTrk > SndTrk_Max) || ((unsigned)iPhr > SndPhr_Max) || ((unsigned)iVol > SndVol_Max)) { DIE(); }
			/* トラック情報を取得する。 */
			pSndTrk = &stSndSeq.SndTrk[iTrk];
			/* このサウンドシーケンサチャネルが制御しているトラックで,かつ,
			 * │新しいフレーズがイントロフレーズであると仮定し(①)、再生中のフレーズがイントロフレーズに一致する(②),又は,再生中のフレーズがループフレーズに一致する(③)
			 * │,又は,
			 * │新しいフレーズがループフレーズ  であると仮定し(④)、再生中のフレーズがイントロフレーズに一致する(⑤),又は,再生中のフレーズがループフレーズに一致する(⑥)
			 * ならば… */
			iNext = (*stSndSeq.pSndSeqInfo->fnNext)(iPhr);
			iPrev = (*stSndSeq.pSndSeqInfo->fnPrev)(iPhr);
			if((pSndTrk->iSndSeqCh == iSndSeqCh) &&
			   (((iNext != -1)/*①*/ && ((pSndTrk->iPhr == iNext)/*③*/ || (pSndTrk->iPhr == iPhr)/*②*/)) ||
			    ((iPrev != -1)/*④*/ && ((pSndTrk->iPhr == iPrev)/*⑤*/ || (pSndTrk->iPhr == iPhr)/*⑥*/)))) {
				/* 再生中のボリュームと新しいボリュームが違ったら… */
				if(pSndTrk->iVol != iVol) {
					/* トラック情報を更新する。 */
					pSndTrk->iVol = iVol; /* 有効値 */
					/* ボリューム更新処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnVol)(iTrk, iVol);
				}
			/* そうでなければ… */
			} else {
				/* ループフレーズからの再生開始が指示された場合、イントロフレーズからの再生開始に変換する。 */
				if((iPrev != -1) && (iPrev != iPhr)) {
					iPhr = iPrev;
					/* ループフレーズは、変換前のiPhrであるはずだが、確実のために再取得することにする。 */
					iNext = (*stSndSeq.pSndSeqInfo->fnNext)(iPhr);
				}
				/* トラック情報を格納する。 */
				pSndTrk->iSndSeqCh = iSndSeqCh;
				pSndTrk->iPhr = iPhr; /* 有効値 */
				pSndTrk->iVol = iVol; /* 有効値 */
				pSndTrk->iPan =   -1; /* 無効値 */
				/* イントロフレーズならば、イントロカウンタに再生時間を格納する。
				 * イントロフレーズ以外ならば、イントロカウンタに(-1)を格納する。 */
				pSndTrk->Intr = (iNext != -1) && (iNext != iPhr) ? (*stSndSeq.pSndSeqInfo->fnLen)(iPhr) : -1;
				/* 再生処理を実行する。 */
				(*stSndSeq.pSndSeqInfo->fnPlay)(iTrk, iPhr, iVol, -1/*無効値*/);
			}
			break;
		/* ボリューム更新 */
		case 0x20: /* trk vol */
			/* トラック番号を取得する。 */
			iTrk = *pSndSeqCh->pData++;
			/* ボリュームを取得する。 */
			iVol = *pSndSeqCh->pData++;
			/* トラック番号,ボリュームが正常であることを確認する。 */
			if(((unsigned)iTrk > SndTrk_Max) || ((unsigned)iVol > SndVol_Max)) { DIE(); }
			/* トラック情報を取得する。 */
			pSndTrk = &stSndSeq.SndTrk[iTrk];
			/* このサウンドシーケンサチャネルが制御しているトラックならば… */
			if(pSndTrk->iSndSeqCh == iSndSeqCh) {
				/* 再生中のボリュームと新しいボリュームが違ったら… */
				if(pSndTrk->iVol != iVol) {
					/* トラック情報を更新する。 */
					pSndTrk->iVol = iVol; /* 有効値 */
					/* ボリューム更新処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnVol)(iTrk, iVol);
				}
			}
			break;
		/* パン更新 */
		case 0x21: /* trk pan */
			/* トラック番号を取得する。 */
			iTrk = *pSndSeqCh->pData++;
			/* パンを取得する。 */
			iPan  = *pSndSeqCh->pData++;
			/* トラック番号,パンが正常であることを確認する。 */
			if(((unsigned)iTrk > SndTrk_Max) || ((unsigned)iPan > SndPan_Max)) { DIE(); }
			/* トラック情報を取得する。 */
			pSndTrk = &stSndSeq.SndTrk[iTrk];
			/* このサウンドシーケンサチャネルが制御しているトラックならば… */
			if(pSndTrk->iSndSeqCh == iSndSeqCh) {
				/* 再生中のパンと新しいパンが違ったら… */
				if(pSndTrk->iPan != iPan) {
					/* トラック情報を更新する。 */
					pSndTrk->iPan = iPan; /* 有効値 */
					/* パン更新処理を実行する。 */
					(*stSndSeq.pSndSeqInfo->fnPan)(iTrk, iPan);
				}
			}
			break;
		default:
			DIE(); /* パターンコマンドが不正 */
		}
	}
}
/*--------------------------------------------------------------------------*/
/* パターンデータを再生開始する。
 * [in]
 *	iSndSeqCh	サウンドシーケンサチャネル番号(0～SndSeqCh_Max)
 *	pData		パターンデータのアドレス
 */
void SndSeq_Play(int iSndSeqCh, const void* pData) {
	ST_SndSeqCh* pSndSeqCh;
	/* サウンドシーケンサチャネル番号とパターンデータのアドレスが正常であることを確認する。 */
	if(((unsigned)iSndSeqCh > SndSeqCh_Max) || (!pData)) { DIE(); }
	/* サウンドシーケンサチャネルを確実に停止する。 */
	SndSeq_Stop(iSndSeqCh, 0);
	/* サウンドシーケンサチャネルを取得する。 */
	pSndSeqCh = &stSndSeq.SndSeqCh[iSndSeqCh];
ENTER_CS;
	/* パターンデータのアドレスを格納する。 */
	pSndSeqCh->pData = pData;
	/* ウェイトカウンタをクリアする。 */
	pSndSeqCh->Wait  = 0;
	/* パターンデータ先頭の0msタイミングに相当する処理を即実行する。
	 * - こうしないと、SndSeq_Play()とSndSeq_Stop()を連続で呼び出した時に、パターンデータ先頭の0msタイミングに相当する処理が、実行されたりされなかったりして不安定になる。
	 *   特に、パターンデータ先頭の0msタイミングに、無限ループフレーズが設定されていたり、再生中の無限ループフレーズを止める処理が設定されていた場合に、問題が顕著となる。
	 *   パターンデータ先頭の0msタイミングに相当する処理は、割り込みタイミングに関係無く必ず実行するのが自然であると考え、パターンデータ先頭の処理を即実行することにした。
	 */
	SndSeqCh_Exec(iSndSeqCh, 0);
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
/* サウンドシーケンサチャネルを停止する。
 * [in]
 *	iSndSeqCh	サウンドシーケンサチャネル番号(0～SndSeqCh_Max)
 *	bForce		強制停止フラグ。強制停止とは、無限ループフレーズも含め、このサウンドシーケンサチャネルが制御している(又は,していた)トラックを全て停止することである。
 * [note]
 *	- 本関数は、「アプリケーションによる、割り込み外からの呼び出し」と「SndSeqCh_Exec()による、割り込み内(又は割り込み外でも可)からの呼び出し」の、両方から呼び出される。
 *	  本関数内の処理順序を工夫したので、本関数内での割り込み禁止制御は不要となった。
 */
void SndSeq_Stop(int iSndSeqCh, int bForce) {
	ST_SndSeqCh* pSndSeqCh;
	ST_SndTrk* pSndTrk;
	int iTrk;
	/* サウンドシーケンサチャネル番号が正常であることを確認する。 */
	if((unsigned)iSndSeqCh > SndSeqCh_Max) { DIE(); }
	/* サウンドシーケンサチャネルを取得する。 */
	pSndSeqCh = &stSndSeq.SndSeqCh[iSndSeqCh];
/*{{この順序で処理すれば、割り込み禁止処理が不要である。*/
	/* 強制停止が指定されておらず,かつ,停止中ならば、何もしない。 */	//サウンドシーケンサチャネルが停止する時に、無限ループフレーズは停止しないので、再生継続している可能性がある。
	if(!bForce && !pSndSeqCh->pData) { return; }				//強制停止が指定された場合は、無限ループフレーズも停止するために、サウンドシーケンサチャネルが停止していても、以下の処理を行う。
	/* パターンデータのアドレスをクリアする。 */
	pSndSeqCh->pData = NULL;						//既に停止しているサウンドシーケンサチャネルに対して、強制停止が指定された場合は、この処理は無駄処理となるがであるが、害は無い。
	/* 各トラックについて… */
	for(iTrk = 0; iTrk <= SndTrk_Max; iTrk++) {
		/* トラック情報を取得する。 */
		pSndTrk = &stSndSeq.SndTrk[iTrk];
		/* このサウンドシーケンサチャネルが制御しているトラックならば… */
		if(pSndTrk->iSndSeqCh == iSndSeqCh) {
			/* 強制停止が指定されているか,又は,再生中のフレーズが通常フレーズならば… */
			if(bForce || (*stSndSeq.pSndSeqInfo->fnNext)(pSndTrk->iPhr) == -1) {
				/* トラック情報をクリアする。 */
				memset(pSndTrk, -1, sizeof(ST_SndTrk)); /* 無制御,無効値 */
				/* トラック停止処理を実行する。 */
				(*stSndSeq.pSndSeqInfo->fnStop)(iTrk);
			}
		}
	}
/*}}この順序で処理すれば、割り込み禁止処理が不要である。*/
}
/*--------------------------------------------------------------------------*/
/* サウンドシーケンサチャネルを全て停止する。
 * [in]
 *	bForce		強制停止フラグ。強制停止とは、無限ループフレーズも含め、このサウンドシーケンサチャネルが制御している(又は,していた)トラックを全て停止することである。
 */
void SndSeq_StopAll(int bForce) {
	int iSndSeqCh;
	/* 各サウンドシーケンサチャネルについて… */
	for(iSndSeqCh = 0; iSndSeqCh <= SndSeqCh_Max; iSndSeqCh++) {
		/* サウンドシーケンサチャネルを停止する。 */
		SndSeq_Stop(iSndSeqCh, bForce);
	}
}
/*--------------------------------------------------------------------------*/
/* トラック情報を取得する。
 * [in]
 *	iTrk		トラック番号
 *	pSndTrk		トラック情報を格納する構造体へのポインタ
 * [note]
 *	- stSndSeq.SndTrk[]の内容は、割り込み内から呼び出されたSndSeq_Exec()によって、非同期に変更される可能性があるので、直接参照してはいけない。
 *	  アプリケーションがトラック情報を参照する場合は、本関数を呼び出してスナップショットを取得すること。
 */
void SndSeq_GetTrk(int iTrk, ST_SndTrk* pSndTrk) {
	/* トラック番号とトラック情報を格納する構造体へのポインタが正常であることを確認する。 */
	if(((unsigned)iTrk > SndTrk_Max) || !pSndTrk) { DIE(); }
ENTER_CS;/*コピー中にトラック情報が変化しないよう、割り込み禁止制御が必要である。*/
	/* トラック情報をコピーする。 */
	memcpy(pSndTrk, &stSndSeq.SndTrk[iTrk], sizeof(ST_SndTrk));
LEAVE_CS;/*コピー中にトラック情報が変化しないよう、割り込み禁止制御が必要である。*/
}
/****************************************************************************
 *	dSndSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_SNDDEF_BIN
void SndSeq_PlayNo(int iSndSeqCh, const void* pSndDefBin, int iSndSeqNo) {
	const int* const TBL_SndSeqNo = pSndDefBin;						//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
	const int nSndSeqNo = TBL_SndSeqNo[0];							//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
	const unsigned char* const TBL_SndSeq = (const unsigned char*)&TBL_SndSeqNo[nSndSeqNo];	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
	if((iSndSeqNo <= 0) || (iSndSeqNo >= nSndSeqNo)) { DIE(); }
	SndSeq_Play(iSndSeqCh, &TBL_SndSeq[TBL_SndSeqNo[iSndSeqNo]]);
}
#endif//USE_SNDDEF_BIN
/****************************************************************************
 *	サンプル
 ****************************************************************************/
//	//フレーズ番号定義。実際にはsound.hにて定義する。
//	#define SND_BGM1_INTR 1
//	#define SND_BGM1_LOOP 2
//	#define SND_BGM2_LOOP 3
//	#define SND_BGM3_LOOP 4
//	#define SND_SE1       5
//	#define SND_SE2       6
//	#define SND_SE3       7
//	//以下は、サウンドマネージャとシーケンサを併用する例である。サウンドマネージャを使用することは必須ではない。直接、sound_play()等を呼び出しても構わない。
//	extern const ST_SndMgrInfo stSndMgrInfo; //サウンドマネージャの定義例は、clipsndm.hを参照せよ。
//	ST_SndMgr* pSndMgr;
//	//--- シーケンサコールバック関数 ---
//	static int SndSeq_fnLen(int iPhr) {
//		return ppd_playback_time(sound_table[iPhr]);
//	}
//	static int SndSeq_fnNext(int iPhr) {
//		switch(iPhr) {
//		case SND_BGM1_INTR: return SND_BGM1_LOOP;
//		case SND_BGM1_LOOP: return SND_BGM1_LOOP;
//		case SND_BGM2_LOOP: return SND_BGM2_LOOP;
//		case SND_BGM3_LOOP: return SND_BGM3_LOOP;
//		}
//		return -1;
//	}
//	static int SndSeq_fnPrev(int iPhr) {
//		switch(iPhr) {
//		case SND_BGM1_INTR: return SND_BGM1_INTR;
//		case SND_BGM1_LOOP: return SND_BGM1_INTR;
//		case SND_BGM2_LOOP: return SND_BGM2_LOOP;
//		case SND_BGM3_LOOP: return SND_BGM3_LOOP;
//		}
//		return -1;
//	}
//	static void SndSeq_fnPan(int iTrk, int iPan) {
//		//P/ECEにはステレオ再生は無いので、本関数は使用しない。
//	}
//	static void SndSeq_fnVol(int iTrk, int iVol) {
//		int Att = (SndVol_Max - iVol) * 64 / SndVol_Max; //ボリューム⇒減衰量変換
//		SndMgr_AttCh(pSndMgr, iTrk, Att, 0/*即時*/); //変化時間は即時とせよ。サウンドマネージャによる減衰量を連続的に変化する機能は使わない。シーケンサが随時ボリュームを指示する。
//	}
//	static void SndSeq_fnStop(int iTrk) {
//		SndMgr_StopCh(pSndMgr, iTrk);
//	}
//	static void SndSeq_fnPlay(int iTrk, int iPhr, int iVol, int iPan) {
//		SndMgr_PlayCh(pSndMgr, iTrk, iPhr, -1/*無限ループ*/); //常に無限ループとして再生せよ。停止タイミングはその時にシーケンサが明示的に指示する。
//		SndSeq_fnVol(iTrk, iVol);
//		//P/ECEにはステレオ再生は無いので、iPan引数を無視する。
//	}
//	static int SndSeq_fnTime() {
//		return pceTimerGetCount(); //現在時刻を[ms]単位で返せ。
//	}
//	static const ST_SndSeqInfo stSndSeqInfo = { &SndSeq_fnTime, &SndSeq_fnPlay, &SndSeq_fnStop, &SndSeq_fnVol, &SndSeq_fnPan, &SndSeq_fnLen, &SndSeq_fnNext, &SndSeq_fnPrev };
//	//--- テスト初期化関数 ---
//	void TestInit() {
//		pSndMgr = SndMgr_Init(&stSndMgrInfo, 0, SOUND_MAXCH-1); //Sound Manager
//		SndSeq_Init(&stSndSeqInfo);
//	}
//	//--- テストメイン関数 ---
//	static const unsigned char PatternData[] = {
//		SndSeqCmd_PlayM(0,SND_BGM1_INTRO,128,64),	//トラック0でBGM1を再生開始。イントロフレーズを再生後、自動的にループフレーズへ遷移する。
//		SndSeqCmd_Wait2(1000),				//1秒待つ
//		SndSeqCmd_Vol(0,64),				//トラック0のBGM1を減音
//		SndSeqCmd_PlayM(1,SND_SE1,128,64),		//トラック1でSE1を再生開始
//		SndSeqCmd_PlayM(2,SND_SE2,128,64),		//トラック2でSE2を再生開始
//		SndSeqCmd_Wait2(1000),				//1秒待つ
//		SndSeqCmd_Stop(1),				//トラック1のSE1を停止
//		SndSeqCmd_Vol(0,128),				//トラック0のBGM1を減音復帰
//		SndSeqCmd_Wait2(1000),				//1秒待つ
//		SndSeqCmd_Stop(0),				//トラック0のBGM1を停止。しかしBGM1は無限ループフレーズなので、実際には停止しない。このコマンドは無くても構わない。
//		SndSeqCmd_End(),				//パターン終了。トラック0のBGM1は再生継続する。トラック2のSE2は自動停止する。
//	};
//	void TestMain() {
//		for(;;) {
//			schedule();
//			SndMgr_Exec(pSndMgr);	//SndMgr_Exec()は1f単位に同期して動作するが、
//			SndSeq_Exec();		//SndSeq_Exec()は実時間に同期して動作することに注意せよ。
//			if(joy & TRG_A) { SndSeq_Play(0, PatternData); }
//			if(joy & TRG_B) { SndSeq_Stop(0, 0); }
//		}
//	}
