65.9K
CodeProject 正在变化。 阅读更多。
Home

MIDI 输出设置器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2009年5月10日

CPOL

3分钟阅读

viewsIcon

107032

downloadIcon

7663

允许更改 Vista 和 Windows 7 中的默认 MIDI 输出设备

概述

普通用户通常只有一个 MIDI 播放设备,即内置的软件合成器“Microsoft GS Wavetable Synth”,所有 MIDI 文件都通过它播放——这没问题。然而,许多用户(包括我和你,因为你正在阅读这篇文章)拥有更优越的或多个设备,无论是软件还是硬件的,所以我们想要/需要能够选择系统使用的播放设备。

在 Vista 之前,你可以通过控制面板中的声音和音频设备|音频来选择播放设备。不知何故,微软决定在 Vista 中移除此选项,并且至今为止的任何服务包或 Windows 7 (Beta) 中都没有重新添加它,尽管有许多关于将其包含在内的请求。

2008 年 2 月,我制作了一个微小的小应用程序来解决这个问题,它在我的 Vista 系统上运行良好,并且我在其他地方公开提供了。但它并不适用于所有人。有些人根本没有列出任何设备,另一些人只列出了一些可用设备。我目前正在编写一个全面的托管包装器,涵盖所有 MIDI 相关的内容,我意识到我应该重新审视这个问题,并将其作为该项目的一部分来彻底解决。本文就是结果。

我之前解决方案的问题在于它使用注册表来检索已安装设备的信息。并非所有设备都会创建这些注册表条目,因此应用程序并未显示所有设备。

解决方案

获取设备

检索所有 MIDI 输出设备原来很简单——尽管这需要使用一点 PInvoke,调用 *winmm.dll* 中的函数:midiOutGetNumDevs。这是一个非常简单的函数。它不接受任何参数,只返回输出设备的数量。

[DllImport("winmm.dll")]
static extern UInt32 midiOutGetNumDevs();

然后我们可以简单地使用任何设备 ID,从 0 到该函数结果减 1(它是基于零的)并保存它,但这不够用户友好。没有人知道他们设备的 ID 号,只知道名称,所以我们需要另一个函数来获取每个设备的信息,包括名称,即 midiOutGetDevCaps。这个函数稍微复杂一些。

[DllImport("winmm.dll")]
static extern UInt32 midiOutGetDevCaps(Int32 uDeviceID, 
       ref MIDIOUTCAPS lpMidiOutCaps, UInt32 cbMidiOutCaps);

第一个参数是我们想要查询的设备的 ID。设备 ID 是顺序的且基于零的,所以我们可以简单地使用一个基于上面检索到的结果的 foreach 循环并递归调用此函数。

第二个参数是一个结构体 MIDIOUTCAPS,函数将在此结构体中存储设备信息。

[StructLayout(LayoutKind.Sequential)]
struct MIDIOUTCAPS
{
    public UInt16 wMid;
    public UInt16 wPid;
    public UInt32 vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, 
     SizeConst = Constants.MAXPNAMELEN)]
    public string szPname;
    public UInt16 wTechnology;
    public UInt16 wVoices;
    public UInt16 wNotes;
    public UInt16 wChannelMask;
    public UInt32 dwSupport;
}

我们只对 szPname 感兴趣。常量 MAXPNAMELEN 来自 *MMSystem.h* 文件(我没有将其包含在项目中,因为大部分内容是不需要的),其值为 32。

第三个参数是 MIDIOUTCAPS 结构体的大小。该函数返回一个值,指示调用函数时发生的错误(如果有)。我们只关心没有错误的情况(当它返回 MMSYSERR_NOERROR 时,该常量的值为 0)。

输出类

为了从托管世界调用此函数(针对每个 ID),我们需要一个类 Output,它在构造函数中接收一个 MIDIOUTCAPS

public class Output
{
    public Output(Int32 id, MIDIOUTCAPS caps)
    {
        ID = id;
        Name = caps.szPname;
    }

    public Int32 ID
    {
        get;
        private set;
    }

    public string Name
    {
        get;
        private set;
    }
}

现在,我们可以递归调用 midiOutGetDevCaps 并返回一个只读列表。

public static void Load()
{
    outputs = null;
    List<output> devices = new List<output>();
    UInt32 numberOfDevices = Functions.midiOutGetNumDevs();
    if (numberOfDevices > 0)
    {
        for (Int32 i = 0; i < numberOfDevices; i++)
        {
            MIDIOUTCAPS caps = new MIDIOUTCAPS();
            if (Functions.midiOutGetDevCaps(i, ref caps, 
               (UInt32)Marshal.SizeOf(caps)) == Constants.MMSYSERR_NOERROR)
            {
                devices.Add(new Output(i, caps));
            }
        }
    }
    outputs = devices.AsReadOnly();
}

保存 ID

Windows 使用一个注册表设置,其中包含默认 MIDI 输出设备的 ID。键是:HKEY_CURRENT_USER\Software\Microsoft\ActiveMovie\devenum\{4EFE2452-168A-11D1-BC76-00C04FB9453B}\Default MidiOut Device,需要更改的值是一个名为 MidiOutIdDWORD

我们只需要将此值更改为我们首选设备的 ID 即可完成。

// Where value is the desired ID
RegistryKey defaultKey = null;
try
{
    defaultKey = Registry.CurrentUser.OpenSubKey(DefaultMidiOutDevice, 
        RegistryKeyPermissionCheck.ReadWriteSubTree, 
        RegistryRights.SetValue);
        defaultKey.SetValue(MidiOutId, value);
}
finally
{
    if (defaultKey != null)
    {
        defaultKey.Close();
    }
}

结论

除非微软在后来的操作系统中对注册表或 *winmm.dll* 的这部分进行破坏性更改,否则这应该适用于所有未来的版本。我在 XP(不需要,但它也能工作!)、Vista 和 Windows 7 Beta(build 7000)上进行了测试。

我包含了二进制文件(以及源代码),供那些只需要应用程序来重新控制 MIDI 系统的人使用。

参考文献

历史

  • 2009 年 5 月 10 日:初始版本
© . All rights reserved.