//
//	clipaudh_uni.cs
//
//	Audioヘルパー関数
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Fri Mar 24 23:38:46 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//	- /clip/tool/SofdecHelper/の、AdxHelper関数群に相当するモジュールです。
//	  「CRIWARE Unity plugin」と組み合わせて使用する事を想定していますが、一応、汎用的に作ってあります。
//	  当モジュール内には、「CRIWARE Unity plugin」や「Unity」の機能を直接利用している箇所は有りません。
//	  とは言え、「CRIWARE Unity plugin」と組み合わせて使用する事が、主目的である事には違い有りません。
//	  動作確認に使用したバージョンは、「criware_sdk_unity_smartphone_v2_88_j.zip」(2017-03-03)です。
//	- 「CRIWARE Unity plugin」は、スクリプト形式で提供されており、参照設定で使用する事が出来ません。
//	  当モジュール内にスクリプト形式のままで取り込む事も出来ないので、下記の方法で使うようにしました。
//	  「CRIWARE Unity plugin」のCriAtomExPlayerに相当する機能を、IAudioPlayerとして定義しました。
//	  アプリケーションは、IAudioPlayerを実装したクラスを定義して下さい。
//	  AudioHelper_New()の引数fnCreateAudioPlayerに、そのインスタンスを作成する関数を指定して下さい。
//	  当モジュールは論理チャネルの管理を行い、デバイスチャネルの動作をそのインスタンスを使って行います。
//	- 論理チャネルの管理のアルゴリズムは、概ね既存の/clip/tool/SofdecHelper/のアルゴリズムと同じです。
//	  ただし、多少簡略化しており、完全に同じではありません。
//	  また、2017/03/24時点では、AudioHelper_SetVolume()とAudioHelper_SetPan()が未実装です。		⇒{{2017/03/27コメント追記:AudioHelper_SetVolume()とAudioHelper_SetPan()を実装しました。}}
//	- 当モジュールの論理チャネルは、cliptaps.csモジュールの観点からは、デバイスチャネルとなります。
//	  当モジュールをデバイスチャネルと見なし、cliptaps.csモジュールと組み合わせて使用して下さい。
//	* Mon Mar 27 22:39:32 JST 2017 Naoyuki Sawa
//	- AudioHelper_SetVolume()とAudioHelper_SetPan()を実装しました。
//	  ただし、2Dパンを設定するADX2の関数がわからないので、IAudioPlayerの実装例ではダミー関数としました。
//	  既存の/clip/tool/SofdecHelper/SofdecHelper.cでも、AdxHelper_SetPan()はダミー関数です。
//	* Sun Apr 09 22:05:18 JST 2017 Naoyuki Sawa
//	- AudioHelper_Prepare()も、MovieHelper_Prepare()と同様に、再生準備完了フラグを返すように変更しました。
//
using System;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	定数
		//*****************************************************************************
		//プレイヤーの状態
		public enum AudioPlayerStatus {
			Stop,		//停止中		== CriAtomExPlayer.Status.Stop
			Prep,		//再生準備中		== CriAtomExPlayer.Status.Prep
			Playing,	//再生中		== CriAtomExPlayer.Status.Playing
			PlayEnd,	//再生完了		== CriAtomExPlayer.Status.PlayEnd
			Error,		//エラーが発生		== CriAtomExPlayer.Status.Error
		}
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		//デバイスチャネルのプレイヤーのインターフェイス
		public interface IAudioPlayer : IDisposable {
			AudioPlayerStatus GetStatus();		//≒CriAtomExPlayer.GetStatus()					完了復帰型の関数です。あらゆるタイミングで呼び出される可能性が有ります。
			void Prepare(int iPhrNo);		//≒CriAtomExPlayer.SetFile()+CriAtomExPlayer.Prepare()		完了復帰型の関数ではありません。この操作が可能なタイミングでのみ呼び出されるので、アプリケーション側で状態を確認する必要は有りません。
			void Start();				//≒CriAtomExPlayer.Resume(CriAtomEx.ResumeMode.AllPlayback)	完了復帰型の関数ではありません。この操作が可能なタイミングでのみ呼び出されるので、アプリケーション側で状態を確認する必要は有りません。
			void Stop();				//≒CriAtomExPlayer.Stop()+CriAtomExPlayer.GetStatus()		完了復帰型の関数です。あらゆるタイミングで呼び出される可能性が有りますが、CriAtomExPlayer.Stop()はあらゆるタイミングで実行可能なので、アプリケーション側で状態を確認する必要は有りません。
			void SetVolume(double volume);		//≒CriAtomExPlayer.SetVolume()+CriAtomExPlayer.UpdateAll()	完了復帰型の関数です。この操作が可能なタイミングでのみ呼び出されるので、アプリケーション側で状態を確認する必要は有りません。
			void SetPan(double pan);		//※TODO:2Dパンを設定するADX2の関数がわかりません…		完了復帰型の関数です。この操作が可能なタイミングでのみ呼び出されるので、アプリケーション側で状態を確認する必要は有りません。
		}
		//-----------------------------------------------------------------------------
		//管理構造体
		public class ST_AudioHelper {
			public int				nCh;			//チャネル数
			public ST_AudioLogCh[/*nCh*/]		TBL_AudioLogCh;		//論理チャネル配列
			public ST_AudioDevCh[/*nCh*/]		TBL_AudioDevCh;		//デバイスチャネル配列
		}
		//-----------------------------------------------------------------------------
		//デバイスチャネル
		public class ST_AudioDevCh {
			public IAudioPlayer			pAudioPlayer;		//プレイヤー						アプリケーション定義のオブジェクトです。AudioHelper_New()の引数fnCreateAudioPlayerで指定した関数が呼び出される事によって作成されます。
			public int				iPhrNo;			//フレーズ番号						Prepare()で格納する。Stop(),又は,自然停止で0にする。
			public int				iPrepAge;		//再生準備の古さ					Prepare()～Start()の間((iPhrNo!=0)&&(!bInUse))のみ有効。
			public bool				bInUse;			//使用中フラグ						Start()でtrueにする。Stop(),又は,自然停止でfalseにする。Prepare()ではtrueにしない事に注意せよ。
		}
		//-----------------------------------------------------------------------------
		//論理チャネル
		public class ST_AudioLogCh {
			public ST_AudioDevCh			pAudioDevCh;		//この論理チャネルが使用しているデバイスチャネル	null=停止中
		}
		//*****************************************************************************
		//	
		//*****************************************************************************
		//AudioHelperを動的に作成する。
		public static ST_AudioHelper AudioHelper_New(int nCh, Func<object/*arg*/,IAudioPlayer> fnCreateAudioPlayer, object arg) {
			ST_AudioHelper pAudioHelper = new ST_AudioHelper();
			AudioHelper_Init(pAudioHelper, nCh, fnCreateAudioPlayer, arg);
			return pAudioHelper;
		}
		//-----------------------------------------------------------------------------
		//AudioHelperを静的に初期化する。
		public static void AudioHelper_Init(ST_AudioHelper pAudioHelper, int nCh, Func<object/*arg*/,IAudioPlayer> fnCreateAudioPlayer, object arg) {
			//チャネル数を格納する。
			pAudioHelper.nCh = nCh;
			//デバイスチャネル配列を作成する。
			pAudioHelper.TBL_AudioDevCh = new ST_AudioDevCh[nCh];
			for(int iCh = 0; iCh < nCh; iCh++) {
				ST_AudioDevCh pAudioDevCh = new ST_AudioDevCh();
				pAudioHelper.TBL_AudioDevCh[iCh] = pAudioDevCh;
				pAudioDevCh.pAudioPlayer = fnCreateAudioPlayer.Invoke(arg);
			}
			//論理チャネル配列を作成する。
			pAudioHelper.TBL_AudioLogCh = new ST_AudioLogCh[nCh];
			for(int iCh = 0; iCh < nCh; iCh++) {
				ST_AudioLogCh pAudioLogCh = new ST_AudioLogCh();
				pAudioHelper.TBL_AudioLogCh[iCh] = pAudioLogCh;
			}
		}
		//-----------------------------------------------------------------------------
		//AudioHelperの終了処理を実行する。
		public static void AudioHelper_Exit(ST_AudioHelper pAudioHelper) {
			//プレイヤーを削除する。
			foreach(ST_AudioDevCh pAudioDevCh in pAudioHelper.TBL_AudioDevCh) {
				pAudioDevCh.pAudioPlayer.Dispose();
			}
		}
		//-----------------------------------------------------------------------------
		//周期処理を実行する。
		public static void AudioHelper_Exec(ST_AudioHelper pAudioHelper) {
			//各論理チャネルについて…
			foreach(ST_AudioLogCh pAudioLogCh in pAudioHelper.TBL_AudioLogCh) {
				//この論理チャネルが使用しているデバイスチャネルを取得する。
				ST_AudioDevCh pAudioDevCh = pAudioLogCh.pAudioDevCh;
				//この論理チャネルが再生中ならば…
				if(pAudioDevCh != null) {
					//デバイスチャネルのフレーズ番号と使用中フラグがセットされている事を確認する。
					if((pAudioDevCh.iPhrNo == 0) || !pAudioDevCh.bInUse) { throw new ApplicationException(); }
					//プレイヤーの状態によって…
					switch(pAudioDevCh.pAudioPlayer.GetStatus()) {
					default:throw new ApplicationException();
					case AudioPlayerStatus.Prep:	//再生準備中
					case AudioPlayerStatus.Playing:	//再生中
						/** no job **/
						break;
					case AudioPlayerStatus.Stop:	//停止中
					case AudioPlayerStatus.PlayEnd:	//再生完了
						//この論理チャネルが使用しているデバイスチャネルをクリアする。
						pAudioLogCh.pAudioDevCh = null;
						//デバイスチャネルのフレーズ番号をクリアする。
						pAudioDevCh.iPhrNo = 0;
						//デバイスチャネルの使用中フラグをクリアする。
						pAudioDevCh.bInUse = false;
						break;
					}
				}
			}
		}
		//-----------------------------------------------------------------------------
		//再生準備を開始する。
//{{2017/04/09変更:AudioHelper_Prepare()も、MovieHelper_Prepare()と同様に、再生準備完了フラグを返すように変更しました。
//		public static void AudioHelper_Prepare(ST_AudioHelper pAudioHelper, int iPhrNo) {
//			//フレーズ番号を検査する。
//			if((iPhrNo < 1) || (iPhrNo > int.MaxValue)) { throw new ApplicationException(); }
//			//①使用中でなく指定されたフレーズ番号を再生準備中のデバイスチャネルが有れば、世代管理だけを行う。
//			//②使用中でなく再生準備中でもないデバイスチャネルか、又は、使用中でなく一番古い再生準備中のデバイスチャネルの再生準備を中断し、指定されたフレーズ番号の再生準備を開始して、世代管理を行う。
//			//③使用中でないデバイスチャネルが無ければ、何もしない。
//			AudioHelper_Prepare_subr1(pAudioHelper, iPhrNo);
//		}
//↓2017/04/09変更:AudioHelper_Prepare()も、MovieHelper_Prepare()と同様に、再生準備完了フラグを返すように変更しました。
		public static bool AudioHelper_Prepare(ST_AudioHelper pAudioHelper, int iPhrNo) {
			//フレーズ番号を検査する。
			if((iPhrNo < 1) || (iPhrNo > int.MaxValue)) { throw new ApplicationException(); }
			//①使用中でなく指定されたフレーズ番号を再生準備中のデバイスチャネルが有れば、世代管理を行い、再生準備が完了していればtrue,再生準備が完了していなければfalseを返す。
			//②使用中でなく再生準備中でもないデバイスチャネルか、又は、使用中でなく一番古い再生準備中のデバイスチャネルの再生準備を中断し、指定されたフレーズ番号の再生準備開始と世代管理を行って、(有り得ないとは思うが)再生準備が完了していればtrue,再生準備が完了していなければfalseを返す。
			//③使用中でないデバイスチャネルが無ければ、falseを返す。
			ST_AudioDevCh pAudioDevCh = AudioHelper_Prepare_subr1(pAudioHelper, iPhrNo);
			if(pAudioDevCh == null) { return false; }	//③
			//プレイヤーの状態によって…
			switch(pAudioDevCh.pAudioPlayer.GetStatus()) {
			default:throw new ApplicationException();
			case AudioPlayerStatus.Prep:	//再生準備中
				return false;	//①②
			case AudioPlayerStatus.Playing:	//再生中
				return true;	//①(②)
		    //	case AudioPlayerStatus.Stop:	//停止中	AudioHelper_Prepare_subr1()が停止中のデバイスチャネルを返す事は無いはずです。
		    //	case AudioPlayerStatus.PlayEnd:	//再生完了	AudioHelper_Prepare_subr1()が再生完了のデバイスチャネルを返す事は無いはずです。
			}
		}
//}}2017/04/09変更:AudioHelper_Prepare()も、MovieHelper_Prepare()と同様に、再生準備完了フラグを返すように変更しました。
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//①使用中でなく指定されたフレーズ番号を再生準備中のデバイスチャネルが有れば、世代管理だけを行って、それを返す。
		//②使用中でなく再生準備中でもないデバイスチャネルか、又は、使用中でなく一番古い再生準備中のデバイスチャネルの再生準備を中断し、指定されたフレーズ番号の再生準備開始と世代管理を行って、それを返す。
		//③使用中でないデバイスチャネルが無ければ、nullを返す。
		private static ST_AudioDevCh AudioHelper_Prepare_subr1(ST_AudioHelper pAudioHelper, int iPhrNo) {
			//フレーズ番号を検査する。
			if((iPhrNo < 1) || (iPhrNo > int.MaxValue)) { throw new ApplicationException(); }
			//①使用中でなく指定されたフレーズ番号を再生準備中のデバイスチャネルが有れば、世代管理だけを行って、それを返す。
			foreach(ST_AudioDevCh pAudioDevCh in pAudioHelper.TBL_AudioDevCh) {
				//使用中でなく指定されたフレーズ番号を再生準備中のデバイスチャネルならば…
				if(!pAudioDevCh.bInUse && (pAudioDevCh.iPhrNo == iPhrNo)) {
					//世代管理だけを行って、それを返す。
					AudioHelper_Prepare_subr3(pAudioHelper, pAudioDevCh);
					return pAudioDevCh;	//ここまで
				}
			}
			//②使用中でなく再生準備中でもないデバイスチャネルか、又は、使用中でなく一番古い再生準備中のデバイスチャネルの再生準備を中断し、指定されたフレーズ番号の再生準備開始と世代管理を行って、それを返す。
			int           oldestPrepAge    = int.MinValue;
			ST_AudioDevCh oldestAudioDevCh = null;
			foreach(ST_AudioDevCh pAudioDevCh in pAudioHelper.TBL_AudioDevCh) {
				if(!pAudioDevCh.bInUse) {
					//使用中でなく再生準備中でもないデバイスチャネルならば…
					if(pAudioDevCh.iPhrNo == 0) {
						//指定されたフレーズ番号の再生準備開始と世代管理を行って、それを返す。
						AudioHelper_Prepare_subr2(pAudioDevCh, iPhrNo);
						AudioHelper_Prepare_subr3(pAudioHelper, pAudioDevCh);
						return pAudioDevCh;	//ここまで
					}
					//使用中でなくこれまでで一番古い再生準備中のデバイスチャネルならば…
					if(pAudioDevCh.iPrepAge > oldestPrepAge) {
						//これまでで一番古い再生準備中のデバイスチャネルを記憶する。
						oldestPrepAge    = pAudioDevCh.iPrepAge;
						oldestAudioDevCh = pAudioDevCh;
					}
				}
			}
			//使用中でないデバイスチャネルが有れば…
			if(oldestAudioDevCh != null) {
				//再生準備を中断し、指定されたフレーズ番号の再生準備開始と世代管理を行って、それを返す。
				AudioHelper_Stop_subr(oldestAudioDevCh);
				AudioHelper_Prepare_subr2(oldestAudioDevCh, iPhrNo);
				AudioHelper_Prepare_subr3(pAudioHelper, oldestAudioDevCh);
				return oldestAudioDevCh;	//ここまで
			}
			//③使用中でないデバイスチャネルが無ければ、nullを返す。
			return null;
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//指定されたデバイスチャネルで、再生準備を開始する。
		private static void AudioHelper_Prepare_subr2(ST_AudioDevCh pAudioDevCh, int iPhrNo) {
			//デバイスチャネルのフレーズ番号と使用中フラグがクリアされている事を確認する。
			if((pAudioDevCh.iPhrNo != 0) || pAudioDevCh.bInUse) { throw new ApplicationException(); }
			//フレーズ番号を検査する。
			if((iPhrNo < 1) || (iPhrNo > int.MaxValue)) { throw new ApplicationException(); }
			//フレーズ番号を格納する。
			pAudioDevCh.iPhrNo = iPhrNo;
			//再生準備を開始する。
			pAudioDevCh.pAudioPlayer.Prepare(iPhrNo);
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//世代管理を実行する。
		private static void AudioHelper_Prepare_subr3(ST_AudioHelper pAudioHelper, ST_AudioDevCh pAudioDevCh) {
			//指定されたデバイスチャネルの、再生準備の古さをクリアする。
			pAudioDevCh.iPrepAge = 0;
			//指定されたデバイスチャネル以外で、使用中でなく再生準備中で再生準備の古さが0のデバイスチャネルが有るか調べる。
			bool bFound = false;
			foreach(ST_AudioDevCh otherAudioDevCh in pAudioHelper.TBL_AudioDevCh) {
				if((otherAudioDevCh != pAudioDevCh) &&
				   !otherAudioDevCh.bInUse &&
				   (otherAudioDevCh.iPhrNo != 0) &&
				   (otherAudioDevCh.iPrepAge == 0)) {
					bFound = true;
					break;	//ここまで
				}
			}
			//指定されたデバイスチャネル以外で、使用中でなく再生準備中で再生準備の古さが0のデバイスチャネルが無ければ…
			if(!bFound) { return; }	//ここまで
			//指定されたデバイスチャネル以外で、使用中でなく再生準備中のデバイスチャネルの再生準備の古さを増やす。
			foreach(ST_AudioDevCh otherAudioDevCh in pAudioHelper.TBL_AudioDevCh) {
				if((otherAudioDevCh != pAudioDevCh) &&
				   !otherAudioDevCh.bInUse &&
				   (otherAudioDevCh.iPhrNo != 0)) {
					otherAudioDevCh.iPrepAge++;	//このアルゴリズムならば、(iPrepAge≧nCh)にはならないはずなので、飽和処理は不要です。
				}
			}
		}
		//-----------------------------------------------------------------------------
		//再生を開始する。
		public static void AudioHelper_Play(ST_AudioHelper pAudioHelper, int iCh, int iPhrNo) {
			//論理チャネル番号を検査する。
			if((uint)iCh >= pAudioHelper.nCh) { throw new ApplicationException(); }
			//フレーズ番号を検査する。
			if((iPhrNo < 1) || (iPhrNo > int.MaxValue)) { throw new ApplicationException(); }
			//論理チャネルを取得する。
			ST_AudioLogCh pAudioLogCh = pAudioHelper.TBL_AudioLogCh[iCh];
			//確実に再生を停止する。
			AudioHelper_Stop(pAudioHelper, iCh);
			//指定されたフレーズ番号を再生準備中のデバイスチャネルを取得する。
			ST_AudioDevCh pAudioDevCh = AudioHelper_Prepare_subr1(pAudioHelper, iPhrNo);
			if(pAudioDevCh == null) { throw new ApplicationException(); }	//使用中でないデバイスチャネルが無く、再生準備を開始出来なかった。実際には、論理チャネル数とデバイスチャネル数が同じなので、ここでエラー停止する事は無いはずだ。もしここでエラー停止した場合は、当モジュールのバグです。
			//使用中フラグをセットする。
			pAudioDevCh.bInUse = true;
			//この論理チャネルが使用しているデバイスチャネルを格納する。
			pAudioLogCh.pAudioDevCh = pAudioDevCh;
			//再生準備が完了したら、再生が開始するようにしておく。
			pAudioDevCh.pAudioPlayer.Start();
		}
		//-----------------------------------------------------------------------------
		//確実に再生を停止する。
		public static void AudioHelper_Stop(ST_AudioHelper pAudioHelper, int iCh) {
			//論理チャネル番号を検査する。
			if((uint)iCh >= pAudioHelper.nCh) { throw new ApplicationException(); }
			//論理チャネルを取得する。
			ST_AudioLogCh pAudioLogCh = pAudioHelper.TBL_AudioLogCh[iCh];
			//この論理チャネルが使用しているデバイスチャネルを取得する。
			ST_AudioDevCh pAudioDevCh = pAudioLogCh.pAudioDevCh;
			//この論理チャネルが再生中ならば…
			if(pAudioDevCh != null) {
				//デバイスチャネルのフレーズ番号と使用中フラグがセットされている事を確認する。
				if((pAudioDevCh.iPhrNo == 0) || !pAudioDevCh.bInUse) { throw new ApplicationException(); }
				//この論理チャネルが使用しているデバイスチャネルをクリアする。
				pAudioLogCh.pAudioDevCh = null;
				//デバイスチャネルの再生を停止する。
				AudioHelper_Stop_subr(pAudioDevCh);
			}
		}
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//指定されたデバイスチャネルの再生,又は,再生準備を停止する。
		private static void AudioHelper_Stop_subr(ST_AudioDevCh pAudioDevCh) {
			//少なくともデバイスチャネルのフレーズ番号が格納されている事を確認する。
			if(pAudioDevCh.iPhrNo == 0) { throw new ApplicationException(); }	//AudioHelper_Prepare_subr1()から呼び出された場合は(pAudioDevCh.bInUse==false)なので、使用中フラグを確認してはいけない。
			//プレイヤーの状態によって…
			switch(pAudioDevCh.pAudioPlayer.GetStatus()) {
			default:throw new ApplicationException();
			case AudioPlayerStatus.Stop:	//停止中
			case AudioPlayerStatus.PlayEnd:	//再生完了
				/** no job **/
				break;
			case AudioPlayerStatus.Prep:	//再生準備中
			case AudioPlayerStatus.Playing:	//再生中
				//再生,又は,再生準備を停止する。
				pAudioDevCh.pAudioPlayer.Stop();
				break;
			}
			//デバイスチャネルのフレーズ番号をクリアする。
			pAudioDevCh.iPhrNo = 0;
			//デバイスチャネルの使用中フラグを確実にクリアする。
			pAudioDevCh.bInUse = false;	//AudioHelper_Prepare_subr1()から呼び出された場合は、既に(pAudioDevCh.bInUse==false)なのでダミー処理となる。
		}
		//-----------------------------------------------------------------------------
		//ボリュームを設定する。
		public static void AudioHelper_SetVolume(ST_AudioHelper pAudioHelper, int iCh, double volume) {
			//論理チャネル番号を検査する。
			if((uint)iCh >= pAudioHelper.nCh) { throw new ApplicationException(); }
			//論理チャネルを取得する。
			ST_AudioLogCh pAudioLogCh = pAudioHelper.TBL_AudioLogCh[iCh];
			//この論理チャネルが使用しているデバイスチャネルを取得する。
			ST_AudioDevCh pAudioDevCh = pAudioLogCh.pAudioDevCh;
			//この論理チャネルが再生中ならば…
			if(pAudioDevCh != null) {
				//デバイスチャネルのフレーズ番号と使用中フラグがセットされている事を確認する。
				if((pAudioDevCh.iPhrNo == 0) || !pAudioDevCh.bInUse) { throw new ApplicationException(); }
				//デバイスチャネルのボリュームを設定する。
				pAudioDevCh.pAudioPlayer.SetVolume(volume);
			}
		}
		//-----------------------------------------------------------------------------
		//パンを設定する。
		public static void AudioHelper_SetPan(ST_AudioHelper pAudioHelper, int iCh, double pan) {
			//論理チャネル番号を検査する。
			if((uint)iCh >= pAudioHelper.nCh) { throw new ApplicationException(); }
			//論理チャネルを取得する。
			ST_AudioLogCh pAudioLogCh = pAudioHelper.TBL_AudioLogCh[iCh];
			//この論理チャネルが使用しているデバイスチャネルを取得する。
			ST_AudioDevCh pAudioDevCh = pAudioLogCh.pAudioDevCh;
			//この論理チャネルが再生中ならば…
			if(pAudioDevCh != null) {
				//デバイスチャネルのフレーズ番号と使用中フラグがセットされている事を確認する。
				if((pAudioDevCh.iPhrNo == 0) || !pAudioDevCh.bInUse) { throw new ApplicationException(); }
				//デバイスチャネルのパンを設定する。
				pAudioDevCh.pAudioPlayer.SetPan(pan);
			}
		}
	}
}
//*****************************************************************************
//	IAudioPlayerの実装例
//*****************************************************************************
#if false
using System;
using static org.piece_me.libclip;
using static Const;
using static Program;
public static partial class Program {
	public class AudioPlayer : IAudioPlayer {
		public CriAtomExPlayer	criAtomExPlayer;	//AtomExプレイヤー
		//-----------------------------------------------------------------------------
		public AudioPlayer() {
			//AtomExプレイヤーを作成する。
			criAtomExPlayer = new CriAtomExPlayer(260/*最大パス文字列長*/, 1/*同時再生ファイル数*/);
		#if     (!UNITY_EDITOR && UNITY_ANDROID)
			criAtomExPlayer.SetSoundRendererType(CriAtomEx.SoundRendererType.Native);
		#endif//(!UNITY_EDITOR && UNITY_ANDROID)
		}
		//-----------------------------------------------------------------------------
		void IDisposable.Dispose() {
			//AtomExプレイヤーを削除する。
			criAtomExPlayer.Dispose();
		}
		//-----------------------------------------------------------------------------
		AudioPlayerStatus IAudioPlayer.GetStatus() {
			//プレイヤーの状態を取得する。
			return (AudioPlayerStatus)criAtomExPlayer.GetStatus();
		}
		//-----------------------------------------------------------------------------
		void IAudioPlayer.Prepare(int iPhrNo) {
			//フレーズ番号を検査する。
			if((iPhrNo < 1) || (iPhrNo > PhrNo_Max)) { throw new ApplicationException(); }
			//フレーズのパス名を取得する。
			string path = REG_get_string_l(TBL_RegTbl, RegKey_PhrDef, iPhrNo, RegKey_Path);
			if(path == null) { throw new ApplicationException(); }
			//フレーズのパス名を設定する。
			criAtomExPlayer.SetFile(binder, path);
			//ヘッダ解析を開始する。
			criAtomExPlayer.Prepare();
		}
		//-----------------------------------------------------------------------------
		void IAudioPlayer.Start() {
			//再生準備が完了したら、再生が開始するようにしておく。
			criAtomExPlayer.Resume(CriAtomEx.ResumeMode.AllPlayback);
		}
		//-----------------------------------------------------------------------------
		void IAudioPlayer.Stop() {
			//再生,又は,再生準備を停止する。
			criAtomExPlayer.Stop(true/*リリース時間を無視して音声を即座に停止する*/);
			//プレイヤーの状態が停止中になるまで待つ。
			for(;;) {
				CriAtomExPlayer.Status status = criAtomExPlayer.GetStatus();
				if(status == CriAtomExPlayer.Status.Error) { throw new ApplicationException(); }
				if(status == CriAtomExPlayer.Status.Stop) { break; }
				//Unity版のCRIにはAtomEx_ExecuteMain()に相当する関数が無く、ここで呼び出さなくても構わないようです。(実験より)
			}
		}
		//-----------------------------------------------------------------------------
		void IAudioPlayer.SetVolume(double volume) {
			//ボリュームを設定する。
			criAtomExPlayer.SetVolume((float)volume);
			//再生パラメータを更新する。
			criAtomExPlayer.UpdateAll();	//忘れないで!!
		}
		//-----------------------------------------------------------------------------
		void IAudioPlayer.SetPan(double pan) {
			//※TODO:2Dパンを設定するADX2の関数がわかりません…
		}
	}
}
#endif
