=================================
=================================
=================================
나우누리 게임제작 포럼
엘리시아 이브의 심심강좌
Direct X : Windows Game Programing
====================================================================
Episode 1 ... 게임 엔진 제작
====================================================================
Section 28 소리 연주
에고^^; 원래는 윈도우 프로그래밍을 하려고 했었는데요.
아직 화면 캡춰 프로그램을 못구해서 ( 그림을 넣으려고^^;; ) 늦어지내요
( HTML 파일로 만들라고 했거든요 )
흠, 먼저 소리 출력에 대해서 쓸께요^^
이번에 만들 함수 및 클래스들은 다음과 같습니다.
int InitDSound(HWND hWnd,int Samples=22050,int Bits=16);
void CloseDSound();
class CWAVE
{
LPDIRECTSOUNDBUFFER lpDSB;
char* szName;
LPVOID lpvLockAddress;
DWORD dwLockSize;
int Lock();
void Unlock();
public:
CWAVE();
~CWAVE();
int Load(char* pszName);
int Create
(int Size,
WAVEFORMATEX *pWaveFormEx,DWORD dwFlag=DSBCAPS_STATIC);
void Clear();
void SetFormat(int Samples,int Bits);
void SetPos(DWORD Pos);
void Play();
void SetBuffer(LPDIRECTSOUNDBUFFER lpDSBCopy);
LPDIRECTSOUNDBUFFER DuplicateBuffer();
};
그럼 이제 만들어 보기로 하죠^^
1. DirectSound 만들기
음.. 곧 모양이 바뀌겠지만요^^; 다이렉트 사운드를 만드는 것을 보겠습니
다. 음.. 기본적인 모양은 DDraw 랑... 거의 흡사합니다.
먼저 다이렉트사운드 전역 변수를 만들어 주어야 합니다^^
저번에 다이렉트 드로우를 만든 것 처럼요.
LPDIRECTSOUND lpDS=NULL;
다이렉트 드로우때도... 1차버퍼, 프라이머리 서피스라는게 있었죠? 소리
쪽에서도 1차 버퍼, 프라이머리 버퍼라는 것이 있습니다^^
LPDIRECTSOUNDBUFFER lpDSB;
여기에... 소리들을 넣어주면^^ 출력이 되는 것이지요.
간단하죠?
소리 섞는 일같은건... DSound 가 알아서 해주거든요
자, 그럼 실제 소스 부분으로 들어가겠습니다
if(FAILED(DirectSoundCreate(NULL,&lpDS,NULL))) return 0;
Create 함수 처음에 나오는 거 아시죠^^?
먼저 DSound를 만들어 줍니다
if(FAILED(lpDS->SetCooperativeLevel(hWnd,DSSCL_PRIORITY))) return 0;
다음은...협력 레벨을 정해 줍니다. DDSCL_NORMAL 이나 DSSCL_PRIORITY 중
에서 골라주세요^^
( 소스들을 보니까 다들 이걸 썼더만유^^; 그래서... 저도 그냥 이걸...)
이제 구체적으로 1차 버퍼를 만들어 봅시다
DDSURFACEDESC 와 작성 방식이 비슷합니다.
DSBUFFERDESC DSBufDesc;
변수 선언하공^^
memset(&DSBufDesc,0,sizeof(DSBUFFERDESC));
메모리 깨끗이 청소하고
DSBufDesc.dwSize=sizeof(DSBUFFERDESC);
크기 정해주고요
DSBufDesc.dwFlags=DSBSCL_PRIMARYBUFFER;
이거 1차 표면을 만든다고 이야기를 해주는 것이지요
DSBufDesc.dwBufferBytes=0;
DSBufDesc.lpwfxFormat=NULL;
1차버퍼의 크기와 형식은 그냥 0으로 해주어야 한다는 군요
lpDS->CreateSoundBuffer(&DSBufDesc,&lpDSB,NULL);
마지막으로 1차 버퍼 생성~~
하하^^ 너무나도 간단합니다...
라고 하기엔 이르죠?
여기에 출력 형식을 정해주어야 하거든요^^;;
출력 형식은 버퍼 생성에 성공했을 경우 사용합니다.
( 모.. 실패하면, 그냥 기본 설정쓰라는 이야기래요. 에러 처리 할 필요가
없대요~~ )
소리 데이터를 만들기 위해서는 3가지 정도의 정보값이 필요합니다;;
채널 ->
1개면 모노, 2개면 스테레오죠?
요즘에 모노쓰는 겜은 없으니... 그냥 여기서 무조건 스테레오로
하겠습니다.
초당샘플링 ->
소리 연주 프로그램들을 보면... 음질을 결정하는 수치가 있습니
다. 11025hz, 22050hz, 44100hz 이런 수치들이 쪼만하게 나와있죠?
역시 클수록 음질은 좋아지고^^; 소리 파일 크기도 커집니다.
에... 연주할 내용이 임의면 어떻하냐고요^^;
모... DSound가 알아서 처리해준다네요. 역시 걱정할 필요는 없습
니다. 다만, 수치가 똑같으면... 별로 처리가 필요없으니... 부하
가 적게 걸리겠죠?
변수명 Samples;
초당비트수 ->
흣^^; 음... 소리는 아날로그죠? 그런데... 컴터는 디지털만 알아
듣는답니다. 그래서 디지털화 할때... 단계를 구분해서, 아날로그
신호를 일정단위로 끊거든요.몇단계나 끊을 것인지 결정하는 것입
니다. 8비트면 256단계, 16비트면 65536 단계^^
역시 클수록 음질이 좋겠죠? 더불어 파일크기도 팍팍~!!
변수명 Bits;
WAVEFORMATEX WaveFormEx;
memset(&WaveFormEx,0,sizeof(WAVEFORMATEX));
WaveFormEx.wFormatTag=WAVE_FORMAT_PCM;
WaveFormEx.nChannels=2;
WaveFormEx.nSamplesPerSec=Samples;
WaveFormEx.wBitsPerSample=Bits;
WaveFormEx.nBlockAlign=Bits/8*2;
WaveFormEx.nAvgBytesPerSec=Bits*WaveFormEx.nBlockAlign;
lpDSB->SetFormat(&WaveFormEx);
핫^^ 내용은 그냥 예제에서 그대로 옮긴 것이니... 세부적인 내용은
묻지마세요
자자, 그냥 넘어갑시다. 소리만 출력되면 되는 것이지요. 모. 핫핫
( 사실 제가 음악쪽에 약해서리요^^;; )
이렇게 DSound 설정 및 1 차 버퍼 만들기는 끝이 납니다요.
왠지 어정쩡하죠?
2. DSound 없에기
흠.. 이정도는 충분히 짐작하실듯...
버퍼랑, DSound랑 모두 Release해주시면 됩니다
lpDSB->Release();
lpDS->Release();
3. 좀더 멋진 방법으로...
자^^ 이제 클래스로 만들어 보기로 합시다
먼저, 버퍼를 만드는 함수부터 보기로 할까요?
int CWAVE::Create(int Size,WAVEFORMATEX *pWFE,DWORD dwFlag)
{
Clear();
DSBUFFERDESC DSBufDesc;
memset(&DSBufDesc,0,sizeof(DSBUFFERDESC));
DSBufDesc.dwSize=sizeof(DSBUFFERDESC);
DSBufDesc.dwFlags=dwFlag;
DSBufDesc.dwBufferBytes=Size;
DSBufDesc.lpwfxFormat=pWFE;
if(FAILED(lpDS->CreateSoundBuffer(&DSBufDesc,&lpDSB,NULL)))
return 0;
return 1;
}
에... 버퍼 만들때의 구성요소에는 3가지가 있습니다.
버퍼의 크기, 소리의 형식, 그리고... 어떤 버퍼인가 하는 것이지요
크기나 형식은 아실테고요. 버퍼의 종류에 대해서 간단히 설명해 드릴께요
프라이머리 버퍼 - 아까 만들어 본거죠? *
스태틱 버퍼 - 짧은 소리용 버퍼입니다 *
스트림 버퍼 - 긴소리용 버퍼입니다
(*는 이번 강좌에서 다루는 내용만 체크한 것입니다^^ )
흠... 기본적인 내용은 위에서 다루었고, 나머지는 다 대입식이라 설명할
것이 없을듯 싶내요^^
다음에 보실것은 소리 형식을 만드는 함수입니다.
void CWAVE::SetFormat(int Samples,int Bits)
{
WAVEFORMATEX WaveFormEx;
memset(&WaveFormEx,0,sizeof(WAVEFORMATEX));
WaveFormEx.wFormatTag=WAVE_FORMAT_PCM;
WaveFormEx.nChannels=2;
WaveFormEx.nSamplesPerSec=Samples;
WaveFormEx.wBitsPerSample=Bits;
WaveFormEx.nBlockAlign=Bits/8*2;
WaveFormEx.nAvgBytesPerSec=Bits*WaveFormEx.nBlockAlign;
lpDSB->SetFormat(&WaveFormEx);
}
흠;; 역시 위에서 다루었던 내용이죠?
호호. 그냥 넘어갑니다.
자, 버퍼를 청소하는 것도 미리 만들어 둡시다
void CWAVE::Clear()
{
if(lpDSB)
{
lpDSB->Release();
lpDSB=NULL;
}
}
CWAVE::CWAVE()
{
lpDSB=NULL;
}
CWAVE::~CWAVE()
{
Clear();
}
역시 위에서 이미 설명했습니다요^^;
자, 요런 함수들을 가지고... 다이렉트 사운드 만들기를 깨끗이 다시 만들
어 봅시다
LPDIRECTSOUND lpDS=NULL; // 다이렉트 사운드
CWAVE* CWavePrimary; // 프라이머리 버퍼
int InitDSound(HWND hWnd,int Samples,int Bits)
{
// 다이렉트 사운드 만들기
if(FAILED(DirectSoundCreate(NULL,&lpDS,NULL))) return 0;
// 협력 레벨 정하기
if(FAILED(lpDS->SetCooperativeLevel(hWnd,DSSCL_PRIORITY)))
return 0;
// 프라이머리 버퍼 만들기
CWavePrimary=new CWAVE;
if(CWavePrimary->Create(0,NULL,DSBCAPS_PRIMARYBUFFER))
// 소리 형식 설정
CWavePrimary->SetFormat(Samples,Bits);
return 1;
}
void CloseDSound()
{
if(CWavePrimary) delete CWavePrimary;
lpDS->Release();
}
뜨아... 이번 강좌는 정말 설명해 드릴께 별로 없군요^^
역시 소스 내용은 위에서 다루었습니다
4. 소리내기
자, 이제 핵심 작업인 소리내기 작업을 해보도록 합시다.
모.. 웨이브 Stream 연주도 있습니다만, 다들 시디음원쓰니 -.-; 필요가 없
겠죠? 그냥 짧은 소리 내는 거만 다루겠습니다.
먼저 소리를 내기위해서는 웨이브 파일을 읽어드려야 합니다.
소스 먼저 볼까요?
int CWAVE::Load(char* pszName)
{
HMMIO hMmIO;
MMCKINFO MmCkInfo;
MMCKINFO MmCkInfoParent;
WAVEFORMATEX* pWaveFormEx;
if(WaveOpenFile(pszName,&hMmIO,&pWaveFormEx,&MmCkInfoParent)!=0)
return 0;
if(WaveStartDataRead(&hMmIO,&MmCkInfo,&MmCkInfoParent)!=0)
return 0;
if(!Create(MmCkInfo.cksize,pWaveFormEx))
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
return 0;
}
if(!Lock())
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
Clear();
return 0;
}
UINT uiSize;
if
(
WaveReadFile
(hMmIO,dwLockSize,(BYTE*)lpvLockAddress,&MmCkInfo,&uiSize)
)
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
Clear();
return 0;
}
Unlock();
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
return 1;
}
흠... 이상한 함수들이 많군요^^ 호호
HMMIO hMmIO; // 파일 핸들
MMCKINFO MmCkInfo; // 청크 정보
MMCKINFO MmCkInfoParent; // 부모 청크 정보
변수이름들이 묘하군요^^; 이런게... DSound에 있을리가 없겠죠?
내. mmsystem.h 에 있는 것들입니다.
mmsystem.h 은 윈도우 멀티미디어 관련 내용들이 있는 헤더입니다.
( 쓰실때는 mmsystem.lib 이 아니라 WinMm.Lib 을 연결하셔야 합니다 )
if(WaveOpenFile(pszName,&hMmIO,&pWaveFormEx,&MmCkInfoParent)!=0)
return 0;
웨이브 파일을 여는 함수입니다^^;
여기서... 웨이브 형식정보를 얻어냅니다
흠.. 이걸 만들어야 하나.. 걱정하시는듯..;
에.. 그러실 필요가 없습니다. 예제중에 있거든요^^
Wave.C 하고 Wave.H 라는^^;;
이걸 그냥 복사하셔서... 프로젝트에 연결해주세요
( Wave- 요런 파일은 모두 Wave.C 파일입니다 )
if(WaveStartDataRead(&hMmIO,&MmCkInfo,&MmCkInfoParent)!=0)
return 0;
웨이브 파일 읽을 준비를 하는 것입니다.
if(!Create(MmCkInfo.cksize,pWaveFormEx))
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
return 0;
}
웨이브파일에서 얻은 정보를 토대로 버퍼를 만듭니다
만약 실패한다면... 웨이브 파일을 닫습니다.
에... 저번 DDraw를 했지만... 메모리 제어를 하려면...꼭 Lock/Unlock처리
를 해주셔야 합니다. 다음 순서가^^ 데이터를 읽는 부분이기 땜시로...
Lock과정을 먼저 다루어 보겠습니다
( 핫^^; 배에서 갑자기 꼬로록 소리가... 잠시 식사 좀 하겠습니다 )
( 으흠^^ 오징어 국은 정말 맛있군요. 아침을 안 먹어서인지...더 맛있어
요. 홍홍홍. ^^; 요즘 매일 새벽 5시 자서 1시에 일어나니... 아침을 못 먹
거든요. 속쓰려라^^; - 아, 대학 입학할때까지는 계속 이런 생활을^^ 룰룰
루~~ )
쫍쩝. 먹으면서^^ 계속 강좌를 쓰겠습니다
int CWAVE::Lock()
{
if(!lpDSB) return 0;
if
(
FAILED
(
lpDSB->Lock
(
0, // 락 시작 오프셋
0, // 무시
&lpvLockAddress, // 락 메모리 주소
&dwLockSize, // 락 크기
NULL, // 순환 시작주소
NULL, // 순환 끝주소
DSBLOCK_ENTIREBUFFER //;; 그냥 쓰길래.. 썼음
)
)
) return 0;
return 1;
}
에...Lock을 하기위해서는 두가지의 변수가 필요합니다.
락할 메모리 주소와 락한 메모리 크기이죠
LPVOID lpvLockAddress;
DWORD dwLockSize;
멤버 변수로 선언한 것들중에 있죠?
다시 아까 그곳으로 넘어갑시다
if(!Lock())
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
Clear();
return 0;
}
에.. 역시 락을 실패하면... 웨이브 닫고요, 버퍼를 제거해줍니다.
이제 파일을 읽어야 겠죠?
UINT uiSize;
if
(
WaveReadFile
(hMmIO,dwLockSize,(BYTE*)lpvLockAddress,&MmCkInfo,&uiSize)
)
{
WaveCloseReadFile(&hMmIO,&pWaveFormEx);
Clear();
return 0;
}
흠^^; 모.. 그냥 소스 그대로 이해해 주시길.. uiSize의 용도는 아직 찾질
못했습니다. 죄송^^
자, 메모리 처리가 끝났으니까.. 이제 언락과정을 해야 합니다.
역시^^; 언락은 간단합니다.
void CWAVE::Unlock()
{
if(!lpDSB) return;
lpDSB->Unlock(lpvLockAddress,dwLockSize,NULL,0);
}
홋홋. 쉽죠? 아, 첫줄은... 버퍼가 설정안되었는데... 처리할 경우를 대비
해서 써넣은 것입니다. 버퍼가 없으면... 그냥 리턴하죠.
자, 이제 중요한 소리 연주입니다.
void CWAVE::Play()
{
if(!lpDSB) return;
SetPos(0);
lpDSB->Play(0,0,0);
}
역시 버퍼가 없을 경우 에러처리를 하고요.
에... SetPos또 처음 보는 거죠?
void CWAVE::SetPos(DWORD Pos)
{
if(!lpDSB) return;
lpDSB->SetCurrentPosition(Pos);
}
출력 버퍼의 위치를 기록하는 것입니다. 그냥 소리 출력이니...언제나 처음
으로 설정했습니다.
다음줄이^^ 바로 소리 연주 짜잔~~
간단하죠?
아^^ 맛있어. 밥을 다먹었내요. 잠시 설겆이하러~~ ^^
착. 흠... 국그릇하고 숟가락, 컵밖에 없어서 금방 끝나는군요.
에... 다시...
간혹... 버퍼를 놓치는 경우도 있습니다^^;
다이렉트 드로우 경우도... Alt+Tab을 누르면 버퍼를 잃어 버리곤하죠
그럴때는...
if(FAILED(lpDDS->IsLost())) lpDDS->Restore();
else 출력...
을 하죠^^;
에... 소리에서도 비슷합니다. Play 함수에서 버퍼를 잃어버리는 경우,
DSERR_BUFFERLOST 라는 에러를 리턴하게 됩니다. 이럴경우, Restore를 해준
다음.. 웨이브 파일을 다시 읽어 드리시면 됩니다.
void CWAVE::Play()
{
if(!lpDSB) return;
SetPos(0);
if(lpDSB->Play(0,0,0)==DSERR_BUFFERLOST)
if(SUCCEEDED(lpDSB->Restore()))
if(Load(szName)) lpDSB->Play(0,0,0);
}
에... szName처리과정을^^; 위 소스에서는 뺐습니다.
모... 간단히 적으라면요.
CWAVE::CWAVE()
{
lpDSB=NULL;
szName=NULL;
}
void CWAVE::Clear()
{
if(lpDSB)
{
lpDSB->Release();
lpDSB=NULL;
}
if(szName) delete szName;
}
int CWAVE::Load(char* pszName)
{
... 끝에쯤해서...
szName=new char[strlen(pszName)+1];
strcpy(szName,pszName);
return 1;
}
이런 내용을 포함시켜 주시면 됩니다.
5. 똑같은 버퍼 만들기
음. 게임을 하다보면... 똑같은 소리가 나는 경우가 있습니다.
총알 버튼 누르고 있음 ( 전 버튼 누르는 속도가 느려서^^; 그냥 누르고 있
습니다. ) 따다다계속 소리가 나죠?
그냥 출력시마다, Play함수를 실행시키면... 연주되던게 끊기고... 다시 첨
부터 플레이가 됩니다. 이러면 원하는 결과가 나오지 않겠죠? 그래서... 제
어에 제약은 따르지만, 소리만 낼수 있는 버퍼를 만들 수 있습니다.다시 버
퍼를 읽는 과정이 없이 이전꺼에서.. 복사를 하는 것이지요^^
LPDIRECTSOUNDBUFFER CWAVE::DuplicateBuffer()
{
LPDIRECTSOUNDBUFFER lpDSBCopy;
lpDS->DuplicateSoundBuffer(lpDSB,&lpDSBCopy);
return lpDSBCopy;
}
짜잔^^ 이렇게 하시면... 현재 클래스속의 버퍼를 복사해 올 수 있습니다.
그럼 지정하는 방법은?
void CWAVE::SetBuffer(LPDIRECTSOUNDBUFFER lpDSBCopy)
{
Clear();
lpDSB=lpDSBCopy;
}
요 함수를 이용해 지정을 해줍니다.
6. 예제 보기
자, 그럼 예제 뿌리고 마칩니다
#include <windows.h>
#include <Eter.H>
#include <DSound.H>
#include <DD.H>
#include <mmsystem.h>
#include "Wave.H"
/*
함수 및 클래스 만드는 부분은 생략...
*/
CWAVE *CWave;
CWAVE *CWave2;
int Start()
{
InitDDraw(ghWnd);
InitDSound(ghWnd);
CFont=new CFONT;
CWave=new CWAVE;
CWave2=new CWAVE;
if(CWave->Load("Demo.Wav"))
{
// 버퍼 복제
CWave2->SetBuffer(CWave->DuplicateBuffer());
CWave->Play();
CWave2->Play();
}
return 1;
}
void End()
{
delete CWave;
delete CWave2;
CloseDSound();
CloseDDraw();
delete CFont;
}
int Main()
{
return 0;
}
int Idle()
{
return 1;
}
LRESULT wmKeyDown(MSGPARAM)
{
PostQuitMessage(0);
return 0;
}
int Window(LPSTR szArgs)
{
MsgProc[WM_KEYDOWN]=wmKeyDown;
return InitWindow("DSound",LoadIcon(NULL,IDI_APPLICATION),"MENU");
}
=====================================================================
e-mail myevan@nownuri.net & nowngm3@nownuri.net
나우누리 게임 제작 포럼 NGM
http://blue.nownuri.net/ngm/
게임 제작 팀 이터니티
http://mypage.channeli.net/blaze79/
=================================
=================================
=================================
'프로그래밍 관련 > 사운드' 카테고리의 다른 글
동일 사운드 반복 플레이 DuplicateSoundBuffer (칼, 총기류 난사시 사운드) (0) | 2011.01.06 |
---|---|
ogg 파일을 스트림 버퍼로 읽어 들이기 (0) | 2011.01.06 |
다이렉트 사운드 더블 버퍼링 또는 다중버퍼관리출력 (0) | 2011.01.05 |
박한규님 사운드 프로그래밍 강좌 (0) | 2011.01.05 |
다이렉트 사운드 녹음,재생 (0) | 2011.01.05 |