MIDI

この記事では、MIDI (Musical Instrument Digital Interface) デバイスを列挙する方法と、ユニバーサル Windows アプリとの間で MIDI メッセージを送受信する方法について説明します。 Windows 10 では、USB 経由の MIDI (クラス準拠の最もプロプライエタリなドライバー)、Bluetooth LE 経由の MIDI (Windows 10 Anniversary Edition 以降)、および、無料提供されているサードパーティ製品を経由して、Ethernet 経由の MIDI とルーティングされた MIDI をサポートしています。

MIDI デバイスの列挙

MIDI デバイスを列挙して使う前に、次の名前空間をプロジェクトに追加します。

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

XAML ページに ListBox コントロールを追加して、システムに接続されている MIDI 入力デバイスのいずれかをユーザーが選択できるようにします。 また、MIDI 出力の一覧を表示する別のコントロールを追加します。

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

FindAllAsync メソッドの DeviceInformation クラスは、Windows によって認識される多くの異なる種類のデバイスを列挙するのに使われます。 メソッドで MIDI 入力デバイスだけを検索するよう指定するには、MidiInPort.GetDeviceSelector によって返されるセレクター文字列を使います。 FindAllAsync は、システムに登録されている各 MIDI 入力デバイスの DeviceInformation が格納された DeviceInformationCollection を返します。 返されたコレクションに項目が含まれていない場合、利用可能な MIDI 入力デバイスはありません。 コレクションに項目が含まれる場合は、DeviceInformation オブジェクトのループ処理を行い、各デバイスの名前を MIDI 入力デバイスの ListBox に追加します。

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

MIDI 出力デバイスの列挙も入力デバイスの列挙とまったく同じように動作しますが、FindAllAsync を呼び出すときに、MidiOutPort.GetDeviceSelector によって返されるセレクター文字列を指定する必要があります。

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

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

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

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

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

  • デバイスの変更を監視する DeviceWatcher オブジェクト。
  • 1 つのインスタンスには MIDI 入力ポートのセレクター文字列、もう 1 つのインスタンスには MIDI 出力ポートのセレクター文字列が格納される、デバイス セレクター文字列。
  • 利用可能なデバイスの名前が格納される ListBox コントロール。
  • UI スレッド以外のスレッドから UI を更新するために必要な CoreDispatcher
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

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

public DeviceInformationCollection DeviceInformationCollection { get; set; }

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

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

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

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

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

これらの各イベントのイベント ハンドラーで、ヘルパー メソッド UpdateDevices が呼び出され、現在のデバイスの一覧で ListBox が更新されます。 UpdateDevices は UI 要素を更新し、これらのイベント ハンドラーは UI スレッドでは呼び出されないため、各呼び出しを RunAsync の呼び出しにラップすることで、指定したコードが UI スレッドで実行されるようにする必要があります。

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

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 に設定する、デストラクターを用意します。

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

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

ページの分離コードで、MyMidiDeviceWatcher ヘルパー クラスの 2 つのインスタンスを保持するメンバー変数を宣言します。1 つは入力デバイス用、もう 1 つは出力デバイス用です。

MyMidiDeviceWatcher inputDeviceWatcher;
MyMidiDeviceWatcher outputDeviceWatcher;

ウォッチャー ヘルパー クラスの新しいインスタンスを作成し、デバイスのセレクター文字列、格納する ListBox、およびページの Dispatcher プロパティでアクセスできる CoreDispatcher オブジェクトを渡します。 次に、各オブジェクトの DeviceWatcher を起動するメソッドを呼び出します。

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

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

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

選択したデバイスの Id プロパティを渡して MidiInPort.FromIdAsync を呼び出すことにより、選択した入力デバイスを表す MidiInPort オブジェクトを作成します。

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

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

MessageReceived ハンドラーが呼び出されると、MidiMessageReceivedEventArgsMessage プロパティにメッセージが格納されます。 メッセージ オブジェクトの 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 です。 IMidiOutPort オブジェクトの SendMessage メソッドが呼び出されて、メッセージを送信します。

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;

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

midiOutPort.Dispose();
midiOutPort = null;

組み込みの Windows General MIDI シンセサイザーを使用する

上記で説明した手法を使用して MIDI 出力デバイスを列挙すると、アプリは、"Microsoft GS Wavetable Synth" という MIDI デバイスを検出します。 このデバイスは、アプリから使用できる組み込みの General MIDI シンセサイザーです。 ただし、組み込みのシンセサイザーの SDK 拡張機能をプロジェクトに含めていない限り、このデバイスへの MIDI アウトポートを作成しようとすると失敗します。

General MIDI シンセサイザーの SDK 拡張機能をアプリ プロジェクトに含めるには、次の手順に従います。

  1. ソリューション エクスプローラーで、プロジェクトの下にある [参照設定] を右クリックし、[参照の追加] を選択します。
  2. [Universal Windows] ノードを展開します。
  3. [拡張機能] を選択します。
  4. 拡張機能の一覧から [Microsoft General MIDI DLS for Universal Windows Apps] を選択します。

    注意

    複数のバージョンの拡張機能がある場合、アプリのターゲットと一致するバージョンを選んでください。 プロジェクトのプロパティの [アプリケーション] タブで、アプリがターゲットとしている SDK バージョンを確認できます。