MIDI

Den här artikeln visar hur du räknar upp MIDI-enheter (Musical Instrument Digital Interface) och skickar och tar emot MIDI-meddelanden från en WinUI-app. Windows stöder MIDI via USB (klasskompatibla och de flesta proprietära drivrutiner), MIDI via Bluetooth LE och via fritt tillgängliga produkter från tredje part, MIDI via Ethernet och dirigerad MIDI.

Skapa en hjälpklass för enhetsövervakare

Windows. Devices.Enumeration ger namnområdet DeviceWatcher som kan meddela appen om enheter läggs till eller tas bort från systemet, eller om informationen för en enhet uppdateras. Eftersom MIDI-aktiverade appar vanligtvis är intresserade av både in- och utdataenheter skapar det här exemplet en hjälpklass som implementerar DeviceWatcher-mönstret , så att samma kod kan användas för både MIDI-indata och MIDI-utdataenheter, utan att du behöver duplicering.

Lägg till en ny klass i projektet som ska fungera som enhetsövervakare. I det här exemplet heter klassen MidiDeviceWatcher. Resten av koden i det här avsnittet används för att implementera hjälpklassen.

Lägg till några medlemsvariabler i klassen:

  • Ett DeviceWatcher objekt som övervakar enhetsändringar.
  • En enhetsväljarsträng som kommer att innehålla väljarsträngen för MIDI-in-porten i en instans och väljarsträngen för MIDI-ut-porten i en annan instans.
  • En ListBox-kontroll som fylls i med namnen på de tillgängliga enheterna.
  • En DispatcherQueue som krävs för att uppdatera användargränssnittet från en annan tråd än användargränssnittstråden.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
DispatcherQueue dispatcherQueue;

Lägg till egenskapen DeviceInformationCollection som används för att komma åt den aktuella listan över enheter utanför hjälpklassen.

public DeviceInformationCollection? DeviceInformationCollection { get; set; }

I klasskonstruktorn anger anroparen strängen för val av MIDI-enhet, den ListBox-kontroll som används för att lista enheterna och den DispatcherQueue som behövs för att uppdatera användargränssnittet.

Anropa DeviceInformation.CreateWatcher för att skapa en ny instans av klassen DeviceWatcher och skicka in MIDI-enhetsväljarens sträng.

Registrera hanterare för bevakarens händelsehanterare.

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 har följande händelser:

  • Tillagd – Upphöjt när en ny enhet läggs till i systemet.
  • Borttagen – Upphöjt när en enhet tas bort från systemet.
  • Uppdaterad – Utlöses när informationen som är associerad med en befintlig enhet uppdateras.
  • EnumerationCompleted – Upphöjt när bevakaren har slutfört sin uppräkning av den begärda enhetstypen.

I händelsehanteraren för var och en av dessa händelser anropas en hjälpmetod, UpdateDevices, för att uppdatera ListBox med den aktuella listan över enheter. Eftersom UpdateDevices uppdaterar användargränssnittselement och dessa händelsehanterare inte anropas i användargränssnittstråden måste varje anrop omslutas i ett anrop till 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();
    });
}

Hjälpmetoden UpdateDevices anropar DeviceInformation.FindAllAsync och uppdaterar ListBox med namnen på de returnerade enheterna enligt beskrivningen tidigare i den här artikeln.

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

Lägg till metoder för att starta bevakaren med hjälp av DeviceWatcher-objektetsStart-metod och för att stoppa bevakaren med hjälp av metoden Stoppa .

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

Ange en destruktor för att avregistrera watcher-händelsehanterare och ställ in enhetsskådaren på null.

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

Skapa MIDI-portar för att skicka och ta emot meddelanden

I koden bakom för ditt fönster deklarerar du medlemsvariabler så att de innehåller två instanser av hjälpklassen MidiDeviceWatcher , en för indataenheter och en för utdataenheter.

MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;

Deklarera även medlemsvariabler för MIDI-indata- och utdataportobjekten.

MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;

Skapa en ny instans av watcher-hjälpklasserna, skicka in enhetsväljarens sträng, ListBox som ska fyllas i och DispatcherQueue-objektet . Anropa sedan metoden för att starta varje objekts DeviceWatcher.

Strax efter att varje DeviceWatcher har startats kommer den att slutföra uppräkningen av de aktuella enheterna som är anslutna till systemet och höja händelsen EnumerationCompleted , vilket gör att varje ListBox uppdateras med de aktuella MIDI-enheterna.

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

När användaren väljer ett objekt i MIDI-indatalistan utlöses händelsen SelectionChanged. I hanteraren för den här händelsen öppnar du egenskapen DeviceInformationCollection för hjälpklassen för att hämta den aktuella listan över enheter. Om det finns poster i listan väljer du deviceinformation-objektet med indexet som motsvarar ListBox-kontrollensSelectedIndex.

Skapa objektet MidiInPort som representerar den valda indataenheten genom att anropa MidiInPort.FromIdAsync, skickar egenskapen Id för den valda enheten.

Registrera en hanterare för händelsen MessageReceived, som genereras när ett MIDI-meddelande tas emot via den angivna enheten.

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

När hanteraren MessageReceived anropas, meddelandet finns i egenskapen Message för egenskapen MidiMessageReceivedEventArgs. Type för meddelandeobjektet är ett värde från MidiMessageType uppräkning som anger vilken typ av meddelande som togs emot. Meddelandets data beror på typen av meddelande. Det här exemplet kontrollerar om meddelandet är ett Note On-meddelande och skriver i så fall ut MIDI-kanalen, noten och meddelandets hastighet.

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

Hanteraren SelectionChanged för utdataenheten ListBox fungerar på samma sätt som hanteraren för indataenheter, förutom att ingen händelsehanterare har registrerats.

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

När utdataenheten har skapats kan du skicka ett meddelande genom att skapa en ny IMidiMessage för den typ av meddelande som du vill skicka. I det här exemplet är meddelandet en NoteOnMessage. Objektet SendMessage för IMidiOutPort anropas för att skicka meddelandet.

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

midiOutPort.SendMessage(midiMessageToSend);

När appen stängs måste du rensa appens resurser. Avregistrera dina händelsehanterare och ställ objekten för MIDI-inport och MIDI-utport till null. Stoppa enhetens bevakare och ställ in dem på 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;
}

Använda den inbyggda Windows General MIDI-synthesizern

När du räknar upp MIDI-utdataenheter med hjälp av den teknik som beskrivs ovan identifierar appen en MIDI-enhet med namnet "Microsoft GS Wavetable Synth". Det här är en inbyggd General MIDI-synt som du kan spela upp från din app.

UWP Extension SDK för Allmänt MIDI ("Microsoft General MIDI DLS for Universal Windows Apps") är inte tillgängligt i WinUI 3-projekt. Den gamla dialogrutan Lägg till referenstillägg > var specifik för UWP. Men för de flesta skrivbordsscenarier fungerar GS Wavetable Synth utan tilläggs-SDK eftersom skrivbordsappar kan komma åt systemets gm.dls ljudbank direkt. Om du upptäcker att MIDI-utdata inte genererar något ljud kontrollerar du att en MIDI-utdataenhet har valts och att ljudutdata är korrekt konfigurerade.