[DirectX11] Tutorial 14 - Direct Sound
Tutorial 14 - Direct Sound
원문 : http://www.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를 살펴보겠습니다. 이 클래스는 DirectSound 기능과 .wav 오디오로드 및 재생 기능을 캡슐화합니다.
Soundclass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #pragma once class SoundClass { private: 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(); bool Initialize(HWND); void Shutdown(); private: bool InitializeDirectSound(HWND); void ShutdownDirectSound(); bool LoadWaveFile(char*, IDirectSoundBuffer8**); void ShutdownWaveFile(IDirectSoundBuffer8**); bool PlayWaveFile(); private: IDirectSound8* m_DirectSound = nullptr; IDirectSoundBuffer* m_primaryBuffer = nullptr; IDirectSoundBuffer8* m_secondaryBuffer1 = nullptr; }; | cs |
Soundclass.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | #include "stdafx.h" #include "SoundClass.h" SoundClass::SoundClass() { } SoundClass::SoundClass(const SoundClass& other) { } SoundClass::~SoundClass() { } bool SoundClass::Initialize(HWND hwnd) { // Initialize direct sound and the primary sound buffer. if (!InitializeDirectSound(hwnd)) { return false; } // Load a wave audio file onto a secondary buffer. if (!LoadWaveFile("../Dx11Demo_14/data/sound01.wav", &m_secondaryBuffer1)) { return false; } return PlayWaveFile(); } void SoundClass::Shutdown() { // Release the secondary buffer. ShutdownWaveFile(&m_secondaryBuffer1); // Shutdown the Direct Sound API. ShutdownDirectSound(); } bool SoundClass::InitializeDirectSound(HWND hwnd) { // Initialize the direct sound interface pointer for the default sound device. if (FAILED(DirectSoundCreate8(NULL, &m_DirectSound, NULL))) { return false; } // Set the cooperative level to priority so the format of the primary sound buffer can be modified. if (FAILED(m_DirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY))) { return false; } // Setup the primary buffer description. DSBUFFERDESC bufferDesc; 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. if (FAILED(m_DirectSound->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL))) { return false; } // 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). WAVEFORMATEX waveFormat; 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. if (FAILED(m_primaryBuffer->SetFormat(&waveFormat))) { return false; } return true; } 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; } } bool SoundClass::LoadWaveFile(char* filename, IDirectSoundBuffer8** secondaryBuffer) { // Open the wave file in binary. FILE* filePtr = nullptr; int error = fopen_s(&filePtr, filename, "rb"); if (error != 0) { return false; } // Read in the wave file header. WaveHeaderType waveFileHeader; unsigned int 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; } // Set the wave format of secondary buffer that this wave file will be loaded onto. WAVEFORMATEX waveFormat; 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. DSBUFFERDESC bufferDesc; bufferDesc.dwSize = sizeof(DSBUFFERDESC); bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME; bufferDesc.dwBufferBytes = waveFileHeader.dataSize; bufferDesc.dwReserved = 0; bufferDesc.lpwfxFormat = &waveFormat; bufferDesc.guid3DAlgorithm = GUID_NULL; // Create a temporary sound buffer with the specific buffer settings. IDirectSoundBuffer* tempBuffer = nullptr; if (FAILED(m_DirectSound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL))) { return false; } // Test the buffer format against the direct sound 8 interface and create the secondary buffer. if (FAILED(tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&*secondaryBuffer))) { return false; } // Release the temporary buffer. tempBuffer->Release(); tempBuffer = 0; // 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. unsigned char* 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. unsigned char* bufferPtr = nullptr; unsigned long bufferSize = 0; if (FAILED((*secondaryBuffer)->Lock(0, waveFileHeader.dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0))) { 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. if (FAILED((*secondaryBuffer)->Unlock((void*)bufferPtr, bufferSize, NULL, 0))) { return false; } // Release the wave data since it was copied into the secondary buffer. delete[] waveData; waveData = 0; return true; } void SoundClass::ShutdownWaveFile(IDirectSoundBuffer8** secondaryBuffer) { // Release the secondary sound buffer. if (*secondaryBuffer) { (*secondaryBuffer)->Release(); *secondaryBuffer = 0; } } bool SoundClass::PlayWaveFile() { // Set position at the beginning of the sound buffer. if (FAILED(m_secondaryBuffer1->SetCurrentPosition(0))) { return false; } // Set volume of the buffer to 100%. if (FAILED(m_secondaryBuffer1->SetVolume(DSBVOLUME_MAX))) { return false; } // Play the contents of the secondary sound buffer. if (FAILED(m_secondaryBuffer1->Play(0, 0, 0))) { return false; } return true; } | cs |
Systemclass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #pragma once class InputClass; class GraphicsClass; class SoundClass; class SystemClass { public: SystemClass(); SystemClass(const SystemClass&); ~SystemClass(); bool Initialize(); void Shutdown(); void Run(); LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM); private: bool Frame(); void InitializeWindows(int&, int&); void ShutdownWindows(); private: LPCWSTR m_applicationName; HINSTANCE m_hinstance; HWND m_hwnd; InputClass* m_Input = nullptr; GraphicsClass* m_Graphics = nullptr; SoundClass* m_Sound = nullptr; }; static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static SystemClass* ApplicationHandle = 0; | cs |
Systemclass.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | #include "stdafx.h" #include "inputclass.h" #include "graphicsclass.h" #include "soundclass.h" #include "systemclass.h" SystemClass::SystemClass() { } SystemClass::SystemClass(const SystemClass& other) { } SystemClass::~SystemClass() { } bool SystemClass::Initialize() { // 윈도우 창 가로, 세로 넓이 변수 초기화 int screenWidth = 0; int screenHeight = 0; // 윈도우 생성 초기화 InitializeWindows(screenWidth, screenHeight); // m_Input 객체 생성. 이 클래스는 추후 사용자의 키보드 입력 처리에 사용됩니다. m_Input = new InputClass; if (!m_Input) { return false; } // m_Input 객체 초기화 if(!m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight)) { MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK); return false; } // m_Graphics 객체 생성. 그래픽 랜더링을 처리하기 위한 객체입니다. m_Graphics = new GraphicsClass; if (!m_Graphics) { return false; } // m_Graphics 객체 초기화. if(!m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd)) { return false; } // Create the sound object. m_Sound = new SoundClass; if(!m_Sound) { return false; } // Initialize the sound object. if(!m_Sound->Initialize(m_hwnd)) { MessageBox(m_hwnd, L"Could not initialize Direct Sound.", L"Error", MB_OK); return false; } return true; } void SystemClass::Shutdown() { // Release the sound object. if(m_Sound) { m_Sound->Shutdown(); delete m_Sound; m_Sound = 0; } // m_Graphics 객체 반환 if (m_Graphics) { m_Graphics->Shutdown(); delete m_Graphics; m_Graphics = 0; } // m_Input 객체 반환 if (m_Input) { m_Input->Shutdown(); delete m_Input; m_Input = 0; } // Window 종료 처리 ShutdownWindows(); } void SystemClass::Run() { // 메시지 구조체 생성 및 초기화 MSG msg; ZeroMemory(&msg, sizeof(MSG)); // 사용자로부터 종료 메시지를 받을때까지 메시지루프를 돕니다 while (true) { // 윈도우 메시지를 처리합니다 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // 종료 메시지를 받을 경우 메시지 루프를 탈출합니다 if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { // 그 외에는 Frame 함수를 처리합니다. if (!Frame()) { MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK); break; } } // 사용자가 ESC키를 눌렀는지 확인 후 종료 처리함 if(m_Input->IsEscapePressed() == true) { break; } } } bool SystemClass::Frame() { int mouseX = 0, mouseY = 0; // 입력 프레임 처리를 수행합니다 if(!m_Input->Frame()) { return false; } // 그래픽 객체의 Frame을 처리합니다 if(!m_Graphics->Frame()) { return false; } return m_Graphics->Render(); } LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { return DefWindowProc(hwnd, umsg, wparam, lparam); } void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight) { // 외부 포인터를 이 객체로 지정합니다 ApplicationHandle = this; // 이 프로그램의 인스턴스를 가져옵니다 m_hinstance = GetModuleHandle(NULL); // 프로그램 이름을 지정합니다 m_applicationName = L"Dx11Demo_14"; // windows 클래스를 아래와 같이 설정합니다. WNDCLASSEX wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = m_hinstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hIconSm = wc.hIcon; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = m_applicationName; wc.cbSize = sizeof(WNDCLASSEX); // windows class를 등록합니다 RegisterClassEx(&wc); // 모니터 화면의 해상도를 읽어옵니다 screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); int posX = 0; int posY = 0; // FULL_SCREEN 변수 값에 따라 화면을 설정합니다. if (FULL_SCREEN) { // 풀스크린 모드로 지정했다면 모니터 화면 해상도를 데스크톱 해상도로 지정하고 색상을 32bit로 지정합니다. DEVMODE dmScreenSettings; memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth; dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight; dmScreenSettings.dmBitsPerPel = 32; dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // 풀스크린으로 디스플레이 설정을 변경합니다. ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); } else { // 윈도우 모드의 경우 800 * 600 크기를 지정합니다. screenWidth = 800; screenHeight = 600; // 윈도우 창을 가로, 세로의 정 가운데 오도록 합니다. posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2; posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2; } // 윈도우를 생성하고 핸들을 구합니다. m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP, posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL); // 윈도우를 화면에 표시하고 포커스를 지정합니다 ShowWindow(m_hwnd, SW_SHOW); SetForegroundWindow(m_hwnd); SetFocus(m_hwnd); } void SystemClass::ShutdownWindows() { // 풀스크린 모드였다면 디스플레이 설정을 초기화합니다. if (FULL_SCREEN) { ChangeDisplaySettings(NULL, 0); } // 창을 제거합니다 DestroyWindow(m_hwnd); m_hwnd = NULL; // 프로그램 인스턴스를 제거합니다 UnregisterClass(m_applicationName, m_hinstance); m_hinstance = NULL; // 외부포인터 참조를 초기화합니다 ApplicationHandle = NULL; } LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam) { switch (umessage) { // 윈도우 종료를 확인합니다 case WM_DESTROY: { PostQuitMessage(0); return 0; } // 윈도우가 닫히는지 확인합니다 case WM_CLOSE: { PostQuitMessage(0); return 0; } // 그 외의 모든 메시지들은 시스템 클래스의 메시지 처리로 넘깁니다. default: { return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam); } } } | cs |
출력 화면
제대로 컴파일 되어 실행이 되었다면 아래와 같은 간단한 효과음을 들을 수 있습니다.
마치면서
이제 엔진에서 Direct Sound의 기본적인 기능을 지원합니다. 프로그램을 시작하게 되면 하나의 웨이브 파일을 재생합니다.
연습문제
1. 프로그램을 다시 컴파일하고 스테레오로 웨이브 파일을 재생하는지 확인해 보십시오. esc 키를 눌러 윈도우를 닫습니다.
2. sound01.wav 파일을 여러분의 44.1KHz 16bit 2채널의 오디오 파일로 바꾸고 다시 프로그램을 실행해 보십시오.
3. 두 개의 웨이브 파일을 불러 동시에 재생하도록 프로그램을 고쳐 보십시오.
4. 음악을 한번만 재생하는 것이 아니라 반복되도록 해 보십시오.
소스코드
소스코드 : Dx11Demo_14.zip
'DirectX 11 > Basic' 카테고리의 다른 글
[DirectX11] Tutorial 16 - 프러스텀 컬링 (5) | 2017.12.12 |
---|---|
[DirectX11] Tutorial 15 - FPS, CPU 사용량, 타이머 (1) | 2017.12.10 |
[DirectX11] Tutorial 13 - Direct Input (0) | 2017.12.09 |
[DirectX11] Tutorial 12 - 글꼴 엔진 (0) | 2017.12.08 |
[DirectX11] Tutorial 11 - 2D 렌더링 (1) | 2017.12.07 |