Partilhar via


MIDI

Este artigo mostra como enumerar dispositivos MIDI (Musical Instrument Digital Interface) e enviar e receber mensagens MIDI de um aplicativo Universal do Windows. O Windows 10 dá suporte a MIDI por USB (compatível com a classe e a maioria dos drivers proprietários), MIDI por Bluetooth LE (Windows 10 Anniversary Edition e posterior) e por meio de produtos de terceiros disponíveis gratuitamente, MIDI sobre Ethernet e MIDI roteado.

Enumerar dispositivos MIDI

Antes de enumerar e usar dispositivos MIDI, adicione os namespaces a seguir ao seu projeto.

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

Adicione um controle ListBox à sua página XAML que permitirá ao usuário selecionar um dos dispositivos de entrada MIDI anexados ao sistema. Adicione outro para listar os dispositivos de saída MIDI.

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

A classe DeviceInformation do método FindAllAsync é usada para enumerar muitos tipos diferentes de dispositivos que são reconhecidos pelo Windows. Para especificar que você deseja apenas que o método localize dispositivos de entrada MIDI, use a cadeia de caracteres seletora retornada por MidiInPort.GetDeviceSelector. FindAllAsync retorna um DeviceInformationCollection que contém um DeviceInformation para cada dispositivo de entrada MIDI registrado no sistema. Se a coleção retornada não contiver itens, não haverá dispositivos de entrada MIDI disponíveis. Se houver itens na coleção, percorra os objetos DeviceInformation e adicione o nome de cada dispositivo à ListBox do dispositivo de entrada MIDI.

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

A enumeração de dispositivos de saída MIDI funciona exatamente da mesma maneira que a enumeração de dispositivos de entrada, exceto que você deve especificar a cadeia de caracteres seletora retornada por MidiOutPort.GetDeviceSelector ao chamar FindAllAsync.

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

Criar uma classe auxiliar do inspetor de dispositivo

O namespace Windows.Devices.Enumeration fornece o DeviceWatcher que pode notificar seu aplicativo se os dispositivos forem adicionados ou removidos do sistema ou se as informações de um dispositivo forem atualizadas. Como os aplicativos habilitados para MIDI normalmente estão interessados em dispositivos de entrada e saída, este exemplo cria uma classe auxiliar que implementa o padrão DeviceWatcher , para que o mesmo código possa ser usado para dispositivos de entrada e saída MIDI, sem a necessidade de duplicação.

Adicione uma nova classe ao seu projeto para servir como observador do dispositivo. Neste exemplo, a classe é chamada MyMidiDeviceWatcher. O restante do código nesta seção é usado para implementar a classe auxiliar.

Adicione algumas variáveis de membro à classe:

  • Um objeto DeviceWatcher que monitorará as alterações do dispositivo.
  • Uma string de seletor de dispositivo que conterá a string do seletor de porta MIDI in para uma instância e a string do seletor de porta de saída MIDI para outra instância.
  • Um controle ListBox que será preenchido com os nomes dos dispositivos disponíveis.
  • Um CoreDispatcher necessário para atualizar a interface do usuário de um thread diferente do thread da interface do usuário.
DeviceWatcher deviceWatcher;
string deviceSelectorString;
ListBox deviceListBox;
CoreDispatcher coreDispatcher;

Adicione uma propriedade DeviceInformationCollection que é usada para acessar a lista atual de dispositivos de fora da classe auxiliar.

public DeviceInformationCollection DeviceInformationCollection { get; set; }

No construtor de classe, o chamador passa a cadeia de caracteres do seletor de dispositivo MIDI, o ListBox para listar os dispositivos e o Dispatcher necessário para atualizar a interface do usuário.

Chame DeviceInformation.CreateWatcher para criar uma nova instância da classe DeviceWatcher, passando a cadeia de caracteres do seletor de dispositivo MIDI.

Registre manipuladores para os manipuladores de eventos do observador.

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

O DeviceWatcher tem os seguintes eventos:

  • Adicionado - Gerado quando um novo dispositivo é adicionado ao sistema.
  • Removido - Gerado quando um dispositivo é removido do sistema.
  • Atualizado - Gerado quando as informações associadas a um dispositivo existente são atualizadas.
  • EnumerationCompleted - Gerado quando o observador concluiu sua enumeração do tipo de dispositivo solicitado.

No manipulador de eventos para cada um desses eventos, um método auxiliar, UpdateDevices, é chamado para atualizar o ListBox com a lista atual de dispositivos. Como UpdateDevices atualiza elementos da interface do usuário e esses manipuladores de eventos não são chamados no thread da interface do usuário, cada chamada deve ser encapsulada em uma chamada para RunAsync, o que faz com que o código especificado seja executado no thread da interface do usuário.

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

O método auxiliar UpdateDevices chama DeviceInformation.FindAllAsync e atualiza o ListBox com os nomes dos dispositivos retornados, conforme descrito anteriormente neste artigo.

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

Adicione métodos para iniciar o inspetor, usando o método Start do objeto DeviceWatcher, e para parar o observador, usando o método Stop.

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

Forneça um destruidor para cancelar o registro dos manipuladores de eventos do inspetor e defina o inspetor do dispositivo como nulo.

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

Crie portas MIDI para enviar e receber mensagens

No code-behind da sua página, declare as variáveis de membro para conter duas instâncias da classe auxiliar MyMidiDeviceWatcher , uma para dispositivos de entrada e outra para dispositivos de saída.

MyMidiDeviceWatcher inputDeviceWatcher;
MyMidiDeviceWatcher outputDeviceWatcher;

Crie uma nova instância das classes auxiliares do inspetor, passando a cadeia de caracteres do seletor de dispositivo, a ListBox a ser preenchida e o objeto CoreDispatcher que pode ser acessado por meio da propriedade Dispatcher da página. Em seguida, chame o método para iniciar o DeviceWatcher de cada objeto.

Logo após cada DeviceWatcher ser iniciado, ele terminará de enumerar os dispositivos atuais conectados ao sistema e gerará seu evento EnumerationCompleted, o que fará com que cada ListBox seja atualizado com os dispositivos MIDI atuais.

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

inputDeviceWatcher.StartWatcher();

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

outputDeviceWatcher.StartWatcher();

Quando o usuário seleciona um item na ListBox de entrada MIDI, o evento SelectionChanged é gerado. No manipulador desse evento, acesse a propriedade DeviceInformationCollection da classe auxiliar para obter a lista atual de dispositivos. Se houver entradas na lista, selecione o objeto DeviceInformation com o índice correspondente ao SelectedIndex do controle ListBox.

Crie o objeto MidiInPort que representa o dispositivo de entrada selecionado chamando MidiInPort.FromIdAsync, passando a propriedade Id do dispositivo selecionado.

Registre um manipulador para o evento MessageReceived , que é gerado sempre que uma mensagem MIDI é recebida por meio do dispositivo especificado.

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

Quando o manipulador MessageReceived é chamado, a mensagem está contida na propriedade Message do MidiMessageReceivedEventArgs. O Type do objeto de mensagem é um valor da enumeração MidiMessageType que indica o tipo de mensagem que foi recebida. Os dados da mensagem dependem do tipo de mensagem. Este exemplo verifica se a mensagem é uma nota na mensagem e, em caso afirmativo, gera o canal midi, a nota e a velocidade da mensagem.

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

O manipulador SelectionChanged para o dispositivo de saída ListBox funciona da mesma forma que o manipulador para dispositivos de entrada, exceto que nenhum manipulador de eventos é registrado.

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

}

Depois que o dispositivo de saída for criado, você poderá enviar uma mensagem criando um novo IMidiMessage para o tipo de mensagem que deseja enviar. Neste exemplo, a mensagem é um NoteOnMessage. O método SendMessage do objeto IMidiOutPort é chamado para enviar a mensagem.

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

midiOutPort.SendMessage(midiMessageToSend);

Quando seu aplicativo estiver desativado, certifique-se de limpar os recursos de seus aplicativos. Cancele o registro de seus manipuladores de eventos e defina os objetos de porta de entrada e porta de saída MIDI como nulos. Pare os observadores do dispositivo e defina-os como nulos.

inputDeviceWatcher.StopWatcher();
inputDeviceWatcher = null;

outputDeviceWatcher.StopWatcher();
outputDeviceWatcher = null;

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

midiOutPort.Dispose();
midiOutPort = null;

Usando o sintetizador MIDI geral do Windows integrado

Quando você enumera dispositivos MIDI de saída usando a técnica descrita acima, seu aplicativo descobrirá um dispositivo MIDI chamado "Microsoft GS Wavetable Synth". Este é um sintetizador MIDI geral integrado que você pode reproduzir no seu aplicativo. No entanto, a tentativa de criar uma porta de saída MIDI para este dispositivo falhará, a menos que você tenha incluído a extensão SDK para o sintetizador integrado em seu projeto.

Para incluir a extensão do SDK do Sintetizador MIDI Geral no projeto do app

  1. No Gerenciador de Soluções, em seu projeto, clique com o botão direito do mouse em Referências e selecione Adicionar referência...
  2. Expanda o nó Universal do Windows .
  3. Selecione Extensões.
  4. Na lista de extensões, selecione Microsoft General MIDI DLS para Aplicativos Universais do Windows.

    Observação

    Se houver várias versões da extensão, selecione a versão que corresponde ao destino do seu aplicativo. Você pode ver qual versão do SDK seu aplicativo está direcionando na guia Aplicativo das Propriedades do projeto.