/*
 *	clipleds.c
 *
 *	LEDシーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015 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()を追加しました。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static ST_LedSeqCh* LedSeq_GetLedSeqCh(ST_LedSeq* pLedSeq, int iLedSeqCh);
static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/]);
static int LedSeqCh_ExecSeq(ST_LedSeq* pLedSeq, int iLedSeqCh);
/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/
ST_LedSeq* LedSeq_Init(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_InitStatic(pLedSeq, nLedCh, nLedSeqCh);
	return pLedSeq;
}
/*--------------------------------------------------------------------------*/
void LedSeq_InitStatic(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;
}
/*--------------------------------------------------------------------------*/
void LedSeq_Exec(ST_LedSeq* pLedSeq, unsigned char Buffer[/*nLedCh*/]) {
	unsigned char* const TBL_Dimmer/*[(nLedCh+3)&~3]*/ = (unsigned char*)(pLedSeq + 1);
	int iLedSeqCh, iLedCh, Dimmer;
	//展開バッファを1(消灯)でクリアする。
	memset(Buffer, 1/*消灯*/, pLedSeq->nLedCh);	//0(透過)ではなく1(消灯)でクリアする。当関数の処理で0は格納しないので展開バッファの値は1〜LedSeq_Dimmer_Maxの範囲になる。アプリケーションが当関数から返されたBuffer[]の値を処理する時に0は考慮しなくて良い。
	//各LEDシーケンサチャネルを実行する。
	for(iLedSeqCh = 0; iLedSeqCh < pLedSeq->nLedSeqCh; iLedSeqCh++) {	//チャネル番号が小さい方が奥,大きい方が手前とした。もし逆の方が良ければループ順序を逆に変更せよ。
		LedSeqCh_ExecDat(pLedSeq, iLedSeqCh, Buffer);
	}
	//各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;
		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);
	//シーケンサが停止中ならば0,再生中ならば1を返す。
	return !!pLedSeqCh->pSeq;
}
/*--------------------------------------------------------------------------*/
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;
}
/*--------------------------------------------------------------------------*/
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データ再生処理
static void LedSeqCh_ExecDat(ST_LedSeq* pLedSeq, int iLedSeqCh, unsigned char Buffer[/*nLedCh*/]) {
	ST_LedSeqCh* const pLedSeqCh = LedSeq_GetLedSeqCh(pLedSeq, iLedSeqCh);
	ST_BytePairEncodingDecoder* const pDecoder = &pLedSeqCh->Decoder;
	int iLedCh, Dimmer;
	if(!pLedSeqCh->pSeq) { return; }							//シーケンサが停止していたら何もしない。
	for(iLedCh = 0; iLedCh < pLedSeq->nLedCh; iLedCh++) {					//各LEDチャネルについて…
		Dimmer = BytePairEncodingDecoder_Getc(pDecoder);				//輝度をデコードする。
		if(Dimmer == -1) {								//圧縮データの終端ならば…	このように圧縮データの終端でイントロ部分の終端,及び,ループ部分の終端を判断出来るので、イントロ部分のフレーム数やループ部分のフレーム数をデータ内に格納しておく必要が無い。
			if(iLedCh) { DIE(); }	//データバグ					//圧縮データの終端は最初のLEDチャネルでしか発生しないはずである。もしここで止まったらdLedSeqC.exeのバグかデータ破損の可能性が有る。
			if(!pLedSeqCh->pDatLoop) {						//イントロ部分を再生中だったら…
				pLedSeqCh->pDatLoop = pDecoder->blk + pDecoder->ofs + 2/*!!*/;	//ループ部分のアドレスを格納する。デコーダは圧縮データの空でない最終ブロック(=空ブロックの直前)の終端符号の位置でstayしているので、(+2)する事に注意せよ。
			}
			if(*pLedSeqCh->pDatLoop) {						//ループ部分が有れば…
				BytePairEncodingDecoder_Init(pDecoder, pLedSeqCh->pDatLoop);	//ループ部分を再生開始する。	イントロ部分⇒ループ部分,又は,ループ部分⇒ループ部分の遷移です。
			} else {								//ループ部分が無ければ…
				if(!LedSeqCh_ExecSeq(pLedSeq, iLedSeqCh)) { return; }		//LEDシーケンス処理を行い、次のLEDデータのイントロ部分,又は,ループ部分を再生開始する。シーケンサが停止したら、ここまで。
			}
			Dimmer = BytePairEncodingDecoder_Getc(pDecoder);			//新しく再生開始したLEDデータの最初の輝度をデコードする。ここで(-1)が返される事は無いはず。もし(-1)が返されたらバグであり、すぐ下の検査でエラー停止する。
		}
		if((unsigned)Dimmer > LedSeq_Dimmer_Max) { DIE(); }	//データバグ
		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;						//┘
		}
	}
}
/*--------------------------------------------------------------------------*/
//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;
			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
