Click here to Skip to main content
Click here to Skip to main content

MIDI out setter

, 10 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Allows changing of the default MIDI out device in Vista and Windows 7.

Overview

The average user only has one MIDI playback device on their system, the internal software synthesizer 'Microsoft GS Wavetable Synth', and any MIDI files are played back via that - no problem. However, many users (including me and probably you as you're reading this) have superior or multiple devices, either software or hardware based, so we want/need to be able to select the playback device that the system uses.

Pre Vista, you could select the playback device via Sounds and Audio Devices|Audio in the Control Panel. For some reason, Microsoft decided to remove this option in Vista, and it hasn't reappeared in any service pack to date or in Windows 7 (Beta) either, even though there have been many requests for its inclusion.

In February 2008, I made a tiny application to address this which worked fine on my Vista systems and I made publicly available elsewhere. This didn't work for everybody though. Some people didn't get any devices listed, others only a selection of the ones available. I'm currently in the process of writing a comprehensive managed wrapper around all things MIDI, and it occurred to me that I should revisit the problem and solve it conclusively as part of that project. This article is the result.

The problem with my previous solution was that it used the Registry to retrieve information about installed devices. Not all devices create these Registry entries, hence they weren't all shown by the application.

The solution

Getting the devices

To retrieve all the MIDI out devices turns out to be trivial - although it requires the use of a little PInvoke, calling a function in winmm.dll: midiOutGetNumDevs. This is a very simple function. It takes no parameters, and simply returns the number of output devices.

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

We could then simply use any device ID, from 0 to the result of this function -1 (it's zero based) and save this, but that's not very user friendly. Nobody knows the ID number of their devices, only the name, so we need another function to get information about each device including the name, midiOutGetDevCaps. This function is slightly more complicated.

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

The first parameter is the ID of the device we want to query. Device IDs are sequential and zero based, so we can simply use a foreach loop based on the result we retrieved above and recursively call this function.

The second parameter is a structure MIDIOUTCAPS where the function will store the device information.

[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;
}

It's only the szPname that we're interested in. The constant MAXPNAMELEN is from the MMSystem.h file (I've not included it in the project as most of it is not needed), and has a value of 32.

The third parameter is the size of the MIDIOUTCAPS structure. The function returns a value that indicates the error (if any) that occurred when the function was called. We're only interested if there was no error (when it returns MMSYSERR_NOERROR, this constant's value is 0).

Output class

To call this function for each ID from the managed world, we need a class Output that takes a MIDIOUTCAPS in the constructor.

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;
    }
}

Now, we can call the midiOutGetDevCaps recursively and return a read only list.

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();
}

Saving the ID

Windows uses a Registry setting that contains the ID of the default MIDI out device. The key is: HKEY_CURRENT_USER\Software\Microsoft\ActiveMovie\devenum\{4EFE2452-168A-11D1-BC76-00C04FB9453B}\Default MidiOut Device, and the value that needs changing is a DWORD called MidiOutId.

All we need to do is change this value to the ID of our preferred device and we're done.

// 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();
    }
}

Conclusion

Unless Microsoft decides to make breaking changes in later OSes to this part of the Registry or to winmm.dll, this should work under all future versions too. I've tested it on XP (not needed, but it works anyway!), Vista, and Windows 7 Beta (build 7000).

I've included the binary (as well as the source) for anyone that just needs the application to take back control of their MIDI system.

References

History

  • 10th May 2009: Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DaveyM69
CEO Dave Meadowcroft
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
QuestionThank you Pinmemberfarhadfar6-Jun-12 11:17 
AnswerRe: Thank you PinmentorDaveyM697-Jun-12 7:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 10 May 2009
Article Copyright 2009 by DaveyM69
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid