ミディ

この記事では、MIDI (楽器デジタル インターフェイス) デバイスを列挙し、WinUI アプリから MIDI メッセージを送受信する方法について説明します。 Windowsは、MIDI over USB (クラス準拠およびほとんどのプロプライエタリドライバー)、MIDI over Bluetooth LE、および自由に利用可能なサードパーティ製品、イーサネット経由の MIDI、ルーティング MIDI をサポートしています。

デバイス ウォッチャー ヘルパー クラスを作成する

Windows。Devices.Enumeration 名前空間は、DeviceWatcher を提供します。これは、デバイスがシステムに追加または削除された場合、またはデバイスの情報が更新された場合にアプリに通知できます。 通常、MIDI 対応アプリは入力デバイスと出力デバイスの両方に関心があるため、この例では DeviceWatcher パターンを実装するヘルパー クラスを作成し、同じコードを MIDI 入力デバイスと MIDI 出力デバイスの両方で使用できるように、重複を必要としません。

デバイス ウォッチャーとして機能する新しいクラスをプロジェクトに追加します。 この例では、クラスの名前は MidiDeviceWatcher です。 このセクションの残りのコードは、ヘルパー クラスを実装するために使用されます。

いくつかのメンバー変数をクラスに追加します。

  • デバイスの変更を監視する DeviceWatcher オブジェクト。
  • あるインスタンスでは MIDI 入力ポート セレクター文字列を、別のインスタンスでは MIDI 出力ポート セレクター文字列を含むデバイス セレクター文字列。
  • 使用可能なデバイスの名前が設定される ListBox コントロール。
  • UI スレッド以外のスレッドから UI を更新するために必要な DispatcherQueue
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
DispatcherQueue dispatcherQueue;

ヘルパー クラスの外部からデバイスの現在のリストにアクセスするために使用するDeviceInformationCollection プロパティを追加します。

public DeviceInformationCollection? DeviceInformationCollection { get; set; }

クラス コンストラクターでは、呼び出し元は MIDI デバイス セレクター文字列、デバイスを一覧表示するための ListBox 、UI を更新するために必要な DispatcherQueue を渡します。

DeviceInformation.CreateWatcher を呼び出して DeviceWatcher クラスの新しいインスタンスを作成し、MIDI デバイス セレクター文字列を渡します。

ウォッチャーのイベントハンドラー用のハンドラーを登録します。

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 には、次のイベントがあります。

  • 追加 - 新しいデバイスがシステムに追加されたときに発生します。
  • 削除済み - デバイスがシステムから削除されたときに発生します。
  • 更新 - 既存のデバイスに関連付けられている情報が更新されたときに発生します。
  • EnumerationCompleted - ウォッチャーが要求されたデバイスの種類の列挙を完了したときに発生します。

これらの各イベントのイベント ハンドラーでは、ヘルパー メソッド UpdateDevices が呼び出され、 ListBox が現在のデバイスの一覧で更新されます。 UpdateDevices は UI 要素を更新し、これらのイベント ハンドラーは UI スレッドで呼び出されないため、各呼び出しは 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();
    });
}

UpdateDevices ヘルパー メソッドは、DeviceInformation.FindAllAsync を呼び出し、この記事で前述したように、返されたデバイスの名前で ListBox を更新します。

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

DeviceWatcher オブジェクトの Start メソッドを使用してウォッチャーを開始し、Stop メソッドを使用してウォッチャーを停止するメソッドを追加します。

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

ウォッチャー イベント ハンドラーの登録を解除し、デバイス ウォッチャーを null に設定するデストラクターを指定します。

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

メッセージを送受信する MIDI ポートを作成する

ウィンドウの分離コードで、 MidiDeviceWatcher ヘルパー クラスの 2 つのインスタンス (入力デバイス用と出力デバイス用) を保持するメンバー変数を宣言します。

MidiDeviceWatcher? inputDeviceWatcher;
MidiDeviceWatcher? outputDeviceWatcher;

また、MIDI 入力および出力ポート オブジェクトのメンバー変数も宣言します。

MidiInPort? midiInPort;
IMidiOutPort? midiOutPort;

ウォッチャー ヘルパー クラスの新しいインスタンスを作成し、デバイス セレクター文字列、設定する ListBoxDispatcherQueue オブジェクトを渡します。 次に、メソッドを呼び出して、各オブジェクトの DeviceWatcher を開始します

DeviceWatcher が起動すると、システムに接続されている現在のデバイスの列挙が完了し、 EnumerationCompleted イベントが 発生します。これにより、各 ListBox が現在の MIDI デバイスで更新されます。

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

ユーザーが MIDI 入力 ListBox で項目を選択すると、 SelectionChanged イベントが発生します。 このイベントのハンドラーで、ヘルパー クラスの DeviceInformationCollection プロパティにアクセスして、デバイスの現在のリストを取得します。 リストにエントリがある場合は、ListBox コントロールの SelectedIndex に対応するインデックスを持つ DeviceInformation オブジェクトを選択します

選択した入力デバイスを表す MidiInPort オブジェクトMidiInPort.FromIdAsync 選択したデバイスの Id プロパティを渡します。

指定したデバイスを介して MIDI メッセージを受信するたびに発生する MessageReceived イベントのハンドラーを登録します。

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

MessageReceived ハンドラーが呼び出されると、メッセージは Message プロパティMidiMessageReceivedEventArgsに格納されます。 メッセージ オブジェクトの Type は、受信したメッセージの種類を示す MidiMessageType 列挙型の値です。 メッセージのデータは、メッセージの種類によって異なります。 この例では、メッセージがノートオンメッセージかどうかを確認し、該当する場合は、そのメッセージの MIDI チャンネル、ノート、およびベロシティを出力します。

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

出力デバイス ListBoxSelectionChanged ハンドラーは、イベント ハンドラーが登録されていない場合を除き、入力デバイスのハンドラーと同じように動作します。

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

出力デバイスが作成されたら、送信するメッセージの種類に対して新しい IMidiMessage を作成してメッセージを送信できます。 この例では、メッセージは NoteOnMessage です。 メッセージを送信するために、SendMessageIMidiOutPort オブジェクトのメソッドが呼び出されます。

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

midiOutPort.SendMessage(midiMessageToSend);

アプリが終了したら、必ずアプリのリソースをクリーンアップしてください。 イベント ハンドラの登録を解除し、ポートオブジェクトと出力ポートオブジェクトの MIDI を null に設定します。 デバイス ウォッチャーを停止し、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;
}

組み込みのWindows汎用 MIDI シンセを使用する

上記の手法を使用して出力 MIDI デバイスを列挙すると、アプリは "Microsoft GS Wavetable Synth" という MIDI デバイスを検出します。 これは、あなたのアプリから再生することができ、組み込みの一般的なMIDIシンセサイザーです。

UWP Extension SDK for General MIDI ("Microsoft General MIDI DLS for Universal Windows Apps") は、WinUI 3 プロジェクトでは使用できません。 以前の [拡張機能 > 参照の追加 ] ダイアログは、UWP に固有でした。 ただし、ほとんどのデスクトップ シナリオでは、GS Wavetable Synth は拡張 SDK なしで動作します。デスクトップ アプリはシステムの gm.dls サウンド バンクに直接アクセスできるためです。 MIDI 出力でサウンドが生成されない場合は、MIDI 出力デバイスが選択されていること、およびオーディオ出力が正しく構成されていることを確認します。