MIDI

In dit artikel leest u hoe u MIDI-apparaten (Musical Instrument Digital Interface) opsommen en MIDI-berichten verzendt en ontvangt van een WinUI-app. Windows ondersteunt MIDI via USB (klasse-compatibele en meest eigen stuurprogramma's), MIDI via Bluetooth LE en via vrij beschikbare producten van derden, MIDI over Ethernet en gerouteerde MIDI.

Een helperklasse voor apparaatbewaking maken

De Windows. Devices.Enumeration naamruimte biedt de DeviceWatcher die uw app kunnen waarschuwen als apparaten worden toegevoegd aan of verwijderd uit het systeem, of als de informatie voor een apparaat wordt bijgewerkt. Omdat MIDI-apps doorgaans geïnteresseerd zijn in zowel invoer- als uitvoerapparaten, wordt in dit voorbeeld een helperklasse gemaakt die het DeviceWatcher-patroon implementeert, zodat dezelfde code kan worden gebruikt voor zowel MIDI-invoer- als MIDI-uitvoerapparaten, zonder duplicatie.

Voeg een nieuwe klasse toe aan uw project om te fungeren als apparaat-watcher. In dit voorbeeld heet de klasse MidiDeviceWatcher. De rest van de code in deze sectie wordt gebruikt om de helperklasse te implementeren.

Voeg enkele lidvariabelen toe aan de klasse:

  • Een DeviceWatcher-object dat controleert op apparaatwijzigingen.
  • Een apparaatkiezertekenreeks die voor het ene exemplaar de tekenreeks van de MIDI-inpoortkiezer bevat en voor een ander exemplaar de tekenreeks van de MIDI-uitpoortkiezer.
  • Een keuzelijstbesturingselement dat wordt gevuld met de namen van de beschikbare apparaten.
  • Een DispatcherQueue die is vereist om de gebruikersinterface bij te werken vanuit een andere thread dan de UI-thread.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
DispatcherQueue dispatcherQueue;

Voeg een eigenschap DeviceInformationCollection toe die wordt gebruikt voor toegang tot de huidige lijst met apparaten van buiten de helperklasse.

public DeviceInformationCollection? DeviceInformationCollection { get; set; }

In de klasseconstructor geeft de aanroeper de selectorreeks van het MIDI-apparaat door, de keuzelijst voor het weergeven van de apparaten en de DispatcherQueue die nodig is om de gebruikersinterface bij te werken.

Roep DeviceInformation.CreateWatcher aan om een nieuw exemplaar van de DeviceWatcher-klasse te maken, waarbij de selectortekenreeks van het MIDI-apparaat wordt doorgegeven.

Registreer afhandelaren voor de gebeurtenisafhandelaren van de watcher.

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 heeft de volgende gebeurtenissen:

  • Toegevoegd : verhoogd wanneer een nieuw apparaat wordt toegevoegd aan het systeem.
  • Verwijderd - Gegenereerd wanneer een apparaat uit het systeem wordt verwijderd.
  • Bijgewerkt - Treedt op wanneer de informatie over een bestaand apparaat wordt bijgewerkt.
  • OpsommingVoltooid - Wordt geactiveerd wanneer de watcher de opsomming van het aangevraagde type apparaat heeft voltooid.

In de gebeurtenis-handler voor elk van deze gebeurtenissen wordt een helpermethode, UpdateDevices, aangeroepen om het keuzelijstvak bij te werken met de huidige lijst met apparaten. Omdat met UpdateDevices UI-elementen worden bijgewerkt en deze gebeurtenishandlers niet op de UI-thread worden aangeroepen, moet elke aanroep worden ingesloten in een aanroep van 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();
    });
}

Met de helpermethode UpdateDevices wordt DeviceInformation.FindAllAsync aangeroepen en wordt het keuzelijstvak bijgewerkt met de namen van de geretourneerde apparaten, zoals eerder in dit artikel is beschreven.

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

Voeg methoden toe om de watcher te starten, met behulp van de startmethode van het DeviceWatcher-object en om de watcher te stoppen met behulp van de stopmethode.

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

Implementeer een destructor om de gebeurtenis-handlers van de watcher af te melden en de apparaatwatcher op null in te stellen.

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

MIDI-poorten maken om berichten te verzenden en te ontvangen

Declareer in de code achter het venster lidvariabelen voor het opslaan van twee exemplaren van de helperklasse MidiDeviceWatcher , één voor invoerapparaten en één voor uitvoerapparaten.

MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;

Declareer ook lidvariabelen voor de MIDI-invoer- en uitvoerpoortobjecten.

MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;

Maak een nieuw exemplaar van de helperklassen voor watchers, waarbij u de apparaatselector-tekenreeks, de ListBox die moet worden ingevuld, en het object DispatcherQueue doorgeeft. Roep vervolgens de methode aan om de DeviceWatcher van elk object te starten.

Kort nadat elke DeviceWatcher is gestart, rondt deze het inventariseren van de momenteel op het systeem aangesloten apparaten af en activeert deze de gebeurtenis EnumerationCompleted, waardoor elke ListBox wordt bijgewerkt met de momenteel aangesloten MIDI-apparaten.

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

Wanneer de gebruiker een item selecteert in de MIDI-invoerlijstbox, wordt de gebeurtenis SelectionChanged gegenereerd. Open in de handler voor deze gebeurtenis de eigenschap DeviceInformationCollection van de helperklasse om de huidige lijst met apparaten op te halen. Als er vermeldingen in de lijst staan, selecteert u het DeviceInformation-object met de index die overeenkomt met de SelectedIndex van het besturingselement ListBox.

Maak het object MidiInPort dat het geselecteerde invoerapparaat vertegenwoordigt door MidiInPort.FromIdAsync aan te roepen, geeft de eigenschap Id van het geselecteerde apparaat door.

Registreer een handler voor de MessageReceived-gebeurtenis , die wordt gegenereerd wanneer een MIDI-bericht wordt ontvangen via het opgegeven apparaat.

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

Wanneer de handler MessageReceived wordt aangeroepen, bevindt het bericht zich in de eigenschap Message van de eigenschap MidiMessageReceivedEventArgs. De Type van het berichtobject is een waarde uit de MidiMessageType opsomming die aangeeft welk type bericht is ontvangen. De gegevens van het bericht zijn afhankelijk van het type bericht. In dit voorbeeld wordt gecontroleerd of het bericht een Note On-bericht is en, zo ja, worden het MIDI-kanaal, de noot en de velocity van het bericht weergegeven.

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

De SelectionChanged-handler voor het uitvoerapparaat ListBox werkt hetzelfde als de handler voor invoerapparaten, behalve dat er geen gebeurtenis-handler is geregistreerd.

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

Zodra het uitvoerapparaat is gemaakt, kunt u een bericht verzenden door een nieuwe IMidiMessage te maken voor het type bericht dat u wilt verzenden. In dit voorbeeld is het bericht een NoteOnMessage. Het SendMessage van de methode IMidiOutPort wordt aangeroepen om het bericht te verzenden.

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

midiOutPort.SendMessage(midiMessageToSend);

Wanneer uw app wordt gesloten, moet u de resources van uw app opschonen. Maak de registratie van uw eventhandlers ongedaan en stel de MIDI-inpoort- en MIDI-uitpoortobjecten in op null. Stop de apparaatwatchers en stel deze in op 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;
}

De ingebouwde Windows General MIDI synth gebruiken

Wanneer u MIDI-uitvoerapparaten inventariseert met behulp van de hierboven beschreven techniek, vindt uw app een MIDI-apparaat met de naam "Microsoft GS Wavetable Synth". Dit is een ingebouwde General MIDI-synthesizer die u vanuit uw app kunt afspelen.

De UWP Extension SDK voor General MIDI ('Microsoft General MIDI DLS for Universal Windows Apps') is niet beschikbaar in WinUI 3-projecten. Het oude dialoogvenster Verwijzingsextensies > toevoegen was specifiek voor UWP. Voor de meeste desktopscenario's werkt de GS Wavetable Synth echter zonder de extensie-SDK, omdat desktop-apps rechtstreeks toegang hebben tot de geluidsbank van gm.dls het systeem. Als u merkt dat MIDI-uitvoer geen geluid produceert, controleert u of een MIDI-uitvoerapparaat is geselecteerd en of uw audio-uitvoer juist is geconfigureerd.