/***************************************************************************/
/** @file       Sound.cpp
    @brief      
    @author     shom
    @internal
****************************************************************************/

#include "pch_core.h"
#include "Sound.h"


/***************************************************************************
	Sound
****************************************************************************/

CSound::CSound()
:
wvfile(),

m_pDSound(0),

m_p3DListener(0),
m_3DListenerParams()
{
}


CSound::~CSound()
{
	my_safe_rel( m_pDSound );
}


/**
 * DirectSoundの初期化
 * （IDSの取得, 協調レベルの設定,
 *   プライマリバッファの設定, I3Dリスナの取得）
 * 
 * @param HWnd
 * 
 * @return 
 */
HRESULT CSound::Initialize(
	HWND HWnd
	)
{
	my_safe_rel( m_pDSound );	   // 初期化

	///IDirectSoundの取得
	if( FAILED( DirectSoundCreate8(
					NULL,		   // デフォルトデバイスを選択
					&m_pDSound,  // 格納先
					NULL ) ) )
	{
		return DXTRACE_ERR( _T(""), GetLastError() );
	}

	///協調レベルの設定
	if( FAILED( m_pDSound->SetCooperativeLevel(
					HWnd,
					DSSCL_PRIORITY	// 優先強調レベルに設定
					) ) )
	{
		return DXTRACE_ERR( _T(""), GetLastError() );
	}

	///プライマリバッファのフォーマットの設定
	if( FAILED( SetPrimaryBufFormat( m_pDSound ) ) )
	{
		return DXTRACE_ERR( _T(""), E_FAIL );
	}

	///I3Dリスナの取得
	//インタフェースを取得
	if( FAILED( GetI3DListener( &m_p3DListener ) ) )
	{
		return DXTRACE_ERR( _T(""), GetLastError() );
	}

	//パラメータを取得
	m_3DListenerParams.dwSize = sizeof( DS3DLISTENER );
	m_p3DListener->GetAllParameters( &m_3DListenerParams ); 

	return S_OK;
}


/**
 * InitDSoundの内部関数
 * プライマリバッファのフォーマットの設定
 * 
 * @param pDSound
 * 
 * @return 
 */
HRESULT CSound::SetPrimaryBufFormat(
	LPDIRECTSOUND8 pDSound
	)
{
	// === プライマリバッファの取得 ===
	// - バッファ記述子を作成する -
	DSBUFFERDESC BufDesc;
	ZeroMemory( &BufDesc, sizeof(DSBUFFERDESC) );

	// サイズ
	BufDesc.dwSize   = sizeof(DSBUFFERDESC);  // 必ずsizeof(DSBUFFERDESC)
	// 能力を指定するフラグ
	BufDesc.dwFlags   = DSBCAPS_PRIMARYBUFFER;	// プライマリバッファを指定
	// 大きさ
	BufDesc.dwBufferBytes  = 0;							// サウンドデバイスに依存
	// フォーマット
	BufDesc.lpwfxFormat = NULL;				  // プライマリバッファでは指定できない

	// - バッファ記述子を使って、バッファを取得する -
	LPDIRECTSOUNDBUFFER   pPrimaryBuf = NULL;

	if( FAILED( pDSound->CreateSoundBuffer( &BufDesc, &pPrimaryBuf, NULL ) ) )
	{
		return DXTRACE_ERR( _T(""), GetLastError() );
	}


	// === フォーマットを設定 ===
	// - フォーマットを作成 -
	WAVEFORMATEX pcmwf;
	ZeroMemory( &pcmwf, sizeof(WAVEFORMATEX) );

	// WAVEフォーマットの種類
	pcmwf.wFormatTag = WAVE_FORMAT_PCM;
	// チャンネル数
	pcmwf.nChannels = kPRM_BUF_CHANNELS;
	// サンプリングレート（周波数）
	pcmwf.nSamplesPerSec = kPRM_BUF_FREQ;
	// ビットレート: 1サンプル当たりのビット数
	pcmwf.wBitsPerSample = kPRM_BUF_BITRATE;
	// ブロックアライメント
	pcmwf.nBlockAlign = (pcmwf.wBitsPerSample / 8 * pcmwf.nChannels);
	// 1秒間に転送するバイト数
	pcmwf.nAvgBytesPerSec = (pcmwf.nSamplesPerSec * pcmwf.nBlockAlign);

	// - フォーマットをセット -
	HRESULT hr;
	if( FAILED( hr = pPrimaryBuf->SetFormat( &pcmwf ) ) )
	{
		my_safe_rel( pPrimaryBuf );
		return DXTRACE_ERR( _T(""), E_FAIL );
	}

	// - プライマリバッファへのポインタを解放 -
	my_safe_rel( pPrimaryBuf );

	return S_OK;
}


/**
 * InitDSoundの内部関数
 * I3Dリスナの取得
 * 
 * @param pp3DListener
 * 
 * @return 
 */
HRESULT CSound::GetI3DListener(
	LPDIRECTSOUND3DLISTENER* pp3DListener
	)
{
	// === DirectSoundが初期化されていない、引数が無効ならエラー ===
	if( ( pp3DListener == NULL ) || ( m_pDSound == NULL ) )
	{
		return DXTRACE_ERR( _T(""), E_FAIL );
	}


	// === プライマリバッファを取得、同時に3Dコントロールが可能であるか確認 ===
	// - バッファ記述子を設定する -
	DSBUFFERDESC BufDesc;
	ZeroMemory( &BufDesc, sizeof(DSBUFFERDESC) );

	// サイズ
	BufDesc.dwSize     = sizeof(DSBUFFERDESC);
	// 能力を指定するフラグ
	BufDesc.dwFlags = DSBCAPS_PRIMARYBUFFER	 // プライマリバッファを指定
					  | DSBCAPS_CTRL3D;					// 3Dコントロールが可能


	// - バッファを取得する、また3Dコントロールは可能か確かめる -
	LPDIRECTSOUNDBUFFER   pPrimaryBuf = NULL;
	if( FAILED( m_pDSound->CreateSoundBuffer( &BufDesc, &pPrimaryBuf, NULL ) ) )
	{
		return DXTRACE_ERR( _T(""), GetLastError() );
	}


	// === I3Dリスナの取得 ===
	*pp3DListener = NULL;
	if( FAILED( pPrimaryBuf->QueryInterface(
			IID_IDirectSound3DListener,
			(void**)pp3DListener
			) ) )
	{
		my_safe_rel( pPrimaryBuf );
		return DXTRACE_ERR( _T(""), GetLastError() );
	}

	// - プライマリバッファへのポインタを解放 -
	my_safe_rel( pPrimaryBuf );

	return S_OK;
}


/**
 * Waveファイルを元に、セカンダリバッファを作成
 * 
 * @param WvName
 * @param Flags
 * @param dwNofBufs
 * @param pSnd
 */
void CSound::LoadFile(
	LPCWSTR  WvName,
	u32     Flags,
	u32     dwNofBufs,
	theSound* pSnd
	)
{
	// === 開始処理 ===
	// - エラーチェック -
	ASSERT( WvName );
	ASSERT( dwNofBufs > 0 );

	// - 初期化 -
	InitTheSound( pSnd );

	// === Waveファイルの読み込み ===
	// - パスの生成 -
#if 0
	std::string PathWv;
	std::string tmp1( ".\\" ), tmp2( kWV_PATH ), tmp3( "\\" ), tmp4( WvName );
	PathWv = tmp1 + tmp2 + tmp3 + tmp4;
#endif

	// - 読み込み -
	if( FAILED( wvfile.Open( CCAST< LPWSTR >( WvName ), &pSnd->WvData ) ) )
	{
#if 0
		std::string MsgErr;
		std::string tmp1( "Waveファイル『" ), tmp2( WvName ),
		tmp3( "』が見つかりませんでした" );
		MsgErr = tmp1 + tmp2 + tmp3;
		MessageBox( NULL, (LPWSTR)MsgErr.c_str(), _T("Error"), MB_OK );
#endif
		return;
	}

	// === セカンダリバッファの作成 ===
	// - バッファ記述子の作成 -
	DSBUFFERDESC BufDesc;
	ZeroMemory( &BufDesc, sizeof(DSBUFFERDESC) );

	// サイズ
	BufDesc.dwSize  = sizeof(DSBUFFERDESC);
	// 生成時フラグ
	BufDesc.dwFlags = Flags;
	// バッファサイズ
	BufDesc.dwBufferBytes = pSnd->WvData.DataInfo.cksize;  // Waveファイルのデータと同じ
	// RIFFファイルに入っているPCM音声フォーマット
	BufDesc.lpwfxFormat = &(pSnd->WvData.WvFormatEx);

	// - 仮格納バッファの確保 -
	std::vector<LPDIRECTSOUNDBUFFER> DSBufs(dwNofBufs);

	// - バッファの作成 -
	HRESULT hr;
	if( FAILED( hr = m_pDSound->CreateSoundBuffer( &BufDesc,
												   (LPDIRECTSOUNDBUFFER* )&(DSBufs.at(0)), NULL ) ) )
	{
		DEBUG_BREAK();
	}

	// - バッファの複製 -
	std::vector<LPDIRECTSOUNDBUFFER>::iterator it = DSBufs.begin();
	for( ; it != DSBufs.end() ; ++it )
	{
		if( FAILED( m_pDSound->DuplicateSoundBuffer( DSBufs.at(0), &(*it) ) ) )
		{
			DEBUG_BREAK();
		}
	}

	// - バッファの数をtheSound構造体に記録 -
	pSnd->WvData.dwNofBufs = dwNofBufs;

	// - 仮格納バッファの情報をtheSound構造体に記録 -
	// 領域確保
	pSnd->ppDSBuf = new LPDIRECTSOUNDBUFFER[dwNofBufs];
	// コピー
	for( u32 i=0; i<dwNofBufs; ++i )
	{
		pSnd->ppDSBuf[i] = DSBufs.at(i);
	}

	// - バッファにサウンドデータを書き出す -
	fillup_sound_buffer( pSnd, 0 );
}


/**
 * CreateSoundDataの内部関数
 * pSndのNumBuf番目のDSバッファに波形データを読み込む
 * 
 * @param pSnd
 * @param NumBuf
 * 
 * @return 
 */
HRESULT CSound::fillup_sound_buffer(
	theSound* pSnd,
	u32     NumBuf
	)
{
	// === エラーチェック ===
	if( pSnd->ppDSBuf[NumBuf] == NULL )
	{
		return E_FAIL;
	}


	// === バッファを確認する、無ければ復旧を試みる ===
	if( FAILED( restore_ds_buffer( pSnd->ppDSBuf[NumBuf], NULL ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:RestoreBuffer"), GetLastError() );
	}


	// === バッファをロックする ===
	VOID* pLockBuf = NULL;
	DWORD LockSz   = 0;

	if( FAILED( pSnd->ppDSBuf[NumBuf]->Lock( 0, pSnd->WvData.DataInfo.cksize,
											 &pLockBuf, &LockSz, NULL, NULL, 0L ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:Lock"), GetLastError() );
	}


	// === Waveファイルの頭出しをする ===
	if( FAILED( wvfile.Rewind( &pSnd->WvData ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:Rewind"), GetLastError() );
	}


	// === WaveファイルのサウンドデータをDSバッファに読み出す ===
	u32 ReadedSz = 0;

	if( FAILED( wvfile.Read( &pSnd->WvData, LockSz,
							 (BYTE*)pLockBuf, &ReadedSz ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:Read"), GetLastError() );
	}

	// - 波形データが無い -
	if( ReadedSz == 0 )
	{
		// 無音で埋める
		FillMemory( (BYTE*) pLockBuf, LockSz, 
					(BYTE)( pSnd->WvData.WvFormatEx.wBitsPerSample == 8 ? 128 : 0 )
					);
	}
	// - 波形データが短い -
	else if( ReadedSz < LockSz )
	{
		// 残りを無音で埋める
		FillMemory( (BYTE*) pLockBuf + ReadedSz, LockSz - ReadedSz, 
					(BYTE)( pSnd->WvData.WvFormatEx.wBitsPerSample == 8 ? 128 : 0 )
					);
	}


	// === バッファをアンロックする ===
	if( FAILED( pSnd->ppDSBuf[NumBuf]->Unlock( pLockBuf, LockSz, NULL, 0 ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:Unlock"), GetLastError() );
	}


	return S_OK;
}


/**
 * fillup_sound_bufferの内部関数
 * DSバッファが失われていないか確認し、もし失われていたら復旧する
 * 
 * @param pDSBuf
 * @param pDone
 * 
 * @return 
 */
HRESULT CSound::restore_ds_buffer(
	LPDIRECTSOUNDBUFFER pDSBuf,
	bool*               pDone
	)
{
	// === 開始処理 ===
	// - エラーチェック -
	ASSERT( pDSBuf );

	// - （フラグがあれば）リセット -
	if( pDone != NULL )
	{
		*pDone = false;
	}

	// === バッファの状態を確認する ===
	DWORD stat;
	if( FAILED( pDSBuf->GetStatus( &stat ) ) )
	{
		return DXTRACE_ERR( _T("FAIL:GetStatus"), GetLastError() );
	}

	// === ロストしていたら、復旧を試みる ===
	if( stat & DSBSTATUS_BUFFERLOST )
	{
		while( pDSBuf->Restore() == DSERR_BUFFERLOST )
		{
			Sleep(10);
		}

		// - 復旧が成功したので、フラグをtrueに -
		if( pDone != NULL )
		{
			*pDone = true;
		}
	}


	return S_OK;
}


/**
 * サウンドを再生する
 * 
 * @return 
 */
HRESULT CSound::PlaySound(
	theSound*	p_snd,
	u32		prio,
	u32		flag,
	BOOL		b_override
	)
{
	//if( !IsDebugSwitch( eDEBUG_SWITCH_MUTE_SE ) )
	{
		///使用中でないバッファを探す
		u32 NumFreeBuf;
		if( FAILED( SelectFreeBuffer( p_snd, b_override, &NumFreeBuf ) ) )
		{
			return DXTRACE_ERR( _T("FAIL:SelectFreeBuffer"), E_FAIL );
		}
	
		//使用中でないバッファが見つからなかったら処理を終了
		if( NumFreeBuf == -1 )
		{
			return S_FALSE;
		}
	
		///バッファが失われていないか確認
		bool bRestored;
	
		ASSERT( p_snd->ppDSBuf[ NumFreeBuf ] );
		if( FAILED( restore_ds_buffer( p_snd->ppDSBuf[NumFreeBuf], &bRestored ) ) )
		{
			return DXTRACE_ERR( _T("FAIL:RestoreDSBuf"), GetLastError() );
		}
	
		//失われている場合は復旧を試みる
		if( bRestored )
		{
			if( FAILED( fillup_sound_buffer( p_snd, NumFreeBuf ) ) )
			{
				return DXTRACE_ERR( _T("FAIL:FillupSoundBuffer"), GetLastError() );
			}
		}
	
		///バッファのサウンドを再生する
		if( FAILED( p_snd->ppDSBuf[NumFreeBuf]->Play( 0, prio, flag ) ) )
		{
			return DXTRACE_ERR( _T("FAIL:Play"), GetLastError() );
		}
	}

	return S_OK;
}


/**
 * PlaySoundsの内部関数
 * 再生中でないバッファを見つける
 * 
 * @param pSnd
 * @param Override
 * @param pNumFreeBuf
 * 
 * @return 
 */
HRESULT CSound::SelectFreeBuffer(
	theSound*	pSnd,
	BOOL		Override,
	u32*		pNumFreeBuf
	)
{
	// === エラーチェック ===
	ASSERT( pSnd->ppDSBuf[0] );	//wavファイルのサイズが大きすぎるかも

	// === 再生中でないバッファを探す ===
	for( u32 i=0; i< pSnd->WvData.dwNofBufs; i++ )
	{
		if( pSnd->ppDSBuf[i] != NULL )
		{
			DWORD stat;

			if( FAILED( pSnd->ppDSBuf[i]->GetStatus( &stat ) ) )
			{
				return DXTRACE_ERR( _T("GetStatus"), GetLastError() );
			}

			if( ( stat & DSBSTATUS_PLAYING ) == 0 )
			{
				*pNumFreeBuf = i;  return S_OK;
			}
		}
	}

	// === 再生中でないバッファが見つからなかった ===
	// - 上書きフラグが立っていれば、ランダムで選ぶ -
	if( Override )
	{
		*pNumFreeBuf = rand() % ( pSnd->WvData.dwNofBufs );

		// 選んだバッファを停止、巻戻し
		pSnd->ppDSBuf[*pNumFreeBuf]->Stop();
		pSnd->ppDSBuf[*pNumFreeBuf]->SetCurrentPosition(0);

		return S_OK;
	}

	// - 負数を格納して、関数を出る -
	*pNumFreeBuf = (u32)-1;  return S_FALSE;
}


/**
 * サウンドを停止する
 * 
 * @param pSnd
 * 
 * @return 
 */
HRESULT CSound::StopSound(
	theSound *pSnd
	)
{
	ASSERT( pSnd );

	// === 再生中のバッファを探して、停止、巻き戻しする ===
	bool boDone = false;
	u32 dwNofBufs = pSnd->WvData.dwNofBufs;
	LPDIRECTSOUNDBUFFER* ppDSBuf = pSnd->ppDSBuf;

	for( UINT iNumBuf=0; iNumBuf< dwNofBufs; iNumBuf++ )
	{
		if( ppDSBuf[iNumBuf] != NULL )
		{
			DWORD stat;

			if( FAILED( ppDSBuf[iNumBuf]->GetStatus( &stat ) ) )
			{
				return DXTRACE_ERR( _T("GetStatus"), GetLastError() );
			}

			if( (stat & DSBSTATUS_PLAYING) != 0 )
			{
				ppDSBuf[iNumBuf]->Stop();
				ppDSBuf[iNumBuf]->SetCurrentPosition(0);
				boDone = true;
			}
		}
	}

	// === 値を返す ===
	return boDone? S_OK: S_FALSE;
}


/**
 * theSound構造体の初期化
 * 
 * @param pSnd
 */
void CSound::InitTheSound(
	theSound*	pSnd
	)
{
	ASSERT( pSnd );
	
	// === 初期化 ===
	pSnd->WvData.dwCreatFlag	= 0;
	pSnd->WvData.dwNofBufs		= (u32)-1;

	pSnd->ppDSBuf	= NULL;

	pSnd->pp3DBuf		= NULL;
	pSnd->p3DBufParams	= NULL;
}


/**
 * theSound構造体のゼロリセット
 * 
 * @param pSnd
 */
void CSound::ZeroTheSound(
	theSound*	pSnd
	)
{
	// === エラーチェック　--- */
	ASSERT( pSnd );

	// === ゼロリセット ===
	pSnd->WvData.dwCreatFlag	= 0;
	pSnd->WvData.dwNofBufs		= 0;

	pSnd->ppDSBuf	= NULL;

	pSnd->pp3DBuf		= NULL;
	pSnd->p3DBufParams	= NULL;
}


/**
 * theSound構造体の解放
 * 
 * @param pSnd
 */
void	CSound::ReleaseTheSound(
	theSound*	pSnd
	)
{
	ASSERT( pSnd );

	delete[] pSnd->ppDSBuf;
	delete[] pSnd->pp3DBuf;
	delete[] pSnd->p3DBufParams;

	///ファイルを閉じる
	if( FAILED( wvfile.Close( &pSnd->WvData ) ) )
	{
		DEBUG_BREAK();
	}
}
