/******************************************************************************
Ootake
・Vista以降だとDirectSoundの処理が遅く、バッファが多く必要なため、XAudio2を利
  用。まだこれで改善できるかどうかはわからない。


  ＜まだほとんどできていなく開発中。v2.17＞


・Windows2000や9xでは利用できないため、その場合、DirectSoundを使う。

Copyright(C)2006-2009 Kitao Nakamura.
	改造版・後継版を公開なさるときは必ずソースコードを添付してください。
	その際に事後でかまいませんので、ひとことお知らせいただけると幸いです。
	商的な利用は禁じます。
	あとは「GNU General Public License(一般公衆利用許諾契約書)」に準じます。

*******************************************************************************/
#define _WIN32_DCOM //v2.19追加

#include <stdio.h>
#include <xaudio2.h>
#include "AudioOutX.h"
#include "WinMain.h"
#include "Printf.h"
#include "App.h"

//Kitao更新。ソフトでPSG,ADPCM,CDDAをミックスして音を鳴らした場合、ダイナミックレンジを1/3に下げなくてはいけないので音質が大きく落ちてしまう。そのため各Ch専用のバッファで鳴らすようにした。
static	IXAudio2*				_pXA	= NULL;
static	IXAudio2MasteringVoice*	_pMV	= NULL; //マスタリングボイス
static	IXAudio2SourceVoice*	_pSV1	= NULL; //PSG再生用
static	IXAudio2SourceVoice*	_pSV2	= NULL; //ADPCM再生用
static	IXAudio2SourceVoice*	_pSV3	= NULL; //CDDA再生用
static	XAUDIO2_BUFFER			_SV1BUF = {0};
static	XAUDIO2_BUFFER			_SV2BUF = {0};
static	XAUDIO2_BUFFER			_SV3BUF = {0};
static	BYTE*					_pBuf1;
static	BYTE*					_pBuf2;
static	BYTE*					_pBuf3;

static	LPVOID					_LpvPtr1A; //Kitao追加。PSG再生用。少しでも速度アップするため配列にせずPSG,ADPCM,CDDA用それぞれの変数を用意。
static	DWORD					_DwBytes1A; 
static	LPVOID					_LpvPtr1B;
static	DWORD					_DwBytes1B;
static	LPVOID					_LpvPtr2A; //Kitao追加。ADPCM再生用
static	DWORD					_DwBytes2A; 
static	LPVOID					_LpvPtr2B;
static	DWORD					_DwBytes2B;
static	LPVOID					_LpvPtr3A; //Kitao追加。CDDA再生用
static	DWORD					_DwBytes3A; 
static	LPVOID					_LpvPtr3B;
static	DWORD					_DwBytes3B;

//Kitao追加。WAVファイル出力用
static	FILE*					_fpOutputWAV = NULL;
static  Sint32					_OutputWavMode;
static	DWORD					_OutputWavFileSize;
static  BOOL					_bOutputWavExecute = FALSE;
static	Sint32					_OutputWavWaitFinish;

static	DWORD				_dwBufSize;
static	HANDLE				_hThread;
static	DWORD				_dwThreadID;

static	volatile BOOL		_bPlay; //v2.05更新。volatileに。
static	volatile BOOL		_bPlayStart; //Kitao追加。初回再生時はバッファ全体ぶんの時間をウェイトする（PSGキューに値が用意される時間を待つ）ようにした。
static	volatile BOOL		_bThreadEnd; //Kitao追加。スレッドを終了したいときTRUEにして知らせる。

static	Sint16*				_pAudioBuf1 = NULL; //Kitao追加。PSG再生用
static	Sint16*				_pAudioBuf2 = NULL; //Kitao追加。ADPCM再生用
static	Sint16*				_pAudioBuf3 = NULL; //Kitao追加。CDDA再生用
static	Sint16*				_pAudioBuf0 = NULL; //Kitao追加。Output時の波形合成用
static 	void				(*_pCallBack)(int ch, Sint16* pBuf, Sint32 nSamples) = NULL; //Kitao更新。ch(チャンネルナンバー)を追加

static	BOOL				_bAudioInit = FALSE;


/*-----------------------------------------------------------------------------
	[write_streaming_buffer]
		DirectSoundBuffer に出力する音声データを書き込みます。
-----------------------------------------------------------------------------*/
//Kitao更新。PSG専用。メモリのロックは、開始時に１度だけ行うようにし、高速化した。引数を少なくし、戻り値を省略。v1.02
static inline void
write_streaming_buffer_1(
	DWORD	dwOffset)
{
	CopyMemory((LPVOID)((DWORD)_LpvPtr1A + dwOffset), (LPBYTE)_pAudioBuf1, _DwBytes1A);
	if (_LpvPtr1B != NULL)
		CopyMemory((LPVOID)((DWORD)_LpvPtr1B + dwOffset), (LPBYTE)(_pAudioBuf1+_DwBytes1A), _DwBytes1B);
}

//Kitao追加。ADPCM専用。
static inline void
write_streaming_buffer_2(
	DWORD	dwOffset)
{
	CopyMemory((LPVOID)((DWORD)_LpvPtr2A + dwOffset), (LPBYTE)_pAudioBuf2, _DwBytes2A);
	if (_LpvPtr2B != NULL)
		CopyMemory((LPVOID)((DWORD)_LpvPtr2B + dwOffset), (LPBYTE)(_pAudioBuf2+_DwBytes2A), _DwBytes2B);
}

//Kitao更新。CDDA専用。
static inline void
write_streaming_buffer_3(
	DWORD	dwOffset)
{
	CopyMemory((LPVOID)((DWORD)_LpvPtr3A + dwOffset), (LPBYTE)_pAudioBuf3, _DwBytes3A);
	if (_LpvPtr3B != NULL)
		CopyMemory((LPVOID)((DWORD)_LpvPtr3B + dwOffset), (LPBYTE)(_pAudioBuf3+_DwBytes3A), _DwBytes3B);
}

//Kitao追加
static DWORD
nosound_check(
	Sint16*		pAudioBuf)
{
	int		i,j;
	DWORD	d = _dwBufSize;

	i = 0;
	
	if (_OutputWavWaitFinish < 4)
	{
		if (_OutputWavWaitFinish < 3)
		{	//ポーズ復帰後の無音状態が終わるまで待つ
			while (i < (int)_dwBufSize/2) //16ビット単位なので/2
			{
				if (*(pAudioBuf+i) != 0)
					break; //音があればbreak;
				else
					d -= 2;
				i++;
			}
			if (d != 0)
				_OutputWavWaitFinish = 3;
			else
			{	//バッファの最後まで無音だった場合は、無音のときに録音を開始したと推定
				if (++_OutputWavWaitFinish == 3) //誤認を防ぐため、３バッファぶんまで無音かどうか確かめる。※多く確かめすぎても逆に誤認することがある。
					_OutputWavWaitFinish = 4; //次に音が発生したらすぐに録音開始。
				return 0;
			}
		}
		//現在鳴っている曲が終わるまで待つ(_OutputWavWaitFinish == 3)
		while (i < (int)_dwBufSize/2) //16ビット単位なので/2
		{
			if (d < 256) //256…小さすぎると曲中で無音判定になってしまう場合がる。大きすぎると、よく使われるhes用再生プログラムの曲切り替えで無音判定にならない。
				return 0;
			for (j=0; j<256/2; j++) //16ビット単位なので/2
				if	(*(pAudioBuf+i+j) != 0)
					break;
			if (j >= 256/2)
				break; //256バイトが連続して00なら無音と推定しbreak;
			else
				d -= 4; //ステレオなので4バイトずつ進めてチェック
			i++;
			i++;
		}
		if (d != 0)
			_OutputWavWaitFinish = 4;
		else
			return 0;
	}
	
	//音が鳴り始めるまで待つ。鳴り始めていない場合d=0。(_OutputWavWaitFinish == 4)
	while (i < (int)_dwBufSize/2) //16ビット単位なので/2
	{
		if (*(pAudioBuf+i) != 0)
			break; //音があればbreak;
		else
			d -= 2;
		i++;
	}
	if (d != 0)
	{
		PRINTF("Start Output \"%s\".", APP_GetWavFileName());
		_OutputWavWaitFinish = 5; //録音スタート
	}

	return d;
}


/*-----------------------------------------------------------------------------
	[playback_thread]
		サウンドバッファの更新を行なうスレッドです。 
-----------------------------------------------------------------------------*/
static DWORD WINAPI
playback_thread(
	LPVOID	param)
{
	int		i;
	Sint32	a;
	DWORD	d;
//	DWORD	n;
	XAUDIO2_VOICE_STATE		state;

	CoInitializeEx(NULL, COINIT_MULTITHREADED); //v2.19追加。それぞれのスレッドでCoInitializeExしないと安定しない環境もあるかもしれない。(COINIT_MULTITHREADEDの場合、COMを使うスレッドそれぞれでCoInitializeExすることをMSが推奨)

	while (TRUE)
	{
		if (_bThreadEnd)
			ExitThread(TRUE);

		//PSGチャンネルのバッファをチェックし、足りない場合追加する。
		_pSV1->GetState(&state);
		if (state.BuffersQueued < _dwBufSize - 1)
		{
			//追加処理
		}	

		//ADPCMチャンネルのバッファをチェックし、足りない場合追加する。
		_pSV2->GetState(&state);
		if (state.BuffersQueued < _dwBufSize - 1)
		{
			//追加処理
		}	
	
		//CDDAチャンネルのバッファをチェックし、足りない場合追加する。
		_pSV3->GetState(&state);
		if (state.BuffersQueued < _dwBufSize - 1)
		{
			//追加処理
		}	

		//Kitao追加。WAVファイルへ出力処理
		if (_fpOutputWAV != NULL)
		{
			_bOutputWavExecute = TRUE;//排他処理用
			switch (_OutputWavMode)
			{
				case 1: //PSGチャンネルのみ
					if (_OutputWavWaitFinish < 5) //再生開始時に無音状態の場合、音が鳴るまで書き込まない。
					{
						d = nosound_check(_pAudioBuf1);
						if (d > 0)
							fwrite(_pAudioBuf1+(_dwBufSize-d)/2, d, 1, _fpOutputWAV);
					}
					else
					{
						d = _dwBufSize;
						fwrite(_pAudioBuf1, d, 1, _fpOutputWAV);
					}
					break;
				case 2: //ADPCMチャンネルのみ
					if (_OutputWavWaitFinish < 5) //再生開始時に無音状態の場合、音が鳴るまで書き込まない。
					{
						d = nosound_check(_pAudioBuf2);
						if (d > 0)
							fwrite(_pAudioBuf2+(_dwBufSize-d)/2, d, 1, _fpOutputWAV);
					}
					else
					{
						d = _dwBufSize;
						fwrite(_pAudioBuf2, d, 1, _fpOutputWAV);
					}
					break;
				case 3: //CDDAチャンネルのみ
					if (_OutputWavWaitFinish < 5) //再生開始時に無音状態の場合、音が鳴るまで書き込まない。
					{
						d = nosound_check(_pAudioBuf3);
						if (d > 0)
							fwrite(_pAudioBuf3+(_dwBufSize-d)/2, d, 1, _fpOutputWAV);
					}
					else
					{
						d = _dwBufSize;
						fwrite(_pAudioBuf3, d, 1, _fpOutputWAV);
					}
					break;
				case 12: //PSG+ADPCMチャンネル(ドラゴンスレイヤー英雄伝説などで必須)
					for (i = 0; i < (int)_dwBufSize/2; i++) //16ビット単位なので/2
					{
						a = *(_pAudioBuf1+i) + *(_pAudioBuf2+i);
						if (a < -32768) //サチレーション
						{
							PRINTF("WAV Output Saturation. (Recommend Volume Down)%d", a);
							a = -32768;
						}
						if (a > 32767) //サチレーション
						{
							PRINTF("WAV Output Saturation. (Recommend Volume Down)%d", a);
							a = 32767;
						}
						*(_pAudioBuf0+i) = (Sint16)a;
					}
					if (_OutputWavWaitFinish < 5) //再生開始時に無音状態の場合、音が鳴るまで書き込まない。
					{
						d = nosound_check(_pAudioBuf0);
						if (d > 0)
							fwrite(_pAudioBuf0+(_dwBufSize-d)/2, d, 1, _fpOutputWAV);
					}
					else
					{
						d = _dwBufSize;
						fwrite(_pAudioBuf0, d, 1, _fpOutputWAV);
					}
					break;
				case 123: //PSG+ADPCM+CD全て(各チャンネルの音量を落とさなくちゃいけないので音質は下がる)
					for (i = 0; i < (int)_dwBufSize/2; i++) //16ビット単位なので/2
					{
						a = *(_pAudioBuf1+i) + *(_pAudioBuf2+i) + *(_pAudioBuf3+i);
						if (a < -32768) //サチレーション
						{
							PRINTF("WAV Output Saturation. (Recommend Volume Down)%d", a);
							a = -32768;
						}
						if (a > 32767) //サチレーション
						{
							PRINTF("WAV Output Saturation. (Recommend Volume Down)%d", a);
							a = 32767;
						}
						*(_pAudioBuf0+i) = (Sint16)a;
					}
					if (_OutputWavWaitFinish < 5) //再生開始時に無音状態の場合、音が鳴るまで書き込まない。
					{
						d = nosound_check(_pAudioBuf0);
						if (d > 0)
							fwrite(_pAudioBuf0+(_dwBufSize-d)/2, d, 1, _fpOutputWAV);
					}
					else
					{
						d = _dwBufSize;
						fwrite(_pAudioBuf0, d, 1, _fpOutputWAV);
					}
					break;
				default: //通常はありえないが念のため
					d = _dwBufSize;
					fwrite(_pAudioBuf1, d, 1, _fpOutputWAV);
					break;
			}
			_OutputWavFileSize += d;
			_bOutputWavExecute = FALSE;//排他処理用
			if (_OutputWavFileSize > 0xFFF00000) //バッファオーバーになりそうなら
				APP_OutputWavEnd();
		}
		
//		if ((_bPlay)&&(n == AOUTX_BUFFERRATE)) //初回は最終地点まで待ったら、ウェイト解除。
//			_bPlayStart = FALSE;

		//WaitForSingleObject(Context.hBufferEndEvent, INFINITE); //次のバッファが演奏終了するまで待機(CPUパワー節約)
	}

	CoUninitialize(); //v2.19追加

	return 0;
}


/*-----------------------------------------------------------------------------
	Deinit DirectSound

-----------------------------------------------------------------------------*/
#define SAFE_RELEASE(p)		{ if (p) {(p)->Release(); (p) = NULL;} }
static BOOL
d_deinit() 	//Kitao更新。3chぶんのリソースを開放
{
	if (!_bAudioInit)
		return FALSE;

	_bPlay = FALSE;

	//音を止めてスレッドの終了を待つ 
	//
	_bThreadEnd = TRUE;
	WaitForSingleObject(_hThread, INFINITE);

	CloseHandle(_hThread);

	if (_pSV1 != NULL) _pSV1 = NULL;
	if (_pSV2 != NULL) _pSV2 = NULL;
	if (_pSV3 != NULL) _pSV3 = NULL;

	_dwBufSize = 0;

	return TRUE;
}


/*-----------------------------------------------------------------------------
	Initialize DirectSound

-----------------------------------------------------------------------------*/
static BOOL
d_init(
	Sint32	soundType, //Kitao追加。1…通常(ソフトバッファ＆ミキシング。環境にもよるかもしれないがベストな音質)。2…ハードバッファ＆ソフトミキシング(それなりにいい音が鳴る)。
	HWND	hWnd,
	WORD	nChannels,
	WORD	nSamplesPerSec,
	WORD	wBitsPerSample,
	DWORD	dwBufSize)			// in bytes
{
	WAVEFORMATEX	waveFormat;

	//XAudio2を初期化
	if (FAILED(XAudio2Create(&_pXA, 0, XAUDIO2_DEFAULT_PROCESSOR)))
	{
		MessageBox(WINMAIN_GetHwnd(), "ERROR: XAUDIO2::XAudio2Create() failed.    ", "Ootake", MB_OK);
		return FALSE;
	}

	//マスタリングボイスを作成
	if (FAILED(_pXA->CreateMasteringVoice(&_pMV, XAUDIO2_DEFAULT_CHANNELS, 44100, 0, NULL)))
	{
		MessageBox(WINMAIN_GetHwnd(), "ERROR: XAUDIO2::CreateMasteringVoice() failed.    ", "Ootake", MB_OK);
		return FALSE;
	}

	//Waveフォーマットを設定
	ZeroMemory(&waveFormat, sizeof(WAVEFORMATEX));
	waveFormat.wFormatTag		= WAVE_FORMAT_PCM;
	waveFormat.nChannels		= nChannels; 
	waveFormat.nSamplesPerSec	= nSamplesPerSec;
	waveFormat.nAvgBytesPerSec	= waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.nBlockAlign		= waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
	waveFormat.wBitsPerSample	= wBitsPerSample;

	//ソースボイスを作成。3chぶん作成。
	if (FAILED(_pXA->CreateSourceVoice(&_pSV1, &waveFormat, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL)))
	{
		MessageBox(WINMAIN_GetHwnd(), "ERROR: XAUDIO2::CreateSourceVoice() failed.    ", "Ootake", MB_OK);
		return FALSE;
	}
	if (FAILED(_pXA->CreateSourceVoice(&_pSV2, &waveFormat, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL)))
	{
		MessageBox(WINMAIN_GetHwnd(), "ERROR: XAUDIO2::CreateSourceVoice() failed.    ", "Ootake", MB_OK);
		return FALSE;
	}
	if (FAILED(_pXA->CreateSourceVoice(&_pSV3, &waveFormat, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL)))
	{
		MessageBox(WINMAIN_GetHwnd(), "ERROR: XAUDIO2::CreateSourceVoice() failed.    ", "Ootake", MB_OK);
		return FALSE;
	}

	//バッファを設定。3chぶん作成。
	_SV1BUF.AudioBytes	= dwBufSize * AOUTX_BUFFERRATE;
	_SV1BUF.pAudioData	= _pBuf1;
	_SV1BUF.Flags		= XAUDIO2_END_OF_STREAM;
	_SV2BUF.AudioBytes	= dwBufSize * AOUTX_BUFFERRATE;
	_SV2BUF.pAudioData	= _pBuf2;
	_SV2BUF.Flags		= XAUDIO2_END_OF_STREAM;
	_SV3BUF.AudioBytes	= dwBufSize * AOUTX_BUFFERRATE;
	_SV3BUF.pAudioData	= _pBuf3;
	_SV3BUF.Flags		= XAUDIO2_END_OF_STREAM;

	// オーディオバッファを確保する 
	_pAudioBuf1 = (Sint16*)GlobalAlloc(GMEM_FIXED, dwBufSize);
	_pAudioBuf2 = (Sint16*)GlobalAlloc(GMEM_FIXED, dwBufSize);
	_pAudioBuf3 = (Sint16*)GlobalAlloc(GMEM_FIXED, dwBufSize);
	_pAudioBuf0 = (Sint16*)GlobalAlloc(GMEM_FIXED, dwBufSize);
	if (_pAudioBuf1 == NULL)
	{
		d_deinit();
		return FALSE;
	}

	_dwBufSize = dwBufSize;
	_bPlay = FALSE;

	// スレッドを開始する前にオーディオ初期化完了フラグをたてる。
	// [2004.04.28] fixed
	_bAudioInit = TRUE;

	// スレッドを作成し実行する 
	_bThreadEnd = FALSE;
	_hThread = CreateThread(NULL, 0, playback_thread, NULL, 0, &_dwThreadID);
	if (_hThread == NULL)
	{
		d_deinit();
		_bAudioInit = FALSE;
		return FALSE;
	}

	return TRUE;
}


/*-----------------------------------------------------------------------------
	[Init]
		
-----------------------------------------------------------------------------*/
BOOL
AOUTX_Init(
	Sint32		soundType,	//Kitao追加
	Uint32		bufSize,	// in samples 
	Uint32		sampleRate,
	void		(*pCallBack)(int ch, Sint16* pBuf, Sint32 nSamples)) //Kitao更新。ch(チャンネルナンバー)を追加
{
	if (d_init(soundType, WINMAIN_GetHwnd(), 2, (WORD)sampleRate, 16, (DWORD)bufSize*2*2))
	{
		_pCallBack = pCallBack;
		return TRUE;
	}

	return FALSE;
}


/*-----------------------------------------------------------------------------
	[Play]
		
-----------------------------------------------------------------------------*/
void
AOUTX_Play(
	BOOL	bPlay)
{
	if (!_bAudioInit)
		return;

	_bPlay = bPlay;
	if (_bPlay)
	{
//		_pDSB1->Play(0, 0, DSBPLAY_LOOPING);
//		_pDSB2->Play(0, 0, DSBPLAY_LOOPING);
//		_pDSB3->Play(0, 0, DSBPLAY_LOOPING);
	}
}


/*-----------------------------------------------------------------------------
	[Deinit]
		
-----------------------------------------------------------------------------*/
void
AOUTX_Deinit()
{
	if (!_bAudioInit)
		return;

	d_deinit();

	_bAudioInit = FALSE;
}


//Kitao追加
void
AOUTX_SetPlayStart()
{
	int i;

	if (!_bAudioInit)
		return;

	// 再生をストップ
//	_pDSB1->Stop();
//	_pDSB2->Stop();
//	_pDSB3->Stop();

	// Bufferに無音を書き込む 
	ZeroMemory(_pAudioBuf1, _dwBufSize);
	ZeroMemory(_pAudioBuf2, _dwBufSize);
	ZeroMemory(_pAudioBuf3, _dwBufSize);

	// ストリーミングバッファを無音で埋める
	for (i = 0; i<AOUTX_BUFFERRATE; i++) //Kitao追加
	{
		write_streaming_buffer_1(i*_dwBufSize);
		write_streaming_buffer_2(i*_dwBufSize);
		write_streaming_buffer_3(i*_dwBufSize);
	}

	// 再生位置を0にする
//	_pDSB1->SetCurrentPosition(0);
//	_pDSB2->SetCurrentPosition(0);
//	_pDSB3->SetCurrentPosition(0);
	
	_bPlayStart = TRUE;// 初回再生を表す
}

//Kitao追加
void
AOUTX_SetFpOutputWAV(
	FILE*	fp,
	Sint32	mode)
{
	while (_bOutputWavExecute) //書き込み処理中だった場合終わるまで待つ
		Sleep(0);
	_OutputWavMode = mode;
	if (APP_GetOutputWavNext())
		_OutputWavWaitFinish = 0;
	else
		_OutputWavWaitFinish = 4; //通常モードの場合、待ちが終わったことにする。
	_fpOutputWAV = fp; //この瞬間にWAV出力が開始される
}

//Kitao追加
FILE*
AOUTX_GetFpOutputWAV()
{
	return _fpOutputWAV;
}

//Kitao追加
void
AOUTX_SetOutputWavFileSize(
	DWORD	size)
{
	_OutputWavFileSize = size;
}

//Kitao追加
DWORD
AOUTX_GetOutputWavFileSize()
{
	return _OutputWavFileSize;
}

//Kitao追加
Sint32
AOUTX_GetOutputWavWaitFinish()
{
	return _OutputWavWaitFinish;
}

//Kitao追加
Sint32
AOUTX_GetPlay()
{
	return _bPlay;
}
