开发游戏音频程序——播放MIDI文件

时间:2021-08-03 17:27:14

 

开发游戏音频程序——播放MIDI文件

       在沉寂了一段时间之后。我开始开发游戏的音频程序了。今天我要为大家展示的是我写的播放MIDI的程序,其实不要以为MIDI的载入和播放很复杂,其实它是相当的简单、只需要选用合适的解码器就行了。现在又几种解码器可以选择。MCI和DirectMusic。其中MCI是windows特别对多媒体应用程序提供的API集合,它可以播放很多流行的格式。但是对MIDI的控制性比较低。而DirectMusic则对MIDI有着较丰富的支持,最典型的就是可以控制节奏(tempo)。

       我的代码没有对DirectMusic进行封装,使用C的风格制作的。所以大家看起来很好理解。下面我就来展示我写的代码吧。

 

Main.h

/*---------------------------------------------------------------------------

蒋轶民制作E-mail:jiangcaiyang123@163.com

最后编辑:年月日:42:29

文件名:main.h

作用:头文件的定义

----------------------------------------------------------------------------*/

/*-----------------------------头文件---------------------------------------*/

#include <dmusici.h>

#include <dmusicf.h>

#include "CWaveFile.h"

 

// 库文件

#pragma comment( lib, "dxguid.lib" )

/*-----------------------------全局变量-------------------------------------*/

IDirectMusicLoader8* g_pLoader                          = NULL;

IDirectMusicSegment8* g_pSegment                   = NULL;

IDirectMusicPerformance8* g_pPerformance           = NULL;

IDirectMusicSegmentState* g_pSegmentState          = NULL;

IDirectMusicAudioPath8* g_pAudioPath               = NULL;

/*------------------------获取控制台窗口句柄----------------------------*/

HWND GetConsoleWindowHandle( void )

{

     TCHAR title[512];

     HWND hWnd;

 

     GetConsoleTitle( title, sizeof( title ) );

     hWnd = FindWindow( NULL, title );

     return( hWnd );

}

/*------------------------初始化----------------------------------------*/

void Initialize( void )

{

     HWND hConsoleWnd = GetConsoleWindowHandle();

     HRESULT hr;// 结果的句柄

 

     // 1、初始化COM组件

     hr = CoInitialize( NULL );

     ThrowIfFailed( hr, "1、初始化COM组件错误。" );

 

     // 2、创建装载器

     hr = CoCreateInstance( CLSID_DirectMusicLoader, NULL,

         CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&g_pLoader );

     ThrowIfFailed( hr, "2、创建装载器错误" );

 

     // 3、创建演艺对象

     hr = CoCreateInstance( CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,

         IID_IDirectMusicPerformance8, (void**)&g_pPerformance );

     ThrowIfFailed( hr, "3、创建演艺对象错误" );

 

     // 4、初始化音频

     hr = g_pPerformance->InitAudio( NULL, NULL, hConsoleWnd,

         DMUS_APATH_DYNAMIC_STEREO, 128, DMUS_AUDIOF_ALL, NULL );

     if ( hr == DSERR_NODRIVER )

     {

         MessageBox( hConsoleWnd, TEXT( "程序无法定位音频硬件,请确认硬件是否支持DirectX 8。" ), TEXT( "4、初始化音频" ), MB_ICONSTOP );

         return;

     }

     else ThrowIfFailed( hr, "初始化音频错误。" );

 

     return;// 正确返回

}

/*--------------------------释放内存------------------------------------*/

void UnInitialize( void )

{

#define SAFE_RELEASE( p ) if ( p ) { p->Release(); p = NULL; }

     if ( g_pPerformance )

     {

         g_pPerformance->Stop( NULL, NULL, 0, 0 );

         g_pPerformance->CloseDown();

         SAFE_RELEASE( g_pPerformance );

     }

     SAFE_RELEASE( g_pLoader );

}

/*--------------------------载入声音------------------------------------*/

void LoadSound( TString filename )

{

     HRESULT hr;// 结果的句柄

 

     // 载入文件对象

     hr = g_pLoader->LoadObjectFromFile( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8,

         (WCHAR*)filename.c_str(), (void**)&g_pSegment );

     ThrowIfFailed( hr, "载入文件对象错误。" );

}

/*--------------------------------------------------------------------------*/

bool LoadCWaveFile( CWaveFile& obj )               // 载入CWaveFile数据

{

     HRESULT hr;                                             // 结果的句柄

     if ( obj.GetData() == 0 )

         Throw( "PlayWaveFile:无法获取数据(是不是忘记载入声音文件?)" );

 

     // 设置DMUS_OBJECTDESC结构

     DMUS_OBJECTDESC desc;

     desc.dwSize = sizeof( DMUS_OBJECTDESC );

     desc.dwValidData = DMUS_OBJ_MEMORY | DMUS_OBJ_CLASS;

     desc.guidClass = CLSID_DirectMusicSegment;

     desc.llMemLength = obj.GetSize();

     desc.pbMemData = obj.SaveData();

 

     hr = g_pLoader->GetObject( &desc, IID_IDirectMusicSegment8, (void**)&g_pSegment );

     ThrowIfFailed( hr, "PlayWaveFile:无法获取对象。" );

 

     return true;

}

/*--------------------------------------------------------------------------*/

bool Play( void )                                       // 播放波形文件

{

     //g_pSegment->Download( g_pPerformance );

 

     if ( !g_pSegment || !g_pPerformance ) return false;// 无法播放

     g_pSegment->Download( g_pPerformance );

     g_pPerformance->PlaySegmentEx( g_pSegment, NULL, NULL,

         DMUS_SEGF_SECONDARY, 0, (IDirectMusicSegmentState **)&g_pSegmentState, NULL, g_pAudioPath );

 

     return true;

}

/*--------------------------检测声音是否正在播放------------------------*/

bool IsPlaying( void )

{

     if ( !g_pSegment || !g_pPerformance ) return false;

     return g_pPerformance->IsPlaying( NULL, g_pSegmentState ) == S_OK;

}

/*--------------------------加载资源文件-------------------------------*/

bool LoadWaveResource( HMODULE hMod, TString strResName, WORD resID )

{

     HRESULT hr;                                             // 结果的句柄

 

     // 载入资源

     HRSRC hRes = FindResource( hMod, MAKEINTRESOURCE( resID ), strResName.c_str() );

     if ( hRes == NULL ) Throw( "LoadWaveResource:无法找到资源。" );

     HGLOBAL hGlb = LoadResource( hMod, hRes );

     if ( hGlb == NULL ) Throw( "LoadWaveResource:无法载入资源。" );

 

     // 设置DMUS_OBJECTDESC结构

     DMUS_OBJECTDESC desc;

     desc.dwSize = sizeof( DMUS_OBJECTDESC );

     desc.dwValidData = DMUS_OBJ_MEMORY | DMUS_OBJ_CLASS;

     desc.guidClass = CLSID_DirectMusicSegment;

     desc.llMemLength = SizeofResource( hMod, hRes );

     desc.pbMemData = (LPBYTE)LockResource( hGlb );

 

     hr = g_pLoader->GetObject( &desc, IID_IDirectMusicSegment8, (void**)&g_pSegment );

     ThrowIfFailed( hr, "PlayWaveFile:无法获取对象。" );

 

     return true;

}

/*--------------------------设置全局音量-----------------------------------*/

bool SetGlobalVolume( long decibel )

{

     HRESULT hr;                                             // 结果的句柄

     long volume = decibel * 100;                       // 音量在全局中是分贝的倍

     hr = g_pPerformance->SetGlobalParam( GUID_PerfMasterVolume, &volume, sizeof( decibel ) );

     ThrowIfFailed( hr, "SetGlobalVolume:设置全局声音错误。" );

 

     return true;

}

/*--------------------------设置音量---------------------------------------*/

bool SetVolume( long decibel, DWORD dwDuration )

{

     HRESULT hr;                                             // 结果的句柄

     long volume = decibel * 100;                       // 音量在全局中是分贝的倍

 

     // 获得音频的路径,用于设置音量

     /*-------------------------------

     hr = g_pPerformance->GetDefaultAudioPath( &g_pAudioPath );// 获取默认的音频路径

     ThrowIfFailed( hr, "Initialize:无法获取默认音频路径。" );

     /*-------------------------------*/

     IUnknown* pConfig;

     hr = g_pSegment->GetAudioPathConfig( &pConfig );

     if ( hr != DMUS_E_NO_AUDIOPATH_CONFIG )// 无法获取音频的配置

     {

         ThrowIfFailed( hr, "Initialize:在获取音频配置的时候遇到了未知的错误。" );

         hr = g_pPerformance->CreateAudioPath( pConfig, TRUE, &g_pAudioPath );

         ThrowIfFailed( hr, "Initialize:无法创建音频的路径。" );

     }

 

     // 手动创建一个音频的路径

     hr = g_pPerformance->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_STEREO, 128/*通道数*/, TRUE, &g_pAudioPath );

     ThrowIfFailed( hr, "Initialize:创建音频路径错误。" );

     /*-------------------------------*/

 

     g_pSegment->Download( g_pAudioPath );

 

     // 设置音量

     hr = g_pAudioPath->SetVolume( volume, dwDuration );

     ThrowIfFailed( hr, "SetVolume:设置声音错误。" );

 

     return true;

}

/*--------------------------设置播放的速度(对MIDI有效)-------------------*/

bool SetTempo( int iTempo )

{

     HRESULT hr;

     DMUS_TEMPO_PARAM tempo;

     tempo.dblTempo = iTempo;

     hr = g_pSegment->SetParam( GUID_TempoParam, 0xFFFF, 0, 0, &tempo );

     ThrowIfFailed( hr, "SetTempo:设置参数错误。" );

 

     // 发送消息

     DMUS_TEMPO_PMSG* pTempo;

     hr = g_pPerformance->AllocPMsg( sizeof(DMUS_TEMPO_PMSG), (DMUS_PMSG**)&pTempo );

     ThrowIfFailed( hr, "SetTempo:分配信息空间错误。" );

 

     // 设置消息结构体

     ZeroMemory( pTempo, sizeof(DMUS_TEMPO_PMSG) );

     pTempo->dwSize = sizeof(DMUS_TEMPO_PMSG);

     pTempo->dblTempo = iTempo;

     pTempo->dwFlags = DMUS_PMSGF_REFTIME;

     pTempo->dwType = DMUS_PMSGT_TEMPO;

     hr = g_pPerformance->SendPMsg( (DMUS_PMSG*)pTempo );

     ThrowIfFailed( hr, "SetTempo:发送信息错误。" );

 

     return true;

}

/*--------------------------获取播放的速度(对MIDI有效)-------------------*/

int GetCurrentTempo( void )

{

     HRESULT hr;

 

     DMUS_TEMPO_PARAM tempo;

     hr = g_pSegment->GetParam( GUID_TempoParam, 0xFFFF, 0, 0, NULL, &tempo );

     ThrowIfFailed( hr, "GetCurrentTempo:获取参数错误。" );

     return int(tempo.dblTempo);

}

 

Main.cpp

/*---------------------------------------------------------------------------

蒋轶民制作E-mail:jiangcaiyang123@163.com

最后编辑:年月日:29:06

文件名:main.cpp

作用:头文件的实现

----------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/

// 头文件

#include <iostream>

#include <conio.h>

#include "main.h"

 

using namespace std;

 

int main( int, char** )

{   

     try

     {

         cout << "初始化设备\n";

         Initialize();// 初始化DirectMusic

 

         cout << "载入声音\n";

         LoadSound( TEXT( "test.mid" ) );               // 载入声音文件

 

         cout << "播放声音\n";

         g_pSegment->SetRepeats( NULL );                    // 仅仅播放一次

         SetVolume( 0, 0 );

         Play();

         Sleep( 1000 );

         SetTempo( 160 );

         cout << "当前播放的速率为" << GetCurrentTempo() << '\n';

         while ( IsPlaying() );

 

         UnInitialize();                                   cout << "释放设备\n";

     }

 

     catch( CError& e )

     {

         e.SaveToFile();

         cout << "错误报告已保存。\n";

     }

     catch( ... )

     {

         cout << "遇到了未知的错误。\n";

     }

 

     return 0;

}

 

大家可以下载我这个资源,以便完全掌握MIDi的载入和播放方法。

http://download.csdn.net/source/3571213

另外我使用的DirectX SDK的版本是2006年八月的。从下面的链接中可以下载。

http://down.gougou.com/down?cid=8B28FF668DD328D9B3A20A7ED1D3476EFC5AB0D6#com_anchor" target="_blank">http://down.gougou.com/down?cid=8B28FF668DD328D9B3A20A7ED1D3476EFC5AB0D6#com_anchor