//
//	cliptapx.cs
//
//	TAPミキサー
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Sat Mar 25 23:03:55 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	* Mon Mar 27 22:39:32 JST 2017 Naoyuki Sawa
//	- TapMxr_GetAtt()において、pMaskの最後の次の要素まで読もうとしてしまう可能性が有ったバグを修正しました。
//	  C言語版(/clip/cliptapx.c)では配列の一要素先まで読んでも一般保護例外が発生する事はまず無いので、これまで気付きませんでした。
//	  今回、同じアルゴリズムでC#版(/clip/libclip.net/cliptapx.cs)を作成して、IndexOutOfRangeExceptionが発生したので気付きました。
//
using System;
using System.Collections.Generic;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	定数、構造体
		//*****************************************************************************
		//ミキサーチャネル
		public class ST_TapMxrCh {
			public byte		iAttNow;		//現在減衰量		0=減衰無し。1以上の値はアプリケーションが任意に解釈して下さい。
			public byte		iAttTo;			//目標減衰量		0=減衰無し。1以上の値はアプリケーションが任意に解釈して下さい。
			public ushort		iRate;			//減衰量変化速度(分子)	1[ms]あたり(Rate/Scale)の速度で変化します。
			public ushort		iScale;			//減衰量変化速度(分母)	1[ms]あたり(Rate/Scale)の速度で変化します。
			public ushort		iErrTerm;		//誤差項
		}
		//-----------------------------------------------------------------------------
		//ミキサー
		public class ST_TapMxr {
			public byte		nMxrCh;			//ミキサーチャネル数	1～UINT8_MAX
			public ST_TapMxrCh[]	TBL_MxrCh/*[nMxrCh]*/;	//ミキサーチャネル
		}
		//*****************************************************************************
		//	グローバル関数
		//*****************************************************************************
		//ミキサーを動的に作成する。
		public static ST_TapMxr TapMxr_New(int nMxrCh) {
			ST_TapMxr pTapMxr;
			if(((nMxrCh < 1) || (nMxrCh > byte.MaxValue))) { throw new ApplicationException(); }	//ミキサーチャネル数が範囲外ならばエラー
			//メモリを確保する。
			pTapMxr = new ST_TapMxr();
			//ミキサーを静的に初期化する。
			TapMxr_Init(pTapMxr, nMxrCh);
			return pTapMxr;
		}
		//-----------------------------------------------------------------------------
		//ミキサーを静的に初期化する。
		public static void TapMxr_Init(ST_TapMxr pTapMxr, int nMxrCh) {
			if(((nMxrCh < 1) || (nMxrCh > byte.MaxValue))) { throw new ApplicationException(); }	//ミキサーチャネル数が範囲外ならばエラー
			//ミキサーチャネル数を格納する。
			pTapMxr.nMxrCh = (byte)nMxrCh;
			//ミキサーチャネル配列を作成する。
			pTapMxr.TBL_MxrCh = new ST_TapMxrCh[nMxrCh];
			for(int iMxrCh = 0; iMxrCh < nMxrCh; iMxrCh++) {
				pTapMxr.TBL_MxrCh[iMxrCh] = new ST_TapMxrCh();
			}
		}
		//-----------------------------------------------------------------------------
		//ミキサーを実行する。
		// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
		//   割り込み処理から呼び出す場合は排他制御が不要ですが、タイマ管理の周期処理(TimMgr_Exec())から呼び出す使い方も有り得るからです。
		public static void TapMxr_Exec(ST_TapMxr pTapMxr, int iElapTime) {
			if(iElapTime <    0) { iElapTime =    0; }	//経過時間が0秒未満ならば、0秒に補正する。
			if(iElapTime > 1000) { iElapTime = 1000; }	//経過時間が1秒超過ならば、1秒に補正する。
			lock(pTapMxr) {
				//ミキサーチャネルを実行する。
				for(int iMxrCh = 0; iMxrCh < pTapMxr.nMxrCh; iMxrCh++) {
					TapMxrCh_Exec(pTapMxr, iMxrCh, iElapTime);
				}
			}
		}
		//-----------------------------------------------------------------------------
		//ミキサーチャネルの減衰量変化を開始します。
		// - パラメータの仕様はCntMgr_SetVal()と同様です。CntMgr_SetVal()の説明を参照して下さい。
		// - アプリケーションが任意のコンテキストで呼び出す可能性が有るので、排他制御が必要です。
		public static void TapMxrCh_SetAtt(ST_TapMxr pTapMxr, int iMxrCh, int iAttTo, int iRate, int iScale) {
			//ミキサーチャネルを取得する。
			ST_TapMxrCh pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
			lock(pTapMxr) {
				//パラメータが一個でも違ったら、新しい変化を開始する。パラメータが同じならば、現在の変化を継続する。
				if((pMxrCh.iAttTo != iAttTo) || (pMxrCh.iRate != iRate) || (pMxrCh.iScale != iScale)) {
					//パラメータを格納する。
					pMxrCh.iAttTo   = (byte)iAttTo;		//目標減衰量
					pMxrCh.iRate    = (ushort)iRate;	//減衰量変化速度(分子)
					pMxrCh.iScale   = (ushort)iScale;	//減衰量変化速度(分母)
					pMxrCh.iErrTerm = 0;			//誤差項
					//減衰量変化速度のパラメータが片方でも0(=即反映)ならば、目標減衰量を現在減衰量へ即反映する。
					if((iRate == 0) || (iScale == 0)) { pMxrCh.iAttNow = (byte)iAttTo; }
				}
			}
		}
		//-----------------------------------------------------------------------------
		//iMxrChMaskで指定したミキサーチャネル(0～複数可)の、現在減衰量の合計を求める。
		// - アプリケーションが任意のコンテキストで呼び出す可能性が有りますが、当関数内での排他制御は不要です。
		//   当関数の実行中に割り込みが掛かって、途中から減衰量が変化しても、聴いて判る程の違いは生じないからです。
		//   ただし、もし今後、厳密な原子性が必要になった場合は、排他制御を追加して下さい。
		public static int TapMxr_GetAtt(ST_TapMxr pTapMxr, IEnumerable<int> aMxrChMask/*[(nMxrCh+31)/32]*/) {
			//合計減衰量を0としておく。
			int iAttSum = 0;
			//◆高速化した実装。処理内容がやや判り辛いですが、0が連続しているビットに対応するチャネルをまとめて飛ばせるので早いです。
			IEnumerator<int> pMask = aMxrChMask.GetEnumerator();
			uint uMask = 0;	//ミキサーチャネルマスクのビット配列を、32ビットずつ取り出す変数
			int  nBits = 0;	//32ビット単位で取り出したミキサーチャネルマスクの、残りビット数
			//各ミキサーチャネルについて…
			int iMxrCh = 0;
		    //{{2017/03/27修正:TapMxr_GetAtt()において、pMaskの最後の次の要素まで読もうとしてしまう可能性が有ったバグを修正しました。
		    //	while(iMxrCh < pTapMxr.nMxrCh) {
		    //		//取り出したミキサーチャネルマスクに、セットされているビットがまだ残っていれば…
		    //		if(uMask != 0) {
		    //			//ミキサーチャネルマスクのビット配列の、このミキサーチャネルに対応するビットがセットされていたら…
		    //			if((uMask & 1) != 0) {
		    //				//合計減衰量に、このミキサーチャネルの現在減衰量を加算する。
		    //				ST_TapMxrCh pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
		    //				iAttSum += pMxrCh.iAttNow;	//iAttNowは8bitフィールドなので、合計が32bitを超える恐れは無い。
		    //			}
		    //			iMxrCh++;	//ミキサーチャネル番号を次へ進める。
		    //			uMask >>= 1;	//32ビット単位で取り出したミキサーチャネルマスクを、1ビットシフトアウトする。
		    //			nBits--;	//1ビットシフトアウトした分、残りビット数を減らす。
		    //		//取り出したミキサーチャネルマスクに、セットされているビットが残っていなければ…
		    //		} else {
		    //			iMxrCh += nBits;	//残りの(クリアされている)ビット数分、ミキサーチャネル番号を進める。
		    //			if(!pMask.MoveNext()) { throw new ApplicationException(); }	//┬ミキサーチャネルマスクのビット配列の、次の32ビットを取り出す。
		    //			uMask = (uint)pMask.Current;					//┘
		    //			nBits = 32;		//取り出したミキサーチャネルマスクの、残りビット数を32ビットにする。
		    //		}
		    //	}
		    //↓2017/03/27修正:TapMxr_GetAtt()において、pMaskの最後の次の要素まで読もうとしてしまう可能性が有ったバグを修正しました。
			for(;;) {
				//取り出したミキサーチャネルマスクに、セットされているビットがまだ残っていれば…
				if(uMask != 0) {
					//ミキサーチャネルマスクのビット配列の、このミキサーチャネルに対応するビットがセットされていたら…
					if((uMask & 1) != 0) {
						//合計減衰量に、このミキサーチャネルの現在減衰量を加算する。
						ST_TapMxrCh pMxrCh = TapMxr_GetMxrCh(pTapMxr, iMxrCh);
						iAttSum += pMxrCh.iAttNow;		//iAttNowは8bitフィールドなので、合計が32bitを超える恐れは無い。
					}
					iMxrCh++;					//ミキサーチャネル番号を次へ進める。
					if(iMxrCh >= pTapMxr.nMxrCh) { break; }		//全てのミキサーチャネルを処理したら、ループを抜ける。
					uMask >>= 1;					//32ビット単位で取り出したミキサーチャネルマスクを、1ビットシフトアウトする。
					nBits--;					//1ビットシフトアウトした分、残りビット数を減らす。
				//取り出したミキサーチャネルマスクに、セットされているビットが残っていなければ…
				} else {
					iMxrCh += nBits;				//残りの(クリアされている)ビット数分、ミキサーチャネル番号を進める。
					if(iMxrCh >= pTapMxr.nMxrCh) { break; }		//全てのミキサーチャネルを処理したら、ループを抜ける。
					if(!pMask.MoveNext()) { throw new ApplicationException(); }	//┬ミキサーチャネルマスクのビット配列の、次の32ビットを取り出す。
					uMask = (uint)pMask.Current;					//┘
					nBits = 32;					//取り出したミキサーチャネルマスクの、残りビット数を32ビットにする。
				}
			}
		    //}}2017/03/27修正:TapMxr_GetAtt()において、pMaskの最後の次の要素まで読もうとしてしまう可能性が有ったバグを修正しました。
			//合計減衰量を返す。
			return iAttSum;
		}
		//-----------------------------------------------------------------------------
		//ミキサーチャネルを取得する。
		public static ST_TapMxrCh TapMxr_GetMxrCh(ST_TapMxr pTapMxr, int iMxrCh) {
			if((uint)iMxrCh >= (uint)pTapMxr.nMxrCh) { throw new ApplicationException(); }	//ミキサーチャネル番号が範囲外ならばエラー
			return pTapMxr.TBL_MxrCh[iMxrCh];
		}
		//*****************************************************************************
		//	ローカル関数
		//*****************************************************************************
		//ミキサーチャネルを実行する。
		// - TapMxr_Exec()から呼び出されるので、当関数内では排他制御が不要です。
		private 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で除して今回変化量を求める。
				int quot, rem;
				quot = Math.DivRem(
					pMxrCh.iErrTerm + (pMxrCh.iRate * iElapTime),
					pMxrCh.iScale,
					out rem);
				//今回変化量を除いた残りを、誤差項に書き戻す。
				pMxrCh.iErrTerm = (ushort)rem;
				//今回変化量を、現在減衰量に反映する。
				if(iAttNow < iAttTo) { if((iAttNow += quot) > iAttTo) { iAttNow = iAttTo; } }	//変化方向が増加の場合
				else                 { if((iAttNow -= quot) < iAttTo) { iAttNow = iAttTo; } }	//変化方向が減少の場合
				//現在減衰量を書き戻す。
				pMxrCh.iAttNow = (byte)iAttNow;
			}
		}
	}
}
