MIDI

In diesem Artikel erfahren Sie, wie Sie MIDI-Geräte (Musical Instrument Digital Interface) aufzählen und MIDI-Nachrichten von einer WinUI-App senden und empfangen. Windows unterstützt MIDI über USB (klassenkonforme und proprietäre Treiber), MIDI über Bluetooth LE und über frei verfügbare Drittanbieterprodukte, MIDI über Ethernet und routingfähiges MIDI.

Erstellen einer Geräteüberwachungshilfsklasse

Die Windows. Devices.Enumeration Namespace stellt den DeviceWatcher bereit, der Ihre App benachrichtigen kann, wenn Geräte vom System hinzugefügt oder entfernt werden oder die Informationen für ein Gerät aktualisiert werden. Da MIDI-fähige Apps in der Regel sowohl an Eingabe- als auch an Ausgabegeräten interessiert sind, erstellt dieses Beispiel eine Hilfsklasse, die das DeviceWatcher-Muster implementiert, sodass derselbe Code sowohl für MIDI-Eingabe- als auch für MIDI-Ausgabegeräte verwendet werden kann, ohne dass eine Duplizierung erforderlich ist.

Fügen Sie Ihrem Projekt eine neue Klasse hinzu, die als Gerätebeobachter dient. In diesem Beispiel heißt die Klasse MidiDeviceWatcher. Der restliche Code in diesem Abschnitt wird verwendet, um die Hilfsklasse zu implementieren.

Fügen Sie der Klasse einige Mitgliedsvariablen hinzu:

  • Ein DeviceWatcherObjekt, das auf Geräteänderungen überwacht.
  • Eine Geräte-Selektorzeichenfolge, die bei einer Instanz die MIDI-In-Port-Selektorzeichenfolge und bei einer anderen Instanz die MIDI-Out-Port-Selektorzeichenfolge enthält.
  • Ein ListBox-Steuerelement , das mit den Namen der verfügbaren Geräte aufgefüllt wird.
  • Eine DispatcherQueue, die benötigt wird, um die Benutzeroberfläche aus einem anderen Thread als dem UI-Thread zu aktualisieren.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
DispatcherQueue dispatcherQueue;

Fügen Sie eine DeviceInformationCollectionEigenschaft hinzu, die für den Zugriff auf die aktuelle Liste von Geräten außerhalb der Hilfsklasse verwendet wird.

public DeviceInformationCollection? DeviceInformationCollection { get; set; }

Im Klassenkonstruktor übergibt der Aufrufer die Auswahlzeichenfolge des MIDI-Geräts, das ListBox-Element zum Auflisten der Geräte und die DispatcherQueue , die zum Aktualisieren der Benutzeroberfläche erforderlich ist.

Rufen Sie DeviceInformation.CreateWatcher auf, um eine neue Instanz der DeviceWatcher-Klasse zu erstellen und dabei die MIDI-Geräteauswahlzeichenfolge zu übergeben.

Registrieren Sie Handler für die Ereignishandler des Watchers.

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 hat die folgenden Ereignisse:

  • Hinzugefügt – Wird ausgelöst, wenn dem System ein neues Gerät hinzugefügt wird.
  • Entfernt – Wird ausgelöst, wenn ein Gerät aus dem System entfernt wird.
  • Aktualisiert – Wird ausgelöst, wenn die Informationen, die einem vorhandenen Gerät zugeordnet sind, aktualisiert werden.
  • EnumerationCompleted – Wird ausgelöst, wenn der Watcher die Enumeration des angeforderten Gerätetyps abgeschlossen hat.

Im Ereignishandler für jedes dieser Ereignisse wird eine Hilfsmethode UpdateDevices aufgerufen, um das ListBox mit der aktuellen Liste der Geräte zu aktualisieren. Da UpdateDevices UI-Elemente aktualisiert und diese Ereignishandler nicht im UI-Thread aufgerufen werden, muss jeder Aufruf in einen Aufruf von DispatcherQueue.TryEnqueue eingeschlossen werden.

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

Die UpdateDevices-Hilfsmethode ruft DeviceInformation.FindAllAsync auf und aktualisiert das ListBox-Objekt mit den Namen der zurückgegebenen Geräte, wie zuvor in diesem Artikel beschrieben.

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

Fügen Sie Methoden hinzu, um die Überwachung zu starten, indem Sie die Start-Methode des DeviceWatcher-Objekts verwenden und die Überwachung mithilfe der Stop-Methode beenden.

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

Stellen Sie einen Destruktor bereit, um die Ereignishandler des Device Watchers abzumelden und den Device Watcher auf NULL zu setzen.

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

Erstellen von MIDI-Ports zum Senden und Empfangen von Nachrichten

Deklarieren Sie im CodeBehind für Ihr Fenster Membervariablen, um zwei Instanzen der MidiDeviceWatcher-Hilfsklasse zu speichern, eine für Eingabegeräte und eine für Ausgabegeräte.

MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;

Deklarieren Sie außerdem Membervariablen für die MIDI-Eingabe- und Ausgabeportobjekte.

MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;

Erstellen Sie eine neue Instanz der Watcher-Hilfsklassen, wobei Sie die Geräteselektorzeichenfolge, die zu befüllende ListBox und das DispatcherQueue-Objekt übergeben. Rufen Sie dann die Methode auf, um die DeviceWatcher-Objekte jedes Objekts zu starten.

Kurz nach dem Start jedes DeviceWatcher-Elements wird das Aufzählen der aktuellen Geräte abgeschlossen, die mit dem System verbunden sind, und das EnumerationCompleted-Ereignis auslösen, wodurch jedes ListBox-Element mit den aktuellen MIDI-Geräten aktualisiert wird.

inputDeviceWatcher =
    new MidiDeviceWatcher(MidiInPort.GetDeviceSelector(), midiInPortListBox, DispatcherQueue);

inputDeviceWatcher.StartWatcher();

outputDeviceWatcher =
    new MidiDeviceWatcher(MidiOutPort.GetDeviceSelector(), midiOutPortListBox, DispatcherQueue);

outputDeviceWatcher.StartWatcher();

Wenn der Benutzer ein Element im MIDI-Eingabelistenfeld auswählt, wird das SelectionChanged-Ereignis ausgelöst. Greifen Sie im Handler für dieses Ereignis auf die DeviceInformationCollection-Eigenschaft der Hilfsklasse zu, um die aktuelle Liste der Geräte abzurufen. Wenn die Liste Einträge enthält, wählen Sie das DeviceInformation-Objekt mit dem Index aus, der dem SelectedIndex des ListBox-Steuerelements entspricht.

Erstellen Sie das MidiInPortObjekt, das das ausgewählte Eingabegerät darstellt, indem Sie MidiInPort.FromIdAsync aufrufen, übergeben sie die eigenschaft Id des ausgewählten Geräts.

Registrieren Sie einen Handler für das MessageReceived-Ereignis , das ausgelöst wird, wenn eine MIDI-Nachricht über das angegebene Gerät empfangen wird.

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

Wenn der MessageReceivedHandler aufgerufen wird, ist die Nachricht in der Message-Eigenschaft der MidiMessageReceivedEventArgs enthalten. Die Type des Nachrichtenobjekts ist ein Wert aus dem MidiMessageType Enumeration, der den Typ der empfangenen Nachricht angibt. Die Daten der Nachricht hängen vom Typ der Nachricht ab. In diesem Beispiel wird überprüft, ob es sich bei der Meldung um eine Note-On-Meldung handelt, und wenn ja, werden der MIDI-Kanal, die Note und die Velocity der Meldung ausgegeben.

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

Der SelectionChanged-Handler für das Listenfeld des Ausgabegeräts funktioniert mit dem Handler für Eingabegeräte, außer dass kein Ereignishandler registriert ist.

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

Nachdem das Ausgabegerät erstellt wurde, können Sie eine Nachricht senden, indem Sie eine neue IMidiMessage für den Nachrichtentyp erstellen, den Sie senden möchten. In diesem Beispiel ist die Nachricht eine NoteOnMessage. Die SendMessage Methode der IMidiOutPort-Objekt wird aufgerufen, um die Nachricht zu senden.

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

midiOutPort.SendMessage(midiMessageToSend);

Achten Sie beim Schließen der App darauf, die Ressourcen Ihrer App zu bereinigen. Melden Sie Ihre Ereignishandler ab und setzen Sie die MIDI-Eingangsport- und Ausgangsportobjekte auf null. Beenden Sie die Geräteüberwachungen, und legen Sie sie auf NULL fest.

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

Verwenden des integrierten Windows General MIDI Synth

Wenn Sie ausgabe-MIDI-Geräte mit der oben beschriebenen Technik aufzählen, ermittelt Ihre App ein MIDI-Gerät namens "Microsoft GS Wavetable Synth". Dies ist ein integrierter General-MIDI-Synthesizer, den Sie aus Ihrer App heraus spielen können.

Das UWP Extension SDK für General MIDI ("Microsoft General MIDI DLS for Universal Windows Apps") ist in WinUI 3-Projekten nicht verfügbar. Das alte Dialogfeld " Verweiserweiterungen > hinzufügen " war für UWP spezifisch. Für die meisten Desktopszenarien funktioniert das GS Wavetable Synth jedoch ohne das Erweiterungs-SDK, da Desktop-Apps direkt auf die Soundbank des gm.dls Systems zugreifen können. Wenn Sie feststellen, dass die MIDI-Ausgabe keinen Sound erzeugt, stellen Sie sicher, dass ein MIDI-Ausgabegerät ausgewählt ist und dass die Audioausgabe ordnungsgemäß konfiguriert ist.