다음을 통해 공유


MIDI

이 문서에서는 MIDI(악기 디지털 인터페이스) 디바이스를 열거하고 유니버설 Windows 앱에서 MIDI 메시지를 보내고 받는 방법을 보여줍니다. Windows 10은 USB를 통한 MIDI(클래스 규격 및 대부분의 소유 드라이버), Bluetooth LE를 통한 MIDI(Windows 10 기념일 버전 이상), 이더넷 및 라우팅된 MIDI를 통한 무료로 제공되는 타사 제품, MIDI를 지원합니다.

MIDI 디바이스 열거

MIDI 디바이스를 열거하고 사용하기 전에 프로젝트에 다음 네임스페이스를 추가합니다.

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

사용자가 시스템에 연결된 MIDI 입력 디바이스 중 하나를 선택할 수 있도록 하는 ListBox 컨트롤을 XAML 페이지에 추가합니다. 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 사용 앱은 일반적으로 입력 및 출력 디바이스 모두에 관심이 있으므로 이 예제에서는 중복 없이 MIDI 입력 및 MIDI 출력 디바이스 모두에 동일한 코드를 사용할 수 있도록 DeviceWatcher 패턴을 구현하는 도우미 클래스를 만듭니다.

디바이스 감시자로 사용할 새 클래스를 프로젝트에 추가합니다. 이 예제에서 클래스의 이름은 MyMidiDeviceWatcher입니다. 이 섹션의 나머지 코드는 도우미 클래스를 구현하는 데 사용됩니다.

클래스에 일부 멤버 변수를 추가합니다.

  • 디바이스 변경 내용을 모니터링할 DeviceWatcher 개체입니다.
  • 한 인스턴스에 대한 포트 선택기 문자열의 MIDI와 다른 인스턴스에 대한 MIDI out 포트 선택기 문자열을 포함하는 디바이스 선택기 문자열입니다.
  • 사용 가능한 디바이스의 이름으로 채워지는 ListBox 컨트롤입니다.
  • UI 스레드가 아닌 스레드에서 UI를 업데이트하는 데 필요한 CoreDispatcher입니다.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

도우미 클래스 외부에서 현재 디바이스 목록에 액세스하는 데 사용되는 DeviceInformationCollection 속성을 추가합니다.

public DeviceInformationCollection DeviceInformationCollection { get; set; }

클래스 생성자에서 호출자는 MIDI 디바이스 선택기 문자열, 디바이스를 나열하기 위한 ListBox 및 UI를 업데이트하는 데 필요한 Dispatcher를 전달합니다.

DeviceInformation.CreateWatcher를 호출하여 DeviceWatcher 클래스의 새 인스턴스를 만들고 MIDI 디바이스 선택기 문자열을 전달합니다.

감시자의 이벤트 처리기에 대한 처리기를 등록합니다.

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도우미 클래스의 두 인스턴스를 보유하도록 멤버 변수를 선언합니다. 하나는 입력 디바이스용이고 다른 하나는 출력 디바이스용입니다.

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 개체를 선택합니다.

MidiInPort.FromIdAsync를 호출하고 선택한 디바이스의 Id 속성을 전달하여 선택한 입력 디바이스를 나타내는 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 열거형의 값입니다. 메시지의 데이터는 메시지 유형에 따라 달라집니다. 이 예제에서는 메시지가 메시지에 대한 메모인지 확인하고, 메시지의 미디 채널, 참고 및 속도를 출력하는 검사를 합니다.

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

출력 디바이스 ListBox에 대한 SelectionChanged 처리기는 이벤트 처리기가 등록되지 않은 경우를 제외하고 입력 디바이스의 처리기와 동일하게 작동합니다.

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 디바이스를 검색합니다. 앱에서 재생할 수 있는 기본 제공 일반 MIDI 신시사이저입니다. 그러나 프로젝트에 기본 제공 신디사이저에 대한 SDK 확장을 포함하지 않는 한 이 디바이스에 MIDI 아웃포트를 만들려고 하면 실패합니다.

일반 MIDI 신시사이저 SDK 확장을 앱 프로젝트에 포함하려면

  1. 앱 프로젝트 아래의 Solution Explorer에서 프로젝트 References를 마우스 오른쪽 버튼으로 클릭하고 Add reference를 선택합니다.
  2. Universal Windows 노드를 확장합니다.
  3. 확장을 섡택합니다.
  4. 확장 목록에서 범용 Windows 앱용 Microsoft 일반 MIDI DLS을 선택합니다.

    참고 항목

    확장의 여러 버전이 있는 경우 앱의 대상과 일치하는 버전을 선택해야 합니다. 프로젝트 속성의 Application 탭에서 앱이 대상으로 하는 SDK 버전을 확인할 수 있습니다.