MIDI
En este artículo se muestra cómo enumerar dispositivos MIDI (Interfaz digital instrumenta musical) y enviar y recibir mensajes MIDI desde una aplicación universal de Windows. Windows 10 admite MIDI a través de USB (controladores compatibles con la clase y la mayoría de los controladores propietarios), MIDI sobre Bluetooth LE (Windows 10 Anniversary Edition y versiones posteriores) y a través de productos de terceros disponibles libremente, MIDI a través de Ethernet y MIDI enrutado.
Enumerar dispositivos MIDI
Antes de enumerar y usar dispositivos MIDI, agregue los siguientes espacios de nombres al proyecto.
using Windows.Devices.Enumeration;
using Windows.Devices.Midi;
using System.Threading.Tasks;
Agregue un control ListBox a la página XAML que permitirá al usuario seleccionar uno de los dispositivos de entrada MIDI conectados al sistema. Agregue otro para enumerar los dispositivos de salida MIDI.
<ListBox x:Name="midiInPortListBox" SelectionChanged="midiInPortListBox_SelectionChanged"/>
<ListBox x:Name="midiOutPortListBox" SelectionChanged="midiOutPortListBox_SelectionChanged"/>
La clase FindAllAsync method DeviceInformation se usa para enumerar muchos tipos diferentes de dispositivos reconocidos por Windows. Para especificar que solo desea que el método busque dispositivos de entrada MIDI, use la cadena del selector devuelta por MidiInPort.GetDeviceSelector. FindAllAsync devuelve un DeviceInformationCollection que contiene un deviceInformation para cada dispositivo de entrada MIDI registrado en el sistema. Si la colección devuelta no contiene elementos, no hay ningún dispositivo de entrada MIDI disponible. Si hay elementos en la colección, recorra los objetos DeviceInformation y agregue el nombre de cada dispositivo al dispositivo de entrada MIDI 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;
}
La enumeración de dispositivos de salida MIDI funciona exactamente del mismo modo que enumerar los dispositivos de entrada, excepto que debe especificar la cadena del selector devuelta por MidiOutPort.GetDeviceSelector al llamar a 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;
}
Creación de una clase auxiliar de monitor de dispositivo
El espacio de nombres Windows.Devices.Enumeration proporciona deviceWatcher que puede notificar a la aplicación si los dispositivos se agregan o quitan del sistema, o si se actualiza la información de un dispositivo. Dado que las aplicaciones habilitadas para MIDI suelen estar interesadas en dispositivos de entrada y salida, en este ejemplo se crea una clase auxiliar que implementa el patrón DeviceWatcher , de modo que se pueda usar el mismo código tanto para dispositivos de entrada MIDI como de salida MIDI, sin necesidad de duplicación.
Agregue una nueva clase al proyecto para que actúe como monitor de dispositivos. En este ejemplo, la clase se denomina MyMidiDeviceWatcher. El resto del código de esta sección se usa para implementar la clase auxiliar.
Agregue algunas variables de miembro a la clase :
- Objeto DeviceWatcher que supervisará los cambios del dispositivo.
- Cadena del selector de dispositivos que contendrá el MIDI en la cadena del selector de puertos para una instancia y la cadena del selector de puerto de salida MIDI para otra instancia.
- Control ListBox que se rellenará con los nombres de los dispositivos disponibles.
- CoreDispatcher que es necesario para actualizar la interfaz de usuario desde un subproceso distinto del subproceso de interfaz de usuario.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;
Agregue una propiedad DeviceInformationCollection que se usa para acceder a la lista actual de dispositivos desde fuera de la clase auxiliar.
public DeviceInformationCollection DeviceInformationCollection { get; set; }
En el constructor de clase, el autor de la llamada pasa en la cadena del selector de dispositivos MIDI, listBox para enumerar los dispositivos y el distribuidor necesario para actualizar la interfaz de usuario.
Llame a DeviceInformation.CreateWatcher para crear una nueva instancia de la clase DeviceWatcher, pasando la cadena del selector de dispositivos MIDI.
Registre controladores para los controladores de eventos del monitor.
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;
}
DeviceWatcher tiene los siguientes eventos:
- Agregado : se genera cuando se agrega un nuevo dispositivo al sistema.
- Quitado: se genera cuando se quita un dispositivo del sistema.
- Actualizado : se genera cuando se actualiza la información asociada a un dispositivo existente.
- EnumerationCompleted : se genera cuando el monitor ha completado su enumeración del tipo de dispositivo solicitado.
En el controlador de eventos para cada uno de estos eventos, se llama a un método auxiliar, UpdateDevices, para actualizar listBox con la lista actual de dispositivos. Dado que UpdateDevices actualiza los elementos de la interfaz de usuario y estos controladores de eventos no se llaman en el subproceso de la interfaz de usuario, cada llamada debe encapsularse en una llamada a RunAsync, lo que hace que el código especificado se ejecute en el subproceso de la interfaz de usuario.
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();
});
}
El método auxiliar UpdateDevices llama a DeviceInformation.FindAllAsync y actualiza listBox con los nombres de los dispositivos devueltos, tal como se ha descrito anteriormente en este artículo.
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);
}
}
Agregue métodos para iniciar el monitor mediante el método Start del objeto DeviceWatcher y para detener el monitor mediante el método Stop.
public void StartWatcher()
{
deviceWatcher.Start();
}
public void StopWatcher()
{
deviceWatcher.Stop();
}
Proporcione un destructor para anular el registro de los controladores de eventos del monitor y establezca el monitor de dispositivo en NULL.
~MyMidiDeviceWatcher()
{
deviceWatcher.Added -= DeviceWatcher_Added;
deviceWatcher.Removed -= DeviceWatcher_Removed;
deviceWatcher.Updated -= DeviceWatcher_Updated;
deviceWatcher.EnumerationCompleted -= DeviceWatcher_EnumerationCompleted;
deviceWatcher = null;
}
Crear puertos MIDI para enviar y recibir mensajes
En el código subyacente de la página, declare variables de miembro para contener dos instancias de la clase auxiliar MyMidiDeviceWatcher , una para los dispositivos de entrada y otra para los dispositivos de salida.
MyMidiDeviceWatcher inputDeviceWatcher;
MyMidiDeviceWatcher outputDeviceWatcher;
Cree una nueva instancia de las clases auxiliares del monitor, pasando la cadena del selector de dispositivos, listBox que se va a rellenar y el objeto CoreDispatcher al que se puede acceder a través de la propiedad Dispatcher de la página. A continuación, llame al método para iniciar deviceWatcher de cada objeto.
Poco después de iniciar cada DeviceWatcher, finalizará la enumeración de los dispositivos actuales conectados al sistema y generará su evento EnumerationCompleted, lo que hará que cada ListBox se actualice con los dispositivos MIDI actuales.
inputDeviceWatcher =
new MyMidiDeviceWatcher(MidiInPort.GetDeviceSelector(), midiInPortListBox, Dispatcher);
inputDeviceWatcher.StartWatcher();
outputDeviceWatcher =
new MyMidiDeviceWatcher(MidiOutPort.GetDeviceSelector(), midiOutPortListBox, Dispatcher);
outputDeviceWatcher.StartWatcher();
Cuando el usuario selecciona un elemento en el ListBox de entrada MIDI, se genera el evento SelectionChanged. En el controlador de este evento, acceda a la propiedad DeviceInformationCollection de la clase auxiliar para obtener la lista actual de dispositivos. Si hay entradas en la lista, seleccione el objeto DeviceInformation con el índice correspondiente al control SelectedIndex del control ListBox.
Cree el objeto MidiInPort que representa el dispositivo de entrada seleccionado llamando a MidiInPort.FromIdAsync y pase la propiedad Id del dispositivo seleccionado.
Registre un controlador para el evento MessageReceived , que se genera cada vez que se recibe un mensaje MIDI a través del dispositivo especificado.
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;
}
Cuando se llama al controlador MessageReceived, el mensaje se encuentra en la propiedad Message de MidiMessageReceivedEventArgs. El tipo del objeto message es un valor de la enumeración MidiMessageType que indica el tipo de mensaje que se recibió. Los datos del mensaje dependen del tipo del mensaje. En este ejemplo se comprueba si el mensaje es una nota sobre el mensaje y, si es así, genera el canal midi, la nota y la velocidad del mensaje.
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);
}
}
El controlador SelectionChanged del dispositivo de salida ListBox funciona igual que el controlador para los dispositivos de entrada, excepto que no se registra ningún controlador de eventos.
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;
}
}
Una vez creado el dispositivo de salida, puede enviar un mensaje creando un nuevo IMidiMessage para el tipo de mensaje que desea enviar. En este ejemplo, el mensaje es un NoteOnMessage. Se llama al método SendMessage del objeto IMidiOutPort para enviar el mensaje.
byte channel = 0;
byte note = 60;
byte velocity = 127;
IMidiMessage midiMessageToSend = new MidiNoteOnMessage(channel, note, velocity);
midiOutPort.SendMessage(midiMessageToSend);
Cuando la aplicación esté desactivada, asegúrese de limpiar los recursos de las aplicaciones. Anule el registro de los controladores de eventos y establezca el MIDI en el puerto y los objetos de puerto de salida en NULL. Detenga los monitores de dispositivo y establézcalos en NULL.
inputDeviceWatcher.StopWatcher();
inputDeviceWatcher = null;
outputDeviceWatcher.StopWatcher();
outputDeviceWatcher = null;
midiInPort.MessageReceived -= MidiInPort_MessageReceived;
midiInPort.Dispose();
midiInPort = null;
midiOutPort.Dispose();
midiOutPort = null;
Uso de la síntesis MIDI general de Windows integrada
Al enumerar los dispositivos MIDI de salida mediante la técnica descrita anteriormente, la aplicación detectará un dispositivo MIDI denominado "Microsoft GS Wavetable Synth". Se trata de un sintetizador MIDI general integrado que puedes reproducir desde tu aplicación. Sin embargo, al intentar crear una salida MIDI a este dispositivo, se producirá un error a menos que haya incluido la extensión del SDK para el sintetizador integrado en el proyecto.
Para incluir la extensión general del SDK de MIDI Synth en el proyecto de aplicación
- En Explorador de soluciones, en el proyecto, haga clic con el botón derecho en Referencias y seleccione Agregar referencia...
- Expanda el nodo universal de Windows .
- Seleccione Extensiones.
- En la lista de extensiones, seleccione Microsoft General MIDI DLS para aplicaciones universales de Windows.
Nota:
Si hay varias versiones de la extensión, asegúrese de seleccionar la versión que coincida con el destino de la aplicación. Puede ver qué versión del SDK tiene como destino la aplicación en la pestaña Aplicación de las propiedades del proyecto.