Share via


MIDI

Questo articolo illustra come enumerare i dispositivi MIDI (Musical Instrument Digital Interface) e inviare e ricevere messaggi MIDI da un'app Universal Windows. Windows 10 supporta MIDI tramite USB (driver conformi alla classe e proprietari), MIDI tramite Bluetooth LE (Windows 10 Anniversary Edition e versioni successive) e tramite prodotti di terzi disponibili gratuitamente, MIDI su Ethernet e MIDI indirizzato.

Enumerare i dispositivi MIDI

Prima di enumerare e usare i dispositivi MIDI, aggiungere gli spazi dei nomi seguenti al progetto.

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

Aggiungere un controllo ListBox alla pagina XAML che consentirà all'utente di selezionare uno dei dispositivi di input MIDI collegati al sistema. Aggiungerne un'altra per elencare i dispositivi di output MIDI.

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

Il metodo FindAllAsync classe DeviceInformation viene usata per enumerare molti tipi diversi di dispositivi riconosciuti da Windows. Per specificare che si vuole solo che il metodo trovi i dispositivi di input MIDI, usare la stringa del selettore restituita da MidiInPort.GetDeviceSelector. FindAllAsync restituisce un oggetto DeviceInformationCollection che contiene un DeviceInformation per ogni dispositivo di input MIDI registrato nel sistema. Se la raccolta restituita non contiene elementi, non sono disponibili dispositivi di input MIDI. Se sono presenti elementi nella raccolta, scorrere gli oggetti DeviceInformation e aggiungere il nome di ogni dispositivo al controllo ListBox del dispositivo di input MIDI.

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

L'enumerazione dei dispositivi di output MIDI funziona esattamente come l'enumerazione dei dispositivi di input, ad eccezione del fatto che è necessario specificare la stringa del selettore restituita da MidiOutPort.GetDeviceSelector quando si chiama 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;
}

Creare una classe helper device watcher

Lo spazio dei nomi Windows.Devices.Enumeration fornisce DeviceWatcher che può notificare all'app se i dispositivi vengono aggiunti o rimossi dal sistema o se le informazioni per un dispositivo vengono aggiornate. Poiché le app abilitate per MIDI sono in genere interessate sia ai dispositivi di input che di output, questo esempio crea una classe helper che implementa il modello DeviceWatcher, in modo che lo stesso codice possa essere usato sia per i dispositivi di input MIDI che per i dispositivi di output MIDI, senza la necessità di duplicazione.

Aggiungere una nuova classe al progetto da usare come watcher del dispositivo. In questo esempio la classe è denominata MyMidiDeviceWatcher. Il resto del codice in questa sezione viene usato per implementare la classe helper.

Aggiungere alcune variabili membro alla classe:

  • Oggetto DeviceWatcher che monitorerà le modifiche del dispositivo.
  • Stringa del selettore di dispositivo che conterrà il MIDI nella stringa del selettore di porta per un'istanza e la stringa del selettore di porta MIDI per un'altra istanza.
  • Controllo ListBox che verrà compilato con i nomi dei dispositivi disponibili.
  • CoreDispatcher necessario per aggiornare l'interfaccia utente da un thread diverso dal thread dell'interfaccia utente.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

Aggiungere una proprietà DeviceInformationCollection usata per accedere all'elenco corrente di dispositivi dall'esterno della classe helper.

public DeviceInformationCollection DeviceInformationCollection { get; set; }

Nel costruttore della classe, il chiamante passa la stringa del selettore del dispositivo MIDI, ListBox per elencare i dispositivi e dispatcher necessari per aggiornare l'interfaccia utente.

Chiamare DeviceInformation.CreateWatcher per creare una nuova istanza della classe DeviceWatcher, passando la stringa del selettore del dispositivo MIDI.

Registrare i gestori per i gestori eventi del watcher.

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 presenta gli eventi seguenti:

  • Aggiunto: generato quando viene aggiunto un nuovo dispositivo al sistema.
  • Rimosso: generato quando un dispositivo viene rimosso dal sistema.
  • Aggiornato: generato quando vengono aggiornate le informazioni associate a un dispositivo esistente.
  • EnumerationCompleted: generato quando il watcher ha completato l'enumerazione del tipo di dispositivo richiesto.

Nel gestore eventi per ognuno di questi eventi viene chiamato un metodo helper UpdateDevices per aggiornare ListBox con l'elenco corrente di dispositivi. Poiché UpdateDevices aggiorna gli elementi dell'interfaccia utente e questi gestori eventi non vengono chiamati nel thread dell'interfaccia utente, ogni chiamata deve essere sottoposta a wrapping in una chiamata a RunAsync, causando l'esecuzione del codice specificato nel thread dell'interfaccia utente.

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

Il metodo helper UpdateDevices chiama DeviceInformation.FindAllAsync e aggiorna ListBox con i nomi dei dispositivi restituiti come descritto in precedenza in questo articolo.

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

Aggiungere metodi per avviare il watcher, usando il metodo Start dell'oggetto DeviceWatcher e per arrestare il watcher usando il metodo Stop.

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

Fornire un distruttore per annullare la registrazione dei gestori eventi watcher e impostare Device Watcher su Null.

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

Creare porte MIDI per inviare e ricevere messaggi

Nel code-behind per la pagina dichiarare le variabili membro per contenere due istanze della classe helper MyMidiDeviceWatcher, una per i dispositivi di input e una per i dispositivi di output.

MyMidiDeviceWatcher inputDeviceWatcher;
MyMidiDeviceWatcher outputDeviceWatcher;

Creare una nuova istanza delle classi helper watcher, passando la stringa del selettore del dispositivo, ListBox da compilare e l'oggetto CoreDispatcher accessibile tramite la proprietà Dispatcher della pagina. Chiamare quindi il metodo per avviare DeviceWatcher di ogni oggetto.

Poco dopo l'avvio di ogni DeviceWatcher termina l'enumerazione dei dispositivi correnti connessi al sistema e genera l'evento EnumerationCompleted che causerà l'aggiornamento di ogni ListBox con i dispositivi MIDI correnti.

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

Quando l'utente seleziona un elemento nell'input MIDI ListBox, viene generato l'evento SelectionChanged. Nel gestore per questo evento accedere alla proprietà DeviceInformationCollection della classe helper per ottenere l'elenco corrente dei dispositivi. Se sono presenti voci nell'elenco, selezionare l'oggetto DeviceInformation con l'indice corrispondente all'oggetto SelectedIndex del controllo ListBox.

Creare l'oggetto MidiInPort che rappresenta il dispositivo di input selezionato chiamando MidiInPort.FromIdAsync, passando nella proprietà Id del dispositivo selezionato.

Registrare un gestore per l'evento MessageReceived, che viene generato ogni volta che viene ricevuto un messaggio MIDI tramite il dispositivo specificato.

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

Quando viene chiamato il gestore MessageReceived il messaggio è contenuto nella proprietà Message di MidiMessageReceivedEventArgs. Il tipo dell'oggetto messaggio è un valore dell'enumerazione MidiMessageType che indica il tipo di messaggio ricevuto. I dati del messaggio dipendono dal tipo di messaggio. In questo esempio viene verificato se il messaggio è una nota sul messaggio e, in tal caso, restituisce il canale midi, la nota e la velocità del messaggio.

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

Il gestore SelectionChanged per il dispositivo di output ListBox funziona come il gestore per i dispositivi di input, ad eccezione di nessun gestore eventi registrato.

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

}

Dopo aver creato il dispositivo di output, è possibile inviare un messaggio creando un nuovo IMidiMessage per il tipo di messaggio da inviare. In questo esempio il messaggio è NoteOnMessage. Il metodo SendMessage dell'oggetto IMidiOutPort viene chiamato per inviare il messaggio.

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

midiOutPort.SendMessage(midiMessageToSend);

Quando l'app è disattivata, assicurarsi di pulire le risorse delle app. Annullare la registrazione dei gestori eventi e impostare MIDI in oggetti porta e out su Null. Arrestare i watcher del dispositivo e impostarli su Null.

inputDeviceWatcher.StopWatcher();
inputDeviceWatcher = null;

outputDeviceWatcher.StopWatcher();
outputDeviceWatcher = null;

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

midiOutPort.Dispose();
midiOutPort = null;

Uso del synth MIDI generale di Windows integrato

Quando si enumerano i dispositivi MIDI di output usando la tecnica descritta in precedenza, l'app scoprirà un dispositivo MIDI denominato "Microsoft GS Wavetable Synth". Si tratta di un sintetizzatore MIDI generale integrato che si può riprodurre dall'app. Tuttavia, il tentativo di creare un outport MIDI a questo dispositivo avrà esito negativo, a meno che non sia stata inclusa l'estensione SDK per il synth integrato nel progetto.

Per includere l'estensione General MIDI Synth SDK nel progetto dell'app

  1. In Solution Explorer, sotto il progetto, fare clic con il tasto destro su References e selezionare Add reference...
  2. Espandere il nodo Universal Windows.
  3. Selezionare Estensioni.
  4. Nell'elenco delle estensioni selezionare Microsoft General MIDI DLS for Universal Windows Apps.

    Nota

    Se sono presenti più versioni dell'estensione, assicurarsi di selezionare la versione corrispondente alla destinazione per l'app. È possibile visualizzare la versione dell'SDK di destinazione dell'app nella scheda Applicazione delle proprietà del progetto.