/*
 *	clipleds.c
 *
 *	LEDシーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2017 Naoyuki Sawa
 *
 *	* Sat Feb 28 21:13:50 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- clipleds.h,clipleds.cを大幅に変更しました。
 *	  変更前のclipleds.h,clipleds.cはもう使用しませんが、これまで安定して動作していたので、
 *	  今後の参考のために、「keep/16_LEDシーケンサ変更前保存.20150228/」に保存しておくことにしました。
 *	* Sun Mar 01 08:36:08 JST 2015 Naoyuki Sawa
 *	- dLedSeqC.exeを変更してイントロ部分の圧縮データサイズを格納しなくなった事に伴い、当モジュールの処理も変更しました。
 *	- やや大幅な変更になったので、変更前のコードは当モジュール内には残してありません。
 *	  変更前の処理でも動作確認済みだったので、今後変更前のコードを参考にする場合は、前回のアーカイブを参照してください。
 *	* Tue May 05 17:15:43 JST 2015 Naoyuki Sawa
 *	- LedSeqCh_PlayNo()を追加しました。
 *	* Mon Jun 01 21:03:49 JST 2015 Naoyuki Sawa
 *	- LedSeq_Exec()が展開バッファを1(消灯)でクリアする処理を削除しました。
 *	  今後は、アプリケーションがLedSeq_Exec()を呼び出す前に展開バッファをクリアして下さい。
 *	  1(消灯)以外の値でクリアしたり、LED毎に異なる値でクリアする事も可能です。
 *	  そうする事によって、データ再生中でないLEDのデフォルト輝度をLED毎に個別に設定出来ます。
 *	* Fri Jun 05 21:32:39 JST 2015 Naoyuki Sawa
 *	- dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。
 *	  詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
 *	* Sun Jun 07 19:29:17 JST 2015 Naoyuki Sawa
 *	- LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
 *	  │□修正前の問題点
 *	  │- LedSeq_Exec()は最終フレームを再生した直後にpLedSeqCh->pSeq=NULLにはなりません。
 *	  │  次回にLedSeq_Exec()が呼ばれて終端符合を読み出した時にpLedSeqCh->pSeq=NULLになります。
 *	  │  LedSeqCh_GetStat()はpLedSeqCh->pSeq=NULLかどうかだけを見て(再生中/停止中)を返していました。
 *	  │  つまり、LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていました。
 *	  │- 普段は上記の挙動が大きな問題になる事は無いのですが、下記のケースにおいて問題になる事が発生しました。
 *	  │  アプリケーションがLedSeqCh_GetStat()で特定のシーケンサチャネルをポーリングし、停止したら次のデータを再生開始するようなケースです。
 *	  │  (以下のような単純な要件ならばポーリングせずともデータでループさせれば良いのですが、あくまで説明のためのサンプルと考えてください。)
 *	  │  │//--- LedDef.txt ---
 *	  │  │Test1
 *	  │  │  play  Data1
 *	  │  │  end
 *	  │  │Data1
 *	  │  │  data  31
 *	  │  │  data  31
 *	  │  │  data  31
 *	  │  │  end
 *	  │  │//--- app.c ---
 *	  │  │void app_main() {
 *	  │  │  ST_LedSeq* pLedSeq=LedSeq_New((LedCh_Max+1),1);	//{{2016/03/04変更:関数名を、旧:LedSeq_Init()⇒新:LedSeq_New()、旧:LedSeq_InitStatic()⇒新:LedSeq_Init() に変更しました。}}
 *	  │  │  for(;;){
 *	  │  │    uint8_t Buffer[(LedCh_Max+1)];
 *	  │  │    schedule();
 *	  │  │    if(!LedSeqCh_GetStat(pLedSeq,0)){LedSeqCh_PlayNo(pLedSeq,0,TBL_LedSeqNo,LedSeqNo_Test1);}
 *	  │  │    memset(Buffer,1,(LedCh_Max+1));//1=消灯
 *	  │  │    LedSeq_Exec(pLedSeq,Buffer);
 *	  │  │    printf("%d ",Buffer[0]);
 *	  │  │  }
 *	  │  │}
 *	  │  │//--- 結果 ---
 *	  │  │31 31 31 1 31 31 31 1 31 31 31 1 31 31 31 1
 *	  │  │         ~          ~          ~          ~ 停止中の判断が1フレーム遅れて、正しい連続再生になっていない。
 *	  │□修正した方法
 *	  │- LedSeq_Exec()の動作は、修正前と変えていません。
 *	  │  LedSeqCh_GetStat()の動作を変える事で、修正しました。具体的には、以下の通りです。
 *	  │- LedSeqCh_GetStat()は、当該シーケンサチャネルが再生中ならば、次の1フレームをデコードしてみて、デコードできなければ停止中と判断するようにしました。
 *	  │  厳密には、次の1フレームを全部デコードする必要は無く、先頭部分がデコードできればフレーム全体がデコード可能と判断出来ます。
 *	  │  上記のように変更した結果は、以下の通りです。
 *	  │  │//--- 結果 ---
 *	  │  │31 31 31 31 31 31 31 31 31 31 31 31
 *	  │□修正によるデメリットと、アプリケーションの注意点
 *	  │- 再生中のシーケンサチャネルに対するLedSeqCh_GetStat()呼び出しの処理時間が、長くかかるようになりました。
 *	  │  修正前は単にpLedSeqCh->pSeqがNULLかどうかを見るだけだったのが、修正後は次の1フレームの先頭部分をデコードしてみる時間が掛かるからです。
 *	  │  尚、停止中のシーケンサチャネルに対するLedSeqCh_GetStat()呼び出しでは、上記の処理は行わずに停止中と判断できるので、時間は掛かりません。
 *	  │- LedSeqCh_GetStat()の呼び出しコストが少し高くなったので、アプリケーションは無闇にLedSeqCh_GetStat()を呼び出さないよう注意してください。
 *	  │  極端に避ける必要は有りませんが、全シーケンサチャネルに対して必要の無いポーリングを行うような、無駄な処理は行わないようにしてください。
 *	* Tue Aug 25 21:51:09 JST 2015 Naoyuki Sawa
 *	- LedSeqCh_SetHide()を追加しました。
 *	  非表示に設定されたチャネルは、LedSeq_Exec()が呼ばれた時に、再生処理は進行しますが、展開バッファに格納しません。
 *	- '特定のチャネルを一時的に非表示(=完全透明)にしたい'という要件が生じたために、追加した関数です。
 *	  尚、この要件は、LedSeqCh_SetAtt()では実現出来ません。
 *	  減衰量を最大にしても、輝度1(不透明黒)までしか変化せず、輝度0(透明)にはならないからです。
 *	* Fri Mar 04 21:22:19 JST 2016 Naoyuki Sawa
 *	- 関数名を、旧:LedSeq_Init()⇒新:LedSeq_New()、旧:LedSeq_InitStatic()⇒新:LedSeq_Init() に変更しました。
 *	  最近作成している他のモジュールの関数名に合わせるためです。
 *	* Wed Aug 03 21:50:35 JST 2016 Naoyuki Sawa
 *	- clipleds.cのLedSeqCh_ExecDat()において、ループ部分のアドレスを取得する処理を、BytePairEncodingDecoder_GetEndPtr()を利用するように変更しました。
 *	  これまでは、ST_BytePairEncodingDecoder構造体のフィールドを直接参照して計算していましたが、
 *	  2016/04/28にframbpe.cモジュールにBytePairEncodingDecoder_GetEndPtr()を追加していた事に伴い、BytePairEncodingDecoder_GetEndPtr()を利用するように変更しました。
 *	  処理の結果は変わりません。
 *	* Sun Oct 09 21:21:44 JST 2016 Naoyuki Sawa
 *	- LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
 *	  変更前のLedSeqCh_SetHide(〜,1)に相当する機能は、変更後はLedSeqCh_SetMode(〜,LedSeq_Mode_Hide)となります。
 *	  変更前のLedSeqCh_SetHide(〜,0)に相当する機能は、変更後はLedSeqCh_SetMode(〜,LedSeq_Mode_Normal)となります。
 *	* Sat Feb 25 22:50:01 JST 2017 Naoyuki Sawa
 *	- LedSeq_Exec()に引数fnFilterを追加しました。
 *	  追加した理由は、以下の通りです。
 *	  これまでも、LedSeq_Exec()を実行した結果のバッファの内容に対して、アプリケーション側で輝度を変換する事は可能でした。
 *	  或いは、特定のLEDシーケンサチャネルを一時的に透明にしたい場合は、LedSeqCh_SetMode(〜,LedSeq_Mode_Hide)を指定する事も可能でした。
 *	  しかし、前者はLEDチャネル単位の変換であり、複数のLEDデータが同一のLEDチャネルを光らせていた場合、片方のLEDデータの輝度だけを変換する事は出来ませんでした。
 *	  また、後者はLEDシーケンサチャネル単位の設定であり、あるLEDデータの一部分だけの輝度を変換する事は出来ませんでした。
 *	  今回、アプリケーション作成中に、あるLEDデータの特定の部分の輝度だけを選択的に有効にしたい、という要望が生じたので、フィルタ関数を追加して実現する事にしました。
 *	  LedSeq_Exec()の処理オーバーヘッドが多少増えますが、元々のデコード負荷に較べればさほど大きくないのではないかと思います。(アプリケーション定義のフィルタ関数の処理負荷次第ですが…)
 *	  尚、フィルタ関数には、LEDシーケンサチャネル番号とLEDチャネル番号は渡されますが、どのLEDデータが再生中かは渡されません。(元々、再生開始してしまえば、開始点がどのLEDデータだったかを識別する情報は保持していないので)
 *	  渡されたLEDシーケンサチャネル番号とLEDチャネル番号に対して、輝度変換が必要かどうかを判断する情報は、アプリケーション側で保持して下さい。
 *	- アプリケーション定義のフィルタ関数は、引数Dimmer(0=透明,1=消灯,2〜LedSeq_Dimmer_Max)を、引数pLedSeq,iLedSeqCh,iLedChや、アプリケーション側で保持している情報を参照して輝度を変換し、変換後の輝度(0=透明,1=消灯,2〜LedSeq_Dimmer_Max)を返して下さい。
 *	  □アプリケーション定義のフィルタ関数の実装例:
 *	  │static int fnFilter(ST_LedSeq* pLedSeq, int iLedSeqCh, int iLedCh, int Dimmer) {
 *	  │  if(bSpecialFlag) { //何らかの特殊演出中ならば…
 *	  │    if(iLedSeqCh == LedSeqCh_Dir) { //演出用のLEDシーケンサチャネルならば…
 *	  │      if((iLedCh >= LedCh_Up1) && (iLedCh <= LedCh_Up2)) { Dimmer = 0; }	//上部LEDならば透明にする。
 *	  │      if((iLedCh >= LedCh_Dn1) && (iLedCh <= LedCh_Dn2)) { if((Dimmer >= 2) ( Dimmer = max(((Dimmer - 1) * 2) + 1, LedSeq_Dimmer_Max); }	//下部LEDならば、輝度を二倍にする。
 *	  │    }
 *	  │  }
 *	  │  return Dimmer;
 *	  │}
 *	- アプリケーション定義のフィルタ関数は、(0=透明,1=消灯,2〜LedSeq_Dimmer_Max)の範囲外の輝度を返してはいけません。
 *	  アプリケーション定義のフィルタ関数が上記の範囲外の輝度を返すと、LedSeqCh_ExecDat()内の検査でエラー停止します。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static ST_LedSeqCh* LedSeq_GetLedSeqCh(ST_LedSeq* pLedSeq, int iLedSeqCh);
//{{2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
//static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/]);
//↓2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/], int (*fnFilter)(ST_LedSeq* pLedSeq, int iLedSeqCh, int iLedCh, int Dimmer));
//}}2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
static int LedSeqCh_ExecSeq(ST_LedSeq* pLedSeq, int iLedSeqCh);
/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/
ST_LedSeq* LedSeq_New(int nLedCh, int nLedSeqCh) {
	ST_LedSeq* pLedSeq;
	//LEDシーケンサ構造体のメモリを確保する。
	pLedSeq = malloc(
		   sizeof(ST_LedSeq    )
		+ (sizeof(unsigned char) * ((nLedCh+3)&~3))	//TBL_Dimmer[(nLedCh+3)&~3]
		+ (sizeof(ST_LedSeqCh  ) * nLedSeqCh));		//TBL_LedSeqCh[nLedSeqCh]
	if(!pLedSeq) { DIE(); }	//メモリ不足
	//LEDシーケンサ構造体を初期化する。
	LedSeq_Init(pLedSeq, nLedCh, nLedSeqCh);
	return pLedSeq;
}
/*--------------------------------------------------------------------------*/
void LedSeq_Init(ST_LedSeq* pLedSeq, int nLedCh, int nLedSeqCh) {
	//LEDシーケンサ構造体をクリアする。
	memset(pLedSeq, 0,
		   sizeof(ST_LedSeq    )
		+ (sizeof(unsigned char) * ((nLedCh+3)&~3))	//TBL_Dimmer[(nLedCh+3)&~3]
		+ (sizeof(ST_LedSeqCh  ) * nLedSeqCh));		//TBL_LedSeqCh[nLedSeqCh]
	//LEDシーケンサ構造体に、LEDチャネル数とLEDシーケンサチャネル数を格納する。
	pLedSeq->nLedCh    = nLedCh;
	pLedSeq->nLedSeqCh = nLedSeqCh;
//{{2016/10/09追加:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
	{
		int iLedSeqCh;
		//各LEDシーケンサチャネルのモードの初期値を'通常(上書き)'とする。
		for(iLedSeqCh = 0; iLedSeqCh < pLedSeq->nLedSeqCh; iLedSeqCh++) {
			LedSeqCh_SetMode(pLedSeq, iLedSeqCh, LedSeq_Mode_Normal);
		}
	}
//}}2016/10/09追加:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
}
/*--------------------------------------------------------------------------*/
//{{2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
//void LedSeq_Exec(ST_LedSeq* pLedSeq, unsigned char Buffer[/*nLedCh*/]) {
//↓2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
static int LedSeq_Exec_fnFilter(ST_LedSeq* pLedSeq, int iLedSeqCh, int iLedCh, int Dimmer) { return Dimmer; }	//引数fnFilterにNULLが指定された場合に使用する、何もせずに渡された輝度をそのまま返すフィルタ関数。
void LedSeq_Exec(ST_LedSeq* pLedSeq, unsigned char Buffer[/*nLedCh*/], int (*fnFilter/*NULL可*/)(ST_LedSeq* pLedSeq, int iLedSeqCh, int iLedCh, int Dimmer)) {
//}}2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
	unsigned char* const TBL_Dimmer/*[(nLedCh+3)&~3]*/ = (unsigned char*)(pLedSeq + 1);
	int iLedSeqCh, iLedCh, Dimmer;
    //{{2015/06/01削除:LedSeq_Exec()が展開バッファを1(消灯)でクリアする処理を削除しました。今後は、アプリケーションがLedSeq_Exec()を呼び出す前に展開バッファをクリアして下さい。1(消灯)以外の値でクリアしたり、LED毎に異なる値でクリアする事も可能です。そうする事によって、データ再生中でないLEDのデフォルト輝度をLED毎に個別に設定出来ます。
    //	//展開バッファを1(消灯)でクリアする。
    //	memset(Buffer, 1/*消灯*/, pLedSeq->nLedCh);	//0(透過)ではなく1(消灯)でクリアする。当関数の処理で0は格納しないので展開バッファの値は1〜LedSeq_Dimmer_Maxの範囲になる。アプリケーションが当関数から返されたBuffer[]の値を処理する時に0は考慮しなくて良い。
    //}}2015/06/01削除:LedSeq_Exec()が展開バッファを1(消灯)でクリアする処理を削除しました。今後は、アプリケーションがLedSeq_Exec()を呼び出す前に展開バッファをクリアして下さい。1(消灯)以外の値でクリアしたり、LED毎に異なる値でクリアする事も可能です。そうする事によって、データ再生中でないLEDのデフォルト輝度をLED毎に個別に設定出来ます。
    //{{2017/02/25追加:LedSeq_Exec()に引数fnFilterを追加しました。
	//引数fnFilterにNULLが指定されたら、何もせずに渡された輝度をそのまま返すフィルタ関数を使用する。
	if(!fnFilter) { fnFilter = LedSeq_Exec_fnFilter; }
    //}}2017/02/25追加:LedSeq_Exec()に引数fnFilterを追加しました。
	//各LEDシーケンサチャネルを実行する。
	for(iLedSeqCh = 0; iLedSeqCh < pLedSeq->nLedSeqCh; iLedSeqCh++) {	//チャネル番号が小さい方が奥,大きい方が手前とした。もし逆の方が良ければループ順序を逆に変更せよ。
    //{{2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
    //		LedSeqCh_ExecDat(pLedSeq, iLedSeqCh, Buffer);
    //↓2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
		LedSeqCh_ExecDat(pLedSeq, iLedSeqCh, Buffer, fnFilter);
    //}}2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
	}
	//各LEDチャネルについて、直接指定輝度が有れば(0で無ければ)上書きする。
	for(iLedCh = 0; iLedCh < pLedSeq->nLedCh; iLedCh++) {
		if((Dimmer = TBL_Dimmer[iLedCh])) { Buffer[iLedCh] = Dimmer; }
	}
}
/*--------------------------------------------------------------------------*/
void LedSeqCh_Play(ST_LedSeq* pLedSeq, int iLedSeqCh, const void* pSeq) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
//不要	//まず確実に停止する。
//不要	LedSeqCh_Stop(pLedSeq, iLedSeqCh);
	//シーケンス位置を格納する。
	pLedSeqCh->pSeq = pSeq;
	//初回のLedSeqCh_ExecDat()でLEDシーケンス処理を行うようにするための準備。
	{
		static const unsigned char Dummy[]={
			0,	//+0,1 イントロ部分
			0,	//+1 1 ↓(+2)		dLedSeqC.exeが生成するデータにはこのバイトは無い。LedSeqCh_ExecDat()に対して'イントロ部分の空でない最終ブロック(=空ブロックの直前)の終端符号の位置でstayしている状態と同じに見せかける'ためのハックである。詳細はLedSeqCh_ExecDat()の'ループ部分のアドレスを格納する'処理のコメント,及び,BytePairEncodingDecoder_Getc()の'符号を取り出す処理'のforループの処理を参照せよ。
			0};	//+2,1 ループ部分
		pLedSeqCh->pDatLoop  = NULL;
	    //{{2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
		pLedSeqCh->iLedCh_Lo = 0;
		pLedSeqCh->iLedCh_Hi = 0;
	    //}}2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
		BytePairEncodingDecoder_Init(&pLedSeqCh->Decoder, Dummy);
	}
}
/*--------------------------------------------------------------------------*/
void LedSeqCh_Stop(ST_LedSeq* pLedSeq, int iLedSeqCh) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	//シーケンサを停止する。
	pLedSeqCh->pSeq = NULL;
}
/*--------------------------------------------------------------------------*/
void LedSeq_Stop(ST_LedSeq* pLedSeq) {
	int iLedSeqCh;
	//各LEDシーケンサチャネルを停止する。
	for(iLedSeqCh = 0; iLedSeqCh < pLedSeq->nLedSeqCh; iLedSeqCh++) {
		LedSeqCh_Stop(pLedSeq, iLedSeqCh);
	}
}
/*--------------------------------------------------------------------------*/
int LedSeqCh_GetStat(ST_LedSeq* pLedSeq, int iLedSeqCh) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
    //{{2015/06/07変更:LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
    //	//シーケンサが停止中ならば0,再生中ならば1を返す。
    //	return !!pLedSeqCh->pSeq;
    //↓2015/06/07変更:LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
	ST_LedSeqCh save_LedSeqCh;
	//停止中ならば0を返す。
	if(!pLedSeqCh->pSeq) { return 0; }
	//LEDシーケンサチャネルの状態を保存する。
	save_LedSeqCh = *pLedSeqCh;
	//1フレームデコードしてみる。
    //{{2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
    //	LedSeqCh_ExecDat(pLedSeq, iLedSeqCh, NULL);
    //↓2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
	LedSeqCh_ExecDat(pLedSeq, iLedSeqCh, NULL, NULL);
    //}}2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
	//もし最終フレーム再生直後の状態ならば、デコード出来ずにpLedSeqCh->pSeq=NULLになる。
	//アプリケーションの観点からは停止中と同じ事であるので、0を返す事にした。
	//状態の復元は不要。pLedSeqCh->pSeq=NULLのままにする事で、次回の判定が高速化できる。
	if(!pLedSeqCh->pSeq) { return 0; }
	//LEDシーケンサチャネルの状態を復元する。
	*pLedSeqCh = save_LedSeqCh;
	//再生中なので1を返す。
	return 1;
    //}}2015/06/07変更:LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
}
/*--------------------------------------------------------------------------*/
void LedSeqCh_SetAtt(ST_LedSeq* pLedSeq, int iLedSeqCh, int Att) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	if((unsigned)Att > UCHAR_MAX) { DIE(); }		//減衰量が範囲外ならば、エラー停止する。
	//減衰量を格納する。
	pLedSeqCh->Att = Att;
}
/*--------------------------------------------------------------------------*/
//{{2015/08/25追加:LedSeqCh_SetHide()を追加しました。
//void LedSeqCh_SetHide(ST_LedSeq* pLedSeq, int iLedSeqCh, int Hide) {
//	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
//	//非表示フラグを格納する。
//	pLedSeqCh->Hide = !!Hide;	//確実に0,又は,1とする。動作上は2〜255でも構わないが、デバッグ時の判り易さのためです。
//}
//↓2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
void LedSeqCh_SetMode(ST_LedSeq* pLedSeq, int iLedSeqCh, int Mode) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	if((unsigned)Mode > LedSeq_Mode_Max) { DIE(); }		//モードが範囲外ならば、エラー停止する。
	//モードを格納する。
	pLedSeqCh->Mode = Mode;
}
//}}2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
/*--------------------------------------------------------------------------*/
void LedSeq_SetDimmer(ST_LedSeq* pLedSeq, int iLedCh, int Dimmer) {
	unsigned char* const TBL_Dimmer/*[(nLedCh+3)&~3]*/ = (unsigned char*)(pLedSeq + 1);
	if(((unsigned)iLedCh >= (unsigned)pLedSeq->nLedCh) ||	//LEDチャネル番号が範囲外か、
	   ((unsigned)Dimmer > LedSeq_Dimmer_Max)) { DIE(); }	//輝度が範囲外ならば、エラー停止する。
	//直接指定輝度を格納する。
	TBL_Dimmer[iLedCh] = Dimmer;
}
/*--------------------------------------------------------------------------*/
void LedSeq_SetDimmerNum(ST_LedSeq* pLedSeq, int iLedCh, int nLedCh, int Dimmer) {
#if 0	//コードサイズ優先
	while(--nLedCh >= 0) {
		LedSeq_SetDimmer(pLedSeq, iLedCh++, Dimmer);
	}
#else	//実行速度優先
	unsigned char* const TBL_Dimmer/*[(nLedCh+3)&~3]*/ = (unsigned char*)(pLedSeq + 1);
	if(((unsigned)iLedCh >= (unsigned) pLedSeq->nLedCh        ) ||	//開始LEDチャネル番号が範囲外か、
	   ((unsigned)nLedCh >  (unsigned)(pLedSeq->nLedCh-iLedCh)) ||	//設定するLEDチャネル数が範囲外か、
	   ((unsigned)Dimmer >  LedSeq_Dimmer_Max)) { DIE(); }		//輝度が範囲外ならば、エラー停止する。
	//直接指定輝度を格納する。
	memset(&TBL_Dimmer[iLedCh], Dimmer, nLedCh);
#endif
}
/*--------------------------------------------------------------------------*/
void LedSeq_SetDimmerAll(ST_LedSeq* pLedSeq, int Dimmer) {
	LedSeq_SetDimmerNum(pLedSeq, 0, pLedSeq->nLedCh, Dimmer);
}
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
//LEDシーケンサチャネルを取得する
static ST_LedSeqCh* LedSeq_GetLedSeqCh(ST_LedSeq* pLedSeq, int iLedSeqCh) {
	unsigned char* TBL_Dimmer/*[(nLedCh+3)&~3]*/;
	ST_LedSeqCh* TBL_LedSeqCh/*[nLedSeqCh]*/;
	if((unsigned)iLedSeqCh >= (unsigned)pLedSeq->nLedSeqCh) { DIE(); }			//LEDチャネル番号が範囲外ならば、エラー停止する。
	TBL_Dimmer   = (unsigned char*)(pLedSeq + 1);
	TBL_LedSeqCh = (ST_LedSeqCh*)&TBL_Dimmer[(pLedSeq->nLedCh+3)&~3];
	return &TBL_LedSeqCh[iLedSeqCh];
}
/*--------------------------------------------------------------------------*/
//LEDデータ再生処理
//{{2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
//static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/]) {	//{{2015/06/07コメント追加:LedSeqCh_GetStat()から呼び出された場合のみ(Buffer=NULL)になります。}}
//↓2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/], int (*fnFilter)(ST_LedSeq* pLedSeq, int iLedSeqCh, int iLedCh, int Dimmer)) {	//{{2015/06/07コメント追加:LedSeqCh_GetStat()から呼び出された場合のみ(Buffer=NULL)になります。}}	//{{2017/02/25コメント追加:LedSeqCh_GetStat()から呼び出された場合のみ(fnFilter=NULL)になります。}}
//}}2017/02/25変更:LedSeq_Exec()に引数fnFilterを追加しました。
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	ST_BytePairEncodingDecoder* const pDecoder = &pLedSeqCh->Decoder;
    //{{2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
    //	int iLedCh, Dimmer;
    //↓2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
	int iLedCh, Dimmer, tmp;
    //}}2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
	if(!pLedSeqCh->pSeq) { return; }							//シーケンサが停止していたら何もしない。
    //{{2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
    //	for(iLedCh = 0; iLedCh < pLedSeq->nLedCh; iLedCh++) {					//各LEDチャネルについて…
    //↓2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
	for(iLedCh = pLedSeqCh->iLedCh_Lo; iLedCh <= pLedSeqCh->iLedCh_Hi; iLedCh++) {		//不透明な輝度データが存在するLEDチャネル範囲の、各LEDチャネルについて…
    //}}2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
		Dimmer = BytePairEncodingDecoder_Getc(pDecoder);				//輝度をデコードする。
		if(Dimmer == -1) {								//圧縮データの終端ならば…	このように圧縮データの終端でイントロ部分の終端,及び,ループ部分の終端を判断出来るので、イントロ部分のフレーム数やループ部分のフレーム数をデータ内に格納しておく必要が無い。
		    //{{2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
		    //	if(iLedCh) { DIE(); }	//データバグ					//圧縮データの終端は最初のLEDチャネルでしか発生しないはずである。もしここで止まったらdLedSeqC.exeのバグかデータ破損の可能性が有る。
		    //↓2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
			if(iLedCh != pLedSeqCh->iLedCh_Lo) { DIE(); }	//データバグ		//圧縮データの終端は最初のLEDチャネルでしか発生しないはずである。もしここで止まったらdLedSeqC.exeのバグかデータ破損の可能性が有る。
		    //}}2015/06/05変更:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
			if(!pLedSeqCh->pDatLoop) {						//イントロ部分を再生中だったら…
			    //{{2016/08/03変更:clipleds.cのLedSeqCh_ExecDat()において、ループ部分のアドレスを取得する処理を、BytePairEncodingDecoder_GetEndPtr()を利用するように変更しました。
			    //	pLedSeqCh->pDatLoop = pDecoder->blk + pDecoder->ofs + 2/*!!*/;	//ループ部分のアドレスを格納する。デコーダは圧縮データの空でない最終ブロック(=空ブロックの直前)の終端符号の位置でstayしているので、(+2)する事に注意せよ。
			    //↓2016/08/03変更:clipleds.cのLedSeqCh_ExecDat()において、ループ部分のアドレスを取得する処理を、BytePairEncodingDecoder_GetEndPtr()を利用するように変更しました。
				pLedSeqCh->pDatLoop = BytePairEncodingDecoder_GetEndPtr(pDecoder);	//ループ部分のアドレスを格納する。(展開元データから、イントロ部分を展開し終わった直後の位置が、ループ部分の開始位置である。)
			    //}}2016/08/03変更:clipleds.cのLedSeqCh_ExecDat()において、ループ部分のアドレスを取得する処理を、BytePairEncodingDecoder_GetEndPtr()を利用するように変更しました。
			}
			if(*pLedSeqCh->pDatLoop) {						//ループ部分が有れば…
				BytePairEncodingDecoder_Init(pDecoder, pLedSeqCh->pDatLoop);	//ループ部分を再生開始する。	イントロ部分⇒ループ部分,又は,ループ部分⇒ループ部分の遷移です。
			} else {								//ループ部分が無ければ…
				if(!LedSeqCh_ExecSeq(pLedSeq, iLedSeqCh)) { return; }		//LEDシーケンス処理を行い、次のLEDデータのイントロ部分,又は,ループ部分を再生開始する。シーケンサが停止したら、ここまで。
			    //{{2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
				iLedCh = pLedSeqCh->iLedCh_Lo;					//LEDシーケンス処理を行ったことにより、pLedSeqCh->iLedCh_Loが変化したので、LEDチャネル番号を再取得する。
			    //}}2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
			}
			Dimmer = BytePairEncodingDecoder_Getc(pDecoder);			//新しく再生開始したLEDデータの最初の輝度をデコードする。ここで(-1)が返される事は無いはず。もし(-1)が返されたらバグであり、すぐ下の検査でエラー停止する。
		}
		if((unsigned)Dimmer > LedSeq_Dimmer_Max) { DIE(); }	//データバグ
	    //{{2015/06/07追加:LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
		if(!Buffer) { return; }	//LedSeqCh_GetStat()から呼び出された場合はここまで。先頭部分がデコードできればフレーム全体がデコード可能と判断出来ます。
	    //}}2015/06/07追加:LedSeqCh_GetStat()が停止中を判断するタイミングが1フレーム遅れていた問題を修正しました。
	    //{{2017/02/25追加:LedSeq_Exec()に引数fnFilterを追加しました。
		//アプリケーション定義のフィルタ関数を呼び出して、輝度を変換する。
		Dimmer = (*fnFilter)(pLedSeq, iLedSeqCh, iLedCh, Dimmer);	//(Buffer!=NULL)ならば(=LedSeqCh_GetStat()ではなくLedSeq_Exec()から呼び出されたのならば)、(fnFilter!=NULL)である事も保証出来るので、ここで(fnFilter!=NULL)を検査する必要は有りません。
		if((unsigned)Dimmer > LedSeq_Dimmer_Max) { DIE(); }	//アプリケーション定義のフィルタ関数バグ
	    //}}2017/02/25追加:LedSeq_Exec()に引数fnFilterを追加しました。
	    //{{2015/08/25変更:LedSeqCh_SetHide()を追加しました。
	    //	if(Dimmer) {									//デコードした輝度が0(透過)以外ならば…
	    //		if(Dimmer != 1) {							//┐
	    //			if(pLedSeqCh->Att >= (LedSeq_Dimmer_Max - 1)) {			//│
	    //				Dimmer = 1;						//│
	    //			} else {							//│
	    //				Dimmer -= pLedSeqCh->Att;				//├「減衰前の輝度と、減衰量と、減衰後の輝度の関係」を参照せよ。
	    //				if(Dimmer < 2) { Dimmer = 2; }				//│
	    //			}								//│
	    //		}									//│
	    //		Buffer[iLedCh] = Dimmer;						//┘
	    //	}
	    //↓2015/08/25変更:LedSeqCh_SetHide()を追加しました。
	    //	if(!pLedSeqCh->Hide) {								//非表示フラグがセットされていなければ…
	    //		if(Dimmer) {								//デコードした輝度が0(透過)以外ならば…
	    //			if(Dimmer != 1) {						//┐
	    //				if(pLedSeqCh->Att >= (LedSeq_Dimmer_Max - 1)) {		//│
	    //					Dimmer = 1;					//│
	    //				} else {						//│
	    //					Dimmer -= pLedSeqCh->Att;			//├「減衰前の輝度と、減衰量と、減衰後の輝度の関係」を参照せよ。
	    //					if(Dimmer < 2) { Dimmer = 2; }			//│
	    //				}							//│
	    //			}								//│
	    //			Buffer[iLedCh] = Dimmer;					//┘
	    //		}
	    //	}
	    //↓2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
		switch(pLedSeqCh->Mode) {
		default:DIE();
		//非表示
		case LedSeq_Mode_Hide:
			/** no job **/
			break;
		//通常(上書き)
		case LedSeq_Mode_Normal:
			if(Dimmer) {									//デコードした輝度が0(透過)以外ならば…
				if(Dimmer != 1) {							//┐
					if(pLedSeqCh->Att >= (LedSeq_Dimmer_Max - 1)) {			//│
						Dimmer = 1;						//│
					} else {							//│
						Dimmer -= pLedSeqCh->Att;				//├「減衰前の輝度と、減衰量と、減衰後の輝度の関係」を参照せよ。
						if(Dimmer < 2) { Dimmer = 2; }				//│
					}								//│
				}									//│
				Buffer[iLedCh] = Dimmer;						//┘
			}
			break;
		//加算
		case LedSeq_Mode_Add:
		//減算
		case LedSeq_Mode_Sub:
			if(Dimmer > 1) {								//デコードした輝度が0(透過)でも1(消灯)でもなければ…
				if(pLedSeqCh->Att < (LedSeq_Dimmer_Max - 1)) {				//┐
					Dimmer -= pLedSeqCh->Att;					//├「減衰前の輝度と、減衰量と、減衰後の輝度の関係」を参照せよ。
					if(Dimmer < 2) { Dimmer = 2; }					//┘
					Dimmer -= 1;							//Dimmer         = 1〜 30		デコードした輝度は1ベースなので、-1して0ベースのオフセットとする。
					if(pLedSeqCh->Mode == LedSeq_Mode_Sub) { Dimmer = -Dimmer; }	//Dimmer         = 1〜 30,又は,-30〜-1	減算ならば、オフセットを負の値にする。
					tmp = Buffer[iLedCh];						//tmp            = 0〜255		変更前の輝度を取得する。
					if(tmp <                 1) { tmp =                 1; }	//tmp            = 1〜255		変更前の輝度が0(透明)ならば、1(消灯)に補正する。
					if(tmp > LedSeq_Dimmer_Max) { tmp = LedSeq_Dimmer_Max; }	//tmp            = 1〜 31		変更前の輝度が31(最大輝度)超過ならば、31(最大輝度)に補正する。
					tmp += Dimmer;							//tmp            = 2〜 32,又は,-29〜30	変更前の輝度に、オフセットを加算する。
					if(tmp <                 1) { tmp =                 1; }	//tmp            = 1〜 32,又は,  1〜30	変更後の輝度が1(消灯)未満ならば、1(消灯)に補正する。
					if(tmp > LedSeq_Dimmer_Max) { tmp = LedSeq_Dimmer_Max; }	//tmp            = 2〜 31,又は,  1〜30	変更前の輝度が31(最大輝度)超過ならば、31(最大輝度)に補正する。
					Buffer[iLedCh] = tmp;						//Buffer[iLedCh] = 2〜 31,又は,  1〜30	変更後の輝度を書き戻す。
				}
			}
			break;
		}
	    //}}2016/10/09変更:LedSeqCh_SetHide()を廃止し、LedSeqCh_SetMode()を追加しました。
	}
}
/*--------------------------------------------------------------------------*/
//LEDシーケンス処理
static int LedSeqCh_ExecSeq(ST_LedSeq* pLedSeq, int iLedSeqCh) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	ST_BytePairEncodingDecoder* const pDecoder = &pLedSeqCh->Decoder;
	int SeqCmd;
	const unsigned char* pDat;
	for(;;) {
		SeqCmd  = (char)*pLedSeqCh->pSeq++; SeqCmd <<= 8;				//┐					┃play 相対位置		#0sssssss ssssssss ssssssss	次の命令を基準とした相対距離	┬ﾋﾞｯｸﾞｴﾝﾃﾞｨｱﾝで格納する｡
		SeqCmd |=       *pLedSeqCh->pSeq++; SeqCmd <<= 8;				//├シーケンスコマンドを取得する。	┃goto 相対位置		#1sssssss ssssssss ssssssss	次の命令を基準とした相対距離	┘
		SeqCmd |=       *pLedSeqCh->pSeq++;						//┘					┃end			#11111111 11111111 11111111	goto命令自身の3バイト内部へのgotoは有り得ないので区別できる｡
		if(SeqCmd == -1) {	//end
			pLedSeqCh->pSeq = NULL;							//シーケンサを停止する。
			return 0;								//シーケンサを停止したら0を返す。
		} else if(SeqCmd < 0) {	//goto
			SeqCmd = (SeqCmd << 9) >> 9;						//'_sssssss ssssssss ssssssss'の部分を符号拡張する。
			pLedSeqCh->pSeq += SeqCmd;						//次のシーケンス位置を設定する。
		} else {		//play
			SeqCmd = (SeqCmd << 9) >> 9;						//'_sssssss ssssssss ssssssss'の部分を符号拡張する。
			pDat = pLedSeqCh->pSeq + SeqCmd;
		    //{{2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
			pLedSeqCh->iLedCh_Lo  = *pDat++ << 8;					//┬不透明な輝度データが存在するLEDチャネル番号の下限を取得する。	ﾋﾞｯｸﾞｴﾝﾃﾞｨｱﾝ
			pLedSeqCh->iLedCh_Lo |= *pDat++;					//┘
			pLedSeqCh->iLedCh_Hi  = *pDat++ << 8;					//┬不透明な輝度データが存在するLEDチャネル番号の上限を取得する。	ﾋﾞｯｸﾞｴﾝﾃﾞｨｱﾝ
			pLedSeqCh->iLedCh_Hi |= *pDat++;					//┘
			if((pLedSeqCh->iLedCh_Lo >  pLedSeqCh->iLedCh_Hi) ||			//LEDチャネル番号の下限と上限の範囲が上下逆転していない事を確認する。もしここでエラー停止したらデータバグです。
			   (pLedSeqCh->iLedCh_Hi >= pLedSeq->nLedCh)) { DIE(); }		//LEDチャネル番号の上限がLEDチャネル数未満である事を確認する。もしここでエラー停止したらデータバグです。
		    //}}2015/06/05追加:dLedSeqC.exeの出力データ形式を変更した事に伴い、当モジュールの処理も変更しました。詳細は、clip/tool/dLedSeqC/winapp.h の同日のコメントを参照してください。
			if(*pDat) {								//イントロ部分が有れば…
				pLedSeqCh->pDatLoop = NULL;					//イントロ部分を再生中である事を示す。
				BytePairEncodingDecoder_Init(pDecoder, pDat);			//イントロ部分を再生開始する。
			} else {
				if(!*++pDat) { DIE(); }						//ループ部分が有る事を確認する。	dLedSeqC.exeはイントロ部分とループ部分が両方とも0フレームのデータは生成しないはず。もしここで止まったらdLedSeqC.exeのバグかデータ破損の可能性が有る。
				pLedSeqCh->pDatLoop = pDat;					//ループ部分のアドレスを格納する。
				BytePairEncodingDecoder_Init(pDecoder, pDat);			//ループ部分を再生開始する。
			}
			return 1;								//データを再生開始したら1を返す。
		}
	}
}
/****************************************************************************
 *	dLedSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_LEDDEF_BIN
void LedSeqCh_PlayNo(ST_LedSeq* pLedSeq, int iLedSeqCh, const void* pLedDefBin, int iLedSeqNo) {
	const int* const TBL_LedSeqNo = pLedDefBin;						//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
	const int nLedSeqNo = (unsigned short)TBL_LedSeqNo[0];					//アドレステーブルの先頭[0]の下位ハーフワードに要素数,上位ハーフワードにLEDチャネル数が入っている。要素数はそれ自身([0])も含む。
	const unsigned char* const TBL_LedSeq = (const unsigned char*)&TBL_LedSeqNo[nLedSeqNo];	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
	if((iLedSeqNo <= 0) || (iLedSeqNo >= nLedSeqNo)) { DIE(); }
	LedSeqCh_Play(pLedSeq, iLedSeqCh, &TBL_LedSeq[TBL_LedSeqNo[iLedSeqNo]]);
}
#endif//USE_LEDDEF_BIN
