module y4d_sound.sound;

private import std.thread;

private import SDL_mixer;
//	SDL mixerは、標準のSDLとは別配布

private import ytl.y4d_result;
private import y4d_aux.filesys;
private import y4d_aux.cacheobject;
private import y4d_aux.widestring;
private import y4d_timer.fixtimer;

private import yamalib.log.log;
private import yamalib.log.performancelog;

/*
	このモジュールを作るのに際して、長 健太(ABA."Saba") 氏のp47の
	ソースを参考にさせていただきました。

	この場をお借りして感謝の意を表します。
	素晴らしいソースを提供していただき、ありがとうございますm(_ _)m
*/

private import ytl.singleton;

///	Sound クラスのための再生品質設定クラス。
/**
	<PRE>
	int audio_rate; // = 44100;
	ushort audio_format; // = AUDIO_S16;
	int audio_channels; //	= 2;
	int audio_buffers; // = 4096;

	初期状態では上記のようになっている。

	あえて変更したければsingletonオブジェクト通じて変更すること。
	例)
	singleton!(SoundConfig).audio_channels = 1;
	</PRE>
*/
class SoundConfig {

 	/// 再生周波数(default:44100)
	int audio_rate;

	/// 再生ビット数(default:AUDIO_S16)
	/**
		このAUDIO_S16というのは、SDL_audio.d で定義されている。
	*/
	ushort audio_format;

	///	再生チャンネル数(default:2)
	int audio_channels;

	/// 再生バッファ長[bytes](default:4096)
	int audio_buffers;

	this() {
		noSound = false;
		bInitSuccess = false;
		audio_rate = 44100;
		audio_format = AUDIO_S16;
		audio_channels = 2;
		audio_buffers = 4096;
	}

	~this() {
		if (bInitSuccess) {
			SDL_QuitSubSystem(SDL_INIT_AUDIO);
		}
	}

private:
	bool noSound;	//	サウンドデバイスはついていなければtrue
	bool bInitSuccess;	//	初期化に一度でも成功したらtrue

	y4d_result init() {
		if (bInitSuccess) return y4d_result.no_error;
		if (noSound) return y4d_result.no_error;
		if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
			noSound = true;	//	サウンドデバイス無いんちがう？
			return y4d_result.SDL_error;
		}
		bInitSuccess = true;
		return y4d_result.no_error;
	}
}

///	サウンド再生用クラス。
/**
	<PRE>
	サウンド再生の考えかた
	１．music(bgm) + chuck(se)×8　の9個を同時にミキシングして出力出来る
	２．次のmusicが再生されると前のmusicは自動的に停止する
	３．seを再生するときには1～8のchuck(チャンク)ナンバーを指定できる
		同じchuckナンバーのseを再生すると、前回再生していたものは
		自動的に停止する
	４．musicもchunkも、どちらもwav,riff,ogg...etcを再生する能力を持つ
	５．midi再生に関しては、musicチャンネルのみ。
	つまり、musicとchunkに関しては、５．６．の違いを除けば同等の機能を持つ。
	６．チャンクは、0を指定しておけば、1～8の再生していないチャンクを
	自動的に探す。
	７．bgmとして、musicチャンネルを用いない場合(midiか、途中から再生
		させるわけでもない限りは用いる必要はないと思われる)、
		bgmのクロスフェードなども出来る。

	使用例)
	Sound s = new Sound;
	s.load("1.ogg",-1);
	s.setLoop(-1); // endless
	s.play();

	Sound s2 = new Sound;
	s2.load("extend.wav",1);
	s2.play();
	</PRE>

	@todo volume管理(気が向いたら)
*/
class Sound : ICacheObject {
public:
	
	/// 最後に呼び出すこと
	public static void releaseAllFinal() {
		getManager().releseAllFinal();
		foreach(tmpFile; TMP_FILES) {
			if (tmpFile !is null) {
				tmpFile.release();
			}
		}
	}
	
	/// 安全にサウンドを開放する
	public static void safeRelease(Sound sound) {
		if ( sound is null ) {
			return;
		}
		try {
			sound.release();
			sound = null;
		} catch (Object o) {
			Log.printError("Sound#safeRelease : Error[%s]", o.toString);
			Log.printSoundLog("Sound#safeRelease : Error[%s]", o.toString);
		}
	}

	/// スレッドローディングを使用するかどうか
	public static void useThreadLoading(bool b) {
		THREAD_FLG = b;
	}
	
	/// サウンド専用のアーカイバを設定する
	public synchronized static void setFileArchiver(FileArchiverBase archiver) {
		m_arcReader = archiver;
	}
	public synchronized static FileArchiverBase getFileArchiver() {
		return m_arcReader;
	}

	/// コンフィグの取得
	public static SoundConfig getConfig() {
		return singleton!(SoundConfig).get();
	}

	///	musicチャンネルを(徐々に)フェードアウトさせる
	/**
		speedはfadeoutスピード[ms]
	*/
	public static y4d_result fadeMusic(int speed) {
		if (isNoSound()) return y4d_result.precondition_error;
		getManager().music = null;
		return Mix_FadeOutMusic(speed)
			? y4d_result.SDL_error:y4d_result.no_error;
	}

	/// 0～7のチャンネルを(徐々に)フェードアウトさせる
	/**
		speedはfadeoutスピード[ms]
	*/
	public static y4d_result fadeChunk(int speed) {
		if (isNoSound()) return y4d_result.precondition_error;
		getManager().fadeChunkAll(speed);

//		for(int i=0;i<8;++i) {
//			getManager().chunk[i] = null;
//			Mix_FadeOutChannel(i,speed);
//		}

		return y4d_result.no_error;
	}

	///	すべてのchunk(除くmusicチャンネル)の再生を停止させる
	/**
		このメソッド呼び出し中に他のスレッドから
		サウンド関係をいじることは考慮に入れていない
	*/
	public static synchronized void stopAllChunk() {
		getManager().stopAllChunk();
	}

	///	musicチャンネルの再生を停止させる
	/**
		このメソッド呼び出し中に他のスレッドから
		サウンド関係をいじることは考慮に入れていない
	*/
	public static synchronized y4d_result stopMusic() {
		return getManager().stopMusic();
	}

	///	musicチャンネルと、すべてのchunkの再生を停止させる
	/**
		このメソッド呼び出し中に他のスレッドから
		サウンド関係をいじることは考慮に入れていない
	*/
	public static synchronized y4d_result stopAll() {
		stopAllChunk();
		return stopMusic();
	}
	
	/// コンストラクタ
	public this() { 
		init(); 
	}

	/// デストラクタ
	public ~this() {
		if (music !is null || chunk !is null) {
			release(); 
		}
	}

	/// 生データを取得する
	public Mix_Chunk* getChunk() {
		return chunk;
	}

	/// 生データを取得する
	public Mix_Music* getMusic() {
		return music;
	}
	
	/// 読み込んでいるファイル名を返す(デバッグ用)
	public char[] getFilename() {
		return this.filename;
	}

	///	loadで読み込んでいるサウンドのために確保されているバッファ長を返す
	/**
		全体で使用している容量を計算したりするのに使える．．かな？
	*/
	override public ulong getBufferSize() {
		if (!chunk) return 0;
		if (chunk.allocated)
			return chunk.alen;
		return y4d_result.no_error;
	}

	///	サウンドファイルを読み込む (読み込みエラーならば非0が返る)
	/**
<PRE>
	filename : ファイル名
	ch	: 読み込むチャンネル
		-1	 : musicチャンネル
		0	 : 1～8のchunkのうち再生時(play)に
			再生していないチャンネルをおまかせで
		1～8 : chunkに読み込む
</PRE>
	*/
	public y4d_result load(char[] filename,int ch)
	{
		try {
			if (THREAD_FLG) {
// ↓マルチスレッド版
				this.filename = filename;
				this.ch = ch;
				
				Log.print("Sound#load CREATE LOAD THREAD %s", filename);
				Log.printSoundLog("Sound#load CREATE LOAD THREAD %s", filename);
				thread = new Thread(&loadThread);
				thread.start();
				
				return y4d_result.no_error;
			}

// ↓シングルスレッド版
			release();
			
			// デバッグ用にファイル名を取っておく
			this.filename = filename;
	
			y4d_result result;
			if (ch==-1) result = loadMusic(filename);
			else if (ch==0) result = loadChunk(filename);
			else if (ch>=0 && ch<=8) result = loadChunk(filename,ch-1);
			else { return y4d_result.invalid_parameter; }	//	errorですよ、と。
			
			return result;

		} catch (Exception e) {
			Log.print("Sound#load %*s", e.toString());
			Log.printSoundLog("Sound#load %*s", e.toString());
		}
		
		return y4d_result.invalid_parameter; 
	}
	
	///	サウンドファイルを読み込む (読み込みエラーならば非0が返る)
	public synchronized y4d_result loadFromMem(void[] mem,int ch) {
		release();

		y4d_result result;
		if (ch==-1) return y4d_result.invalid_parameter;
		else if (ch==0) result = loadChunk(mem);
		else if (ch>=0 && ch<=8) result = loadChunk(mem,ch-1);
		else { return y4d_result.invalid_parameter; }	//	errorですよ、と。
		return result;
	}
	
	/// スレッドでロードしている最中かどうかをかえす
	/**
		これは、ローダスレッドとして登録した、スレッドに対してその状態を取得します。
		もし、ローダスレッドからこのメソッドを呼び出しても、常にfalseが返ります。
	*/
	public synchronized bool isLoading(void delegate() dg = null) {
//Log.printLook("isLoading:%s", Thread.getThis().hdl);
		// playを呼び出しスレッドがロードすれどでなくて、もしロードスレッドが実行中ならば
		// 再生開始を遅延し、ロードスレッドが再生を同時に行いようにする
		if (thread !is null && Thread.TS.TERMINATED != thread.getState() && !thread.isSelf()) {
//printf("own thread %d\n", Thread.getThis.hdl);
			if (dg !is null) {
				dg();
			}
			return true;
		}
		return false;
	}
	
	/// ボリュームの設定
	public y4d_result setVolume(int vol) {
		
		if ( isLoading(delegate {this.volume = vol;} ) ) {
//			synchronized (loadLock) {
//				this.volume = vol;
//			}
			return y4d_result.no_error;
		}
		
		if (isNoSound()) return y4d_result.no_error;
		// Fix Mix_VolumeMusic は前のボリュームの値を返却するもんだから、エラーは検知できないん...
		if (music) return Mix_VolumeMusic(vol) ? y4d_result.no_error:y4d_result.no_error;
		// Fix Mix_VolumeChunk は前のボリュームの値を返却するもんだから、エラーは検知できないん...
		if (chunk) { 
			// すべてのチャンネルを最大にする
			Mix_Volume(-1,128); 
			return Mix_VolumeChunk(chunk,vol) ? y4d_result.no_error:y4d_result.no_error; 
		}
		return y4d_result.precondition_error; // Sound読み込んでないぽ
	}
	
	/// ボリュームの取得
	public int getVolume() {
		if (isNoSound()) return -1;
		if (music) return Mix_VolumeMusic(-1);
		if (chunk) return Mix_VolumeChunk(chunk,-1);
		return -1; // Sound読み込んでないぽ
	}

	///	ループ回数の設定
	/**
			-1:endless
			0 :1回のみ(default)
			1 :2回
		以下、再生したい回数-1を指定する

		ここで設定した値は一度設定すれば再度この関数で
		変更しない限り変わらない
	*/
	public void setLoop(int loop)
	{	loopflag = loop; }

	///	ループ回数の取得。setLoopで設定した数字の取得。
	public int getLoop()
	{	
		return loopflag; 
	}
	
	/// 再生中かを調べる
	public bool isPlay() {
		if (isNoSound()) return false;
		return getManager().isPlay(this);
//		
//		if (getManager().music is this && Mix_PlayingMusic()) return true;
//		
//		for(int i=0;i<8;++i){
//			if (getManager().chunk[i] is this && Mix_Playing(i)) return true;
//		}
//		
//		// ロードしていないかロード中
//		return false;
	}

	/// フェード中かどうか
	public bool isFade() {
		fadeTimer.update();
		return cast(bool) (fadeTimer.get() < fadeSpeed); 
	}
		
	///	loadで読み込んだサウンドを再生する(再生エラーならば非0が返る)
	public y4d_result play(){
		try {
//printf("start check loading:%d\n", Thread.getThis.hdl);
			if ( isLoading( delegate {threadFlgAllReset(); playFlag = true;} ) ) {
				Log.print("play request.. but sound now loading..:%s", Thread.getThis.hdl);
				Log.printSoundLog("play request.. but sound now loading..:%s", Thread.getThis.hdl);
				return y4d_result.no_error;
			}
//printf("end check loading %d\n", Thread.getThis.hdl);
	
			if (isNoSound()) {
				return y4d_result.no_error;
			}
			stop(); // 停止させて、sound managerの再生チャンネルをクリアしなければ
			if (music) {
				return playMusic();
			}
			if (chunk) {
				return playChunk();
			}

			// DEBUG
			dumpChunkAll();
		} catch {
			Log.printError("Sound#play");
			Log.printSoundLog("Sound#play");
		}

 		// Sound読み込んでないぽ
 		return y4d_result.precondition_error;
	}
	
	///	フェードイン付きのplay。speedはfade inに必要な時間[ms]
	public y4d_result playFade(int speed) {
		try 
		{
			if ( isLoading(delegate {threadFlgAllReset(); playFadeFlag = true; this.speed = speed;}) ) {
//				synchronized (loadLock) {
//					threadFlgAllReset();
//					playFadeFlag = true;
//					this.speed = speed;
//				}
				Log.print("playFade.. but sound now loading..");
				Log.printSoundLog("playFade.. but sound now loading..");
				return y4d_result.no_error;
			}
	
			if (isNoSound()) {
				return y4d_result.no_error;
			}
			// 停止させて、sound managerの再生チャンネルをクリアしなければ
			try {
				stop();
			} catch { 
				Log.printError("Sound#playFade#1 : stop error ");
				Log.printSoundLog("Sound#playFade#1 : stop error ");
			}
			fadeSpeed = speed;
			fadeTimer.reset();
			if (music !is null) {
				return playMusicFade(speed);
			}
			if (chunk !is null) { 
				return playChunkFade(speed);
			}

			// DEBUG
			dumpChunkAll();
		} 
		catch 
		{
			Log.printError("Sound#playFade#");
			Log.printSoundLog("ERROR Sound#playFade#");
		} 
		// Sound読み込んでないぽ
		return y4d_result.precondition_error; 
	}
	
	/// 一時停止から回復
	public y4d_result resume() {
		//	stopは、channelごとに管理されているので、
		//	自分が再生しているchannelなのかどうかを
		//	このクラスが把握している必要がある
		if (isNoSound()) {
			return y4d_result.no_error;
		}
		
		return getManager().resume(this);
	}

	/// 一時停止する
	public y4d_result pause(){
		//	stopは、channelごとに管理されているので、
		//	自分が再生しているchannelなのかどうかを
		//	このクラスが把握している必要がある
		if (isNoSound()) return y4d_result.no_error;
		
		return getManager().pause(this);
	}

	///	play中のサウンドを停止させる
	/**
		読み込んでいるサウンドデータ自体を解放するには release を
		呼び出すこと。こちらは、あくまで停止させるだけ。次にplayが
		呼び出されれば、再度、先頭から再生させることが出来る。
	*/
	public y4d_result stop(){
		//	stopは、channelごとに管理されているので、
		//	自分が再生しているchannelなのかどうかを
		//	このクラスが把握している必要がある
		if (isNoSound()) return y4d_result.no_error;
		
		if ( isLoading(delegate { threadFlgAllReset(); stopFlag = true; this.speed = speed;}) ) {
//			synchronized (loadLock) {
//				threadFlgAllReset();
//				stopFlag = true;
//				this.speed = speed;
//			}
	//		Log.print("stop.. but sound now loading..\n");
			return y4d_result.no_error;
		}
		
		playFlag = false;
		playFadeFlag = false;
		
		return getManager().stop(this);
	}

	/// stopのフェード版
	/**
		fadeoutのスピードを指定できる。speed の単位は[ms]。
		その他はstopと同じ。
	*/
	public y4d_result stopFade(int speed){
		if (isNoSound()) return y4d_result.no_error;

		if ( isLoading(delegate {threadFlgAllReset(); stopFadeFlag = true; this.speed = speed;}) ) {
//			synchronized (loadLock) {
//				threadFlgAllReset();
//				stopFadeFlag = true;
//				this.speed = speed;
//			}
			Log.print("stopFade.. but sound now loading..");
			Log.printSoundLog("stopFade.. but sound now loading..");
			return y4d_result.no_error;
		}

		playFlag = false;
		playFadeFlag = false;
		
		fadeSpeed = speed;
		fadeTimer.reset();
		return getManager().stopFade(this,speed);
	}

	///	loadで読み込んだサウンドを解放する
	public void release() {
		if ( isLoading(delegate {threadFlgAllReset(); this.releaseFlg = true;}) ) {
			Log.print("release.. but sound now loading..");
			Log.printSoundLog("release.. but sound now loading..");
			return;
		}

		if (music) {
			stop();
			Mix_FreeMusic(music);
			music = null;
		}
		if (chunk) {
			stop();
			Mix_FreeChunk(chunk);
			chunk = null;
		}
	}

private:
	// ローディングにスレッドを使用するかどうかのフラグ
	static bool THREAD_FLG = true;
	// Soundクラス専用のアーカイブリーダー
	static FileArchiverBase m_arcReader;
	
	static FileSys.TmpFile[char[]] TMP_FILES;

	Thread thread;	//!< 音楽ロードスレッド
	// ファイルから読み込んでいればそのファイル名
	char[] filename;
	// 使用しているチャンネル番号
	int ch;
	// BGMの生データ
	Mix_Music* music;
	// チャンクの生データ
	Mix_Chunk* chunk;
	// チャンクのチャンネル番号
	int chunkChannel;
	// ループ回数
	int loopflag;
	// フェード計測タイマ
	FixTimer fadeTimer;
	// フェードスピード
	int fadeSpeed;

	// 再生フラグ
	bool playFlag;
	// フェード再生フラグ
	bool playFadeFlag;
	// 停止フラグ
	bool stopFlag;
	// フェード停止フラグ
	bool stopFadeFlag;
	// 解放フラグ
	bool releaseFlg;
	// フェードスピードフラグ
	int  speed;
	// ボリューム
	int volume = -1;
	
	/// チャンクマネージャの取得
	private static SoundChunkManager getManager() { 
		return singleton!(SoundChunkManager).get(); 
	}
	
	/// サウンドが使用できるかどうか
	private static bool	isNoSound() { 
		return getConfig().noSound; 
	}
	
	/// Sound再生状況をレポートする	
	private static void dumpChunkAll() {
		if (Log.isLogEnable()) {
			SoundChunkManager scm = getManager();
			Log.print("SOUND dumpChunkAll START-----------------");
			try {
				foreach (int i,inout Sound s; scm.chunk) {
					if (s is null) {
						continue;
					}
					Log.print("SOUND [%s]: filename = %s", i, s.getFilename());
					Log.print("SOUND [%s]: playFlag = %s", i, s.isPlay());
					if ( s.isPlay() ) {
						Log.print("----SOUND [%s]: loadingFlag = %s", i, s.isLoading());
						Log.print("----SOUND [%s]: VOLUME = %s", i, s.getVolume());
						Log.print("----SOUND [%s]: fadeFlag = %s", i, s.isFade());
						Log.print("----SOUND [%s]: LOOP = %s", i, s.getLoop());
					}
				}
				Sound musicChunk = scm.music;
				if (musicChunk !is null) {
					Log.print("MUSIC: filename = %s", musicChunk.getFilename());
					Log.print("MUSIC: playFlag = %s", musicChunk.isPlay());
					if ( musicChunk.isPlay() ) {
						Log.print("----MUSIC: loadingFlag = %s", musicChunk.isLoading());
						Log.print("----MUSIC: VOLUME = %s", musicChunk.getVolume());
						Log.print("----MUSIC: fadeFlag = %s", musicChunk.isFade());
						Log.print("----MUSIC: LOOP = %s", musicChunk.getLoop());
					}
				}
				Log.print("SOUND dumpChunkAll END-----------------");
			} catch {
				// このメソッドを同期化するとほかが面倒になるので、NULLアクセスはok
				Log.printWarn("SOUND dumpChunkAll FAILED -----------------");
			}
		}
	}
	
	/// スレッド処理待ちようフラグを全て初期化
	private void threadFlgAllReset() {
		playFlag = false;
		playFadeFlag = false;
		stopFlag = false;
		stopFadeFlag = false;
		releaseFlg = false;
		speed = 0;
		// こいつはなんにせよ独立した変数
//		volume = -1;
	}
	
	/// ファイルロードスレッドメソッド
	private synchronized int loadThread() {
		y4d_result result;
		try {
			try {
				// パフォーマンス測定			
	//			PerformanceLog.logSoundLoad(filename, null);
				release();
	//printf("ch== %d\n",ch);
				if (ch==-1) result = loadMusic(filename);
				else if (ch==0) result = loadChunk(filename);
				else if (ch>=0 && ch<=8) result = loadChunk(filename,ch-1);
				else {
					//	errorですよ、と。 
					return y4d_result.invalid_parameter; 
				}	
			} catch {
				// 何らかのエラー
				printf("Exception Sound#loadthread#1");
				Log.printSoundLog("Exception Sound#loadthread#1");
				return 0;
			}
	
			loadThreadPostExec(result);
			return 0;
		} catch {
			// 何らかのエラー
			printf("Exception Sound#loadthread#2");
			Log.printSoundLog("Exception Sound#loadthread#2");
			return 0;
		} finally {
			// 自分自身に null をいれて終了を明記
			thread = null;
		}
	}
	
	/// ファイルロードスレッドの後処理メソッド
	private synchronized void loadThreadPostExec(inout y4d_result result) {
Log.print("START loadThreadPostExec");
Log.printSoundLog("START loadThreadPostExec");
		try {
			if (result == y4d_result.no_error) {
				if (volume != -1) {
					setVolume(volume);
					volume = -1;
				} else {
					setVolume( MIX_MAX_VOLUME );
				}
//	For thread debug
//	SDL_Delay(5000);
				
				if (playFlag) {
					play();
				} else if (playFadeFlag) {
					playFade(speed);
				} else if (stopFlag) {
					stop();
				} else if (stopFadeFlag) {
					stopFade(speed);
				} else if (releaseFlg) {
					release();
				} else {
					Log.print("until play signale");
					Log.printSoundLog("until play signale");
				}
				
				/// スレッドのフラグをすべて下ろす
				threadFlgAllReset();
				
				Log.print("loadThread : vol %d", getVolume());
				Log.printSoundLog("loadThread : vol %d", getVolume());

//					dumpChunkAll();
			} else {
				Log.print("Error load sound! %*s", filename);
				Log.printSoundLog("Error load sound! %*s", filename);
					stopFlag = true;
					playFlag = false;
					playFadeFlag = false;
			}
//printf("end load end\n");
		} catch {
			// 何らかのエラー
			Log.print("Exception Sound#loadthread");
			Log.printSoundLog("Exception Sound#loadthread");
		}
Log.print("END loadThreadPostExec");
Log.printSoundLog("END loadThreadPostExec");
	}
	
	/// music読込
	private synchronized y4d_result loadMusic(char[] filename) {
		if (isNoSound()) return y4d_result.precondition_error;
		
Log.printSoundLog("START Sound#loadMusic");
		FileSys.TmpFile tmpFile;
		
		if (filename in TMP_FILES) {
			tmpFile = TMP_FILES[filename];
		} else {
			synchronized(m_arcReader) 
			{
Log.printSoundLog("Sound#loadMusic START File load from Archive.");
				try {
					(cast(FileArchiverZip)m_arcReader).DEBUG_PRINT = true;
					tmpFile = FileSys.getTmpFile(filename, m_arcReader);
				} catch (Object o) {
					(cast(FileArchiverZip)m_arcReader).DEBUG_PRINT = false;
					printf("Sound#loadMusic Archve load erro : %*s", o.toString());
					Log.printSoundLog("Archive load error : %s", o.toString());
					return y4d_result.file_not_found;
				}
				(cast(FileArchiverZip)m_arcReader).DEBUG_PRINT = false;
Log.printSoundLog("Sound#loadMusic END File load from Archive.");
			}
		}
		
		char[] f = tmpFile.getFileName();
Log.printLook("Sound#loadMusic filename:%s isMade:%s exist:%s", filename, tmpFile.isMade(), FileSys.isExist(f));
Log.printSoundLog("Sound#loadMusic filename:%s isMade:%s exist:%s", filename, tmpFile.isMade(), FileSys.isExist(f));
		if (f !is null) {
			music = Mix_LoadMUS(std.string.toStringz(f));
		} else {
			Log.printLook("load music error");
			Log.printSoundLog("load music error : %s", filename);
			return y4d_result.file_not_found; // file not found
		}

//		music = Mix_LoadMUS_RW(rwops,1);
//	この関数なんでないかなーヽ(´Д`)ノ

		if (!music) {
			Log.print("load music error2");
			Log.printSoundLog("load music error2 : %s", filename);
			return y4d_result.SDL_error;
		}
		
		// OK だったので、作ったテンプファイルを保存
		TMP_FILES[filename] = tmpFile;

Log.printSoundLog("START Sound#loadMusic");
		
		return y4d_result.no_error;
	}

	private y4d_result loadChunk(char[] name)
	/**
		使用するチャンクナンバーはお任せバージョン
		空きチャンネルを自動的に使用する。
	*/
	{
		return loadChunk(name,0);
	}

	private y4d_result loadChunk(void[] mem)
	/**
		使用するチャンクナンバーはお任せバージョン
		空きチャンネルを自動的に使用する。
	*/
	{
		return loadChunk(mem,0);
	}

	/// チャンクに読み込む。ch=0なら自動で空きを探す 1～8
	private synchronized  y4d_result loadChunk(char[] name, int ch) {
		if (isNoSound()) return y4d_result.precondition_error;
		// ここのファイル読込はかなり時間が掛かる可能性がある
//printf("start loadChunk:%*s\n", name);		

		if( FileSys.isRealExist(name) !is null ) {
			// ファイルから読み込み
			FileSys.RWops rw = FileSys.readRW(name);
			if (rw.rwops)
			{
				chunk = Mix_LoadWAV_RW(rw.rwops,1);
			} else if (rw.filename) {
				// SDL側で予期する文字コードはマルチバイト文字列
				chunk = Mix_LoadWAV(std.string.toStringz(toMBS(rw.filename)));
			} else {
				return y4d_result.file_not_found;	// file not found
			}
			if (!chunk) {
				return y4d_result.file_read_error;	// 読み込みに失敗してやんの
			}
			chunkChannel = ch;
			return y4d_result.no_error;

		}
		// アーカイバにアクセスできるのはただかだ１スレッド
		synchronized (m_arcReader) {
			void[] data = m_arcReader.read(name);
			FileSys.RWops rw;
			rw.data = data;
			rw.rwops = SDL_RWFromMem(&data[0],data.length);
			if (rw.rwops is null) {
				return y4d_result.file_not_found;	// file not found
			}
	
			chunk = Mix_LoadWAV_RW(rw.rwops,1);
			if (!chunk) {
				return y4d_result.file_read_error;	// 読み込みに失敗してやんの
			}
			chunkChannel = ch;
//printf("end loadChunk:%*s\n", name);		
			return y4d_result.no_error;
		}
	}
	
	/// チャンクに読み込む。ch=0なら自動で空きを探す 1～8
	private synchronized y4d_result loadChunk(void[] mem, int ch) {
		if (isNoSound()) return y4d_result.precondition_error;
//printf("start loadChunk:%d\n", ch);		
		char[] filename;
		FileSys.RWops rw;
		rw.data = mem;
		rw.rwops = SDL_RWFromMem(&mem[0], mem.length);

		if (rw.rwops)
		{
			chunk = Mix_LoadWAV_RW(rw.rwops,1);
		} else if (rw.filename) {
			chunk = Mix_LoadWAV(std.string.toStringz(rw.filename));
		} else {
			return y4d_result.file_not_found;	// file not found
		}
		// TODO TEST CODE
		delete mem;
		
		if (!chunk) {
			return y4d_result.file_read_error;	// 読み込みに失敗してやんの
		}
		chunkChannel = ch;
//printf("end loadChunk:%d\n", ch);		
		return y4d_result.no_error;
	}	

	/// BGMを再生する
	private y4d_result playMusic() {
		if (isNoSound()) return y4d_result.precondition_error;
		Log.printSoundLog("Sound#playMusic START");
		try {
			return getManager().playMusic(this, loopflag);
		} catch {
			Log.printSoundLog("Sound#playMusic ERROR");
		} finally {
			Log.printSoundLog("Sound#playMusic END");
		}
		
//		getManager().music = this; // チャンネルの占拠を明示
//		return Mix_PlayMusic(music, loopflag)
//			? y4d_result.SDL_error:y4d_result.no_error;
	}

	/// BGMをフェードインして再生する
	private y4d_result playMusicFade(int speed) {
		if (isNoSound()) return y4d_result.precondition_error;
		return getManager().playMusicFade(this, loopflag, speed);

//		getManager().music = this; // チャンネルの占拠を明示
//		return Mix_FadeInMusic(music, loopflag , speed)
//			? y4d_result.SDL_error:y4d_result.no_error;
	}

	// 音を再生する
	private y4d_result playChunk() {
		if (isNoSound()) return y4d_result.precondition_error;
		return getManager().playChunk(this, chunkChannel, loopflag);

//		if (chunkChannel == 0){
//			int ch = getManager().getEmptyChunk(this);
//			//	↑このチャンクが使用中であることはここで予約が入る
//
//printf("play chunck %d\n", ch);
//			
//			return Mix_PlayChannel(ch , chunk , loopflag) == -1
//				? y4d_result.SDL_error:y4d_result.no_error;
//		} else {
//			// チャンネルの占拠を明示
//			getManager().chunk[chunkChannel-1] = this;
//
//			return Mix_PlayChannel(chunkChannel-1, chunk, loopflag) == -1
//				? y4d_result.SDL_error:y4d_result.no_error;
//		}
	}

	// 音をフェードインで再生する
	private y4d_result playChunkFade(int speed) {
		if (isNoSound()) return y4d_result.precondition_error;
		return getManager().playChunkFade(this,chunkChannel,loopflag, speed);
		
//		if (chunkChannel == 0){
//			int ch = getManager().getEmptyChunk(this);
//			//	↑このチャンクが使用中であることはここで予約が入る
//
////printf("play chunck Fade %d\n", ch);
//
//			return Mix_FadeInChannel(ch,chunk,loopflag,speed) == -1
//				? y4d_result.SDL_error:y4d_result.no_error;
//		} else {
//			// チャンネルの占拠を明示
//			getManager().chunk[chunkChannel-1] = this;
//
//			return Mix_FadeInChannel(chunkChannel-1,chunk,loopflag,speed) == -1
//				? y4d_result.SDL_error:y4d_result.no_error;
//		}
	}
	
	/// このクラスの初期化処理
	private y4d_result		init()	//	成功:0	初期化失敗:非0
	{
		fadeTimer = new FixTimer;
		SoundConfig c = getConfig();
		if (c.init()) return y4d_result.precondition_error; // error

		int audio_rate = c.audio_rate; // = 44100;
		ushort audio_format = c.audio_format; // = AUDIO_S16;
		int audio_channels = c.audio_channels; //  = 2;
		int audio_buffers = c.audio_buffers; // = 4096;

		if (Mix_OpenAudio(audio_rate,audio_format,
			audio_channels,audio_buffers) < 0) {
		// sound deviceが無いのかbufferがあふれたのかは不明なので
		//	サウンドを使えなくすることはない
			return y4d_result.precondition_error;
		}
		//	どうせ高目で設定しても、その通りの能力を
		//	デバイスに要求するわけではないので．．
	//	  Mix_QuerySpec(&audio_rate, &audio_format, &audio_channels);
	//	↑最終的な結果は、これで取得できる
		return y4d_result.no_error;
	}

	// あるチャンクを再生しているインスタンスを把握しておく必要がある
	private static class SoundChunkManager
	{
		private Sound m_music;		//	musicチャンネルを再生中のやつを入れておく
		private Sound[8] m_chunk;	//	chunkチャンネルを再生中のやつを入れておく
		
		package void releseAllFinal() {
			try {
				Sound.safeRelease(m_music);
				for(int i=0;i<8;++i){
					Sound.safeRelease(m_chunk[i]);
				}
			} catch {
			}
		}
		
		/// 全チャンクを取得する
		package  synchronized Sound[] chunk() {		
			return m_chunk;
		}
		/// ミュージックチャンクを取得する
		package Sound music() {
			return m_music;
		}
		
		//	現在再生していないチャンクを探す
		private synchronized int  getEmptyChunk(Sound s){
			if (isNoSound() || s is null) {
				return y4d_result.precondition_error;
			}
			for(int i=0;i<8;++i) {
				if ( !Mix_Playing(i) ) {
					m_chunk[i] = s;
					return i;
				}
			}
			
			// 空きがない！　せめてフェードアウトしてるものがあれば...
			for(int i=0;i<8;++i) {
				if ( Mix_FadingChannel(i) == MIX_FADING_OUT ) {
					m_chunk[i] = s;
					return i;
				}
			}

			// 空きがない！　じゃあ、ポーズしている奴だ
			for(int i=0;i<8;++i) {
				if ( Mix_Paused(i) ) {
					m_chunk[i] = s;
					return i;
				}
			}
			
			Log.printError("Chanel Full! chanel 0 chanceled!");
			Log.printSoundLog("Chanel Full! chanel 0 chanceled!");
			return 0; // 空きが無いので0番を潰すかぁ..(´Д`)
		}

		/// ミュージックチャンクを設定する
		public synchronized Sound music(Sound sound) {		
			return m_music = sound;
		}

		/// 再生中かどうか
		public synchronized bool isPlay(Sound sound) {
			if (sound is null) {
				return false;
			}
			if (m_music is sound && Mix_PlayingMusic()) {
				return true;
			}
			for(int i = 0;i < 8; ++i){
				if (m_chunk[i] is sound && Mix_Playing(i)) {
					return true;
				}
			}
			// ロードしていないかロード中
			return false;
		}
		
		// 音を再生する
		public synchronized y4d_result playChunk(Sound chunk, int chunkChannel, int loopflag) {
			if (chunk is null) {
				return y4d_result.precondition_error;
			}
			if (chunkChannel == 0){
				int ch = getEmptyChunk(chunk);
				//	↑このチャンクが使用中であることはここで予約が入る
//printf("play chunck %d\n", ch);
				return Mix_PlayChannel(ch , chunk.getChunk(), loopflag) == -1
					? y4d_result.SDL_error:y4d_result.no_error;
			} else {
				// チャンネルの占拠を明示
				m_chunk[chunkChannel-1] = chunk;
	
				return Mix_PlayChannel(chunkChannel-1, chunk.getChunk(), loopflag) == -1
					? y4d_result.SDL_error:y4d_result.no_error;
			}
		}

		// 音をフェードインで再生する
		public synchronized y4d_result playChunkFade(Sound chunk, int chunkChannel, int loopflag, int speed) {
			if (chunk is null) {
				return y4d_result.precondition_error;
			}
			if (chunkChannel == 0){
				int ch = getEmptyChunk(chunk);
				//	↑このチャンクが使用中であることはここで予約が入る
//printf("play chunck Fade %d\n", ch);
				return Mix_FadeInChannel(ch,chunk.getChunk(),loopflag,speed) == -1
					? y4d_result.SDL_error:y4d_result.no_error;
			} else {
				// チャンネルの占拠を明示
				getManager().chunk[chunkChannel-1] = chunk;
	
				return Mix_FadeInChannel(chunkChannel-1,chunk.getChunk(),loopflag,speed) == -1
					? y4d_result.SDL_error:y4d_result.no_error;
			}
		}
	
		/// BGMを再生する
		public synchronized y4d_result playMusic(Sound sound, int loopflag) {
			if (sound is null) {
				return y4d_result.precondition_error;
			}
//printf("play music=============================================\n");
			m_music = sound; // チャンネルの占拠を明示
			return Mix_PlayMusic(m_music.getMusic(), loopflag)
				? y4d_result.SDL_error:y4d_result.no_error;
		}

		/// BGMをフェードインで再生する
		public synchronized y4d_result playMusicFade(Sound sound, int loopflag, int speed) {
			if (sound is null) {
				return y4d_result.precondition_error;
			}
			m_music = sound; // チャンネルの占拠を明示
			try {
			return Mix_FadeInMusic(m_music.getMusic(), loopflag , speed)
				? y4d_result.SDL_error:y4d_result.no_error;
			} catch {
				printf("error playMusicFade\n");
				return y4d_result.SDL_error;
			}
		}
		
		//	現在再生しているチャンクを停止させる
		public synchronized y4d_result stop(Sound s) {
			if (s is null) {
				return y4d_result.precondition_error;
			}
			int hr;
			if (s is m_music) {
				Mix_PauseMusic(); // Mix_HaltMusic();
				m_music = null;
				return y4d_result.no_error;
			}
			for(int i=0;i<8;++i){
				if (s is m_chunk[i]) {
//printf("stop chunck %d\n", i);
					Mix_Pause(i); // Mix_HaltChannel(i);
					m_chunk[i] = null;
					return y4d_result.no_error;
				}
			}
//printf("mgr stop end\n");
			return y4d_result.no_error;
		} 

		//	現在再生しているチャンクを停止させる
		public synchronized y4d_result pause(Sound s) {
			if (s is null) {
				return y4d_result.precondition_error;
			}
			int hr;
			if (s is m_music) {
				Mix_PauseMusic(); // Mix_HaltMusic();
				return y4d_result.no_error;
			}
			for(int i=0;i<8;++i){
				if (s is m_chunk[i]) {
//printf("stop chunck %d\n", i);
					Mix_Pause(i); // Mix_HaltChannel(i);
					return y4d_result.no_error;
				}
			}
			return y4d_result.no_error;
		} 
		
		//	現在再生しているチャンクを一時停止させる
		public synchronized y4d_result resume(Sound s) {
			if (s is null) {
				return y4d_result.precondition_error;
			}
			int hr;
			if (s is m_music) {
				Mix_ResumeMusic(); // Mix_HaltMusic();
				return y4d_result.no_error;
			}
			for(int i=0;i<8;++i){
				if (s is m_chunk[i]) {
					Mix_Resume(i); // Mix_HaltChannel(i);
				}
				return y4d_result.no_error;
			}
			return y4d_result.no_error;
		}
		
		//	現在再生しているチャンクを停止させる
		public synchronized y4d_result stopFade(Sound s , int speed) {
			if (s is null) {
				return y4d_result.precondition_error;
			}
			int hr;
			if (s is m_music) {
				hr = Mix_FadeOutMusic(speed);
				m_music = null;
				return hr ? y4d_result.SDL_error:y4d_result.no_error;
			}
			for(int i=0;i<8;++i){
				if (s is m_chunk[i]) {
//printf("stop fade chunck %d\n", i);
					hr = Mix_FadeOutChannel(i,speed);
					m_chunk[i] = null;
					return hr ? y4d_result.SDL_error:y4d_result.no_error;
				}
			}
			return hr ? y4d_result.SDL_error:y4d_result.no_error;
		}
		
		/// 全チャンクを停止する
		public synchronized void fadeChunkAll(int speed){
			for(int i=0;i<8;++i) {
				m_chunk[i] = null;
				Mix_FadeOutChannel(i,speed);
			}
		}
		
		private bool stopingAllChunk;

		/// 全チャンクを停止する
		public void stopAllChunk(){
//SDL_Delay(2000);
			for(int i = 0; i < 8; ++i) {
				if (m_chunk[i] !is null) {
					m_chunk[i].stop();
				}
			}
		}
		
		/// BGMを停止します
		public y4d_result stopMusic(){
			if (m_music !is null) {
				return m_music.stop();
			}
			return y4d_result.no_error;
		}
	}

}

