
In the following tutorials I am going to introduce you to DirectXAudio. It starts with the initialization of DirectXAudio and later tutorials will cover loading and playing of 2D-sounds up to managing several 3D-sound sources.
In previous DirectX versions DirectSound and DirectMusic were two discrete components. Since DirectX 8, DirectMusic interfaces present all necessary components for loading and playing all sounds. DirectSound is still there to manipulate sound buffers on the low-level but that is not done here.
This tutorial is based on DirectX Graphics Tutorial 01: Create a Window so that the window created there is used to demonstrate certain audio features. In this tutorial the window remains empty. A new CAudio class is created to initialize and manage the DirectX audio objects. Creating an object of this class will automatically initialize all audio components. Later, other member functions for manipulating audio features will be added. Destroying the object closes down the audio interfaces and unloads them from memory.
The project consists of the files listed below. The caudio.* files are new and contain the CAudio class, the other files are taken from the first D3D tutorial and I have only listed the changes made to them. Do not forget to link dxguid.lib to your project.
| File | Description |
|---|---|
| caudio.h | audio class header |
| caudio.cpp | audio class source file |
The DirectX and audio class headers are added. The title has to be changed. At last the main audio object is declared extern for use in all source code files.
//includes #include<dmusici.h> //DirectXAudio header (NEW) #include<windows.h> #include"capplication.h" #include"caudio.h" //audio class header (NEW) //constants #define TITLE "DXAudio Tut 01: Initialization" //globals extern bool g_bRunning; extern CApplication g_App; extern CAudio g_Audio; //extern audio object (NEW)
Only the audio object has to be added here.
//globals bool g_bRunning = true; CApplication g_App; CAudio g_Audio; //global audio object (NEW)
I have replaced PeekMessage() by GetMessage() as it halts the application if there are no messages in the
queue and does not block other programs. However, in a game PeekMessage() is the right choice so that the application is running
continuously and can update the game even if there are no window messages.
while(g_bRunning) { if(GetMessage(&Message,NULL,0,0)) //process window messages (NEW) { TranslateMessage(&Message); DispatchMessage(&Message); } }
#include"main.h" LRESULT CALLBACK WindowProcedure(HWND hWindow, UINT uMessage, WPARAM wparam, LPARAM lparam) { switch(uMessage) { case WM_KEYDOWN: { switch(wparam) { case VK_ESCAPE: { DestroyWindow(hWindow); break; } } return false; } case WM_DESTROY: { g_bRunning = false; PostQuitMessage(0); return false; } } return DefWindowProc(hWindow,uMessage,wparam,lparam); }//WindowProcedure
Only InitWindow() has changed in this class. The background color is set to the standard windows color.
bool CApplication::InitWindow(void) { WNDCLASSEX WindowClass; WindowClass.cbSize = sizeof(WNDCLASSEX); WindowClass.style = CS_HREDRAW | CS_VREDRAW; WindowClass.lpfnWndProc = WindowProcedure; WindowClass.cbClsExtra = 0; WindowClass.cbWndExtra = 0; WindowClass.hInstance = GetModuleHandle(NULL); WindowClass.hIcon = NULL; WindowClass.hCursor = NULL; WindowClass.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); //color (NEW) WindowClass.lpszMenuName = NULL; WindowClass.lpszClassName = "ClassName"; WindowClass.hIconSm = LoadIcon(NULL,IDI_APPLICATION); if(!RegisterClassEx(&WindowClass)) { MessageBox(m_hWindow, "RegisterClassEx() failed!", "CreateApplicationWindow()", MB_OK); return false; }
A different window behaviour and style (WS_OVERLAPPEDWINDOW) is passed to CreateWindowEx(). Furthermore, the
size of the window has changed. ShowCursor(false) has been removed because the application is not in fullscreen mode and the windows
cursor might be useful;-)
if(!(m_hWindow = CreateWindowEx(0, "ClassName", TITLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,0,320,240, NULL,NULL, GetModuleHandle(NULL),NULL))) { MessageBox(m_hWindow, "CreateWindowEx() failed!", "CreateApplicationWindow()", MB_OK); return false; } return true; }//CreateApplicationWindow
Now the preparation is finished and the real work starts. At first, the header constant is defined to prevent the header from being included more than once.
#ifndef caudio_h #define caudio_h
Here the class CAudio is defined. It has a constructor which calls InitAudio() to initialize DirectXAudio.
Then a default search directory can be set. this is needed to load sound files. The destructor calls KillAudio() which releases all
interfaces.
class CAudio { public: CAudio(); //standard constructor ~CAudio(); //destructor void InitAudio(); //initializes DXAudio void SetSearchDirectory(const char*); //search directory void KillAudio(); //release DXAudio interfaces
Then two private objects are declared. The performance object is the most important one when using DirectXAudio. It handles anything related with playback. The loader object is responsible for loading sound files.
private: IDirectMusicPerformance8* m_pPerformance; //performance object IDirectMusicLoader8* m_pLoader; //loader object }; #endif
At the beginning main.h is included. The constructor sets the performance and the loader object to NULL and then calls
InitAudio().
CAudio::CAudio()
{
m_pPerformance = NULL;
m_pLoader = NULL;
InitAudio();
}//CAudioThe destructor just calls KillAudio().
CAudio::~CAudio()
{
KillAudio();
}//~CAudioSince DXAudio is all COM (Component Object Model) it must be initialized first otherwise any other function will fail. To get the
performance and loader objects CoCreateInstance() is used. The first parameter is the class identifier (CLSID) of the
object to be retrieved. The fourth parameter contains a reference to the identifier and the last one is the address of the variable that receives
the object.
Then the performance object is used to initialize the performance by calling InitAudio(). In the first two parameters it
is possible to specify own IDirectMusic and IDirectSound objects. Passing NULL in both means that they are created and used
internally by the performance. The next parameter is our window handle. A default audiopath is created with the following argument and 64 channels
are allocated to it. DMUS_AUDIOF_ALL enables all audio features and the last parameter means that default values are used for the
synthesizer.
void CAudio::InitAudio() { CoInitialize(NULL); //initialize COM CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&m_pLoader); //create loader object CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**)&m_pPerformance); //create performance object m_pPerformance->InitAudio(NULL, NULL, NULL, DMUS_APATH_DYNAMIC_STEREO, 64, DMUS_AUDIOF_ALL, NULL); //initialize DirectMusic & DirectSound
Here the current directory is retrieved, the subdirectory sounds is appended and then
SetSearchDirectory() is called.
char szSearchPath[MAX_PATH]; GetCurrentDirectory(MAX_PATH,szSearchPath); strcat(szSearchPath,"//sounds"); SetSearchDirectory(szSearchPath); }//InitAudio
The default serch directory string has to be a wide char so MultiByteToWideChar() converts our standard string to wide
char. Then this directory is set for all music types through the loader object.
void CAudio::SetSearchDirectory(const char* szPath) { WCHAR wszSearchPath[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszSearchPath,MAX_PATH); //convert to wide char m_pLoader->SetSearchDirectory(GUID_DirectMusicAllTypes, wszSearchPath, false); //set as default search path }//SetSearchDirectory
When the audio object is destroyed KillAudio() is called. At first the loader object is released from memory. The
performance object has to be closed down before it can be released. At last COM is uninitialized.
void CAudio::KillAudio() { m_pLoader->Release(); //release loader object m_pPerformance->CloseDown(); //close down performance object m_pPerformance->Release(); //release performance object CoUninitialize(); //uninitialize COM }//KillAudio
That is all for now. DXAudio has been initialized and is prepared to play sounds. I have left out any error handling code in this project to keep it simple. The next tutorial covers loading and playing of 2D-sounds.