// DXDSound.cpp: implementation of the CDXDSound class.
//
//////////////////////////////////////////////////////////////////////


#include <dsound.h>
#include <mmsystem.h>
#include "DXDSound.h"
#include "mydef.h"

#define DSVOLUME_TO_DB(volume)		((DWORD) (-30*(100-(volume))))

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CDXDSound::CDXDSound()
{
	ClassInit();
}

CDXDSound::~CDXDSound()
{
	ClassRelease();
}

////////////////////////////////////////////////////
// 크래스의 초기화.........
////////////////////////////////////////////////////

void CDXDSound::ClassInit()
{
	m_pDSound=NULL;		// 다이렉트 사운드 만큼 해제.
	m_Volume=100;		// 볼륨은 100으로 기준 값을 잡는다.

	for(long i=0;i<EFFECT_MAX_NUM;i++)	
		m_pEffect[i]=NULL;
}


void CDXDSound::ClassRelease()
{
	if(m_pDSound)
	{		
		for(long i=0;i<SBUFFER_MAX_NUM;i++)	
			SBufferRelease(i);

		for(i=0;i<EFFECT_MAX_NUM;i++)
			SAFE_RELEASE(m_pEffect[i]);
	}
	SAFE_RELEASE(m_pDSound);
}

	

bool CDXDSound::DSoundCreate(HWND hwnd)
{
    if(DS_OK!=CoInitialize(NULL))	return false;
    if(DS_OK!=DirectSoundCreate(NULL, &m_pDSound, NULL))	return false;
    if(DS_OK!=m_pDSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY))     return false;

	// 주 버퍼를 생성한다.(사실상 테스트 버퍼)
 	LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
	DSBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
    dsbd.dwSize        = sizeof(DSBUFFERDESC);
    dsbd.dwFlags       = DSBCAPS_PRIMARYBUFFER;
    dsbd.dwBufferBytes = 0;
    dsbd.lpwfxFormat   = NULL;
       
    if(DS_OK!=m_pDSound->CreateSoundBuffer(&dsbd, &pDSBPrimary, NULL)) return false;

    WAVEFORMATEX wfx;
    ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); 
    wfx.wFormatTag      = WAVE_FORMAT_PCM; 
	wfx.nChannels=2;										// 출력형태가 모노..(스테레오가 아님)
	wfx.nSamplesPerSec=22050;								// Sample Rate 가 11KHZ 임...
	wfx.wBitsPerSample=16;									// 8비트임..
	wfx.nBlockAlign=wfx.wBitsPerSample/8*wfx.nChannels;		// 모노에 8비트.스테레오면 2... 16비트면 4..
	wfx.nAvgBytesPerSec=wfx.nSamplesPerSec*wfx.nBlockAlign;

    if(DS_OK!=pDSBPrimary->SetFormat(&wfx))	return false;
    SAFE_RELEASE( pDSBPrimary );
	return true;	
}


bool CDXDSound::Restore(long nBuffer)
{
	if(m_pDSound==NULL)	return false;
	if((NULL==m_SSBuffer[nBuffer].pSBuffer)||(strlen(m_SSBuffer[nBuffer].FileName)<=0))
	{
		char str[256];
		wsprintf(str, "%d번 사운드버퍼에 사운드가 없습니다.", nBuffer);
		AfxErrMsg(str);
		return false;
	}

	long ret=0;
    DWORD flag;
    if(FAILED(ret=m_SSBuffer[nBuffer].pSBuffer->GetStatus(&flag)))
        return false;

    if(flag&DSBSTATUS_BUFFERLOST )
    {
		// 다이렉트 사운드가 컨트롤을 줄 때까지 계속 실행..
        do 
        {
            ret=m_SSBuffer[nBuffer].pSBuffer->Restore();
            if( ret == DSERR_BUFFERLOST )
                Sleep( 10 );
			else break;
        }
        while(ret=m_SSBuffer[nBuffer].pSBuffer->Restore());

		// 컨트롤을 받으면 아예 삭제하고 파일을 다시 로드한다.
		m_SSBuffer[nBuffer].pSBuffer->Restore();
		char fileName[256];
		wsprintf(fileName, "%s", m_SSBuffer[nBuffer].FileName);
		return FileLoad(nBuffer, fileName);
    }
	return true;
}



bool CDXDSound::FileLoad(long nBuffer, char *pFileName)
{
	if(m_pDSound==NULL)	return false;

	HMMIO hwav;
	MMCKINFO	parent;
	MMCKINFO	child;
	UCHAR *snd_buffer=NULL;
	UCHAR *audio_ptr_1=NULL;
	UCHAR *audio_ptr_2=NULL;

	DWORD audio_length_1=0;
	DWORD audio_length_2=0;

	WAVEFORMATEX wfmtx;
	
	parent.ckid = (FOURCC) 0;
	parent.cksize = 0;
	parent.fccType = (FOURCC) 0;
	parent.dwDataOffset = 0;
	parent.dwFlags = 0;
	child = parent;

	// 웨이브 파일 열기.........
	if((hwav=mmioOpen(pFileName, NULL, MMIO_READ|MMIO_ALLOCBUF)) == NULL)
	{
		AfxErrMsg("LoadWav() -> mmioOpen() Error !! \n");
		return false;
	}

	parent.fccType = mmioFOURCC('W','A','V','E');

	if(mmioDescend(hwav, &parent, NULL, MMIO_FINDRIFF))
	{
		mmioClose(hwav, 0);
		AfxErrMsg("LoadWav() -> mmioDescend() Error !! \n");
		return false;
	}

	child.ckid = mmioFOURCC('f','m','t',' ');
	if(mmioDescend(hwav, &child, &parent, 0))
	{
		mmioClose(hwav,0);
		AfxErrMsg("LoadWav() -> mmioDescend() Error !! \n");
	}

	if(mmioRead(hwav, (char *) &wfmtx, sizeof(wfmtx) ) != sizeof(wfmtx))
	{
		mmioClose(hwav,0);
		AfxErrMsg("LoadWav() -> mmioRead() Error !! \n");
	}

	if(wfmtx.wFormatTag != WAVE_FORMAT_PCM)
	{
		mmioClose(hwav,0);
		AfxErrMsg("WAVE_FORMAT_PCM ERROR !! \n");
	}

	if(mmioAscend(hwav, &child, 0))
	{
		mmioClose(hwav,0);
		AfxErrMsg("LoadWav() -> mmioAscend() Error !! !! \n");
	}

	child.ckid = mmioFOURCC('d', 'a', 't', 'a');

	if( mmioDescend(hwav, &child, &parent, MMIO_FINDCHUNK) )
	{
		mmioClose(hwav,0);
		AfxErrMsg("LoadWav() -> mmioDscend() Error !! !! \n");
	}

	// 로드할 버퍼의 생성..
	snd_buffer = (UCHAR *) malloc(child.cksize);
	if(snd_buffer == NULL)
	{
		mmioClose(hwav,0);
		AfxErrMsg("LoadWav() -> malloc() Error !! !! \n");
	}

	// 파일을 버퍼에 저장..
	mmioRead(hwav, (char *)snd_buffer, child.cksize);

	// 파일에 대한 핸들 닫기.......
	mmioClose(hwav,0);


	if(SBufferCreate(nBuffer, child.cksize, &wfmtx) == NULL)
	{
		AfxErrMsg("LoadWav() -> malloc() Error !! !! \n");
		SAFE_FREE(snd_buffer);
		return false;
	}

	if(DS_OK!=m_SSBuffer[nBuffer].pSBuffer->Lock(0, child.cksize, 
												 (void **) &audio_ptr_1,
												 &audio_length_1,
												 (void **) &audio_ptr_2,
												 &audio_length_2,
												 DSBLOCK_FROMWRITECURSOR))		
												 return false;

	memcpy(audio_ptr_1, snd_buffer, audio_length_1);
	memcpy(audio_ptr_2, (snd_buffer+audio_length_1+1), audio_length_2);

	if(DS_OK!=m_SSBuffer[nBuffer].pSBuffer->Unlock(audio_ptr_1,
												   audio_length_1,
												   audio_ptr_2,
												   audio_length_2))										
												   return false;
	SAFE_FREE(snd_buffer);
	return true;
}


void CDXDSound::SBufferRelease(long nBuffer)
{
	if(m_pDSound)
	{
		m_SSBuffer[nBuffer].Release();
	}
}

bool CDXDSound::SBufferCreate(long nBuffer, DWORD bufferSize, WAVEFORMATEX *pWfx)
{
	if(m_pDSound==NULL)	return false;

	SBufferRelease(nBuffer);	// 일단 해제함.	
	DSBUFFERDESC dsbd;		// 다이렉트 사운드의 버퍼에 대한 설명
	ZeroMemory(&dsbd, sizeof(DSBUFFERDESC) );
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY | DSBCAPS_STATIC | DSBCAPS_LOCSOFTWARE;
	dsbd.dwBufferBytes = bufferSize;		// 2초간을 위한 버퍼....
	dsbd.lpwfxFormat = pWfx;				// 웨이브 포맷의 형태.......
	
	if(DS_OK!=m_pDSound->CreateSoundBuffer(&dsbd, &m_SSBuffer[nBuffer].pSBuffer, NULL))
	{
		AfxErrMsg("InitBuffer() -> CreateSoundBuffer() Error !! \n");
		return false;
	}

	return TRUE;
}


void CDXDSound::Play(long nBuffer, bool bLoofOn)				// 배경음악.
{
	if(m_pDSound==NULL)	return;
	if(!m_SSBuffer[nBuffer].pSBuffer)
	{
		char str[256];
		wsprintf(str, "%d번 사운드버퍼에 사운드가 없습니다.", nBuffer);
		AfxErrMsg(str);
		return;
	}

	long ret=false;
	if(bLoofOn)	ret=m_SSBuffer[nBuffer].pSBuffer->Play(0,0,DSBPLAY_LOOPING);
	else		ret=m_SSBuffer[nBuffer].pSBuffer->Play(0,0,0);					

	if(ret!=DS_OK)
	{
		DWORD flag=NULL;
		ret=m_SSBuffer[nBuffer].pSBuffer->GetStatus(&flag);
		if((flag&DSBSTATUS_PLAYING)||(flag&DSBSTATUS_LOOPING))	return;	// 실행 중이면 리턴.

		if(flag&DSBSTATUS_BUFFERLOST)
		{
			if(!Restore(nBuffer))
			{
				char str[256];
				wsprintf(str, "%d번 리스토어 실패.", nBuffer);
				AfxErrMsg(str);
				return;
			}

			// 다시실행.
			if(bLoofOn)	ret=m_SSBuffer[nBuffer].pSBuffer->Play(0,0,DSBPLAY_LOOPING);
			else		ret=m_SSBuffer[nBuffer].pSBuffer->Play(0,0,0);					
		}
	}
}

void CDXDSound::Effect(long nBuffer, long volume, long pan)				// 이펙트 음. 한번 실행하고 지움.
{
	if(m_pDSound==NULL)	return;
	if(!m_SSBuffer[nBuffer].pSBuffer)
	{
		char str[256];
		wsprintf(str, "%d번 사운드버퍼에 사운드가 없습니다.", nBuffer);
		AfxErrMsg(str);
		return;
	}

	// 에러 검출...
	DWORD flag=false;
	if(FAILED(m_SSBuffer[nBuffer].pSBuffer->GetStatus(&flag)))
	{
		if((flag&DSBSTATUS_PLAYING)||(flag&DSBSTATUS_LOOPING))	return;	// 실행 중이면 리턴.
	}
	if(flag&DSBSTATUS_BUFFERLOST)									// 로스트일 경우에 리스토어.
	if(!Restore(nBuffer))
	{
		char str[256];
		wsprintf(str, "%d번 리스토어 실패.", nBuffer);
		AfxErrMsg(str);
		return;
	}

	for(long i=0;i<EFFECT_MAX_NUM;i++)
	{			
		if(NULL==m_pEffect[i])		break;	// 버퍼가 비어있으면 넘어간다.
		if(FAILED(m_pEffect[i]->GetStatus(&flag)))
		{
			// 만약 실행중이거나 오류가 있을 경우에는 삭제한 후에 이곳에 실행버퍼를 복사.
			if(flag==DSBSTATUS_BUFFERLOST)
			{
				m_pEffect[i]->Restore();
				SAFE_RELEASE(m_pEffect[i]);
				break;
			}
		}
		if(!((flag&DSBSTATUS_PLAYING)||(flag&DSBSTATUS_LOOPING)))
		{
			SAFE_RELEASE(m_pEffect[i]);
			break;
		}
		flag=NULL;
	}

	if(i>=EFFECT_MAX_NUM)
	{
		char str[256];
		wsprintf(str, "플레이 버퍼가 꽉 참.", nBuffer);
		AfxErrMsg(str);
		return;
	}

	if(DS_OK==m_pDSound->DuplicateSoundBuffer(m_SSBuffer[nBuffer].pSBuffer, &m_pEffect[i]))
	{
		// 볼륨과 패닝을 세팅.
		m_pEffect[i]->SetVolume(DSVOLUME_TO_DB(m_Volume*volume/100));	// 비율에 맞게 볼륨조절.
		m_pEffect[i]->SetPan(pan*100);									// 팬조절.
		if(DS_OK==m_pEffect[i]->Play(0,0,0))
		{
			return;
		}
		else
		{
			char str[256];
			wsprintf(str, "%d번 PlayBuffer 실행 실패.", nBuffer);
			AfxErrMsg(str);
		}
	}
	else
	{
		char str[256];
		wsprintf(str, "%d번 사운드 복사 실패 실패.", nBuffer);
		AfxErrMsg(str);
	}
}


void CDXDSound::VolumeSet(long nPercent)
{
	if(m_pDSound==NULL)	return;
	if(nPercent<0)		nPercent=0;
	if(nPercent>100)	nPercent=100;

	m_Volume=nPercent;	// 현재 볼륨을 셋팅함.
	
	for(long i=0;i<SBUFFER_MAX_NUM;i++)	
	{
		if(m_SSBuffer[i].pSBuffer)		
			m_SSBuffer[i].pSBuffer->SetVolume(DSVOLUME_TO_DB(nPercent));	// 비율에 맞게 볼륨조절.
	}
}


void CDXDSound::Stop(long nBuffer)
{
	if(m_pDSound==NULL)	return;
	if(!m_SSBuffer[nBuffer].pSBuffer)
	{
		char str[256];
		wsprintf(str, "%d번 사운드버퍼에 사운드가 없습니다.", nBuffer);
		AfxErrMsg(str);
		return;
	}

	DWORD flag;
    m_SSBuffer[nBuffer].pSBuffer->GetStatus(&flag);

	// 작동 중이면 스톱.
	if((flag&DSBSTATUS_PLAYING)||(flag&DSBSTATUS_LOOPING))
	{
		if(DS_OK!=m_SSBuffer[nBuffer].pSBuffer->Stop())
		{
			char str[256];
			wsprintf(str, "%d번 사운드 재생 실패.", nBuffer);
			AfxErrMsg(str);
		}
		m_SSBuffer[nBuffer].pSBuffer->SetCurrentPosition(0L);	// 맨 처음으로 돌림.
	}
}

