DirectX11 Tutorial 14: Direct Sound

강좌번역/DirectX 11 2013. 4. 5. 12:30 by 빠재

원문: http://rastertek.com/dx11tut14.html




이번 튜토리얼에서는 DirectX 11에서의 Direct sound 기초와 .wav 파일을 로드하고 재생하는 방법을 다룹니다. 이 튜토리얼은 이전의 DirectX 11 튜토리얼들에 기초합니다. 튜토리얼을 시작하기에 앞서 DirectX 11에서의 Direct sound와 사운드 포맷을 몇 가지 알아보도록 하겠습니다.


우선, DirectX 11의 Direct sound API는 DirectX 8의 그것과 같습니다. 크게 다른 점이 있다면 최근의 윈도우 OS에서 하드웨어 레벨의 사운드 믹싱이 되지 않는다는 것입니다. 그 이유인즉슨, 모든 하드웨어 호출이 이제는 모두 보안 계층으로 들어갔기 때문에 접근이 허용되지 않게 되었기 때문입니다. 오래된 사운드 카드들은 상대적으로 빠른 DMA(Direct Memory Access)를 지원하지만 오늘날의 윈도우 보안 체계에서 작동하지 않습니다. 따라서 현재는 사운드 믹싱이 소프트웨어 레벨에서 이루어지기 때문에 이 API들은 직접적인 하드웨어 가속이 지원되지 않습니다.


Direct sound의 장점은 원하는 어떤 종류의 오디오 포맷이든지 잘 지원한다는 것입니다. 이 튜토리얼에서는 .wav 파일을 사용하지만 이것 대신 .mp3나 다른 포맷도 사용 가능합니다. 또한 여러분만의 포맷을 만들어 사용하는 것도 가능합니다. Direct sound는 너무 간단해서 단지 사운드 버퍼를 생성하고 불러올 오디오 포맷을 버퍼의 포맷에 복사하기만 해도 재생 준비는 끝이 납니다. 곧 어째서 이 단순함이 많은 어플리케이션들이 direct sound를 사용하게 되었는지 알게 되실 겁니다.


알아두어야 할 것은 Direct sound에서는 1차 버퍼와 2차 버퍼로 두 버퍼를 나누어 사용한다는 것입니다. 1차 버퍼는 사운드 카드나 USB 헤드셋과 같은 것에 있는 메인 사운드 버퍼입니다. 2차 버퍼는 실제로 어플리케이션에서 생성하고 그곳에 소리가 로드되는 메모리 영역입니다. 2차 버퍼의 소리를 재생할 때 Direct sound API에서는 이들을 1차 버퍼의 소리들과 잘 혼합하여 재생하게 됩니다. 만약 여러 개의 2차 버퍼들을 동시에 재생한다면 전부 혼합되어 1차 버퍼에서 재생됩니다. 또한 이 버퍼들은 앞뒤가 이어져 있기 때문에(circular) 무한히 반복 재생되게 할 수 있습니다.


튜토리얼을 시작하기 앞서 갱신된 프레임워크를 보시기 바랍니다. 새로운 SoundClass 클래스가 DirectSound와 .wav 기능을 담당합니다. 다른 클래스들은 단순함을 유지하기 위해 없앴습니다.










Soundclass.h


SoundClass는 DirectSound의 기능과 .wav 파일의 로드 및 재생 기능을 캡슐화합니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: soundclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _SOUNDCLASS_H_
#define _SOUNDCLASS_H_



DirectSound가 컴파일되기 위해서는 다음 라이브러리와 헤더들이 필요합니다.


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")
 
 
//////////////
// INCLUDES //
//////////////
#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>
#include <stdio.h>
 
 
///////////////////////////////////////////////////////////////////////////////
// Class name: SoundClass
///////////////////////////////////////////////////////////////////////////////
class SoundClass
{
private:



WaveHeaderType 구조체는 .wav 파일 포맷을 위한 것입니다. wav 파일을 로드할 때는 우선 파일의 헤더 정보를 읽어 오디오 데이터를 로드하기 위한 정보들을 가져옵니다. 만약 다른 오디오 파일 포맷을 사용한다면 그에 맞추어 다른 헤더 형식으로 바꿀 수 있습니다.


	struct WaveHeaderType
	{
		char chunkId[4];
		unsigned long chunkSize;
		char format[4];
		char subChunkId[4];
		unsigned long subChunkSize;
		unsigned short audioFormat;
		unsigned short numChannels;
		unsigned long sampleRate;
		unsigned long bytesPerSecond;
		unsigned short blockAlign;
		unsigned short bitsPerSample;
		char dataChunkId[4];
		unsigned long dataSize;
	};
 
public:
	SoundClass();
	SoundClass(const SoundClass&);
	~SoundClass();



Initialize와 Shutdown 함수는 이 튜토리얼에서 필요한 모든 것들을 제어합니다. Initialize 함수는 DirectSound를 초기화하고 .wav 오디오 파일을 로드하고 재생하는 일까지 합니다. Shutdown 함수는 .wav 파일과 DirectSound를 해제합니다.


	bool Initialize(HWND);
	void Shutdown();
 
private:
	bool InitializeDirectSound(HWND);
	void ShutdownDirectSound();
 
	bool LoadWaveFile(char*, IDirectSoundBuffer8**);
	void ShutdownWaveFile(IDirectSoundBuffer8**);
 
	bool PlayWaveFile();
 
private:
	IDirectSound8* m_DirectSound;
	IDirectSoundBuffer* m_primaryBuffer;



이 튜토리얼에서는 하나의 사운드만 로드하므로 하나의 2차 버퍼만을 선언합니다.


	IDirectSoundBuffer8* m_secondaryBuffer1;
};
 
#endif






Soundclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: soundclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "soundclass.h"



클래스 생성자에서 전용 변수들을 초기화합니다.


SoundClass::SoundClass()
{
	m_DirectSound = 0;
	m_primaryBuffer = 0;
	m_secondaryBuffer1 = 0;
}
 
 
SoundClass::SoundClass(const SoundClass& other)
{
}
 
 
SoundClass::~SoundClass()
{
}
 
 
bool SoundClass::Initialize(HWND hwnd)
{
	bool result;



우선 DirectSound API와 1차 버퍼를 초기화합니다. 그 다음에는 LoadWaveFile 함수를 호출하여 .wav 파일을 로드하고 그것으로 2차 버퍼를 초기화합니다. 로드가 완료되면 PlayWaveFile을 호출하여 바로 .wav 파일을 재생합니다.


	// Initialize direct sound and the primary sound buffer.
	result = InitializeDirectSound(hwnd);
	if(!result)
	{
		return false;
	}
 
	// Load a wave audio file onto a secondary buffer.
	result = LoadWaveFile("../Engine/data/sound01.wav", &m_secondaryBuffer1);
	if(!result)
	{
		return false;
	}
 
	// Play the wave file now that it has been loaded.
	result = PlayWaveFile();
	if(!result)
	{
		return false;
	}
 
	return true;
}



Shutdown 함수는 ShutdownWaveFile 함수를 통하여 .wav 오디오 데이터를 가지고 있는 2차 버퍼를 해제합니다. 그것이 완료된 후에는 ShutdownDirectSound 함수로 1차 버퍼와 DirectSound 인터페이스를 해제합니다.


void SoundClass::Shutdown()
{
	// Release the secondary buffer.
	ShutdownWaveFile(&m_secondaryBuffer1);

	// Shutdown the Direct Sound API.
	ShutdownDirectSound();
 
	return;
}



InitializeDirectSound 함수는 DirectSound와 1차 버퍼의 인터페이스에 대한 포인터를 얻어옵니다. 여기서 모든 음향 장치를 조회하여 그 중 특정 장치를 1차 버퍼로 정해 그 포인터를 얻어올 수 있지만, 이 튜토리얼에서는 단순함을 유지하기 위하여 기본 음향 장치에 대한 포인터를 얻어오도록 하였습니다.


bool SoundClass::InitializeDirectSound(HWND hwnd)
{
	HRESULT result;
	DSBUFFERDESC bufferDesc;
	WAVEFORMATEX waveFormat;
 
 
	// Initialize the direct sound interface pointer for the default sound device.
	result = DirectSoundCreate8(NULL, &m_DirectSound, NULL);
	if(FAILED(result))
	{
		return false;
	}
 
	// Set the cooperative level to priority so the format of the primary sound buffer can be modified.
	result = m_DirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
	if(FAILED(result))
	{
		return false;
	}



1차 버퍼에 접근하기 위한 방법을 명세하기 위한 description을 작성해야 합니다. 이 구조체에서는 dwFlags가 가장 중요합니다. 이 튜토리얼에서는 1차 버퍼의 볼륨을 조절할 수 있도록 작성합니다. 다른 기능들도 건드려 볼 수 있지만 단순함을 유지하도록 하겠습니다.


	// Setup the primary buffer description.
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = 0;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = NULL;
	bufferDesc.guid3DAlgorithm = GUID_NULL;
 
	// Get control of the primary sound buffer on the default sound device.
	result = m_DirectSound->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL);
	if(FAILED(result))
	{
		return false;
	}



기본 음향 장치를 1차 버퍼로 하여 제어권을 얻었다면 이 장치의 포맷을 우리의 오디오 파일 포맷으로 바꿔야 합니다. 고품질의 사운드 품질을 원하기 때문에 비압축 CD 오디오 품질로 설정합니다.


	// Setup the format of the primary sound bufffer.
	// In this case it is a .WAV file recorded at 44,100 samples per second in 16-bit stereo (cd audio format).
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = 44100;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nChannels = 2;
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;
 
	// Set the primary buffer to be the wave format specified.
	result = m_primaryBuffer->SetFormat(&waveFormat);
	if(FAILED(result))
	{
		return false;
	}
 
	return true;
}



ShutdownDirectSound 함수는 1차 버퍼와 DirectSound의 해제를 담당합니다.


void SoundClass::ShutdownDirectSound()
{
	// Release the primary sound buffer pointer.
	if(m_primaryBuffer)
	{
		m_primaryBuffer->Release();
		m_primaryBuffer = 0;
	}
 
	// Release the direct sound interface pointer.
	if(m_DirectSound)
	{
		m_DirectSound->Release();
		m_DirectSound = 0;
	}
 
	return;
}



LoadWaveFile 함수는 .wav 오디오 파일을 로드하여 그 데이터를 2차 버퍼에 복사합니다. 다른 포맷으로 하기 언한다면 이 함수를 바꾸거나 그런 기능의 새로운 함수를 만들어 사용할 수 있습니다.


bool SoundClass::LoadWaveFile(char* filename, IDirectSoundBuffer8** secondaryBuffer)
{
	int error;
	FILE* filePtr;
	unsigned int count;
	WaveHeaderType waveFileHeader;
	WAVEFORMATEX waveFormat;
	DSBUFFERDESC bufferDesc;
	HRESULT result;
	IDirectSoundBuffer* tempBuffer;
	unsigned char* waveData;
	unsigned char *bufferPtr;
	unsigned long bufferSize;



우선 .wav 파일을 열고 파일의 헤더 정보를 읽어들입니다. 헤더에는 오디오 파일의 각종 정보가 있어서 이를 이용하여 데이터를 담을 2차 버퍼를 생성할 수 있습니다. 오디오 파일의 헤더에는 또한 데이터가 얼마나 크고 어느 지점부터 시작하는지에 대한 정보도 있습니다. 보면 아시겠지만 여기서는 모든 태그들을 읽어서 오디오 파일이 손상되지 않고 RIFF나 WAVE, fmt, data, WAVE_FORMAT_PCM들을 포함한 올바른 오디오 파일인지 확인합니다. 또한 몇 가지 작업을 더 하여 44.1KHz 스테레오 16bit 오디오 파일인지 확인합니다. 만약 이 파일이 22.1KHz이거나 8비트이거나 등등 다른 경우라면 이 파일은 요구사항에 정확히 일치하지 않으므로 실패하게 됩니다.


	// Open the wave file in binary.
	error = fopen_s(&filePtr, filename, "rb");
	if(error != 0)
	{
		return false;
	}
 
	// Read in the wave file header.
	count = fread(&waveFileHeader, sizeof(waveFileHeader), 1, filePtr);
	if(count != 1)
	{
		return false;
	}
 
	// Check that the chunk ID is the RIFF format.
	if((waveFileHeader.chunkId[0] != 'R') || (waveFileHeader.chunkId[1] != 'I') || 
	   (waveFileHeader.chunkId[2] != 'F') || (waveFileHeader.chunkId[3] != 'F'))
	{
		return false;
	}
 
	// Check that the file format is the WAVE format.
	if((waveFileHeader.format[0] != 'W') || (waveFileHeader.format[1] != 'A') ||
	   (waveFileHeader.format[2] != 'V') || (waveFileHeader.format[3] != 'E'))
	{
		return false;
	}
 
	// Check that the sub chunk ID is the fmt format.
	if((waveFileHeader.subChunkId[0] != 'f') || (waveFileHeader.subChunkId[1] != 'm') ||
	   (waveFileHeader.subChunkId[2] != 't') || (waveFileHeader.subChunkId[3] != ' '))
	{
		return false;
	}
 
	// Check that the audio format is WAVE_FORMAT_PCM.
	if(waveFileHeader.audioFormat != WAVE_FORMAT_PCM)
	{
		return false;
	}
 
	// Check that the wave file was recorded in stereo format.
	if(waveFileHeader.numChannels != 2)
	{
		return false;
	}
 
	// Check that the wave file was recorded at a sample rate of 44.1 KHz.
	if(waveFileHeader.sampleRate != 44100)
	{
		return false;
	}
 
	// Ensure that the wave file was recorded in 16 bit format.
	if(waveFileHeader.bitsPerSample != 16)
	{
		return false;
	}
 
	// Check for the data chunk header.
	if((waveFileHeader.dataChunkId[0] != 'd') || (waveFileHeader.dataChunkId[1] != 'a') ||
	   (waveFileHeader.dataChunkId[2] != 't') || (waveFileHeader.dataChunkId[3] != 'a'))
	{
		return false;
	}



여기까지 왔다면 웨이브 파일의 헤더가 충분히 검증된 상태이므로 데이터를 복사할 2차 버퍼를 생성할 수 있습니다. 1차 버퍼와 비슷하게 우선 웨이브 포맷을 설정하고 버퍼의 description을 작성합니다. 1차버퍼가 아니기 때문에 dwFlag나 dwBufferBytes는 그 값이 조금 다릅니다.


	// Set the wave format of secondary buffer that this wave file will be loaded onto.
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = 44100;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nChannels = 2;
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;
 
	// Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = waveFileHeader.dataSize;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = &waveFormat;
	bufferDesc.guid3DAlgorithm = GUID_NULL;



2차 버퍼를 생성하는 방법은 상당히 이상합니다. 우선 버퍼의 description을 가지고 임시적으로 IDirectSoundBuffer 객체를 만듭니다. 이것이 성공하면 QueryInterface 함수를 IID_IDirectSoundBuffer8인자와 함께 호출하여 이 임시 버퍼로 IDirectSoundBuffer8의 2차 버퍼를 만들게 됩니다. 이것까지 성공하면 이제서야 임시 버퍼를 해제하고 2차 버퍼를 사용하게 됩니다.


	// Create a temporary sound buffer with the specific buffer settings.
	result = m_DirectSound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL);
	if(FAILED(result))
	{
		return false;
	}
 
	// Test the buffer format against the direct sound 8 interface and create the secondary buffer.
	result = tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&*secondaryBuffer);
	if(FAILED(result))
	{
		return false;
	}
 
	// Release the temporary buffer.
	tempBuffer->Release();
	tempBuffer = 0;



2차 버퍼가 준비되었다면 오디오 파일의 웨이브 데이터를 가져옵니다. 우선 메모리 버퍼에 불러들여서 필요하다면 적절히 수정을 가합니다. 그 뒤에 2차 버퍼를 잠궈 접근 권한을 취득한 뒤, memcpy를 사용하여 데이터를 복사하고 잠금을 해제합니다. 이제 2차 버퍼는 사용할 준비가 되었습니다. 기억해 둘 것은 2차 버퍼를 잠글 때 두 개의 포인터를 받아 쓴다는 것입니다. 버퍼가 원형으로 구성되어 있기 때문에 중간부터 버퍼를 쓰게 되는 경우 그 지점에서의 버퍼 사이즈를 아는 것이 필요하기 때문입니다. 그래야 버퍼의 범위를 넘어가지 않게 쓰기 작업을 할 수 있습니다. 이런 구조는 오디오를 재생하는 것과 같은 일에 유용합니다. 이 튜토리얼에서는 작업을 간단하게 하기 위해 오디오 파일과 같은 크기의 버퍼를 만들고 버퍼의 시작부터 파일을 기록하게 합니다.


	// Move to the beginning of the wave data which starts at the end of the data chunk header.
	fseek(filePtr, sizeof(WaveHeaderType), SEEK_SET);
 
	// Create a temporary buffer to hold the wave file data.
	waveData = new unsigned char[waveFileHeader.dataSize];
	if(!waveData)
	{
		return false;
	}
 
	// Read in the wave file data into the newly created buffer.
	count = fread(waveData, 1, waveFileHeader.dataSize, filePtr);
	if(count != waveFileHeader.dataSize)
	{
		return false;
	}
 
	// Close the file once done reading.
	error = fclose(filePtr);
	if(error != 0)
	{
		return false;
	}
 
	// Lock the secondary buffer to write wave data into it.
	result = (*secondaryBuffer)->Lock(0, waveFileHeader.dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0);
	if(FAILED(result))
	{
		return false;
	}
 
	// Copy the wave data into the buffer.
	memcpy(bufferPtr, waveData, waveFileHeader.dataSize);
 
	// Unlock the secondary buffer after the data has been written to it.
	result = (*secondaryBuffer)->Unlock((void*)bufferPtr, bufferSize, NULL, 0);
	if(FAILED(result))
	{
		return false;
	}
	
	// Release the wave data since it was copied into the secondary buffer.
	delete [] waveData;
	waveData = 0;
 
	return true;
}


ShutdownWaveFile 함수는 2차 버퍼를 해제하는 일만을 합니다.


void SoundClass::ShutdownWaveFile(IDirectSoundBuffer8** secondaryBuffer)
{
	// Release the secondary sound buffer.
	if(*secondaryBuffer)
	{
		(*secondaryBuffer)->Release();
		*secondaryBuffer = 0;
	}

	return;
}



PlayWaveFile 함수는 2차 버퍼에 저장되어 있는 오디오 파일을 재생합니다. Play 함수에서 1차 버퍼의 오디오와의 믹싱을 자동으로 수행한 뒤 재생합니다. 여기서는 2차 버퍼의 시작 부분으로 시작 위치를 지정하였습니다. 그렇지 않으면 가장 최근에 재생되었던 부분부터 시작하게 됩니다. 그리고 소리를 조정할 수 있게 세팅하였으므로 음량을 최대로 맞춰 봅니다.


bool SoundClass::PlayWaveFile()
{
	HRESULT result;
 
 
	// Set position at the beginning of the sound buffer.
	result = m_secondaryBuffer1->SetCurrentPosition(0);
	if(FAILED(result))
	{
		return false;
	}
 
	// Set volume of the buffer to 100%.
	result = m_secondaryBuffer1->SetVolume(DSBVOLUME_MAX);
	if(FAILED(result))
	{
		return false;
	}
 
	// Play the contents of the secondary sound buffer.
	result = m_secondaryBuffer1->Play(0, 0, 0);
	if(FAILED(result))
	{
		return false;
	}
 
	return true;
}







Systemclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
 
 
///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN
 
 
//////////////
// INCLUDES //
//////////////
#include <windows.h>
 
 
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"


SoundClass 클래스의 헤더 파일을 인클루드합니다.


#include "soundclass.h"
 
 
////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();
 
	bool Initialize();
	void Shutdown();
	void Run();
 
	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
 
private:
	void Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();
 
private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;
 
	InputClass* m_Input;
	GraphicsClass* m_Graphics;



SoundClass 클래스 타입의 전용 변수를 선언합니다.


	SoundClass* m_Sound;
};
 
 
/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 
 
/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;
 
 
#endif





Systemclass.cpp


이전 튜토리얼과 달라진 부분을 중점으로 설명하겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"
 
 
SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;



생성자에서 SoundClass 객체를 null로 초기화합니다.


	m_Sound = 0;
}
 
 
bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}



SoundClass 객체를 생성하고 초기화하여 사용할 준비를 합니다. 이 튜토리얼에서는 초기화와 동시에 파일을 재생할 것입니다.


	// Create the sound object.
	m_Sound = new SoundClass;
	if(!m_Sound)
	{
		return false;
	}
 
	// Initialize the sound object.
	result = m_Sound->Initialize(m_hwnd);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize Direct Sound.", L"Error", MB_OK);
		return false;
	}
 
	return true;
}
 
 
void SystemClass::Shutdown()
{



SystemClass::Shutdown 함수에서 SoundClass의 Shutdown함수를 호출하고 할당 해제합니다.


	// Release the sound object.
	if(m_Sound)
	{
		m_Sound->Shutdown();
		delete m_Sound;
		m_Sound = 0;
	}
 
	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}






마치면서


이제 엔진에서 Direct Sound의 기본적인 기능을 지원합니다. 프로그램을 시작하게 되면 하나의 웨이브 파일을 재생합니다.









연습 문제


1. 프로그램을 다시 컴파일하고 스테레오로 웨이브 파일을 재생하는지 확인해 보십시오. esc 키를 눌러 윈도우를 닫습니다.


2. sound01.wav 파일을 여러분의 44.1KHz 16bit 2채널의 오디오 파일로 바꾸고 다시 프로그램을 실행해 보십시오.


3. 두 개의 웨이브 파일을 불러 동시에 재생하도록 프로그램을 고쳐 보십시오.


4. 음악을 한번만 재생하는 것이 아니라 반복되도록 해 보십시오.







소스 코드


Visual Studio 2008 프로젝트: dx11tut14.zip


소스 코드: dx11src14.zip


실행 파일: dx11exe14.zip

Nav