/*
 *	cliptapx.c
 *
 *	TAPミキサー
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Fri Sep 18 22:38:30 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Feb 14 21:59:00 JST 2016 Naoyuki Sawa
 *	- ミキサーチャネル数の上限を、これまでは32でしたが、UINT8_MAX(=255)に増やしました。
 *	  これまでミキサーチャネル数の上限が32だった理由は、TapMxr_GetAtt()の引数のミキサーチャネルマスクをint引数1個で指定していたからです。
 *	  今回の変更に伴い、int引数1個でミキサーチャネルマスクを指定する事は出来なくなり、ミキサーチャネルマスクをビット配列で指定するように変更しました。
 *	- 変更前のコードはコメントアウトして残していません。
 *	  変更前のコードを参照する必要がある場合は、前回のアーカイブを参照して下さい。
 */
#include "clip.h"
/*****************************************************************************
 *	ローカル関数宣言
 *****************************************************************************/
static void TapMxrCh_Exec(ST_TapMxr* pTapMxr, int iMxrCh, int iElapTime);
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//ミキサーを動的に作成する。
ST_TapMxr* TapMxr_New(int nMxrCh) {
	ST_TapMxr* pTapMxr;
	if(((nMxrCh < 1) || (nMxrCh > UINT8_MAX))) { DIE(); }	//ミキサーチャネル数が範囲外ならばエラー
	//メモリを確保する。
	pTapMxr = malloc(sizeof(ST_TapMxr) +		//ミキサー
		(sizeof(ST_TapMxrCh) * nMxrCh));	//ミキサーチャネル
	if(!pTapMxr) { DIE(); }
	//ミキサーを静的に初期化する。
	TapMxr_Init(pTapMxr, nMxrCh);
	return pTapMxr;
}
/*---------------------------------------------------------------------------*/
//ミキサーを静的に初期化する。
void TapMxr_Init(ST_TapMxr* pTapMxr, int nMxrCh) {
	int iMxrCh;
	if(((nMxrCh < 1) || (nMxrCh > UINT8_MAX))) { DIE(); }	//ミキサーチャネル数が範囲外ならばエラー
	//ミキサーチャネル数を格納する。
	pTapMxr->nMxrCh = nMxrCh;
	//ミキサーチャネルをクリアする。
	for(iMxrCh = 0; iMxrCh < pTapMxr->nMxrCh; iMxrCh++) {
		ST_TapMxrCh* pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
		memset(pMxrCh, 0, sizeof(ST_TapMxrCh));
	}
}
/*---------------------------------------------------------------------------*/
//動的に作成したミキサーを開放する。
void TapMxr_Free(ST_TapMxr* pTapMxr) {
	free(pTapMxr);
}
/*---------------------------------------------------------------------------*/
//ミキサーを実行する。
// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
//   割り込み処理から呼び出す場合は排他制御が不要ですが、タイマ管理の周期処理(TimMgr_Exec())から呼び出す使い方も有り得るからです。
void TapMxr_Exec(ST_TapMxr* pTapMxr, int iElapTime) {
	int iMxrCh;
	if(iElapTime <    0) { iElapTime =    0; }	//経過時間が0秒未満ならば、0秒に補正する。
	if(iElapTime > 1000) { iElapTime = 1000; }	//経過時間が1秒超過ならば、1秒に補正する。
ENTER_CS;
	//ミキサーチャネルを実行する。
	for(iMxrCh = 0; iMxrCh < pTapMxr->nMxrCh; iMxrCh++) {
		TapMxrCh_Exec(pTapMxr, iMxrCh, iElapTime);
	}
LEAVE_CS;
}
/*---------------------------------------------------------------------------*/
//ミキサーチャネルの減衰量変化を開始します。
// - パラメータの仕様はCntMgr_SetVal()と同様です。CntMgr_SetVal()の説明を参照して下さい。
// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
void TapMxrCh_SetAtt(ST_TapMxr* pTapMxr, int iMxrCh, int iAttTo, int iRate, int iScale) {
	//ミキサーチャネルを取得する。
	ST_TapMxrCh* pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
ENTER_CS;
	//パラメータが一個でも違ったら、新しい変化を開始する。パラメータが同じならば、現在の変化を継続する。
	if((pMxrCh->iAttTo != iAttTo) || (pMxrCh->iRate != iRate) || (pMxrCh->iScale != iScale)) {
		//パラメータを格納する。
		pMxrCh->iAttTo   = iAttTo;	//目標減衰量
		pMxrCh->iRate    = iRate;	//減衰量変化速度(分子)
		pMxrCh->iScale   = iScale;	//減衰量変化速度(分母)
		pMxrCh->iErrTerm = 0;		//誤差項
		//減衰量変化速度のパラメータが片方でも0(=即反映)ならば、目標減衰量を現在減衰量へ即反映する。
		if(!iRate || !iScale) { pMxrCh->iAttNow = iAttTo; }
	}
LEAVE_CS;
}
/*---------------------------------------------------------------------------*/
//iMxrChMaskで指定したミキサーチャネル(0〜複数可)の、現在減衰量の合計を求める。
// - アプリケーションが任意のコンテキストで呼び出す可能性が有りますが、当関数内での排他制御は不要です。
//   当関数の実行中に割り込みが掛かって、途中から減衰量が変化しても、聴いて判る程の違いは生じないからです。
//   ただし、もし今後、厳密な原子性が必要になった場合は、排他制御を追加して下さい。
int TapMxr_GetAtt(ST_TapMxr* pTapMxr, const void* aMxrChMask/*int[(nMxrCh+31)/32]*/) {
	//合計減衰量を0としておく。
	int iAttSum = 0;
#if 0
	//◆シンプルな実装。処理内容は判り易いですが、ミキサーチャネルマスクを全チャネルに対して1ビットずつ走査するので遅いです。
	//各ミキサーチャネルについて…
	int iMxrCh;
	for(iMxrCh = 0; iMxrCh < pTapMxr->nMxrCh; iMxrCh++) {
		//ミキサーチャネルマスクのビット配列の、このミキサーチャネルに対応するビットがセットされていたら…
		if(bitarray_lsb1st_get(aMxrChMask, iMxrCh, 1)) {
			//合計減衰量に、このミキサーチャネルの現在減衰量を加算する。
			ST_TapMxrCh* pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
			iAttSum += pMxrCh->iAttNow;	//iAttNowは8bitフィールドなので、合計が32bitを超える恐れは無い。
		}
	}
#else	//↑シンプルな実装でも↓高速化した実装でも結果は同じになります。
	//◆高速化した実装。処理内容がやや判り辛いですが、0が連続しているビットに対応するチャネルをまとめて飛ばせるので早いです。
	const unsigned* pMask = aMxrChMask;
	unsigned uMask = 0;	//ミキサーチャネルマスクのビット配列を、32ビットずつ取り出す変数
	int      nBits = 0;	//32ビット単位で取り出したミキサーチャネルマスクの、残りビット数
	//各ミキサーチャネルについて…
	int iMxrCh = 0;
	while(iMxrCh < pTapMxr->nMxrCh) {
		//取り出したミキサーチャネルマスクに、セットされているビットがまだ残っていれば…
		if(uMask) {
			//ミキサーチャネルマスクのビット配列の、このミキサーチャネルに対応するビットがセットされていたら…
			if(uMask & 1) {
				//合計減衰量に、このミキサーチャネルの現在減衰量を加算する。
				ST_TapMxrCh* pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
				iAttSum += pMxrCh->iAttNow;	//iAttNowは8bitフィールドなので、合計が32bitを超える恐れは無い。
			}
			iMxrCh++;	//ミキサーチャネル番号を次へ進める。
			uMask >>= 1;	//32ビット単位で取り出したミキサーチャネルマスクを、1ビットシフトアウトする。
			nBits--;	//1ビットシフトアウトした分、残りビット数を減らす。
		//取り出したミキサーチャネルマスクに、セットされているビットが残っていなければ…
		} else {
			iMxrCh += nBits;	//残りの(クリアされている)ビット数分、ミキサーチャネル番号を進める。
			uMask = *pMask++;	//ミキサーチャネルマスクのビット配列の、次の32ビットを取り出す。
			nBits = 32;		//取り出したミキサーチャネルマスクの、残りビット数を32ビットにする。
		}
	}
#endif
	//合計減衰量を返す。
	return iAttSum;
}
/*---------------------------------------------------------------------------*/
//ミキサーチャネルを取得する。
ST_TapMxrCh* TapMxr_GetMxrCh(ST_TapMxr* pTapMxr, int iMxrCh) {
	if((unsigned)iMxrCh >= (unsigned)pTapMxr->nMxrCh) { DIE(); }	//ミキサーチャネル番号が範囲外ならばエラー
	return (ST_TapMxrCh*)(pTapMxr + 1) + iMxrCh;
}
/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/
//ミキサーチャネルを実行する。
// - TapMxr_Exec()から呼び出されるので、当関数内では排他制御が不要です。
static void TapMxrCh_Exec(ST_TapMxr* pTapMxr, int iMxrCh, int iElapTime) {
	//ミキサーチャネルを取得する。
	ST_TapMxrCh* pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
	//現在減衰量と目標減衰量を取得する。
	int iAttNow = pMxrCh->iAttNow;	//現在減衰量
	int iAttTo  = pMxrCh->iAttTo;	//目標減衰量
	//現在減衰量と目標減衰量が異なるならば(=変化中ならば)…
	if(iAttNow != iAttTo) {
		//誤差項に(iRate×経過時間)を加算し、iScaleで除して今回変化量を求める。
		div_t d = div(
			pMxrCh->iErrTerm + (pMxrCh->iRate * iElapTime),
			pMxrCh->iScale);
		//今回変化量を除いた残りを、誤差項に書き戻す。
		pMxrCh->iErrTerm = d.rem;
		//今回変化量を、現在減衰量に反映する。
		if(iAttNow < iAttTo) { if((iAttNow += d.quot) > iAttTo) { iAttNow = iAttTo; } }	//変化方向が増加の場合
		else                 { if((iAttNow -= d.quot) < iAttTo) { iAttNow = iAttTo; } }	//変化方向が減少の場合
		//現在減衰量を書き戻す。
		pMxrCh->iAttNow = iAttNow;
	}
}
