Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se muestra cómo enumerar dispositivos MIDI (Musical Instrument Digital Interface) y enviar y recibir mensajes MIDI desde una aplicación WinUI. Windows admite MIDI a través de USB (controladores compatibles con la clase y la mayoría de los controladores propietarios), MIDI sobre Bluetooth LE, y a través de productos de terceros disponibles libremente, MIDI a través de Ethernet y MIDI enrutado.
Crear una clase auxiliar para la supervisión de dispositivos
El Windows. Devices.Enumeration proporciona el espacio de nombres 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.
Agrega una nueva clase a tu proyecto que sirva como observador de dispositivos. En este ejemplo, la clase se denomina MidiDeviceWatcher. 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.
- Una cadena del selector de dispositivo que contendrá la cadena del selector del puerto de entrada MIDI para una instancia y la cadena del selector del puerto de salida MIDI para otra.
- Control ListBox que se rellenará con los nombres de los dispositivos disponibles.
- DispatcherQueue 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;
DispatcherQueue dispatcherQueue;
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 la clase, quien realiza la llamada proporciona la cadena del selector de dispositivos MIDI, el ListBox para mostrar la lista de dispositivos y la DispatcherQueue necesaria 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 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;
}
DeviceWatcher tiene los siguientes eventos:
- Agregado : se genera cuando se agrega un nuevo dispositivo al sistema.
- Eliminado - Se produce cuando se elimina un dispositivo del sistema.
- Actualizado : se genera cuando se actualiza la información asociada a un dispositivo existente.
- EnumerationCompleted - Se desencadena cuando el observador ha completado la 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 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();
});
}
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 observador y establezca el observador de dispositivos en null.
~MidiDeviceWatcher()
{
deviceWatcher.Added -= DeviceWatcher_Added;
deviceWatcher.Removed -= DeviceWatcher_Removed;
deviceWatcher.Updated -= DeviceWatcher_Updated;
deviceWatcher.EnumerationCompleted -= DeviceWatcher_EnumerationCompleted;
}
Crear puertos MIDI para enviar y recibir mensajes
En el código subyacente de la ventana, declare las variables miembro para contener dos instancias de la clase auxiliar MidiDeviceWatcher , una para los dispositivos de entrada y otra para los dispositivos de salida.
MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;
Declare también variables de miembro para los objetos de puerto de entrada y salida MIDI.
MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;
Cree una nueva instancia de las clases auxiliares del observador, pasando la cadena de selector de dispositivo, el ListBox que se rellenará y el objeto DispatcherQueue. 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 MidiDeviceWatcher(MidiInPort.GetDeviceSelector(), midiInPortListBox, DispatcherQueue);
inputDeviceWatcher.StartWatcher();
outputDeviceWatcher =
new MidiDeviceWatcher(MidiOutPort.GetDeviceSelector(), midiOutPortListBox, DispatcherQueue);
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 pasando 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.
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 del MidiMessageReceivedEventArgs. La Type del objeto message es un valor de la enumeración MidiMessageType que indica el tipo de mensaje recibido. 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 se cierre la aplicación, asegúrese de limpiar los recursos de la aplicación. Anule el registro de los manejadores de eventos y establezca en null los objetos de puerto de entrada MIDI y de puerto de salida. Detenga los observadores de dispositivos y establézcalos en 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;
}
Usar el sintetizador General MIDI integrado de Windows
Al enumerar dispositivos MIDI de salida mediante la técnica descrita anteriormente, la aplicación detectará un dispositivo MIDI llamado "Microsoft GS Wavetable Synth". Se trata de un sintetizador MIDI general integrado que puedes reproducir desde tu aplicación.
El SDK de extensión de UWP para General MIDI ("Microsoft General MIDI DLS para aplicaciones universales de Windows") no está disponible en proyectos de WinUI 3. El cuadro de diálogo Agregar extensiones de referencia > anterior era específico de UWP. Sin embargo, para la mayoría de los escenarios de escritorio, GS Wavetable Synth funciona sin el SDK de extensión porque las aplicaciones de escritorio pueden acceder directamente al banco de sonido del gm.dls sistema. Si encuentra que la salida MIDI no produce ningún sonido, compruebe que se selecciona un dispositivo de salida MIDI y que la salida de audio está configurada correctamente.