MIDI

This article shows you how to enumerate MIDI (Musical Instrument Digital Interface) devices and send and receive MIDI messages from a Universal Windows app. Windows 10 supports MIDI over USB (class-compliant and most proprietary drivers), MIDI over Bluetooth LE (Windows 10 Anniversary Edition and later), and through freely-available third-party products, MIDI over Ethernet and routed MIDI.

Enumerate MIDI devices

Before enumerating and using MIDI devices, add the following namespaces to your project.

using Windows.Devices.Enumeration;
using Windows.Devices.Midi;
using System.Threading.Tasks;

Add a ListBox control to your XAML page that will allow the user to select one of the MIDI input devices attached to the system. Add another one to list the MIDI output devices.

<ListBox x:Name="midiInPortListBox" SelectionChanged="midiInPortListBox_SelectionChanged"/>
<ListBox x:Name="midiOutPortListBox" SelectionChanged="midiOutPortListBox_SelectionChanged"/>

The FindAllAsync method DeviceInformation class is used to enumerate many different types of devices that are recognized by Windows. To specify that you only want the method to find MIDI input devices, use the selector string returned by MidiInPort.GetDeviceSelector. FindAllAsync returns a DeviceInformationCollection that contains a DeviceInformation for each MIDI input device registered with the system. If the returned collection contains no items, then there are no available MIDI input devices. If there are items in the collection, loop through the DeviceInformation objects and add the name of each device to the MIDI input device ListBox.

private async Task EnumerateMidiInputDevices()
{
    // Find all input MIDI devices
    string midiInputQueryString = MidiInPort.GetDeviceSelector();
    DeviceInformationCollection midiInputDevices = await DeviceInformation.FindAllAsync(midiInputQueryString);

    midiInPortListBox.Items.Clear();

    // Return if no external devices are connected
    if (midiInputDevices.Count == 0)
    {
        this.midiInPortListBox.Items.Add("No MIDI input devices found!");
        this.midiInPortListBox.IsEnabled = false;
        return;
    }

    // Else, add each connected input device to the list
    foreach (DeviceInformation deviceInfo in midiInputDevices)
    {
        this.midiInPortListBox.Items.Add(deviceInfo.Name);
    }
    this.midiInPortListBox.IsEnabled = true;
}

Enumerating MIDI output devices works the exact same way as enumerating input devices, except that you should specify the selector string returned by MidiOutPort.GetDeviceSelector when calling FindAllAsync.

private async Task EnumerateMidiOutputDevices()
{

    // Find all output MIDI devices
    string midiOutportQueryString = MidiOutPort.GetDeviceSelector();
    DeviceInformationCollection midiOutputDevices = await DeviceInformation.FindAllAsync(midiOutportQueryString);

    midiOutPortListBox.Items.Clear();

    // Return if no external devices are connected
    if (midiOutputDevices.Count == 0)
    {
        this.midiOutPortListBox.Items.Add("No MIDI output devices found!");
        this.midiOutPortListBox.IsEnabled = false;
        return;
    }

    // Else, add each connected input device to the list
    foreach (DeviceInformation deviceInfo in midiOutputDevices)
    {
        this.midiOutPortListBox.Items.Add(deviceInfo.Name);
    }
    this.midiOutPortListBox.IsEnabled = true;
}

Create a device watcher helper class

The Windows.Devices.Enumeration namespace provides the DeviceWatcher which can notify your app if devices are added or removed from the system, or if the information for a device is updated. Since MIDI-enabled apps typically are interested in both input and output devices, this example creates a helper class that implements the DeviceWatcher pattern, so that the same code can be used for both MIDI input and MIDI output devices, without the need for duplication.

Add a new class to your project to serve as your device watcher. In this example the class is named MyMidiDeviceWatcher. The rest of the code in this section is used to implement the helper class.

Add some member variables to the class:

  • A DeviceWatcher object that will monitor for device changes.
  • A device selector string that will contain the MIDI in port selector string for one instance and the MIDI out port selector string for another instance.
  • A ListBox control that will be populated with the names of the available devices.
  • A CoreDispatcher that is required to update the UI from a thread other than the UI thread.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

Add a DeviceInformationCollection property that is used to access the current list of devices from outside the helper class.

public DeviceInformationCollection DeviceInformationCollection { get; set; }

In class constructor, the caller passes in the MIDI device selector string, the ListBox for listing the devices, and the Dispatcher needed to update the UI.

Call DeviceInformation.CreateWatcher to create a new instance of the DeviceWatcher class, passing in the MIDI device selector string.

Register handlers for the watcher's event handlers.

public MyMidiDeviceWatcher(string midiDeviceSelectorString, ListBox midiDeviceListBox, CoreDispatcher dispatcher)
{
    deviceListBox = midiDeviceListBox;
    coreDispatcher = dispatcher;

    deviceSelectorString = midiDeviceSelectorString;

    deviceWatcher = DeviceInformation.CreateWatcher(deviceSelectorString);
    deviceWatcher.Added += DeviceWatcher_Added;
    deviceWatcher.Removed += DeviceWatcher_Removed;
    deviceWatcher.Updated += DeviceWatcher_Updated;
    deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
}

The DeviceWatcher has the following events:

  • Added - Raised when a new device is added to the system.
  • Removed - Raised when a device is removed from the system.
  • Updated - Raised when the information associated with an existing device is updated.
  • EnumerationCompleted - Raised when the watcher has completed its enumeration of the requested device type.

In the event handler for each of these events, a helper method, UpdateDevices, is called to update the ListBox with the current list of devices. Because UpdateDevices updates UI elements and these event handlers are not called on the UI thread, each call must be wrapped in a call to RunAsync, which causes the specified code to be run on the UI thread.

private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    await coreDispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        // Update the device list
        UpdateDevices();
    });
}

private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await coreDispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        // Update the device list
        UpdateDevices();
    });
}

private async void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
    await coreDispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        // Update the device list
        UpdateDevices();
    });
}

private async void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args)
{
    await coreDispatcher.RunAsync(CoreDispatcherPriority.High, () =>
    {
        // Update the device list
        UpdateDevices();
    });
}

The UpdateDevices helper method calls DeviceInformation.FindAllAsync and updates the ListBox with the names of the returned devices as described previously in this article.

private async void UpdateDevices()
{
    // Get a list of all MIDI devices
    this.DeviceInformationCollection = await DeviceInformation.FindAllAsync(deviceSelectorString);

    deviceListBox.Items.Clear();

    if (!this.DeviceInformationCollection.Any())
    {
        deviceListBox.Items.Add("No MIDI devices found!");
    }

    foreach (var deviceInformation in this.DeviceInformationCollection)
    {
        deviceListBox.Items.Add(deviceInformation.Name);
    }
}

Add methods to start the watcher, using the DeviceWatcher object's Start method, and to stop the watcher, using the Stop method.

public void StartWatcher()
{
    deviceWatcher.Start();
}
public void StopWatcher()
{
    deviceWatcher.Stop();
}

Provide a destructor to unregister the watcher event handlers and set the device watcher to null.

~MyMidiDeviceWatcher()
{
    deviceWatcher.Added -= DeviceWatcher_Added;
    deviceWatcher.Removed -= DeviceWatcher_Removed;
    deviceWatcher.Updated -= DeviceWatcher_Updated;
    deviceWatcher.EnumerationCompleted -= DeviceWatcher_EnumerationCompleted;
    deviceWatcher = null;
}

Create MIDI ports to send and receive messages

In the code behind for your page, declare member variables to hold two instances of the MyMidiDeviceWatcher helper class, one for input devices and one for output devices.

MyMidiDeviceWatcher inputDeviceWatcher;
MyMidiDeviceWatcher outputDeviceWatcher;

Create a new instance of the watcher helper classes, passing in the device selector string, the ListBox to be populated, and the CoreDispatcher object that can be accessed through the page's Dispatcher property. Then, call the method to start each object's DeviceWatcher.

Shortly after each DeviceWatcher is started, it will finish enumerating the current devices connected to the system and raise its EnumerationCompleted event, which will cause each ListBox to be updated with the current MIDI devices.

inputDeviceWatcher =
    new MyMidiDeviceWatcher(MidiInPort.GetDeviceSelector(), midiInPortListBox, Dispatcher);

inputDeviceWatcher.StartWatcher();

outputDeviceWatcher =
    new MyMidiDeviceWatcher(MidiOutPort.GetDeviceSelector(), midiOutPortListBox, Dispatcher);

outputDeviceWatcher.StartWatcher();

When the user selects an item in the MIDI input ListBox, the SelectionChanged event is raised. In the handler for this event, access the DeviceInformationCollection property of the helper class to get the current list of devices. If there are entries in the list, select the DeviceInformation object with the index corresponding to the ListBox control's SelectedIndex.

Create the MidiInPort object representing the selected input device by calling MidiInPort.FromIdAsync, passing in the Id property of the selected device.

Register a handler for the MessageReceived event, which is raised whenever a MIDI message is received through the specified device.

MidiInPort midiInPort;
IMidiOutPort midiOutPort;
private async void midiInPortListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var deviceInformationCollection = inputDeviceWatcher.DeviceInformationCollection;

    if (deviceInformationCollection == null)
    {
        return;
    }

    DeviceInformation devInfo = deviceInformationCollection[midiInPortListBox.SelectedIndex];

    if (devInfo == null)
    {
        return;
    }

    midiInPort = await MidiInPort.FromIdAsync(devInfo.Id);

    if (midiInPort == null)
    {
        System.Diagnostics.Debug.WriteLine("Unable to create MidiInPort from input device");
        return;
    }
    midiInPort.MessageReceived += MidiInPort_MessageReceived;
}

When the MessageReceived handler is called, the message is contained in the Message property of the MidiMessageReceivedEventArgs. The Type of the message object is a value from the MidiMessageType enumeration indicating the type of message that was received. The data of the message depends on the type of the message. This example checks to see if the message is a note on message and, if so, outputs the midi channel, note, and velocity of the message.

private void MidiInPort_MessageReceived(MidiInPort sender, MidiMessageReceivedEventArgs args)
{
    IMidiMessage receivedMidiMessage = args.Message;

    System.Diagnostics.Debug.WriteLine(receivedMidiMessage.Timestamp.ToString());

    if (receivedMidiMessage.Type == MidiMessageType.NoteOn)
    {
        System.Diagnostics.Debug.WriteLine(((MidiNoteOnMessage)receivedMidiMessage).Channel);
        System.Diagnostics.Debug.WriteLine(((MidiNoteOnMessage)receivedMidiMessage).Note);
        System.Diagnostics.Debug.WriteLine(((MidiNoteOnMessage)receivedMidiMessage).Velocity);
    }
}

The SelectionChanged handler for the output device ListBox works the same as the handler for input devices, except no event handler is registered.

private async void midiOutPortListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var deviceInformationCollection = outputDeviceWatcher.DeviceInformationCollection;

    if (deviceInformationCollection == null)
    {
        return;
    }

    DeviceInformation devInfo = deviceInformationCollection[midiOutPortListBox.SelectedIndex];

    if (devInfo == null)
    {
        return;
    }

    midiOutPort = await MidiOutPort.FromIdAsync(devInfo.Id);

    if (midiOutPort == null)
    {
        System.Diagnostics.Debug.WriteLine("Unable to create MidiOutPort from output device");
        return;
    }

}

Once the output device is created, you can send a message by creating a new IMidiMessage for the type of message you want to send. In this example, the message is a NoteOnMessage. The SendMessage method of the IMidiOutPort object is called to send the message.

byte channel = 0;
byte note = 60;
byte velocity = 127;
IMidiMessage midiMessageToSend = new MidiNoteOnMessage(channel, note, velocity);

midiOutPort.SendMessage(midiMessageToSend);

When your app is deactivated, be sure to clean up your apps resources. Unregister your event handlers and set the MIDI in port and out port objects to null. Stop the device watchers and set them to null.

inputDeviceWatcher.StopWatcher();
inputDeviceWatcher = null;

outputDeviceWatcher.StopWatcher();
outputDeviceWatcher = null;

midiInPort.MessageReceived -= MidiInPort_MessageReceived;
midiInPort.Dispose();
midiInPort = null;

midiOutPort.Dispose();
midiOutPort = null;

Using the built-in Windows General MIDI synth

When you enumerate output MIDI devices using the technique described above, your app will discover a MIDI device called "Microsoft GS Wavetable Synth". This is a built-in General MIDI synthesizer that you can play from your app. However, attempting to create a MIDI outport to this device will fail unless you have included the SDK extension for the built-in synth in your project.

To include the General MIDI Synth SDK extension in your app project

  1. In Solution Explorer, under your project, right-click References and select Add reference...
  2. Expand the Universal Windows node.
  3. Select Extensions.
  4. From the list of extensions, select Microsoft General MIDI DLS for Universal Windows Apps.

    Note

    If there are multiple versions of the extension, be sure to select the version that matches the target for your app. You can see which SDK version your app is targeting on the Application tab of the project Properties.