/*
 *	clipsndx.c
 *
 *	Sound Mixer
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2014 Naoyuki Sawa
 *
 *	* Mon Mar 24 10:56:48 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	- Sound Mixerを作成した理由は、下記の通りです。
 *	  以前にSound Manager(clipsndm.h,c)を作成したのですが、使いづらくあまり役立ちませんでした。
 *	  そこで、実際のアプリケーションで必要な機能に絞って、Sound Mixerを作成することにしました。
 *	- Sound Mixerは、サウンドシーケンサ(clipsnds.h,c)との併用を想定して作成しました。
 *	  フレーズ単位の音量制御は、サウンドシーケンサに任せます。
 *	  Sound Mixerは、サウンドチャネルのグループ単位で、多重の減衰量制御を行うことが主目的です。
 *	- 使用例は clip/test.pce/app.c を参照してください。
 *	* Sat Jun 21 20:45:00 JST 2014 Naoyuki Sawa
 *	- SndMxr_Init()とSndMxr_InitStatic()の引数nSndMxrChの範囲を、(1〜32)に制限しました。
 *	  SndMxr_GetSndChAtt()とSndMxr_GetSndChAttAll()に、引数iSndMxrChMaskを追加しました。
 */
#include "clip.h"

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

ST_SndMxr* SndMxr_Init(int nSndMxrCh, const int pSndChMask[/*nSndMxrCh*/]) {
	ST_SndMxr* pSndMxr;
	/* Sound Mixer構造体のメモリを確保する。 */
	pSndMxr = malloc(sizeof(ST_SndMxr) + (sizeof(ST_SndMxrCh) * nSndMxrCh));
	if(!pSndMxr) { DIE(); } /* メモリ不足 */
	/* Sound Mixer構造体を初期化する。 */
	SndMxr_InitStatic(pSndMxr, nSndMxrCh, pSndChMask);
	return pSndMxr;
}

/*--------------------------------------------------------------------------*/

void SndMxr_InitStatic(ST_SndMxr* pSndMxr, int nSndMxrCh, const int pSndChMask[/*nSndMxrCh*/]) {
	int iSndMxrCh;
	if((nSndMxrCh < 1) || (nSndMxrCh > 32)) { DIE(); } /* 引数不正 */
	/* Sound Mixer構造体をクリアする。 */
	memset(pSndMxr, 0, sizeof(ST_SndMxr) + (sizeof(ST_SndMxrCh) * nSndMxrCh));
	/* Sound Mixer構造体に、Sound Mixerチャネル数を格納する。 */
	pSndMxr->nSndMxrCh = nSndMxrCh;
	/* Sound Mixerチャネル構造体に、サウンドチャネルマスクを格納する。 */
	for(iSndMxrCh = 0; iSndMxrCh < pSndMxr->nSndMxrCh; iSndMxrCh++) {
		if(!pSndChMask[iSndMxrCh]) { DIE(); } /* サウンドチャネルマスク配列が不正 */
		pSndMxr->pSndMxrCh[iSndMxrCh].SndChMask = pSndChMask[iSndMxrCh];
	}
}

/*--------------------------------------------------------------------------*/

void SndMxr_Exec(ST_SndMxr* pSndMxr) {
	/* 各Sound Mixerチャネルについて… */
	int          nSndMxrCh = pSndMxr->nSndMxrCh;
	ST_SndMxrCh* pSndMxrCh = pSndMxr->pSndMxrCh;
	do {
		/* 現在減衰量と目標減衰量を取り出しておく。 */
		int AttNow = pSndMxrCh->AttNow;	/* 現在減衰量 */
		int AttTo  = pSndMxrCh->AttTo;	/* 目標減衰量 */
		/* 現在減衰量と目標減衰量が違うならば(=変化中ならば)… */
		if(AttNow != AttTo) {
			/* 誤差項に減衰量変化速度を加算し、今回変化量を求める。 */
			div_t d = div(pSndMxrCh->ErrTerm + pSndMxrCh->Rate, pSndMxrCh->Scale);
			/* 今回変化量を除いた残りを、誤差項に書き戻す。 */
			pSndMxrCh->ErrTerm = d.rem;
			/* 今回変化量を、現在減衰量に反映する。 */
			if(AttNow < AttTo) { if((AttNow += d.quot) > AttTo) { AttNow = AttTo; } } /* 変化方向が増加の場合 */
			else               { if((AttNow -= d.quot) < AttTo) { AttNow = AttTo; } } /* 変化方向が減少の場合 */
			/* 現在減衰量を書き戻す。 */
			pSndMxrCh->AttNow = AttNow;
		}
		/* Sound Mixerチャネルを次へ進める。 */
		pSndMxrCh++;
	} while(--nSndMxrCh);
}

/*--------------------------------------------------------------------------*/

void SndMxr_SetMxrChAtt(ST_SndMxr* pSndMxr, int iSndMxrCh, int AttTo, int Rate, int Scale) {
	ST_SndMxrCh* pSndMxrCh;
	if(((unsigned)iSndMxrCh >= pSndMxr->nSndMxrCh) || ((unsigned)AttTo > UCHAR_MAX) || ((unsigned)Rate > UCHAR_MAX) || ((unsigned)Scale > UCHAR_MAX)) { DIE(); } /* 引数不正 */
	/* Sound Mixerチャネル構造体を取得する。 */
	pSndMxrCh = &pSndMxr->pSndMxrCh[iSndMxrCh];
ENTER_CS; /* SndMxr_Exec()を割込処理から呼ぶ場合のために排他制御を行う。SndMxr_Exec()を通常処理から呼ぶ場合は不要だが、行っても害は無い。 */
	/* パラメータが一個でも違ったら、新しい変化を開始する。パラメータが同じならば、現在の変化を継続する。 */
	if((pSndMxrCh->AttTo != AttTo) || (pSndMxrCh->Rate != Rate) || (pSndMxrCh->Scale != Scale)) {
		/* パラメータを格納する。 */
		pSndMxrCh->AttTo   = AttTo;	/* 目標減衰量 */
		pSndMxrCh->Rate    = Rate;	/* 減衰量変化速度 */
		pSndMxrCh->Scale   = Scale;	/* 減衰量変化速度 */
		pSndMxrCh->ErrTerm = 0;		/* 誤差項 */
		/* 減衰量変化速度のパラメータが片方でも0(=即反映)ならば、目標減衰量を現在減衰量へ即反映する。 */
		if(!Rate || !Scale) { pSndMxrCh->AttNow = AttTo; }
	}
LEAVE_CS; /* SndMxr_Exec()を割込処理から呼ぶ場合のために排他制御を行う。SndMxr_Exec()を通常処理から呼ぶ場合は不要だが、行っても害は無い。 */
}

/*--------------------------------------------------------------------------*/

int SndMxr_GetSndChAtt(ST_SndMxr* pSndMxr, int iSndMxrChMask, int iSndCh) {
	int SndChMask, AttSum;
	unsigned uSndMxrChMask;
	ST_SndMxrCh* pSndMxrCh;
	if((unsigned)iSndCh > 31) { DIE(); } /* 引数不正 */
	/* 指定されたサウンドチャネルに対応する、マスクを作成する。 */
	SndChMask = 1 << iSndCh;
	/* 現在減衰量の合計をクリアしておく。 */
	AttSum = 0;
	/* 適用するSound Mixerチャネルのビットマスクを符号無しの値とし、Sound Mixerチャネル数よりも大きいビットをクリアする。 */
	uSndMxrChMask = iSndMxrChMask;
	if(pSndMxr->nSndMxrCh <= 31) { uSndMxrChMask &= ((1 << pSndMxr->nSndMxrCh) - 1); }
	/* 各Sound Mixerチャネルについて… */
	pSndMxrCh = pSndMxr->pSndMxrCh;
	do {
		/* このSound Mixerチャネルを適用するならば… */
		if(uSndMxrChMask & 1) {
			/* このSound Mixerチャネルのサウンドチャネルマスクが、指定されたサウンドチャネルを含むならば、現在減衰量を加算する。 */
			if(pSndMxrCh->SndChMask & SndChMask) { AttSum += pSndMxrCh->AttNow; }
		}
		/* Sound Mixerチャネルを次へ進める。 */
		pSndMxrCh++;
	} while(uSndMxrChMask >>= 1);	/* 適用するSound Mixerチャネルのビットマスクが残っている間、繰り返す。 */
	return (AttSum <= UCHAR_MAX) ? AttSum : UCHAR_MAX;
}

/*--------------------------------------------------------------------------*/

void SndMxr_GetSndChAttAll(ST_SndMxr* pSndMxr, int iSndMxrChMask, unsigned char AttSum[/*32*/]) {
	unsigned uSndMxrChMask;
	ST_SndMxrCh* pSndMxrCh;
	/* 全てのサウンドチャネルの減衰量をクリアしておく。 */
	memset(AttSum, 0, 32);
	/* 適用するSound Mixerチャネルのビットマスクを符号無しの値とし、Sound Mixerチャネル数よりも大きいビットをクリアする。 */
	uSndMxrChMask = iSndMxrChMask;
	if(pSndMxr->nSndMxrCh <= 31) { uSndMxrChMask &= ((1 << pSndMxr->nSndMxrCh) - 1); }
	/* 各Sound Mixerチャネルについて… */
	pSndMxrCh = pSndMxr->pSndMxrCh;
	do {
		/* このSound Mixerチャネルを適用するならば… */
		if(uSndMxrChMask & 1) {
			/* サウンドチャネルマスクを取得する。 */
			unsigned SndChMask = pSndMxrCh->SndChMask;
			/* 現在減衰量を取得する。 */
			int AttNow = pSndMxrCh->AttNow;
			/* 各サウンドチャネルについて… */
			unsigned char* pAttSum = AttSum;
			do {
				/* このSound Mixerチャネルのサウンドチャネルマスクが、指定されたサウンドチャネルを含むならば、現在減衰量を加算する。 */
				if(SndChMask & 1) { int x = *pAttSum + AttNow; *pAttSum = (x <= UCHAR_MAX) ? x : UCHAR_MAX; }
				/* サウンドチャネルを次へ進める。 */
				pAttSum++;
			} while(SndChMask >>= 1);	/* このSound Mixerチャネルのサウンドチャネルマスクに、マスクビットが残っている間、繰り返す。 */
		}
		/* Sound Mixerチャネルを次へ進める。 */
		pSndMxrCh++;
	} while(uSndMxrChMask >>= 1);	/* 適用するSound Mixerチャネルのビットマスクが残っている間、繰り返す。 */
}

