使用 DirectMusic 进行音频播放处理






4.62/5 (11投票s)
DirectMidi 类库在音频处理方面的扩展
0. 目录
1. 介绍
本文重点介绍 DirectMidi 包装类库的最后一部分,该部分允许使用 DirectMusic - DirectX API 执行高级播放和混音音频功能。在第一部分中,我们了解了 DirectMusic 如何处理基本的 MIDI I/O 操作,例如输入和输出端口、从一个端口透传到另一个端口以及下载 DLS 乐器。在这一部分中,我将基本介绍如何通过 DirectMidi 类库使用 DirectMusic,以在同时同步多个音频波形文件、在空间中定位 MIDI 和波形序列、向演奏发送 MIDI 消息以及使用 DLS 乐器时获得完整的性能。
2. 音频播放 DirectMusic 架构
2.1 基本概念
像 MIDI Mapper 这样的基本系统 MIDI 端口的主要问题是音频通道数量限制为 16 个。此限制源于 MIDI 标准并扩展到现代应用程序。由于演奏通道的出现,DirectMusic 克服了此限制。演奏通道类似于 MIDI 通道,但它们几乎是无限的。每个演奏通道包含一个乐器,该乐器来自 MIDI 序列、DLS 乐器或波形。这些部分有自己的声像、音量和移调设置。此时我们可以说一个演奏通道属于一个 MIDI 通道和一个端口的通道组。从现在开始,在使用演奏对象时,我们将始终使用从零开始的演奏通道。下图解释了这种关系
演奏通道由演奏对象处理,这些对象负责将音频数据从源发送到合成器。演奏对象还处理时序、消息路由、工具管理和通知。与演奏和演奏通道相关的另一个重要对象是音频路径。音频路径可以看作是数据流经的对象链。应用程序可以访问这些对象中的任何一个。例如,您可以检索一个缓冲区对象来设置声源的 3D 属性,或者一个 DMO 效果来更改效果的参数。DirectMusic 应用程序中涉及的最后一个重要对象是片段。片段是封装序列化声音数据的对象。数据可以是 MIDI 序列、波形、来自 DirectMusic Producer 的片段文件中的信息集合,或者是运行时由不同组件组成的音乐片段。片段可以作为主要片段或次要片段播放。当作为主要片段播放时,一次只能播放此片段。次要片段通常是同时播放的短音乐音效或与主要片段同步播放的音效。
2.2 DirectMusic 中用于音频播放的主要 COM 接口
DirectMusic 是 DirectX 技术的一部分,它通过 COM 使用分布式对象编程。COM 是构建 ActiveX 控件和 OLE 的基本“对象模型”。COM 允许对象将其功能暴露给其他组件和宿主应用程序。它定义了对象如何暴露自身以及这种暴露如何跨进程和跨网络工作。COM 还定义了对象的生命周期。尽管 DirectMusic 使用这种技术,但不要感到不知所措,因为类库会自动处理这些情况。以下几行介绍了 DirectMusic 音频应用程序中涉及的最重要的接口
-
IDirectMusic8:IDirectMusic8 接口是 DirectMusic 应用程序中的主要对象。每个应用程序应该只有一个此接口实例。演奏需要此对象才能初始化。
-
IDirectMusicPerformance8:此接口负责管理播放。它用于添加和移除端口、映射
演奏通道 到端口、播放片段、分派消息并通过工具路由它们、请求和接收事件通知以及设置和检索各种参数。它还有几种方法可以获取时序信息以及将时间和音乐值 从一个系统转换为另一个系统。 -
IDirectMusicSegment8:它表示一个片段,它是由多个音轨组成的可播放数据单元。
-
IDirectMusicAudioPath8:此接口管理从演奏到最终混音器的数据流阶段。它还提供对 DMO 对象和 DirectSound 缓冲区(例如
IDirectSound3DBuffer8
)的引用。 -
IDirectSound3DBuffer8:它用于检索和设置描述声音缓冲区在 3D 空间中的位置、方向和环境的参数。
-
IDirectSound3DListener8:它用于检索和设置描述监听器在 3D 空间中的位置、方向和监听环境的参数。
3. 使用 DirectMIDI 开发音频应用程序
3.1 简介 - DirectMIDI 音频处理布局
与 DirectMidi 的 MIDI 部分一样,库中用于音频处理的核心基于其十一个相关类。下面将对它们进行评论
在图的左侧,我们可以看到 DirectMusic 应用程序的主要对象。CDirectMusic
类负责为 Win32 应用程序实例化 DirectMusic COM 对象。它初始化 DirectMusic 并创建或指定软件合成器所需的 DirectSound 对象。
第二个基本对象是 CMasterClock
,它执行所需硬件时钟的枚举和选择作为参考时钟。除非用户需要特定的时序配置,否则不使用 CMasterClock
。创建 CDirectMusic
对象后,我们可以继续初始化 DirectMusic 音频播放应用程序中涉及的两个最重要的对象。这些对象是 CPortPerformance
和 CAPathPerformance
,它们是 IDirectMusicPerformance8
接口的封装和操作抽象。CPortPerformance
管理使用系统端口(如 MPU-401 或需要添加到此对象的软件合成器)的性能。这些 CPortPerformance
对象既不允许音频路径也不允许 3D 定位。
另一方面,我们有 CAPathPerformance
,它允许管理使用默认软件合成器端口的性能。这些 CAudioPathPerformances
允许音频路径和 3D 定位,此外还可以使用 DMO 和混响、合唱等效果。为了在音频数据流上实现 3D 效果并获取 DMO,我们将需要 CAudioPath
对象,该对象可以通过使用 CAPathPerformance
方法来检索。
3D 定位所需的两个重要对象是 C3DBuffer
和 C3DListener
,它们分别是 IDirectSound3DBuffer8
和 IDirectSound3DListener
8
的抽象,并且可以从 CAudioPath
对象初始化。最后,我们有两个对象需要保留对由 IDirectMusicSegment8
表示的音频序列的引用。这些对象是 CSegment
和 C3DSegment
。这两个对象之间存在细微差别。CSegment
对象是音频文件的基本单元。此 CSegment
旨在在两种类型的性能中播放。另一方面,C3DSegment
对象是旨在与 3D 功能一起使用的序列所需对象的更高抽象或封装。因此,此 C3DObject
包含一个 CAudioPath
并内部继承自 C3DBuffer
和 C3DListener
。
3.2 创建应用程序
3.2.1 设置开发环境
DirectMidi 包装器可以用于 Visual Studio 向导提供的任何 Win32 应用程序。在下面示例中解释的源代码中,我将使用 Win32 控制台的“简单应用程序”。创建此应用程序的基本文件后,我们可以继续包含 DirectMusic 应用程序所需的头文件 (.h) 和源文件 (.cpp)。因此,例如,如果我们启动一个使用 DirectMidi 库的 Audio 部分的应用程序,我们将需要包含以下头文件:CDirectMidi.h、CDirectBase.h 和 CAudioPart.h。对于 MIDI 应用程序和 Audio 应用程序,我们总是需要包含前两个头文件。然后,根据我们需要的类,我们将必须包含该类的实现的相应 .cpp 文件。例如,如果我们使用 COutputPort 类,我们将需要包含 CMidiPort.cpp 和 COutputPort.cpp 源文件。例如,如果我们使用 CAPathPerformance
,我们将需要 CPerformance.cpp 和 CAPathPerformance.cpp 类。
除了 DirectMidi 类之外,我们还需要包含 DirectMidi 文件夹中 Dsutil 目录下的现有文件,因为它们包含用于波形文件处理的 DirectX 基本源代码实用程序。包含所有必需文件后,不带预编译头文件 (.pch) 编译整个项目非常重要,因为 DirectMidi 项目默认不支持预编译头文件。因此,转到您的 Visual Studio 6 并选择 Project -> Settings 并展开左侧树视图以查看您的项目文件。然后,选择 C/C++ 选项卡,单击组合框中的 Precompiled headers 并为项目中包含的所有文件标记 "Not using precompiled headers"。如果您使用 Visual Studio 7,过程类似:单击 Solution explorer 选项卡,选择所有列出的 .cpp 文件,然后右键单击。在属性中,选择 C/C++、Precompiled headers 并指定“Not Using Precompiled Headers”。最后,删除项目源中所有对“stdafx.h”头的引用。
为了避免在链接 DirectX 函数后出现未解析的外部符号,我们需要包含 DirectX 库和头文件的路径。为此,请转到菜单栏中的 Tools,选择 Options,然后单击 Directories 选项卡以添加 DirectX8/9 头文件和库文件的路径。如果您有 Visual Studio 7 (.NET),请转到菜单栏中的 Tools,单击 Options,然后打开 Projects 文件夹。展开 Show Directories for 组合列表,然后选择 library 和 include files 选项。最后,将头文件和库文件目录添加到各自的列表中。
3.2.2 主要对象
本节解释如何声明和初始化 DirectMidi 音频播放应用程序中涉及的基本对象。在下面的源代码中,您可以看到实例化 DirectMidi 对象的列表
// Main headers // ANSI I/0 headers #include <conio.h> #include <iostream> #include <math.h> // The class wrapper #include ".\\DirectMidi\\CDirectMidi.h" // Inline library inclusion #pragma comment (lib,"dxguid.lib") // guid definitions #pragma comment (lib,"winmm.lib") #pragma comment (lib,"dsound.lib") #pragma comment (lib,"dxerr9.lib") using namespace std; using namespace directmidi; int main(int argc, char* argv[]) { CDirectMusic CDMusic; CDLSLoader CLoader; COutputPort COutPort,COutPort2; CInputPort CInPort; CPortPerformance CPortPerformance; CAPathPerformance CAPathPerformance; CCollection CCollectionA; CInstrument CInstrument1; CAudioPath CAudioPath1; CSegment CSegment1; C3DSegment C3DSegment1; C3DBuffer C3DBuffer1;
在代码的第一行中,我们包含应用程序所需的 ANSI 和 DirectMidi 头文件。之后,我们继续使用 #pragma
预编译器指令指定将链接到应用程序中的库。了解 DirectMidi 库被包装在 directmidi 命名空间中非常重要。因此,您必须调用 using namespace directmidi
以告知编译器我们正在引用此逻辑分组。这些对象中的第一个是 CDirectMusic
,它负责初始化 DirectMusic 应用程序,并且将是最后一个被释放的对象。第二个主要对象是 CDLSLoader
,它管理音频序列和 DLS 文件的加载。接下来的两个重要对象是 COutPort
,它是将被添加到 CPortPerformance
对象中的 COutputPort
对象类型,以及 COutPort2
,它将保留对 CAPathPerformance
对象的内部输出端口的引用。除了这两个输出端口之外,我们还有一个 CInputPort
类型,用于处理从默认 MPU-401 端口到性能合成器的透传,这将在本文末尾解释。然后我们有两个应用程序中使用的性能对象,它们的区别之前已经解释过。此示例代码将使用 DLS 乐器,因此我们将需要一个用于从 DLS 文件中提取的乐器集合的容器,以及一个 CInstrument
对象的实例,以便将它们下载到性能中。此外,我们将需要一个 CAudioPath
对象来对数据流应用效果,以及一个 CSegment
和一个 C3DSegment
来分别播放立体声加混响和 3D 定位 MIDI 文件。最后,我们将通过将 C3DBuffer
对象与 CAudioPathPerformance
结合使用来实现 3D 音频定位。请注意,实例化顺序必须是从父对象到子对象,以便对象的后续销毁遵循 LIFO 准则,从而避免在子对象销毁之前销毁父对象。
3.2.3 启动演奏
正如我们之前所见,DirectMusic 应用程序所需的所有对象都已实例化。现在剩下的是以正确的顺序调用它们的方法来启动音乐输出。请看以下几行
try { /////////////////////////////// INITIALIZATION ////////////////////////// // Initializes DirectMusic CDMusic.Initialize(); // Initializes an audiopath performance CAPathPerformance.Initialize(CDMusic,NULL,NULL,DMUS_APATH_DYNAMIC_3D,128); // Initializes a port performance object CPortPerformance.Initialize(CDMusic,NULL,NULL); // Initializes loader object CLoader.Initialize(); // Initializes output port COutPort.Initialize(CDMusic); // Selects the first software synthesizer port INFOPORT PortInfo; DWORD dwPortCount = 0; do COutPort.GetPortInfo(++dwPortCount,&PortInfo); while (!(PortInfo.dwFlags & DMUS_PC_SOFTWARESYNTH)); cout << "Selected output port: " << PortInfo.szPortDescription << endl; COutPort.SetPortParams(0,0,0,SET_REVERB | SET_CHORUS,44100); COutPort.ActivatePort(&PortInfo); ///////////////////////// PLAYING A SEGMENT //////// // Adds the selected port to the performance CPortPerformance.AddPort(COutPort,0,1);
初始化 DirectMusic 后,它会继续初始化音频路径演奏。为此,它调用 CAPathPerformance::Initialize
方法,其中包含对已创建的 DirectMusic 对象的引用,两个表示要创建的 DirectSound 对象和用于创建 DirectSound 的 HWND
窗口句柄的 NULL 指针。设置好这三个初始参数后,我们现在可以指定应用程序中所需的默认音频路径类型。在本例中,我们选择 DMUS_APATH_DYNAMIC_3D
类型,它适用于 3D 音频应用程序。在上一行中,CPortPerformance
对象通过调用 CPortPerformance::Initialize
方法进行初始化,并传递对主 DirectMusic 对象的引用以及表示 DirectSound 和 HWND
处理程序参数的两个指针。如上面代码所示,我们选择并启动一个由名为 COutPort
的 COutputPort
对象类型处理的软件合成器端口。启动后,我们调用 CPortPerformance::AddPort
方法将端口添加到演奏中,并为其分配 16 个演奏通道块(0 代表通道 0-15,第一个参数),并将其映射到通道组(第二个参数)。
3.2.4 加载和播放片段
在下面的代码中,解释了如何加载 MIDI 文件序列并使用两种类型的演奏对象播放它。
// Loads a MIDI file into the segment CLoader.LoadSegment(_T(".\\media\\laststar.mid"),CSegment1,TRUE); // Repeats the segment ad infinitum CSegment1.SetRepeats(DMUS_SEG_REPEAT_INFINITE); // Downloads the segment to the performance CSegment1.Download(CPortPerformance); // Plays the segment CPortPerformance.PlaySegment(CSegment1); cout << "Playing a segment with the port performance." "Press a key to continue...\n" << endl; getch(); // Stops the playing segment CPortPerformance.Stop(CSegment1); // Downloads the segment to the audiopath performance CSegment1.Download(CAPathPerformance); // Plays the segment in the performance CAPathPerformance.PlaySegment(CSegment1,NULL); cout << "Playing a segment with the audiopath performance." " Press a key to continue...\n" << endl; getch(); // Stops the current playing segment CAPathPerformance.Stop(CSegment1);
使用 CDLSLoader::LoadSegment
方法,我们可以加载表示音频序列的 MIDI 或 WAVE (.WAV) 文件。我们只需要在第三个参数中指定文件的格式,其中 TRUE
布尔值表示我们正在尝试加载标准 MIDI 文件(不带附加 DLS 数据),FALSE
值表示我们将加载包含 DLS 的波形、片段或 MIDI 文件。此外,我们可以选择指定我们希望序列重复多少次。因此,如果我们将 CSegment::SetRepeats
与 DMUS_SEG_REPEAT_INFINITE
标志一起调用,序列将无限重复。如果使用软件合成器,则必须调用 CSegment::Download
才能将文件数据下载到性能合成器。当添加非软件合成器端口时,这不是必需的。一旦我们将序列下载到性能中,我们就可以通过使用 CPortPerformance::PlaySegment
和 CPortPerformance::Stop
方法分别播放或停止它。同样,我们可以通过使用相同的方法在 CAPathPerformance
上播放 CSegment,但这次,我们需要调用 CSegment::Download
。
3.2.5 3D 片段定位
本节的主要目的是解释 C3DSegment
的使用。它是一个一体化组件,用于在 3D 空间中播放声音。
C3DSegment1.Initialize(CAPathPerformance); // Clones the normal segment to a 3D segment C3DSegment1 = CSegment1; // Plays the 3D segment in the audiopath performance CAPathPerformance.PlaySegment(C3DSegment1); // Positions the 3D buffer one unit in the negative X-axis C3DSegment1.SetBufferPosition(-1.0,0.0,0.0); cout << "Playing a 3D segment on your left." " Press a key to continue...\n" << endl; getch(); // Positions the 3D buffer one unit in the positive X-axis C3DSegment1.SetBufferPosition(1.0,0.0,0.0); cout << "Playing a 3D segment on your right. " "Press a key to continue...\n" << endl; getch(); CAPathPerformance.Stop(C3DSegment1);
首先,C3DSegment 对象必须使用对要播放它的 AudiopathPerformance 对象的引用进行初始化。这将初始化其继承的 C3DBuffer 和 C3DListener 对象及其内部 AudioPath。C3DSegment1 = CSegment1
表达式在示例中不是必需的,因为您可以通过调用 CDLSLoader::LoadSegment
初始化 C3DSegment 对象;它只是为了说明目的而存在。原因在于 DirectMidi 支持 CSegment
基类和 C3DSegment
继承类的 "=" 运算符重载。它只是克隆音频数据序列并为新对象调用新的 IDirectMusicSegment
接口。初始化 C3DSegment
后,我们可以通过调用 C3DSegment::SetBufferPosition
来播放片段并定位其 DirectSound 3D 缓冲区。它将对播放的声音应用空间定位。
3.2.6 带有演奏的 MIDI 函数
DirectMidi 最重要的功能之一是能够使用 MIDI 命令和函数来应用不同的效果,例如 3D 定位,同时可以同时播放无限数量的音符。在接下来的几行中,我们可以看到基本情况。它显示了如何将 NOTE_ON
MIDI 消息发送到端口性能。
CPortPerformance.SendMidiMsg(NOTE_ON,64,127,1); cout << "Sending a MIDI note-on to the port " "performance on PChannel 1. Press a key to continue...\n" << endl; getch(); CPortPerformance.SendMidiMsg(NOTE_OFF,64,127,1);
CPortPerformance::SendMidiMsg
方法中的第四个参数是 PChannel 或演奏通道。在这里,分配给下载片段的 MIDI 通道和通道组的集合被映射。此 PChannel 包含特定的乐器和 MIDI 数据。因此,将来在使用演奏时,我们永远不会使用 MIDI 通道。
3.2.6.1 在 3D 空间中发送音符开启 (note-on)
以下简单的代码片段将 NOTE_ON 发送到音频路径演奏并将其定位在左侧。当向音频路径演奏发送 MIDI 消息时,我们必须向 CAudioPathPerformance::SendMidiMsg
提供一个额外的第五个参数,该参数指示我们正在使用的音频路径。在下面的示例中,我们使用 C3DSegment
对象内部的内部音频路径来控制 3D 定位。
C3DSegment1.SetBufferPosition(-1.0,0.0,0.0); // Sends a note-on with the 3D segment audiopath configuration CAPathPerformance.SendMidiMsg(NOTE_ON,64,127,0, C3DSegment1.GetAudioPath()); cout << "Playing a MIDI note-on with the audio path performance" " on your left. Press a key to continue...\n" << endl; getch(); CAPathPerformance.SendMidiMsg(NOTE_OFF,64,127,0, C3DSegment1.GetAudioPath());
3.2.6.2 在 3D 中加载和播放 DLS 乐器
以下代码解释了如何使用 DLS 乐器播放 NOTE_ON MIDI 命令并将其定位在 3D 空间中。它还介绍了一些新的 DirectMidi 方法来处理音频路径。见下文
/////////////// PLAYING A DLS INSTRUMENT IN 3D ///////////// // Gets the defult audiopath for the performance CAPathPerformance.GetDefaultAudioPath(CAudioPath1); // Gets a 3D buffer CAudioPath1.Get3DBuffer(C3DBuffer1); // Positions the 3D buffer one unit in the negative X-axis C3DBuffer1.SetBufferPosition(-1.0,0.0,0.0); // Unloads the segment from the audio path performance CSegment1.Unload(CAPathPerformance); // Loads the default GM/GS collection CLoader.LoadDLS(NULL,CCollectionA); // Gets the instrument 215 CCollectionA.GetInstrument(CInstrument1,215); // Assigns the patch 0 CInstrument1.SetPatch(0); // Set up the note range CInstrument1.SetNoteRange(0,127); DWORD dwGroup,dwMChannel; // Downloads the instrument to the performance channel 1 CAPathPerformance.DownloadInstrument(CInstrument1,1, &dwGroup,&dwMChannel); // Selects the patch and plays the note on PChannel 1 CAPathPerformance.SendMidiMsg(PATCH_CHANGE,0,0,1,CAudioPath1); CAPathPerformance.SendMidiMsg(NOTE_ON,64,127,1,CAudioPath1); cout << "Playing a GM/GS DLS instrument with " "the audio performance on your left. Press a key to coninue...\n" << endl; getch(); CAPathPerformance.SendMidiMsg(NOTE_OFF,64,0,1,CAudioPath1);
我们从这个示例部分开始介绍一种获取默认音频路径的新方法。这是 CAPathPerformance::GetDefaultAudioPath
方法,它检索内部创建的音频路径。一旦我们有了表示音频路径的对象的引用,我们就可以继续通过使用 CAudioPath::Get3DBuffer
方法获取将在应用程序中执行 3D 定位的 C3DBuffer
对象。如果我们要设置监听器在空间中的位置,我们将不得不调用 CAudioPath::Get3DListener
方法。在将 DLS 乐器下载到性能内存之前,我们必须从中卸载以前存储的片段数据。因此,我们调用 CSegment::Unload
方法来实现这一点。现在,我们可以加载 DLS 数据,但我们必须首先为 DLS 文件中的乐器列表提供一个容器,这就是 CCollectionA
对象。在调用 CDLSLoader::LoadDLS
并传递对 CCollection
对象的引用之后,我们通过调用 CCollection::GetInstrument
从中提取一个特定的乐器。之后,我们通过调用 CInstrument::SetPatch
并为其分配 0 MIDI 程序来为新乐器设置补丁号。最后,在继续下载乐器之前,我们应该使用 CInstrument::SetNoteRange
方法为乐器提供一个可播放的音符区域。现在,一切都准备好将乐器下载到性能中。执行此操作的方法是 CAPathPerformance::DownloadInstrument
,它与之前介绍的播放音频路径性能的方法没有太大区别。它只需要分配乐器的 PChannel 和两个 DWORD 来检索将存储它的通道组和 MIDI 通道。下载后,我们必须通过向性能发送带有 PChannel 和要设置的补丁号的 PATCH_CHANGE
消息来选择乐器的补丁。最后,我们可以通过在调用 CAPathPerformance::SendMidiMsg
方法时提供对默认音频路径 (CAudioPath1
) 的引用,在 3D 空间中播放音符。
3.2.6.3 波形样本乐器和透传到 3D 演奏
在这个例子中,我们将测试 DirectMusic 最惊人的功能之一。我们将从波形文件 (.WAV) 加载一个样本并将其下载到音频路径演奏,以便应用 3D 效果。之后,我们将激活一个输入端口以实现端口和演奏之间的透传连接,并使用外部键盘在空间中演奏。以下代码解释了如何实现它
1 //////////////// PLAYING A SAMPLE INSTRUMENT IN 3D /////////// 2 3 // Unloads the instrument from the audiopath performance 4 CAPathPerformance.UnloadInstrument(CInstrument1); 5 6 7 // Gets the port where the performance channel 2 resides 8 9 IDirectMusicPort8* pPort = NULL; 10 11 CAPathPerformance.PChannelInfo(2,&pPort,NULL,NULL); 12 13 // Initializes output port 14 15 COutPort2.Initialize(CDMusic); 16 17 // Activates ouput port from the interface 18 19 COutPort2.ActivatePortFromInterface(pPort); 20 21 CSampleInstrument CSample1; 22 23 // Loads the .wav file 24 25 CDLSLoader::LoadWaveFile(_T(".\\media\\starbreeze.wav"), CSample1,DM_USE_MEMORY); 26 27 // Assigns the patch 28 29 CSample1.SetPatch(2); 30 31 // Sets a continuous wave loop 32 33 CSample1.SetLoop(TRUE); 34 35 // Set additional wave parameters 36 37 CSample1.SetWaveParams(0,0,68,F_WSMP_NO_TRUNCATION); 38 39 REGION region; 40 ARTICPARAMS articparams; 41 42 // Initializes structures 43 44 ZeroMemory(®ion,sizeof(REGION)); 45 ZeroMemory(&articparams,sizeof(ARTICPARAMS)); 46 47 48 // Sets the region parameters 49 50 region.RangeKey.usHigh = 127; 51 region.RangeKey.usLow = 0; 52 region.RangeVelocity.usHigh = 127; 53 54 // Adjust LFO 55 56 articparams.LFO.tcDelay = TimeCents(10.0); 57 articparams.LFO.pcFrequency = PitchCents(5.0); 58 59 // Sets the pitch envelope 60 61 articparams.PitchEG.tcAttack = TimeCents(0.0); 62 articparams.PitchEG.tcDecay = TimeCents(0.0); 63 articparams.PitchEG.ptSustain = PercentUnits(0.0); 64 articparams.PitchEG.tcRelease = TimeCents(0.0); 65 66 67 // Sets the volume envelope 68 69 articparams.VolEG.tcAttack = TimeCents(1.275); 70 articparams.VolEG.tcDecay = TimeCents(0.0); 71 articparams.VolEG.ptSustain = PercentUnits(100.0); 72 articparams.VolEG.tcRelease = TimeCents(10.157); 73 74 75 // Sets the instrument parameters 76 77 CSample1.SetRegion(®ion); 78 CSample1.SetArticulationParams(&articparams); 79 80 // Allocate interface memory 81 82 COutPort2.AllocateMemory(CSample1); 83 84 // Download the sample instrument to the port 85 86 COutPort2.DownloadInstrument(CSample1); 87 88 89 // Positions the buffer 90 91 C3DBuffer1.SetBufferPosition(1.0,0.0,0.0); 92 93 // Selects patch and plays the note 94 95 CAPathPerformance.SendMidiMsg(PATCH_CHANGE,2,0,2,CAudioPath1); 96 97 CAPathPerformance.SendMidiMsg(NOTE_ON,64,127,2,CAudioPath1); 98 99 100 cout << "Playing a downloaded WAV file with the audio path performance on the right. Press a key to continue... \n" << endl; 101 102 getch(); 103 104 //// PLAYING A 3D SAMPLE INSTRUMENT WITH AN EXTERNAL KEYBOARD //// 105 106 CAPathPerformance.SendMidiMsg(NOTE_OFF,64,127,2,CAudioPath1); 107 108 // Initializes and activates default input MIDI port 109 110 CInPort.Initialize(CDMusic); 111 CInPort.GetPortInfo(1,&PortInfo); 112 CInPort.ActivatePort(&PortInfo); 113 114 // Finds out which group and MIDI channel are assigned to PChannel 2 115 116 CAPathPerformance.PChannelInfo(2,NULL,&dwGroup,&dwMChannel); 117 118 // Activates the thru over the retrieved MIDI channel and group 119 120 CInPort.SetThru(0,dwGroup,dwMChannel,COutPort2); 121 122 cout << "Activating MIDI thru from input " "MIDI channel 0 to PChannel 2.\n" \ 123 "Play with an external keyboard. Press a key to " "continue... \n" << endl; 124 125 getch();
首先,在开始样本加载之前,我们通过调用 CAPathPerformance::UnloadInstrument
卸载之前使用的 DLS 乐器。为了将样本下载到演奏,我们需要知道演奏正在使用哪个默认的 IDirectMusicPort8
来访问波形表内存。因此,我们通过调用 CAPathPerformance::PChannelInfo
并以 PChannel 号作为参数来查询此端口。此方法提供对与指定 PChannel 相关联的增强型 IDirectMusicPort8
接口指针的引用。一旦我们获得了 IDirectMusicPort8
接口指针,我们就可以通过调用 COutputPort::ActivatePortFromInterface
方法(第 19 行)激活一个 COutputPort
对象类型,从而在更高级别处理它。完成此重要过程后,我们可以继续实例化一个 CSampleInstrument
对象类型,以便使用 CDLSLoader::LoadWaveFile
方法加载波形文件。之后,我们按照第 29-78 行所示应用乐器参数,例如补丁号、统一键音符、循环、区域和发音。在将数据下载到端口波形表之前,我们需要为下载接口分配内存,因此我们将调用 COutputPort::AllocateMemory
方法。最后,我们可以使用 COutputPort::DownloadInstrument
方法下载 PCM 数据,并通过 PChannel 2 在 3D 空间中播放音符,如第 91-97 行所述。上面示例的第二部分解释了如何从默认输入端口激活 MIDI 透传到演奏。为此,我们需要激活一个 CInputPort
对象(第 110-112 行),并获取分配给 PChannel 2 的通道组和 MIDI 通道。一旦我们获得了这两个参数,我们就可以继续激活从外部 MIDI 端口中的 0 MIDI 通道到包含在调用 CAPathPerformance::PChannelInfo
中先前获得的 MIDI 通道和通道组的 PChannel 的透传。在这种情况下,它是数字 2。
3.2.7 高级音频功能
这最后一部分解释了如何通过使用 DirectX 提供的 DMO 应用高级音频 FX。DMO 代表 DirectX Media Object,是一个 COM 对象,用于处理客户端分配的缓冲区中的多媒体数据流。DMO 通常作为 dll 库中的 COM 对象实现并在系统中注册。如果您想了解更多信息,可以阅读本文的更多信息部分。在接下来的几行中,解释了如何将混响效果应用于下载的样本乐器。见下文
///////////////// RETRIEVING A STANDARD DMO OBJECT ////////////// // Releases the old audiopath handler CAudioPath1.ReleaseAudioPath(); // Removes the default audiopath CAPathPerformance.RemoveDefaultAudioPath(); // Creates a new audiopath CAPathPerformance.CreateAudioPath(CAudioPath1, DMUS_APATH_SHARED_STEREOPLUSREVERB,64,TRUE); // Declares a DMO interface pointer CComPtr<IDirectSoundFXWavesReverb8> pEffectDMO; // Gets the DMO CAudioPath1.GetObjectInPath(DMUS_PCHANNEL_ALL,DMUS_PATH_BUFFER_DMO, 1,GUID_All_Objects,0,IID_IDirectSoundFXWavesReverb8,(void**)&pEffectDMO); // Maximum reverberation DSFXWavesReverb FX; FX.fInGain = DSFX_WAVESREVERB_INGAIN_MAX; FX.fReverbMix = DSFX_WAVESREVERB_REVERBMIX_MAX; FX.fReverbTime = DSFX_WAVESREVERB_REVERBTIME_MAX; FX.fHighFreqRTRatio = DSFX_WAVESREVERB_HIGHFREQRTRATIO_MAX; pEffectDMO->SetAllParameters(&FX); cout << "Playing a sample on PChannel 2 with maximum reverberation.\n" \ "Play with an external keyboard. " "Press a key to end the application...\n" << endl; getch(); } catch (CDMusicException& DMExcp) { cout << "\n" << DMExcp.GetErrorDescription() << "\n" << endl; } return 0; }
在对性能应用效果之前,重要的是释放旧的音频路径,该音频路径已配置用于 3D 定位并且不允许检索 FX 接口。因此,我们调用 CAudioPath::ReleaseAudioPath
方法来删除对上一个音频路径的引用。释放后,我们可以继续创建一个共享其 FX 缓冲区的新的音频路径。因此,我们调用 CAPathPerformance::CreateAudioPath
方法,并以 DMUS_APATH_SHARED_STEREOPLUSREVERB
作为参数来创建它。在此之前应用效果,需要实例化一个由 CComPtr
ATL 模板处理的 IDirectSoundFXWavesReverb8
指针。此模板将自动释放 COM 接口引用。然后,我们可以通过调用 CAudioPath::GetObjectInPath
方法获取 DMO 对象的引用,其中 DMUS_PCHANNEL_ALL
参数用于搜索所有 PChannel,DMUS_PATH_BUFFER_DMO 参数用于获取缓冲区中的 DMO 对象,1 索引表示 DMO 所在的缓冲区,对象的类标识符,0 索引用于查找第一个匹配对象,以及 FX 接口指针。如果上一个方法调用返回成功,我们就可以通过填充 DSFXWavesReverb
成员中合适的参数并调用 IDirectSoundFXWavesReverb8::SetAllParameters
接口方法来执行它来应用效果。下表显示了可以从立体声加混响音频路径中检索到的不同 FX 接口
rguidInterface |
*ppObject |
描述 |
IID_IDirectSoundFXGargle8 | IDirectSoundFXGargle8 | Gargle (颤音) |
IID_IDirectSoundFXChorus8 | IDirectSoundFXChorus8 | Chorus (合唱) |
IID_IDirectSoundFXFlanger8 | IDirectSoundFXFlanger8 | Flanger (镶边) |
IID_IDirectSoundFXEcho8 | IDirectSoundFXEcho8 | Echo (回声) |
IID_IDirectSoundFXDistortion8 | IDirectSoundFXDistortion8 | Distortion (失真) |
IID_IDirectSoundFXCompressor8 | IDirectSoundFXCompressor8 | Compressor (压缩) |
IID_IDirectSoundFXParamEq8 | IDirectSoundFXParamEq8 | ParamEq (参数均衡器) |
IID_IDirectSoundFXWavesReverb8 | IDirectSoundFXWavesReverb8 | Reverb (混响) |
IID_IDirectSoundFXI3DL2Reverb8 | IDirectSoundFXI3DL2Reverb8 | I3DL2Reverb (I3DL2混响) |
3.2.8 异常处理
DirectMidi 类库的实现在源代码中对每次调用 DirectMusic COM 方法的地方都提供了断言和故障检测。这可以防止在发生错误调用时出现错误情况,无论是通过错误顺序调用函数还是通过提供不正确的参数(例如未初始化的变量)。查看 DirectMidi 包装器函数的设计,我们可以看到该方法提供了关于类成员函数、行和抛出异常的 HRESULT
描述的详尽信息
HRESULT CMasterClock::ActivateMasterClock(LPCLOCKINFO ClockInfo) { HRESULT hr = DM_FAILED; TCHAR strMembrFunc[] = _T("CMasterClock::ActivateMasterClock()"); if (!m_pMusic8 || !ClockInfo) throw CDMusicException( strMembrFunc,hr,__LINE__); if (FAILED(hr = m_pMusic8->SetMasterClock(ClockInfo->guidClock))) throw CDMusicException(strMembrFunc,hr,__LINE__); if (FAILED(hr = m_pMusic8->GetMasterClock( &ClockInfo->guidClock,&m_pReferenceClock))) throw CDMusicException(strMembrFunc,hr,__LINE__); return S_OK; }
因此,当我们通过对 CDMusicException
对象的引用捕获 DirectMidi 异常时,我们可以使用以下类成员获取有关错误的信息
* HRESULT m_hrCode: DirectMusic API 调用失败的 HRESULT 代码
* TCHAR m_strMethod: 包含生成错误的函数方法描述的 UNICODE/MBCS 字符串。
* INT m_nLine: 生成错误的模块中的行号。
* LPCTSTR GetErrorDescription(): 检索包含先前注释成员的完整错误描述。
* LPCTSTR ErrorToString(): 返回一个字符串,其中包含生成的错误的定义。
4. 更多信息
您可以在 Microsoft MSDN 网站或 DirectX SDK 文档中找到有关 DirectMusic 和 DirectX 的更多信息。如果您正在寻找有关 DirectMIDI 的更多信息,可以访问 sourceforge.net 网站上的 DirectMIDI 主页。有关 DMO 架构和实现的更多信息,请参阅此 MSDN 链接。
5. 演示应用程序
名为“虚拟岛屿”的演示应用程序是一个简单的 3D 查看器,灵感来自太平洋上一个遥远的失落岛屿。当您进入其中时,您可以听到周围所有的声音,例如海浪、海鸥和风。这个程序在现实世界中没有任何实际用途,但它对于检查 DirectMidi 的功能(如 3D 定位和音频路径)很有用。它完全用 C++ 编程,使用 OpenGL 函数和 DirectX,免费源代码可在 DirectMIDI 主页的贡献部分找到。
远方的海鸥飞向深蓝色的天空
从顶部俯瞰岛屿
6. 历史
MidiStation 特性 | DirectMidi 更改 |
MidiStation 1.4.4 特性
MidiStation 1.8.4 特性
MidiStation 1.9.0 特性
MidiStation 1.9.1 特性
MidiStation 1.9.2 特性
|
DirectMIDI 2.0b 更改
DirectMIDI 2.1b 更改
DirectMIDI 2.2b 更改
DirectMIDI 2.3b 更改
|