/*
 *	clipxmp1.c
 *
 *	YAMAHA YMZ871 XMP-1 (eXtensible Music Player - 1) 簡易ドライバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Thu Oct 13 21:52:21 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	- 既存の、TAPシーケンサ,TAPミキサー,タイマ管理をモジュール利用して、YMZ871を制御する簡易ドライバです。
 *	  YMZ871の特性については、以前に、cliptaps.cの中にもコメントを書いているので、そちらも参照して下さい。
 */
#include "clip.h"
#ifndef __RENESAS__
#include "tool/XmpPifCnv/Xmp1.h"	//WindowsとP/ECE環境では、Xmp1.hはツールフォルダの中に有ります。
#else //__RENESAS__
#include "Xmp1.h"			//Hew環境では、Xmp1.hをプロジェクトフォルダにコピーして下さい。
#endif//__RENESAS__
/*****************************************************************************
 *	ローカル関数宣言
 *****************************************************************************/
static uint8_t Xmp1_Read(ST_Xmp1* pXmp1, uint16_t rb_index/*((rb<<8)|index)*/) __attribute__((unused));		//※未使用:2016/10/13現在の実装では使用していません。
static void Xmp1_Write(ST_Xmp1* pXmp1, uint16_t rb_index/*((rb<<8)|index)*/, uint8_t data);
static ST_Xmp1Trk* Xmp1_GetXmp1Trk(ST_Xmp1* pXmp1, int iDevCh);
static void Xmp1_GetPanVol(ST_Xmp1* pXmp1, int iLogCh, ST_TapLogCh* pLogCh, int* pPan, int* pVol1, int* pVol2);
static void Xmp1_VolUpd(ST_Xmp1* pXmp1, int iDevCh, ST_Xmp1Trk* pXmp1Trk);
static void Xmp1_SetTvo(ST_Xmp1* pXmp1, int iVol);
static void Xmp1_Exec_Xms(void* arg);
static void Xmp1_Exec_X00ms(void* arg);
static void Xmp1_TapSeq_fnPlay(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static void Xmp1_TapSeq_fnStop(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static void Xmp1_TapSeq_fnLoop(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static void Xmp1_TapSeq_fnEnd(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static void Xmp1_TapSeq_fnNext(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static void Xmp1_TapSeq_fnCtrl(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh);
static int Xmp1_TapSeq_fnGetPhrTime(ST_TapSeq* pTapSeq, int iPhr);
static int Xmp1_TapSeq_fnGetPhrNext(ST_TapSeq* pTapSeq, int iPhr);
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//管理構造体を初期化する。
//[note]
// - pTimMgrをST_Xmp1Infoに含めず、Xmp1_Init()の引数とした理由は、以下の二つの理由です。
//   理由1. pTimMgrはXmp1_Init()関数の中だけで使用しそれ以後は使用しないので、ST_Xmp1Infoに含めるのは無駄であるから。
//   理由2. (アプリケーションの構成にもよるが)pTimMgrは静的に初期化できない可能性が高いので、もしpTimMgrをST_Xmp1Infoに含めるとST_Xmp1Infoを静的に定義出来なくなるから。
void Xmp1_Init(ST_Xmp1* pXmp1, const ST_Xmp1Info* pInfo, ST_TimMgr* pTimMgr) {
	//管理構造体をクリアする。
	memset(pXmp1, 0, sizeof(ST_Xmp1));
	//初期化情報へのポインタを格納する。
	pXmp1->pInfo = pInfo;
ENTER_CS;
	//--- レジスタ固定値の設定処理 ---
	{
		Xmp1_Write(pXmp1, XMP1_P1_x_7_0,	0x40);		//上下パンポット	デフォルト値は0x00である。アプリケーションは常に0x40として使用したいので、変更しておく。以後、変更する必要は無い。
		//ここに追加出来ます。
	}
	//--- ボリュームスイッチの初期化処理 ---
	{
		//ボリュームスイッチの初期値を格納する。
		pXmp1->Vol = pXmp1->NewVol = (*pXmp1->pInfo->getVolumeSw)();
		//トータルボリュームを設定する。
		Xmp1_SetTvo(pXmp1, pXmp1->Vol);
	}
	//--- トラック配列の初期化処理 ---
	{
		int iDevCh;
		for(iDevCh = 0; iDevCh <= Xmp1_TapDevCh_Max; iDevCh++) {
			ST_Xmp1Trk* pXmp1Trk = Xmp1_GetXmp1Trk(pXmp1, iDevCh);
		//不要	pXmp1Trk->Flag		= 0;		//フラグ			既にクリアされているので明示的な初期化不要
		//不要	pXmp1Trk->BusyTime	= 0;		//ビジー判定基準時刻[ms]	既にクリアされているので明示的な初期化不要
			pXmp1Trk->PN		= -1;		//再生中フレーズ番号
		//不要	pXmp1Trk->P0		= 0x40;		//左右パン			再生開始及び再生中に設定するので初期化不要
		//不要	pXmp1Trk->V1		= 0x80;		//1次ボリューム			ミキサーの周期処理で設定するので初期化不要
		//不要	pXmp1Trk->V2		= 0x80;		//2次ボリューム			再生開始及び再生中に設定するので初期化不要
		//不要	pXmp1Trk->LOOP		= 0xFF;		//ループ回数			再生開始及び再生中に設定するので初期化不要
		}
	}
	//--- シーケンサの初期化処理 ---
	{
		static const uint8_t TBL_TapSet[(Xmp1_TapLogCh_Max+1)][2]={
			{ 0,-1},{ 1,-1},{ 2,-1},{ 3,-1},	//┐
			{ 4,-1},{ 5,-1},{ 6,-1},{ 7,-1},	//├2016/10/13現在の実装では、論理チャネル数とデバイスチャネルを1:1で割り当てています。Xmp1_TapLogCh_Maxの定義を変更した場合は、このデータも変更して下さい。
			{ 8,-1},{ 9,-1},{10,-1},{11,-1},	//│
			{12,-1},{13,-1},{14,-1},{15,-1}};	//┘
		static const struct {
			ST_TapSeqInfo	_stTapSeqInfo;
			const uint8_t*	_TBL_TapSet[(Xmp1_TapLogCh_Max+1)];
		} stTapSeqInfo = {{
			(8191+1),		//nPhr
			(Xmp1_TapDevCh_Max+1),	//nDevCh
			(Xmp1_TapLogCh_Max+1),	//nLogCh
			(Xmp1_TapSeqCh_Max+1),	//nSeqCh
			Xmp1_TapSeq_iCont,	//iCont
			Xmp1_TapSeq_nEvt,	//nEvt
			Xmp1_TapSeq_iEvtMsk,	//iEvtMsk	変更可
			Xmp1_TapSeq_fnPlay,
			Xmp1_TapSeq_fnStop,
			Xmp1_TapSeq_fnLoop,
			Xmp1_TapSeq_fnEnd,
			Xmp1_TapSeq_fnNext,
			Xmp1_TapSeq_fnCtrl,
			Xmp1_TapSeq_fnGetPhrTime,
			Xmp1_TapSeq_fnGetPhrNext,
		},{
			TBL_TapSet[ 0],TBL_TapSet[ 1],TBL_TapSet[ 2],TBL_TapSet[ 3],	//┐
			TBL_TapSet[ 4],TBL_TapSet[ 5],TBL_TapSet[ 6],TBL_TapSet[ 7],	//├2016/10/13現在の実装では、論理チャネル数とデバイスチャネルを1:1で割り当てています。Xmp1_TapLogCh_Maxの定義を変更した場合は、このデータも変更して下さい。
			TBL_TapSet[ 8],TBL_TapSet[ 9],TBL_TapSet[10],TBL_TapSet[11],	//│
			TBL_TapSet[12],TBL_TapSet[13],TBL_TapSet[14],TBL_TapSet[15],	//┘
		}};
		//シーケンサを初期化する。
		TapSeq_Init(Xmp1_GetTapSeq(pXmp1), &stTapSeqInfo._stTapSeqInfo);
		//シーケンサのタイマ関数を登録する。
		pXmp1->stTimMgrFunc_TapSeq.fn		= Xmp1_Exec_Xms;
		pXmp1->stTimMgrFunc_TapSeq.arg		= pXmp1;
		pXmp1->stTimMgrFunc_TapSeq.uItv		= Xmp1_Exec_Xms_uItv;
		TimMgr_AddFunc(pTimMgr, TimMgrCh_1ms, &pXmp1->stTimMgrFunc_TapSeq);
	}
	//--- ミキサーの初期化処理 ---
	{
		//ミキサーを初期化する。
		TapMxr_Init(Xmp1_GetTapMxr(pXmp1), (Xmp1_TapMxrCh_Max+1));
		//ミキサーのタイマ関数を登録する。
		pXmp1->stTimMgrFunc_TapMxr.fn		= Xmp1_Exec_X00ms;
		pXmp1->stTimMgrFunc_TapMxr.arg		= pXmp1;
		pXmp1->stTimMgrFunc_TapMxr.uItv		= Xmp1_Exec_X00ms_uItv;
		TimMgr_AddFunc(pTimMgr, TimMgrCh_100ms, &pXmp1->stTimMgrFunc_TapMxr);
	}
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
//管理構造体に含まれている、シーケンサへのポインタを取得する。
ST_TapSeq* Xmp1_GetTapSeq(ST_Xmp1* pXmp1) {
	return &pXmp1->_stTapSeq;
}
/*--------------------------------------------------------------------------*/
//管理構造体に含まれている、ミキサーへのポインタを取得する。
ST_TapMxr* Xmp1_GetTapMxr(ST_Xmp1* pXmp1) {
	return &pXmp1->_stTapMxr;
}
/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/
//XMP-1のレジスタを読み出す。
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static uint8_t Xmp1_Read(ST_Xmp1* pXmp1, uint16_t rb_index/*((rb<<8)|index)*/) {
	uint8_t rb    = rb_index >> 8;	//rb_index[15:8]=レジスタバンク(0〜6)
	uint8_t index = rb_index;	//rb_index[ 7:0]=レジスタ番地(0x00〜0xFF)
	return (*pXmp1->pInfo->readXmp1Reg)(rb, index);		//実際の処理は、アプリケーション定義の関数を呼び出して行う。
}
/*---------------------------------------------------------------------------*/
//XMP-1のレジスタに書き込む。
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_Write(ST_Xmp1* pXmp1, uint16_t rb_index/*((rb<<8)|index)*/, uint8_t data) {
	uint8_t rb    = rb_index >> 8;	//rb_index[15:8]=レジスタバンク(0〜6)
	uint8_t index = rb_index;	//rb_index[ 7:0]=レジスタ番地(0x00〜0xFF)
	(*pXmp1->pInfo->writeXmp1Reg)(rb, index, data);		//実際の処理は、アプリケーション定義の関数を呼び出して行う。
}
/*--------------------------------------------------------------------------*/
//トラック構造体を取得する。
// - 当関数は、割り込み許可状態で実行しても安全です。
static ST_Xmp1Trk* Xmp1_GetXmp1Trk(ST_Xmp1* pXmp1, int iDevCh) {
	if((unsigned)iDevCh > Xmp1_TapDevCh_Max) { DIE(); }
	return &pXmp1->TBL_Xmp1Trk[iDevCh];
}
/*---------------------------------------------------------------------------*/
//論理チャネルの左右パン値,1次ボリューム値,2次ボリューム値を求める。
// - 当関数は、割り込み許可状態で実行しても安全です。
static void Xmp1_GetPanVol(ST_Xmp1* pXmp1, int iLogCh, ST_TapLogCh* pLogCh, int* pPan, int* pVol1, int* pVol2) {
	int fMxrChMask, iPan, iVol1, iVol2;
	//左右パン値を求める。
	// - ステレオフレーズの場合は、左右パン値はダミーとなる。
	iPan = 0x40/*左右パンの中央値*/ + pLogCh->iPan;
	if(iPan < 0x00) { iPan = 0x00; }
	if(iPan > 0x80) { iPan = 0x80; }
	*pPan = iPan;
	//1次ボリューム値を求める。
	// - 論理チャネルに適用するミキサーチャネルの合計減衰量を取得し、減衰量⇒音量に変換する。
	if((Xmp1_TapMxrCh_Max + 1) > (sizeof(int) * 8)) { DIE(); }	//TapMxr_GetAtt()自体は(ミキサーチャネル数>32)も対応しているが、当関数は簡単のために(ミキサーチャネル<=32)を前提としている。
	fMxrChMask = (*pXmp1->pInfo->getMxrChMask)(iLogCh);		//ミキサーチャネルマスクを取得する。(ミキサーチャネル数<=32)を前提としているので、(int)配列ではなく(int)値として取得出来る。
	iVol1 = 0x80/*1次ボリュームの最大値*/ - TapMxr_GetAtt(Xmp1_GetTapMxr(pXmp1), &fMxrChMask);	//論理チャネルに適用するミキサーチャネルの合計減衰量を取得し、減衰量⇒音量に変換する。
	if(iVol1 < 0) { iVol1 = 0; }
	*pVol1 = iVol1;
	//2次ボリューム値を求める。
	// - 論理チャネルの減衰量を取得し、減衰量⇒音量に変換する。
	iVol2 = 0x80/*2次ボリュームの最大値*/ - pLogCh->iAtt;
	if(iVol2 < 0) { iVol2 = 0; }
	*pVol2 = iVol2;
}
/*---------------------------------------------------------------------------*/
//トラックの、ボリュームアップデートを行う。
// - Xmp1_Exec_Xms()とXmp1_TapSeq_fnCtrl()の共通処理です。
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_VolUpd(ST_Xmp1* pXmp1, int iDevCh, ST_Xmp1Trk* pXmp1Trk) {
	//ステレオフレーズならば…
	if(pXmp1Trk->Flag & Xmp1Trk_Flag_Stereo) {
		//ボリュームアップデートを行う。
		Xmp1_Write(pXmp1, XMP1_P0_x_7_0,	0x82);			//左右パン			ステレオ(右)
		Xmp1_Write(pXmp1, XMP1_V1_x_7_0,	pXmp1Trk->V1);		//1次ボリューム			ステレオ(右)
		Xmp1_Write(pXmp1, XMP1_V2_x_7_0,	pXmp1Trk->V2);		//2次ボリューム			ステレオ(右)
		Xmp1_Write(pXmp1, XMP1_LOOP_x_7_0,	pXmp1Trk->LOOP);	//ループ回数			ステレオ(右)
		Xmp1_Write(pXmp1, XMP1_PVUP_x_PTRK_6_0,	0x81|(iDevCh<<1));	//ボリュームアップデート	ステレオ(右)	トラック番号=デバイスチャネル番号*2+1
		Xmp1_Write(pXmp1, XMP1_P0_x_7_0,	0x81);			//左右パン			ステレオ(左)
	//不要	Xmp1_Write(pXmp1, XMP1_V1_x_7_0,	pXmp1Trk->V1);		//1次ボリューム			ステレオ(左)	レジスタ値変更不要
	//不要	Xmp1_Write(pXmp1, XMP1_V2_x_7_0,	pXmp1Trk->V2);		//2次ボリューム			ステレオ(左)	レジスタ値変更不要
	//不要	Xmp1_Write(pXmp1, XMP1_LOOP_x_7_0,	pXmp1Trk->LOOP);	//ループ回数			ステレオ(左)	レジスタ値変更不要
		Xmp1_Write(pXmp1, XMP1_PVUP_x_PTRK_6_0,	0x80|(iDevCh<<1));	//ボリュームアップデート	ステレオ(左)	トラック番号=デバイスチャネル番号*2+0
	//モノラルフレーズならば…
	} else {
		//ボリュームアップデートを行う。
		Xmp1_Write(pXmp1, XMP1_P0_x_7_0,	pXmp1Trk->P0);		//左右パン			モノラル
		Xmp1_Write(pXmp1, XMP1_V1_x_7_0,	pXmp1Trk->V1);		//1次ボリューム			モノラル
		Xmp1_Write(pXmp1, XMP1_V2_x_7_0,	pXmp1Trk->V2);		//2次ボリューム			モノラル
		Xmp1_Write(pXmp1, XMP1_LOOP_x_7_0,	pXmp1Trk->LOOP);	//ループ回数			モノラル
		Xmp1_Write(pXmp1, XMP1_PVUP_x_PTRK_6_0,	0x80|(iDevCh<<1));	//ボリュームアップデート	モノラル	トラック番号=デバイスチャネル番号*2
	}
}
/*--------------------------------------------------------------------------*/
//トータルボリュームを設定する。
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_SetTvo(ST_Xmp1* pXmp1, int iVol) {
	Xmp1_Write(pXmp1, XMP1_TV0_7_0,		iVol);		//L0,R0
	Xmp1_Write(pXmp1, XMP1_TV1_7_0,		iVol);		//L1,R1
	Xmp1_Write(pXmp1, XMP1_TV2_7_0,		iVol);		//SUB0
	Xmp1_Write(pXmp1, XMP1_TV3_7_0,		iVol);		//SUB1
}
/*--------------------------------------------------------------------------*/
//1msタイマ関数
// - TimMgr_Intr()から割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
// - ※TODO:2016/10/13現在の実装では、以下の制約が考慮出来ていません。
//   ■参照資料「YMZ871 ｱﾌﾟﾘｹｰｼｮﾝﾏﾆｭｱﾙ CATALOG No. LSI-5MZ871A40 2013.9」(5MZ871A40.pdf)の、「7.1.2. フレーズ再生、シーケンサー」(p.232)より引用
//   │7.1.2. フレーズ再生、シーケンサー
//   │11. フレーズ再生は最大32モノラルフレーズ、16ステレオフレーズの同時再生を行うことができます。
//   │    ただし、以下の処理に関しては、同一フレーム内で処理できるフレーズ数は最大16モノラルフレーズ、8ステレオフレーズになります。
//   │    ・同一フレーム内で同時に発行できる先着優先再生、後着優先再生の数
//   │    ・同一フレーム内で同時に発行できるポーズ、リジューム要求の数
//   │    たとえば、再生開始タイミングをずらしながら合計32モノラルフレーズを先着優先、後着優先再生することはできますが、同時に32モノラルフレーズの先着優先、後着優先再生を開始することはできません。
//   普通の使用方法で上記の制限を超過する事はまず無いと思うので、上記の制限を考慮しなくても問題が顕在化する可能性は低いのですが、やはり確実のためには考慮しておくべきです。
//   再生開始処理は当関数内でしか行っておらず,且つ,当関数は定期的に実行される関数なので、上記の制約を考慮して制約を超えたサレーズの再生開始処理は次回へ遅延する事が可能なはずです。(※TODO:そうしろ)
static void Xmp1_Exec_Xms(void* arg) {
	ST_Xmp1* pXmp1 = arg;
	//--- シーケンサの処理 ---
	{
		//シーケンサを実行する。
		TapSeq_Exec(Xmp1_GetTapSeq(pXmp1), Xmp1_Exec_Xms_uItv/*[ms]*/);
	}
	//--- ビジー待ちフレーズの処理 ---
	{
		int iDevCh, t;
		//現在時刻を取得する。
		t = (*pXmp1->pInfo->getMsecTime)();
		//各トラックについて…
		for(iDevCh = 0; iDevCh <= Xmp1_TapDevCh_Max; iDevCh++) {
			ST_Xmp1Trk* pXmp1Trk = Xmp1_GetXmp1Trk(pXmp1, iDevCh);
			//ビジーならば…
			if(pXmp1Trk->Flag & Xmp1Trk_Flag_Busy) {
				//ビジー判定基準時刻から一定時間以上経過したら、レディとする。
				if((uint8_t)(t - pXmp1Trk->BusyTime) >= Xmp1_BusyToReady_ms) { pXmp1Trk->Flag &= ~Xmp1Trk_Flag_Busy; }
			}
			//ビジー待ちフレーズが有れば…
			if(pXmp1Trk->Flag & Xmp1Trk_Flag_Pending) {
				//レディならば…
				if(!(pXmp1Trk->Flag & Xmp1Trk_Flag_Busy)) {
					//トラック構造体に値を格納する。
					pXmp1Trk->Flag		&= ~Xmp1Trk_Flag_Pending;	//ビジー待ちフレーズをクリアする。
					pXmp1Trk->Flag		|=  Xmp1Trk_Flag_Busy;		//ビジーとする。
					pXmp1Trk->BusyTime	= t;				//ビジー判定基準時刻を格納する。
					//ボリュームアップデートを行う。
					Xmp1_VolUpd(pXmp1, iDevCh, pXmp1Trk);
					//ビジー待ちフレーズを再生開始する。
					Xmp1_Write(pXmp1, XMP1_PN_x0_7_0,				pXmp1Trk->PN);		//┬フレーズ番号
					Xmp1_Write(pXmp1, XMP1_PL_x0_2_0_PN_x0_12_8,			pXmp1Trk->PN>>8);	//┘
					Xmp1_Write(pXmp1, XMP1_PPLAY_x_1_0_PPLAY_PRI_PNRCL_PNUM_x_2_0,	0x40);			//再生開始
				}
			}
		}
	}
}
/*--------------------------------------------------------------------------*/
//100msタイマ関数
// - TimMgr_Exec()から割り込み許可状態で呼び出されるので、当関数内で割り込み禁止処理を行う必要が有ります。
static void Xmp1_Exec_X00ms(void* arg) {
	ST_Xmp1* pXmp1 = arg;
ENTER_CS;
	//--- ミキサーの処理 ---
	{
		int iDevCh, iLogCh;
		ST_TapLogCh* pLogCh;
		//ミキサーを実行する。
		TapMxr_Exec(Xmp1_GetTapMxr(pXmp1), (Xmp1_Exec_X00ms_uItv * 100)/*[ms]*/);
		//各デバイスチャネルについて…
		for(iDevCh = 0; iDevCh <= Xmp1_TapDevCh_Max; iDevCh++) {
			//このデバイスチャネルを制御している論理チャネルが有れば…
			iLogCh = TapSeq_GetDevCh(Xmp1_GetTapSeq(pXmp1), iDevCh)->iLogCh;
			if(iLogCh != UINT8_MAX) {
				//このデバイスチャネルを制御している論理チャネルを取得する。
				pLogCh = TapSeq_GetLogCh(Xmp1_GetTapSeq(pXmp1), iLogCh);
				//デバイスチャネルの音量を設定する。
				// - 値が変化していない場合は、Xmp1_TapSeq_fnCtrl()が判定して、ボリュームアップデートを行わない。
				//   従って、無条件にXmp1_TapSeq_fnCtrl()を呼び出して構わない。
				Xmp1_TapSeq_fnCtrl(Xmp1_GetTapSeq(pXmp1), iLogCh, pLogCh);
			}
		}
	}
	//--- ボリュームスイッチの処理 ---
	{
		int iVol;
		//ボリュームスイッチのポート値を取得する。
		iVol = (*pXmp1->pInfo->getVolumeSw)();
		//ボリュームスイッチのポート値が、ボリュームスイッチの安定値と違うならば…
		if(iVol != pXmp1->Vol) {
			//ボリュームスイッチのポート値が変化していたら…
			if(iVol != pXmp1->NewVol) {
				//ボリュームスイッチの変化時刻を格納する。
				pXmp1->NewVolTime = (*pXmp1->pInfo->getMsecTime)();
			//ボリュームスイッチのポート値が変化しておらず、ボリュームスイッチの変化時刻から一定時間以上経過したら…
			} else if((uint8_t)((*pXmp1->pInfo->getMsecTime)() - pXmp1->NewVolTime) >= Xmp1_VolumeSw_Stable_ms) {
				//ボリュームスイッチの安定値を格納する。
				pXmp1->Vol = iVol;
				//トータルボリュームを設定する。
				Xmp1_SetTvo(pXmp1, pXmp1->Vol);
			}
		}
		//いずれにしても、ボリュームスイッチのポート値を格納する。
		pXmp1->NewVol = iVol;
	}
LEAVE_CS;
}
/*---------------------------------------------------------------------------*/
//再生開始通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnPlay(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	//論理チャネルがデバイスチャネルを獲得していたら…
	int iDevCh = pLogCh->iDevCh;
	if(iDevCh != UINT8_MAX) {
		int iPan, iVol1, iVol2, iLoop, No, Channel;
		//トラック構造体を取得する。
		ST_Xmp1Trk* pXmp1Trk = Xmp1_GetXmp1Trk(pXmp1, iDevCh);
		//論理チャネルの左右パン値,1次ボリューム値,2次ボリューム値を求める。
		// - ステレオフレーズの場合は、左右パン値はダミーとなる。
		Xmp1_GetPanVol(pXmp1, iLogCh, pLogCh, &iPan, &iVol1, &iVol2);
		//ループ回数の設定値を求める。
		// - 0(無限),1(1回)〜255(255回) ⇒ 0(1回)〜254(255回),255(無限)
		iLoop = pLogCh->iLoop - 1;
		//フレーズマップ情報を取得する。
		No      = REG_get_value_l(pXmp1->pInfo->TBL_RegTbl, pXmp1->pInfo->_RegKey_PhrDef, pLogCh->iPhr, pXmp1->pInfo->_RegKey_No,      -1);
		Channel = REG_get_value_l(pXmp1->pInfo->TBL_RegTbl, pXmp1->pInfo->_RegKey_PhrDef, pLogCh->iPhr, pXmp1->pInfo->_RegKey_Channel, -1);
		if(((unsigned)No > 8191) || ((Channel < 1) || (Channel > 2))) { DIE(); }
		//トラック構造体に値を格納する。
		if(Channel == 1) {
			pXmp1Trk->Flag		&= ~Xmp1Trk_Flag_Stereo;	//モノラルフレーズ
		} else {
			pXmp1Trk->Flag		|=  Xmp1Trk_Flag_Stereo;	//ステレオフレーズ
		}
		pXmp1Trk->Flag		|=  Xmp1Trk_Flag_Pending;		//ビジー待ちフレーズをセットする。	重要!!この下のコメントを参照せよ。
		pXmp1Trk->PN		= No;		//フレーズ番号
		pXmp1Trk->P0		= iPan;		//左右パン		ステレオフレーズの場合はダミー
		pXmp1Trk->V1		= iVol1;	//1次ボリューム
		pXmp1Trk->V2		= iVol2;	//2次ボリューム
		pXmp1Trk->LOOP		= iLoop;	//ループ回数
		//当関数内では、再生開始処理を行わない。
		// - レディならば、直後のXmp1_Exec_Xms()で再生開始処理が行われる。
		//   ビジーならば、レディになった後のXmp1_Exec_Xms()で再生開始処理が行われる。
		//   いずれにしても、ここで行う処理は、上記のトラック構造体に値を格納する処理だけで良い。
		// - レディならば、ここで再生開始処理を行っても構わないのだが、再生開始処理をXmp1_Exec_Xms()まで遅延してもほとんど違いが無いだろう。
		//   再生開始処理が二か所に分散して判り辛くなる事を避けるために、再生開始処理はXmp1_Exec_Xms()だけで行う事にした。
	}
}
/*---------------------------------------------------------------------------*/
//再生停止通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnStop(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	//論理チャネルがデバイスチャネルを獲得していたら…
	int iDevCh = pLogCh->iDevCh;
	if(iDevCh != UINT8_MAX) {
		//トラック構造体を取得する。
		ST_Xmp1Trk* pXmp1Trk = Xmp1_GetXmp1Trk(pXmp1, iDevCh);
		//トラック構造体に値を格納する。
		pXmp1Trk->PN		= -1;					//┬トラックを停止中とし、ビジー待ちフレーズを確実にクリアする。	忘れないで!!
		pXmp1Trk->Flag		&= ~Xmp1Trk_Flag_Pending;		//┘
	//不要	pXmp1Trk->Flag		|=  Xmp1Trk_Flag_Busy;			//┬もしビジーだったならば、現在のビジー判定基準時刻を継続する。
	//不要	pXmp1Trk->BusyTime	= (*pXmp1->pInfo->getMsecTime)();	//┘
		//トラックを停止する。
		Xmp1_Write(pXmp1, XMP1_PVUP_x_PTRK_6_0,				0x00|(iDevCh<<1));	//ボリュームアップデート無し	トラック番号=デバイスチャネル番号*2
		Xmp1_Write(pXmp1, XMP1_PPLAY_x_1_0_PPLAY_PRI_PNRCL_PNUM_x_2_0,	0x00);			//停止
	}
}
/*---------------------------------------------------------------------------*/
//自然ループ通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnLoop(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
//不要	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	/** no job **/
}
/*---------------------------------------------------------------------------*/
//自然停止通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnEnd(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
//不要	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	/** no job **/
}
/*---------------------------------------------------------------------------*/
//自然遷移通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnNext(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
//不要	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	Xmp1_TapSeq_fnPlay(pTapSeq, iLogCh, pLogCh);
}
/*---------------------------------------------------------------------------*/
//コントロール通知
// - 割り込み禁止状態で呼び出されるので、当関数内で割り込み禁止処理は不要です。
static void Xmp1_TapSeq_fnCtrl(ST_TapSeq* pTapSeq, int iLogCh, ST_TapLogCh* pLogCh) {
	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	//論理チャネルがデバイスチャネルを獲得していたら…
	int iDevCh = pLogCh->iDevCh;
	if(iDevCh != UINT8_MAX) {
		int iPan, iVol1, iVol2;
		//トラック構造体を取得する。
		ST_Xmp1Trk* pXmp1Trk = Xmp1_GetXmp1Trk(pXmp1, iDevCh);
		//再生中ならば…
		// - pXmp1Trk->PNはビジー待ちフレーズを示している可能性も有るが、その場合もトラック構造体に値を格納する必要が有るので以下の処理を実行する。
		if(pXmp1Trk->PN != -1) {
			//論理チャネルの左右パン値,1次ボリューム値,2次ボリューム値を求める。
			// - ステレオフレーズの場合は、左右パン値はダミーとなる。
			Xmp1_GetPanVol(pXmp1, iLogCh, pLogCh, &iPan, &iVol1, &iVol2);
			//値が一つでも変化していたら…
			if((pXmp1Trk->P0 != iPan) ||		//左右パン		ステレオフレーズの場合はダミー
			   (pXmp1Trk->V1 != iVol1) ||		//1次ボリューム
			   (pXmp1Trk->V2 != iVol2)) {		//2次ボリューム
				//トラック構造体に値を格納する。
				pXmp1Trk->P0		= iPan;		//左右パン		ステレオフレーズの場合はダミー
				pXmp1Trk->V1		= iVol1;	//1次ボリューム
				pXmp1Trk->V2		= iVol2;	//2次ボリューム
				//ビジー待ちフレーズが無ければ…
				if(!(pXmp1Trk->Flag & Xmp1Trk_Flag_Pending)) {
					//ボリュームアップデートを行う。
					Xmp1_VolUpd(pXmp1, iDevCh, pXmp1Trk);
				//ビジー待ちフレーズが有れば…
				} else {
					//pXmp1Trk->PNがビジー待ちフレーズだった場合は、ボリュームアップデートを行わない事にしました。
					//pXmp1Trk->PNがビジー待ちフレーズだった場合も、ボリュームアップデートを行っても構いませんが、無駄です。
					//レディになった後のXmp1_Exec_Xms()で、トラック構造体に格納されている値を用いて、再生開始処理が行われるからです。
					/** no job **/
				}
			}
		}
	}
}
/*---------------------------------------------------------------------------*/
//フレーズ時間[ms]を取得する。
// - 当関数は、割り込み許可状態で実行しても安全です。
static int Xmp1_TapSeq_fnGetPhrTime(ST_TapSeq* pTapSeq, int iPhr) {
	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	int Time = REG_get_value_l(
		pXmp1->pInfo->TBL_RegTbl,
		pXmp1->pInfo->_RegKey_PhrDef,
		iPhr,
		pXmp1->pInfo->_RegKey_Time,
		-1);
	if(Time == -1) { DIE(); }
	return Time;
}
/*---------------------------------------------------------------------------*/
//フレーズチェインの次のフレーズ番号を取得する。
// - 当関数は、割り込み許可状態で実行しても安全です。
static int Xmp1_TapSeq_fnGetPhrNext(ST_TapSeq* pTapSeq, int iPhr) {
	ST_Xmp1* pXmp1 = CONTAINING_RECORD(pTapSeq, ST_Xmp1, _stTapSeq);
	int iNext = REG_get_value_l(
		pXmp1->pInfo->TBL_RegTbl,
		pXmp1->pInfo->_RegKey_PhrDef,
		iPhr,
		pXmp1->pInfo->_RegKey_Next,
		-1);
	iNext = (iNext << 8) >> 8;	//iNext[23]を符号拡張する。	忘れないで!!
	return iNext;
}
