Поделиться через


MIDI

В этой статье показано, как перечислить устройства MIDI (музыкальный цифровой интерфейс) и отправлять и получать сообщения MIDI из универсального приложения Для Windows. Windows 10 поддерживает MIDI через USB (совместимые с классами и большинство собственных драйверов), MIDI через Bluetooth LE (Windows 10 Anniversary Edition и более поздних версий), а также через бесплатные сторонние продукты, MIDI через Ethernet и перенаправленные MIDI.

Перечисление устройств MIDI

Перед перечислением и использованием устройств MIDI добавьте в проект следующие пространства имен.

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

Добавьте элемент управления ListBox на страницу XAML, который позволит пользователю выбрать один из устройств ввода MIDI, подключенных к системе. Добавьте еще один элемент для перечисления выходных устройств MIDI.

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

Класс DeviceInformation метода FindAllAsync используется для перечисления различных типов устройств, распознаваемых Windows. Чтобы указать, что метод будет находить устройства ввода MIDI, используйте строку селектора, возвращаемую MidiInPort.GetDeviceSelector. FindAllAsync возвращает DeviceInformationCollection, содержащий DeviceInformation для каждого устройства ввода MIDI, зарегистрированного в системе. Если возвращаемая коллекция не содержит элементов, то нет доступных устройств ввода 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 без необходимости дублирования.

Добавьте в проект новый класс, который будет использоваться в качестве наблюдателя за устройствами. В этом примере класс называется MyMidiDeviceWatcher. Остальная часть кода в этом разделе используется для реализации вспомогательного класса.

Добавьте в класс некоторые переменные-члены:

  • Объект DeviceWatcher, который будет отслеживать изменения устройства.
  • Строка селектора устройства, содержащая MIDI в строке селектора портов для одного экземпляра и строки селектора портов MIDI для другого экземпляра.
  • Элемент управления ListBox , который будет заполнен именами доступных устройств.
  • ЯдроDispatcher, необходимое для обновления пользовательского интерфейса из потока, отличного от потока пользовательского интерфейса.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

Добавьте свойство DeviceInformationCollection, используемое для доступа к текущему списку устройств за пределами вспомогательного класса.

public DeviceInformationCollection DeviceInformationCollection { get; set; }

В конструкторе классов вызывающий объект передает строку селектора устройства MIDI, ListBox для перечисления устройств и диспетчера , необходимого для обновления пользовательского интерфейса.

Вызов 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 имеет следующие события:

  • Добавлено . Вызывается при добавлении нового устройства в систему.
  • Удалено — возникает при удалении устройства из системы.
  • Обновлено . Вызывается при обновлении сведений, связанных с существующим устройством.
  • ПеречислениеCompleted — возникает, когда наблюдатель завершил его перечисление запрошенного типа устройства.

В обработчике событий для каждого из этих событий вызывается вспомогательный метод UpdateDevices, чтобы обновить ListBox с текущим списком устройств. Так как UpdateDevices обновляет элементы пользовательского интерфейса и эти обработчики событий не вызываются в потоке пользовательского интерфейса, каждый вызов должен быть упакован в вызов RunAsync, что приводит к выполнению указанного кода в потоке пользовательского интерфейса.

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

Добавьте методы для запуска наблюдателя с помощью метода Start объекта DeviceWatcher и остановки наблюдателя с помощью метода 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 для заполнения и объект CoreDispatcher, к которому можно получить доступ через свойство диспетчера страницы. Затем вызовите метод, чтобы запустить устройство DeviceWatcher каждого объекта.

Вскоре после запуска DeviceWatcher будет завершено перечисление текущих устройств, подключенных к системе, и вызовет событие ListionCompleted, что приведет к обновлению каждого ListBox с текущими устройствами MIDI.

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

Когда пользователь выбирает элемент во входных элементах MIDI ListBox, вызывается событие SelectionChanged . В обработчике этого события перейдите к свойству DeviceInformationCollection вспомогательного класса, чтобы получить текущий список устройств. Если в списке есть записи, выберите объект DeviceInformation с индексом, соответствующим элементу управления ListBox SelectedIndex.

Создайте объект MidiInPort, представляющий выбранное устройство ввода, вызвав MidiInPort.FromIdAsync, передав свойство Id выбранного устройства.

Зарегистрируйте обработчик события MessageReceived , который возникает при получении сообщения MIDI через указанное устройство.

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 сообщение содержится в свойстве Message midiMessageReceivedEventArgs. Тип объекта сообщения — это значение перечисления 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);
    }
}

Обработчик SelectionChanged для выходного устройства ListBox работает так же, как обработчик для входных устройств, за исключением того, что обработчик событий не зарегистрирован.

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 для типа сообщения, которое нужно отправить. В этом примере сообщение представляет собой ПримечаниеOnMessage. Метод SendMessage объекта IMidiOutPort вызывается для отправки сообщения.

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;

Использование встроенного synth Windows General MIDI

При перечислении выходных устройств MIDI с помощью описанного выше метода приложение обнаружит устройство MIDI под названием Microsoft GS Wavetable Synth. Это встроенный синтезатор MIDI, который можно воспроизводить из приложения. Однако попытка создать выход MIDI на это устройство завершится ошибкой, если вы не включили расширение ПАКЕТА SDK для встроенного синта в проекте.

Включение расширения общего пакета SDK ДЛЯ MIDI Synth в проект приложения

  1. В Обозреватель решений в проекте щелкните правой кнопкой мыши ссылки и выберите "Добавить ссылку...
  2. Разверните узел универсального windows .
  3. Выберите Расширения.
  4. В списке расширений выберите Microsoft General MIDI DLS для универсальных приложений Windows.

    Примечание.

    Если существует несколько версий расширения, обязательно выберите версию, соответствующую целевому объекту приложения. Вы можете увидеть, какая версия пакета SDK для приложения предназначена на вкладке "Приложение " свойств проекта.