首页 / 软件开发 / .NET编程技术 / UI前沿技术 – WPF应用程序中的MIDI音乐
UI前沿技术 – WPF应用程序中的MIDI音乐2011-07-24 MSDN Charles Petzold每一台 PC 都包含一个内置的 16 人乐队,可播放一些音乐。人们不容易注意此乐队的成员,因为它 们表示的可能是 Windows 所支持的声音和视频功能阵列中利用最不充分的组件。此 16 人乐队是在符合 MIDI(乐器数字接口)标准的硬件或软件中实现的电子音乐合成器。在 Win32 API 中,以单词 midiOut 开头的函数支持使用 MIDI 合成器播放音乐。MIDI 支持不是 .NET Framework 的一部分,但如果要在 Windows 窗体或 Windows Presentation Foundation (WPF) 应用程序中访问此 MIDI 合成器,则需要使用 P/Invoke 或外部库。我在上一期专栏文章中讨论的 CodePlex 上的 NAudio 声音库中提供了 MIDI 支持,发现这一点时, 我感到非常高兴。您可以从 codeplex.com/naudio 下载该库及其源代码。在本文中,我使用的是 NAudio 1.3.8 版。简单示例MIDI 可视为波形音频的高级接口,用于处理乐器和乐音。MIDI 标准形成于 20 世纪 80 年代早期。电子音乐合成器制造商希望通过标准方法来连接电子音乐控 制器(如键盘)和合成器,因此开发出一种系统,通过具有 5 针接头的电缆以 3,125 字节/秒的速度传 输短消息(长度大多为 1、2 或 3 个字节)。其中两条最重要的消息称为 Note On 和 Note Off。当音乐家按下 MIDI 键盘的某个键时,键盘生成 一条 Note On 消息,指示所按的乐音和键的速度。合成器通过演奏该乐音进行响应,通常键速度越高, 声音越大。音乐家释放键时,键盘生成 Note Off 消息,生成器通过关闭乐音进行响应。没有实际音频数 据通过 MIDI 电缆。尽管 MIDI 仍用于连接电子音乐硬件,它还是可以完全通过软件在 PC 中使用。声卡可以包含 MIDI 合成器,Windows 本身完全通过软件模拟 MIDI 合成器。若要在使用 NAudio 库的 WinForms 或 WPF 应用程序中访问该合成器,请将 NAudio.dll 添加为引用 ,并在源代码中包含以下 using 指令:using NAudio.Midi;假设应用程序需要演奏单个 1 秒长的乐音,声音类似于钢琴的中央 C。使用以下代码可实现这一功能 :MidiOut midiOut = new MidiOut(0);
midiOut.Send(MidiMessage.StartNote(60, 127, 0).RawData);
Thread.Sleep(1000);
midiOut.Send(MidiMessage.StopNote(60, 0, 0).RawData);
Thread.Sleep(1000);
midiOut.Close();
midiOut.Dispose();
PC 可能拥有多个 MIDI 合成器的访问权限;MidiOut 构造函数的参数是一个数字 ID,用于选择要打 开的 MIDI 合成器。如果 MIDI 输出设备已被使用,该构造函数将引发异常。程序可以先使用静态 MidiOut.NumberOfDevices 属性发现存在多少合成器,从而获取有关 MIDI 合成 器的信息。数字 ID 的范围从 0 到设备数减 1。静态 MidiOut.DeviceInfo 方法接受数字 ID,返回一个 描述合成器的 MidiOutCapabilities 类型的对象。(我不准备使用这些功能,在本文后面部分,我只使 用 ID 为 0 的默认 MIDI 合成器。)MidiOut 类的 Send 方法向 MIDI 合成器发送一条消息。MIDI 消息包含 1、2 或 3 个字节,但 Win32 API(和 Naudio)需要将其打包为一个 32 位整数。MidiMessage.StartNote 和 MidiMessage.StopNote 方法执行此打包操作。Send 的两个参数可分别替换为 0x007F3C90 和 0x00003C80。StartNote 和 StopNote 的第一个参数是 0 到 127 范围内的代码,用于指示实际乐音,其中值 60 是中央 C。高八度是 72。低八度是 48。第二个参数是按下或释放键的速度。(合成器通常会忽略释放速 度。)这些参数在 0 到 127 范围内。MidiMessage.StartNote 的第二个参数越小,乐音越柔和。(我很 快会讨论第三个参数。)对 Thread.Sleep 的两次调用会将线程挂起 1,000 毫秒。这是用来确定消息时间的很简单的方法,但 应避免在用户界面线程中使用。为了使乐音在被 Close 调用突然截断之前消失,需要第二次调用 Sleep 。