MIDI 包装类
一篇介绍用于播放 MIDI 文件的 C++ 类的文章。
引言
MS DirectX 包含几个组件,它们基本上提供了多媒体编程的接口。DirectX 的音频部分由两个组件组成——DirectSound 和 DirectMusic。DirectSound 用于数字音乐,DirectMusic 用于合成音乐。这两种媒体在技术表示上有所不同,但在性能方面却非常相似。事实上,数字音乐的性能要更准确一些,可以说比合成音乐更“专业”。但在大多数情况下,这并不重要……但从编程的角度来看呢?DirectMusic 是一项庞大的技术,包含许多复杂的概念和技术。与使用 DirectMusic 相比,使用 DirectSound 要容易得多。学习 DirectMusic 编程不需要花上一个星期。
本文旨在介绍如何学习 DirectMusic 编程。特别是,我将重点介绍如何播放合成音乐文件,即 MIDI 文件。MIDI(Musical Instrument Device Interface,乐器数字接口)用于创建(编排)和播放合成音乐文件——.mid 文件。虽然编排与播放有很大不同,但我将向您介绍后者。编排非常复杂,需要一整本书才能完全讨论(甚至可能还不够)。
在这里,我的目的是介绍一些 C++ 类,它们将封装使用 DirectMusic 播放 .mid 文件所需的工作。我不会展示如何调用 CoCreateInstance
来创建 DirectMusic 组件对象以及如何使用它。相反,我将展示如何使用我准备好的类供您(以及我自己)使用。但您仍然可以查看包装类的代码,了解其中的工作原理。
背景
我一直喜欢创建 C++ 类,尤其是包装类。我一直喜欢将“坏”的东西变成“好”的东西。为了创造另一个好东西,我创建了四个类:
CDMusicPerf
CDMusicMIDI
CDMException
CDMusicMIDIHelper
我认为您可以通过查看这些类的代码来学习播放 MIDI 文件的底层代码。我在这些类的各种方法中添加了大量的注释来支持我的操作。现在,让我们看看这些类是什么。它们分布在四个文件中:
- DirectMusic.h / DirectMusic.cpp – 包含
CDMusicPerf
、CDMusicMIDI
和CDMusicMIDIHelper
类。 - DMEcxept.h / DMExcept.cpp – 包含
CDMException
类。
我首先想谈谈 CDMException
。这个类代表由 CDMusicPerf
和 CDMusicMIDI
类引发的异常;CDMusicMIDIHelper
不使用这个类。CDMException
派生自 MFC 类 CException
并重写了其方法。但您不必担心 CDMException
中的内容;您只需像这样使用 try
/catch
块即可:
try { // some code } catch (CDMException *e) { e->ReportError(); // you may omit this e->Delete(); // do not omit this }
接下来是 CDMusicPerf
。这个类代表 DirectMusic 接口 IDirectMusicPerformance8
。该接口充当管理器,为 DirectMusic 的其他接口提供基本服务。它用于添加和删除端口,将 performance channels 映射到端口,播放段,调度消息并将它们通过工具路由,设置和检索音乐参数等等。CDMusicPerf
的声明(简化)如下:
class CDMusicPerf { CComPtr<IDirectMusicPerformance8> m_pDMusicPerf; public: CDMusicPerf(); ~CDMusicPerf(); BOOL Create(…); void Destroy(); BOOL IsInitialized() const; void StopAll(); void CollectGarbage(); operator IDirectMusicPerformance8 *() { return m_pDMusicPerf; } };
请注意最后一个方法。事实上,这是一个类型转换运算符,它将 CDMusicPerf
对象转换为 IDirectMusicPerformance8
接口指针。这个特性稍后将被 CDMusicMIDI::Create
方法使用。我认为其他方法的名称足以说明它们的作用。因此,我将向您介绍另一个类……
接下来是 CDMusicMIDI
。严格来说,这个类代表 DirectMusic 接口 IDirectMusicSegment
。该接口代表一个段,一段可以播放的音乐。CDMusicMIDI
类提供了从磁盘驱动器加载 MIDI 文件、播放/停止、设置/获取速度等方法。它的一些方法如下:
class CDMusicMIDI { public: CDMusicMIDI(); ~CDMusicMIDI(); BOOL Create(IDirectMusicPerformance8 *pPerf); void Destroy(); BOOL LoadMIDI(LPCTSTR lpszFileName); BOOL IsInitialized() cons; BOOL Play(); BOOL Stop(); BOOL IsPlaying() const; void SetRepeats(DWORD dwRepeats); BOOL SetTempo(double dTempo); double GetTempo() const; };
这两个类足以提供 MIDI 播放功能。但是,有时我需要加载十多个 MIDI 文件。这是为一个游戏准备的,该游戏使用 .mid 文件进行音乐播放,并在某个播放文件结束时,另一个文件必须开始播放。创建十个 CDMusicMIDI
对象并单独管理它们很不方便。我觉得我需要一个数组来存储这些对象。我的需求促使我创建了一个类似数组的类,它能够以调用其方法的方式轻松访问这些对象。于是就诞生了 CDMusicMIDIHelper
。它的接口(简化)如下:
class CDMusicMIDIHelper { CDMusicMIDIHelper(); ~CDMusicMIDIHelper(); UINT Add(…); UINT Insert(…); BOOL Remove(…); void RemoveAll(); int GetSize() const; UINT GetIDByFileName(…) const; BOOL SetActiveID(…); UINT GetActiveID() const; CDMusicMIDI* GetMIDI(…); CDMusicMIDI& GetActiveMIDI(); BOOL Play(…); BOOL Stop(…); BOOL IsPlaying(…) const ; };
示例应用程序大量使用了这个类。查看其代码以了解更多信息。
使用代码
现在该看看这些类是如何使用的了。这并不难。您只需声明一个 CDMusicPerf
实例和一个或多个 CDMusicMIDI
实例:
#include "DirectMusic.h" #include "DMExcept.h" using namespace DirectMusic; class CMyDialog : public CDialog { CDMusicPerf m_perf; CDMusicMIDI m_midi; // ... };
您需要创建这些对象。首先,在 OnInitDialog
中创建 m_perf
:
m_perf.Create(GetSafeHwnd());
然后,您可以创建 CDMusicMIDI
对象。在这种情况下:
m_midi.Create(m_perf);
之后,加载一个 MIDI 文件:
m_midi.LoadMIDI( strFilePath );
现在,您可以播放该文件:
m_midi.Play(2); // play this song for two times
就是这样。我创建了一个演示应用程序,展示了这些类提供的许多功能。尽情享受吧!