Muistiinpano
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää kirjautua sisään tai vaihtaa hakemistoa.
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää vaihtaa hakemistoa.
This article shows you how to enumerate MIDI (Musical Instrument Digital Interface) devices and send and receive MIDI messages from a WinUI app. Windows supports MIDI over USB (class-compliant and most proprietary drivers), MIDI over Bluetooth LE, and through freely-available third-party products, MIDI over Ethernet and routed MIDI.
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 MidiDeviceWatcher. 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 DispatcherQueue that is required to update the UI from a thread other than the UI thread.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
DispatcherQueue dispatcherQueue;
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 the class constructor, the caller passes in the MIDI device selector string, the ListBox for listing the devices, and the DispatcherQueue 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 MidiDeviceWatcher(string midiDeviceSelectorString, ListBox midiDeviceListBox, DispatcherQueue dispatcher)
{
deviceListBox = midiDeviceListBox;
dispatcherQueue = 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 DispatcherQueue.TryEnqueue.
private void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
dispatcherQueue.TryEnqueue(() =>
{
UpdateDevices();
});
}
private void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
dispatcherQueue.TryEnqueue(() =>
{
UpdateDevices();
});
}
private void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
dispatcherQueue.TryEnqueue(() =>
{
UpdateDevices();
});
}
private void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args)
{
dispatcherQueue.TryEnqueue(() =>
{
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.
~MidiDeviceWatcher()
{
deviceWatcher.Added -= DeviceWatcher_Added;
deviceWatcher.Removed -= DeviceWatcher_Removed;
deviceWatcher.Updated -= DeviceWatcher_Updated;
deviceWatcher.EnumerationCompleted -= DeviceWatcher_EnumerationCompleted;
}
Create MIDI ports to send and receive messages
In the code behind for your window, declare member variables to hold two instances of the MidiDeviceWatcher helper class, one for input devices and one for output devices.
MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;
Also declare member variables for the MIDI input and output port objects.
MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;
Create a new instance of the watcher helper classes, passing in the device selector string, the ListBox to be populated, and the DispatcherQueue object. 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 MidiDeviceWatcher(MidiInPort.GetDeviceSelector(), midiInPortListBox, DispatcherQueue);
inputDeviceWatcher.StartWatcher();
outputDeviceWatcher =
new MidiDeviceWatcher(MidiOutPort.GetDeviceSelector(), midiOutPortListBox, DispatcherQueue);
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.
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 closing, be sure to clean up your app's 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;
if (midiInPort != null)
{
midiInPort.MessageReceived -= MidiInPort_MessageReceived;
midiInPort.Dispose();
midiInPort = null;
}
if (midiOutPort != 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.
The UWP Extension SDK for General MIDI ("Microsoft General MIDI DLS for Universal Windows Apps") is not available in WinUI 3 projects. The old Add reference > Extensions dialog was specific to UWP. However, for most desktop scenarios, the GS Wavetable Synth works without the extension SDK because desktop apps can access the system's gm.dls sound bank directly. If you find that MIDI output produces no sound, verify that a MIDI output device is selected and that your audio output is configured correctly.
Related topics
Windows developer