用DirectX Audio和DirectShow播放声音和音乐(6)
DirectMusic加载器在使用固有文件或者MIDI文件的时候会自动加载默认的音色库。乐器总是被一组一组地使用,很多组乐器音色的集合被称之为DLS音色库(可下载的音乐)。每组乐器使用三个值编号,它们是:最高有效位(most-significant byte,MSB),最低有效位(least-significant byte,LSB)和组编号。
通常播放MIDI文件的乐器组是标准化的,也就是说编号为1的乐器总是钢琴,如果想使用新的钢琴作为乐器,可以从DLS集合中加载。DirectMusic包含了标准的乐器集合,通常称之为GM/GS集合(GM = General MIDI,GS = General Synthesizer),这个集合由日本罗兰(Roland)公司提出,称为MIDI合成器标准。
如果使用新的乐器取代标准MIDI乐器库中的乐器,需要确定该乐器的MSB和LSB为0,否则就需要为乐器库中的每个乐器都尝试新的赋值,以免打乱乐器库乐器排列。如果只想修改音色库中的一对乐器,只需要将它们保存在乐器库中即可。在下次加载DLS的时候,就会自动用新修改的乐器覆盖住内存中的旧乐器。当DLS加载完成的时候,就可以通知DirectMusic使用音色库对音乐进行播放了。
加载DLS音色库,需要从加载器中获取一个IDirectMusicCollection8对象,然后再次使用IDirectMusicLoader8::GetObject加载音色库,但是这一次指定的是音色库对象和音色库文件名。
以下代码演示了如何加载指定的音色库:
// Load DirectMusic collection object.
//--------------------------------------------------------------------------------
IDirectMusicCollection8* Load_DLS_Collection(char* filename)
{
DMUS_OBJECTDESC dm_obj_desc;
IDirectMusicCollection8* dm_collection;
// get the object
ZeroMemory(&dm_obj_desc, sizeof(DMUS_OBJECTDESC));
dm_obj_desc.dwSize = sizeof(DMUS_OBJECTDESC);
dm_obj_desc.guidClass = CLSID_DirectMusicCollection;
dm_obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);
// retrieves an object from a file or resource and returns the speficied interface
if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicCollection8, (LPVOID*)&dm_collection)))
return NULL;
return dm_collection;
}
音色库被加载后,还需要给音乐片段指定音色库,这个工作通过设置指定的音乐片段的音轨参数来完成,设置参数使用函数 IDirectMusicSegment8::SetParam。
The SetParam method sets data on a track inside this segment.
Syntax
HRESULT SetParam(
REFGUID rguidType,
DWORD dwGroupBits,
DWORD dwIndex,
MUSIC_TIME mtTime,
void* pParam
);
Parameters
rguidType
Reference to (C++) or address of (C) the type of data to set. See Standard Track Parameters.
dwGroupBits
Group that the desired track is in. Use 0xFFFFFFFF for all groups. For more information, see Identifying the Track.
dwIndex
Index of the track in the group identified by dwGroupBits in which to set the data, or DMUS_SEG_ALLTRACKS to set the parameter on all tracks in the group that contain the parameter.
mtTime
Time at which to set the data.
pParam
Address of a structure containing the data, or NULL if no data is required for this parameter. The structure must be of the appropriate kind and size for the data type identified by rguidType.
Return Values
If the method succeeds, the return value is S_OK.
If it fails, the method can return one of the error values shown in the following table.
Return code |
DMUS_E_SET_UNSUPPORTED |
DMUS_E_TRACK_NOT_FOUND |
E_POINTER |
MIDI的配置
一首歌曲如果已经和音色库一起被完整地加载到了内存中,这首音乐基本上已经可以使用了,唯一存在的问题是因为系统需要进行配置,以便适应一般MIDI文件的配置,所以需要告诉系统加载的文件是否是一个MIDI文件。告诉DirectMusic使用的是MIDI文件,需要再一次调用参数配置函数IDirectMusicSegment8:: SetParam。
以下代码演示了如何使用设置MIDI配置:
if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicSegment8, (LPVOID*)&g_dm_segment)))
return FALSE;
// setup midi playing
if(strstr(filename, ".mid"))
{
// set data on a track inside the segment
if(FAILED(g_dm_segment->SetParam(GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, NULL)))
return FALSE;
}
播放音乐的下一个准备工作就是配置音色库,将音色库装载到演奏器中,通过调用IDirectMusicSegment8:: Download函数来完成操作。
The Download method downloads band data to a performance or audiopath.
Syntax
HRESULT Download(
IUnknown* pAudioPath
);
Parameters
pAudioPath
Pointer to the IUnknown interface of the performance or audiopath that receives the data.
Return Values
If the method succeeds, the return value is S_OK or DMUS_S_PARTIALDOWNLOAD. See Remarks for IDirectMusicBand8::Download.
If it fails, the method may return one of the error values shown in the following table.
Return code |
DMUS_E_NOT_FOUND |
DMUS_E_TRACK_NOT_FOUND |
E_POINTER |
Remarks
All bands and waveform data in the segment are downloaded.
Always call IDirectMusicSegment8::Unload before releasing the segment.
仅当音乐文件为MIDI文件的时候,才需要调用这个函数,因为该函数会改变音乐文件的信息,如果在不适当的时候强制调用该函数,会导致主音轨改变或者丢失。如果要改变音色库(比如重新指定一个新的DLS给一个段音乐),必须首先卸载音色库数据,然后再加载新的音色库,并重新播放音乐。
以下代码示例了如何使用Download。
if(FAILED(g_dm_segment->Download(g_dm_performance)))
return FALSE;
当完成播放或者切换音色库时,必须调用一个卸载函数IDirectMusicSegment8::Unload,用它释放音色库及其他资源。
g_pDMSegment->Unload(g_pDS);
循环和重复
在播放之前最后一个要做的工作是设置重复点和重复次数,设置循环点是IDirectMusicSegment8::SetLoopPoints函数的职责。
The SetLoopPoints method sets the start and end points of the part of the segment that repeats the number of times set by the IDirectMusicSegment8::SetRepeats method.
Syntax
HRESULT SetLoopPoints(
MUSIC_TIME mtStart,
MUSIC_TIME mtEnd
);
Parameters
mtStart
Point at which to begin the loop.
mtEnd
Point at which to end the loop. A value of 0 loops the entire segment.
Return Values
If the method succeeds, the return value is S_OK.
If it fails, the method can return DMUS_E_OUT_OF_RANGE.
Remarks
When the segment is played, it plays from the segment start time until mtEnd, then loops to mtStart, plays the looped portion the number of times set by IDirectMusicSegment8::SetRepeats, and then plays to the end.
The default values are set to loop the entire segment from beginning to end.
The method fails if mtStart is greater than or equal to the length of the segment, or if mtEnd is greater than the length of the segment. If mtEnd is 0, mtStart must be 0 as well.
This method does not affect any currently playing segment states created from this segment.
The loop points of a cached segment persist even if the segment is released, and then reloaded. To ensure that a segment is not subsequently reloaded from the cache, call IDirectMusicLoader8::ReleaseObject on it before releasing it.
很多情况下,我们希望重复播放整首歌曲,这个时候就不需要使用SetLoopPoints函数。设置完循环点后,就可以设置重复次数了(当然,先设置重复次数也是可以的)。如果希望音乐播放一次后就停止,设置重复次数为0,如果希望将曲目播放两次,需要将重复次数设置为1。设置重复次数使用IDirectMusicSegment8::SetRepeats函数,这个函数只有一个参数,就是曲目重复的次数,如果将这个值设置为 DMUS_SEG_REPEAT_INFINITE,那么播放就会一直持续。
The SetRepeats method sets the number of times the looping portion of the segment is to repeat.
Syntax
HRESULT SetRepeats(
DWORD dwRepeats
);
Parameters
dwRepeats
Number of times that the looping portion of the segment is to repeat, or DMUS_SEG_REPEAT_INFINITE to repeat until explicitly stopped. A value of 0 specifies a single play with no repeats.
Return Values
The method returns S_OK.
播放和停止播放
如果要让演奏器播放音乐片段,只需要调用 IDirectMusicPerformance8::PlaySegmentEx函数就可以了。
The PlaySegmentEx method begins playback of a segment. The method offers greater functionality than IDirectMusicPerformance8::PlaySegment.
Syntax
HRESULT PlaySegmentEx(
IUnknown* pSource,
WCHAR *pwzSegmentName,
IUnknown* pTransition,
DWORD dwFlags,
__int64 i64StartTime,
IDirectMusicSegmentState** ppSegmentState,
IUnknown* pFrom,
IUnknown* pAudioPath
);
Parameters
pSource
Address of the IUnknown interface of the object to play.
pwzSegmentName
Reserved. Set to NULL.
pTransition
IUnknown interface pointer of a template segment to use in composing a transition to this segment. Can be NULL. See Remarks.
dwFlags
Flags that modify the method's behavior. See DMUS_SEGF_FLAGS.
i64StartTime
Performance time at which to begin playing the segment, adjusted to any resolution boundary specified in dwFlags. The time is in music time unless the DMUS_SEGF_REFTIME flag is set. A value of zero causes the segment to start playing as soon as possible.
ppSegmentState
Address of a variable that receives an IDirectMusicSegmentState interface pointer for this instance of the playing segment. Use QueryInterface to obtain IDirectMusicSegmentState8. The reference count of the interface is incremented. This parameter can be NULL if no segment state pointer is wanted.
pFrom
IUnknown interface pointer of a segment state or audiopath to stop when the new segment begins playing. If it is an audiopath, all segment states playing on that audiopath are stopped. This value can be NULL. See Remarks.
pAudioPath
IUnknown interface pointer of an object that represents the audiopath on which to play, or NULL to play on the default path.
Return Values
If the method succeeds, the return value is S_OK.
If it fails, the method can return one of the error values shown in the following table.
Return code |
DMUS_E_AUDIOPATH_INACTIVE |
DMUS_E_AUDIOPATH_NOPORT |
DMUS_E_NO_MASTER_CLOCK |
DMUS_E_SEGMENT_INIT_FAILED |
DMUS_E_TIME_PAST |
E_OUTOFMEMORY |
E_POINTER |
如果希望停止播放,只需要调用IDirectMusicPerformance8::Stop函数就可以了。
The Stop method stops playback of a segment or segment state.
This method has been superseded by IDirectMusicPerformance8::StopEx, which can stop playback of a segment, segment state, or audiopath.
Syntax
HRESULT Stop(
IDirectMusicSegment* pSegment,
IDirectMusicSegmentState* pSegmentState,
MUSIC_TIME mtTime,
DWORD dwFlags
);
Parameters
pSegment
Segment to stop playing. All segment states based on this segment are stopped at mtTime. See Remarks.
pSegmentState
Segment state to stop playing. See Remarks.
mtTime
Time at which to stop the segment, segment state, or both. If the time is in the past or if 0 is passed in this parameter, the specified segment and segment states stop playing immediately.
dwFlags
Flag that indicates when the stop should occur. Boundaries are in relation to the current primary segment. For a list of values, see IDirectMusicPerformance8::StopEx.
Return Values
If the method succeeds, the return value is S_OK.
If it fails, the method can return E_POINTER.
Remarks
If pSegment and pSegmentState are both NULL, all music stops, and all currently cued segments are released. If either pSegment or pSegmentState is not NULL, only the requested segment states are removed from the performance. If both are non-NULL and DMUS_SEGF_DEFAULT is used, the default resolution from the pSegment is used.
If you set all parameters to NULL or 0, everything stops immediately, and controller reset messages and note-off messages are sent to all mapped performance channels.
卸载音乐数据
使用完音乐之后,第一件要做的事就是卸载音色库数据,可以通过IDirectMusicSegment8::Unload来完成。
The Unload method unloads instrument data from a performance or audiopath.
Syntax
HRESULT Unload(
IUnknown *pAudioPath
);
Parameters
pAudioPath
Pointer to the IUnknown interface of the performance or audiopath from which to unload the instrument data.
Return Values
If the method succeeds, the return value is S_OK.
If it fails, the method can return one of the error values shown in the following table.
Return code |
DMUS_E_TRACK_NOT_FOUND |
E_POINTER |
Remarks
The method succeeds even if no data was previously downloaded.
还要释放加载器中的高速缓存数据,通过IDirectMusicLoader8::ReleaseObjectByUnknown来实现。
The ReleaseObjectByUnknown method releases the loader's reference to an object. This method is similar to IDirectMusicLoader8::ReleaseObject and is suitable for releasing objects for which the IDirectMusicObject8 interface is not readily available.
Syntax
HRESULT ReleaseObject(
IUnknown * pObject
);
Parameters
pObject
Address of the IUnknown interface pointer of the object to release.
Return Values
If the method succeeds, the return value is S_OK, or S_FALSE if the object has already been released or cannot be found in the cache.
If it fails, the method can return E_POINTER.
如果开始的时候加载了音色库,现在是时候从加载器对象中将它卸载掉了,就像刚才从加载器中卸载音乐片段对象一样。另外,清空缓存很重要,有一个函数可以强制清空整个缓存区,但是并不需要在卸载加载器前调用它,因为在卸载过程中这个操作是自动完成的。
The ClearCache method removes all saved references to a specified object type.
Syntax
HRESULT ClearCache(
REFGUID rguidClass
);
Parameters
rguidClass
Reference to (C++) or address of (C) the identifier of the class of objects to clear, or GUID_DirectMusicAllTypes to clear all types. For a list of standard loadable classes, see IDirectMusicLoader8.
Return Values
The method returns S_OK.
Remarks
This method clears all objects that are currently being held, but does not turn off caching. Use the IDirectMusicLoader8::EnableCache method to turn off automatic caching.
To clear a single object from the cache, call the IDirectMusicLoader8::ReleaseObject method.
以下给出一个完整的示例,来演示如何加载和播放MIDI音乐。
点击下载源码和工程
完整源码示例:
PURPOSE:
Midi Playing Demo
***************************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <dsound.h>
#include <dmusici.h>
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
#pragma warning(disable : 4996)
#define Safe_Release(p) if((p)) (p)->Release();
// window handles, class.
HWND g_hwnd;
char g_class_name[] = "MidiPlayClass";
IDirectSound8* g_ds; // directsound component
IDirectMusicPerformance8* g_dm_performance; // directmusic performance
IDirectMusicLoader8* g_dm_loader; // directmusic loader
IDirectMusicSegment8* g_dm_segment; // directmusic segment
//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}
//--------------------------------------------------------------------------------
// Load DirectMusic collection object.
//--------------------------------------------------------------------------------
IDirectMusicCollection8* Load_DLS_Collection(char* filename)
{
DMUS_OBJECTDESC dm_obj_desc;
IDirectMusicCollection8* dm_collection;
// get the object
ZeroMemory(&dm_obj_desc, sizeof(DMUS_OBJECTDESC));
dm_obj_desc.dwSize = sizeof(DMUS_OBJECTDESC);
dm_obj_desc.guidClass = CLSID_DirectMusicCollection;
dm_obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);
// retrieves an object from a file or resource and returns the speficied interface
if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicCollection8, (LPVOID*)&dm_collection)))
return NULL;
return dm_collection;
}
//--------------------------------------------------------------------------------
// Play midi file which specified with filename.
//--------------------------------------------------------------------------------
BOOL Play_Midi(char* filename)
{
DMUS_OBJECTDESC dm_obj_desc;
// get the object
ZeroMemory(&dm_obj_desc, sizeof(DMUS_OBJECTDESC));
dm_obj_desc.dwSize = sizeof(DMUS_OBJECTDESC);
dm_obj_desc.guidClass = CLSID_DirectMusicSegment;
dm_obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);
// retrieves an object from a file or resource and returns the speficied interface
if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicSegment8, (LPVOID*)&g_dm_segment)))
return FALSE;
// setup midi playing
if(strstr(filename, ".mid"))
{
// set data on a track inside the segment
if(FAILED(g_dm_segment->SetParam(GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, NULL)))
return FALSE;
}
// downloads band data to a performance
if(FAILED(g_dm_segment->Download(g_dm_performance)))
return FALSE;
// set to loop forever
g_dm_segment->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
// play on default audio path
g_dm_performance->PlaySegmentEx(g_dm_segment, NULL, NULL, 0, 0, NULL, NULL, NULL);
return TRUE;
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASS win_class;
MSG msg;
// create window class and register it
win_class.style = CS_HREDRAW | CS_VREDRAW;
win_class.lpfnWndProc = Window_Proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = DLGWINDOWEXTRA;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(inst, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
if(! RegisterClass(&win_class))
return FALSE;
// create the main window
g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_MIDIPLAY), 0, NULL);
ShowWindow(g_hwnd, cmd_show);
UpdateWindow(g_hwnd);
// initialize and configure directsound
// creates and initializes an object that supports the IDirectSound8 interface
if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
{
MessageBox(NULL, "Unable to create DirectSound object", "Error", MB_OK);
return 0;
}
// set the cooperative level of the application for this sound device
g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);
// initialize COM
//
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
// apartment (STA).
CoInitialize(0);
// create the DirectMusic performance object
//
// creates a single uninitialized object of the class associated with a specified CLSID.
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8,
(void**)&g_dm_performance);
// create the DirectMusic loader object
CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&g_dm_loader);
// initialize the performance with the standard audio path.
// this initialize both directmusic and directsound and sets up the synthesizer.
g_dm_performance->InitAudio(NULL, NULL, g_hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 128, DMUS_AUDIOF_ALL, NULL);
// tell directmusic where the default search path is
char path[MAX_PATH];
WCHAR search_path[MAX_PATH];
GetCurrentDirectory(MAX_PATH, path);
// maps a character string to a wide-character (Unicode) string
MultiByteToWideChar(CP_ACP, 0, path, -1, search_path, MAX_PATH);
// set a search path for finding object files
g_dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, search_path, FALSE);
// play midi
Play_Midi("escape.mid");
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// release directsound objects
if(g_dm_segment)
g_dm_segment->Unload(g_ds);
if(g_dm_loader)
g_dm_loader->ReleaseObjectByUnknown(g_dm_segment);
if(g_dm_segment)
g_dm_segment->Release();
if(g_ds)
g_ds->Release();
UnregisterClass(g_class_name, inst);
// release COM system
//
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
// resources that the thread maintains, and forces all RPC connections on the thread to close.
CoUninitialize();
return (int) msg.wParam;
}
运行截图: