Este artigo foi traduzido por máquina.
Fronteiras da interface do usuário
Música MIDI em aplicativos do WPF
Charles Petzold
Cada PC contém uma banda interna de 16 componentes pronta para tocar música. Os membros desta banda provavelmente sentem muito inativos, para que eles representam talvez o componente mais subutilizado do array de recursos de som e vídeo com suporte do Windows.
Essa faixa 16 parte é um sintetizador de música eletrônico implementado no hardware ou software que está de acordo com o padrão conhecido como MIDI — a Musical Instrument Digital Interface. Na API Win32, tocando música por meio do sintetizador MIDI é suportado através do funções começando com midiOut palavras.
Suporte de MIDI não é parte do .NET Framework, no entanto, portanto, se você desejar acessar este sintetizador MIDI em um aplicativo Windows Forms ou do Windows Presentation Foundation (WPF), será necessário usar P/Invoke ou uma biblioteca externa.
Fiquei muito contente em Localizar suporte MIDI na disponível em CodePlex que discuti em minha última coluna da biblioteca som NAudio. Você pode fazer o download dessa biblioteca com código-fonte do codeplex.com/naudio . Este artigo usei NAudio versão 1.3.8.
Um exemplo de resumo
Você pode considerar MIDI como uma interface de alto nível para forma de onda de áudio no qual está trabalhando com instrumentos musicais e anotações.
O padrão MIDI foi desenvolvido no início dos anos 80. Os fabricantes de sintetizadores de música eletrônico queriam uma maneira padrão de conectar música eletrônico controladores (como teclados) com sintetizadores e eles surgiu com um sistema para transmitir mensagens pequenas (principalmente um, dois ou três bytes de comprimento) através de um cabo com um conector de 5 pinos na taxa pokey de 3,125 bytes por segundo.
Dois dos mais importantes dessas mensagens são chamados anotação e anotações desativação. Quando um músico pressiona uma tecla no teclado MIDI, o teclado gera uma mensagem de observação em indicando a anotação que foi pressionada e velocidade da chave. O sintetizador responde tocando que a anotação, geralmente mais alto para velocities chaves mais altas. Quando o músico libera a chave, o teclado gera uma mensagem de Observação Off e o sintetizador responde, desativando a anotação. Nenhum dado de áudio real vai através do cabo de MIDI.
Embora MIDI ainda é usado para conectar-se o hardware de música eletrônicos, ele pode ser usado inteiramente dentro de um PC através de software. Placas de som podem incluir MIDI sintetizadores e próprio Windows emula um sintetizador MIDI inteiramente em software.
Para acessar esse sintetizador do aplicativo WinForms ou WPF usando a biblioteca NAudio, adicione NAudio.dll como referência e incluir this usando a diretiva em seu código-fonte:
using NAudio.Midi;
Suponha que você queira que seu aplicativo para executar uma anotação de um segundo única que parece ser o meio de C um piano. Você pode fazer isso com o código a seguir:
MidiOut midiOut = new MidiOut(0);
midiOut.Send(MidiMessage.StartNote(60, 127, 0).RawData);
Thread.Sleep(1000);
midiOut.Send(MidiMessage.StopNote(60, 0, 0).RawData);
Thread.Sleep(1000);
midiOut.Close();
midiOut.Dispose();
Um PC pode ter acesso a vários sintetizadores de MIDI; o argumento para o construtor MidiOut é uma identificação numérica para selecionar o abrir. O construtor irá disparar uma exceção se o dispositivo de saída de MIDI já estiver em uso.
Um programa pode obter informações sobre sintetizadores MIDI, primeiro utilizando a propriedade MidiOut.NumberOfDevices estática para descobrir quantos sintetizadores estão presentes. O numérico identificações variam de 0 a menos que o número de dispositivos. O método estático MidiOut.DeviceInfo aceita uma identificação numérica e retorna um objeto do tipo MidiOutCapabilities que descrevem o sintetizador. (Eu não usar esses recursos. Para o restante deste artigo simplesmente usarei o sintetizador de MIDI padrão disponível com uma identificação de zero.)
O método Send da classe MidiOut envia uma mensagem para o sintetizador MIDI. Uma mensagem MIDI compreende um, dois ou três bytes, mas a Win32 API (e NAudio) desejar incluídos em um único inteiro de 32 bits. Os métodos MidiMessage.StartNote e MidiMessage.StopNote fazer essa remessa para você. Você pode substituir os dois argumentos para enviar por 0x007F3C90 e 0x00003C80, respectivamente.
O primeiro argumento para StartNote e StopNote é um código que varia de 0 a 127 indicando a anotação real, onde o valor 60 é o meio C. Uma oitava maior é 72. Uma oitava inferior é 48. O segundo argumento é a velocidade que a chave é pressionada ou liberada. (Versão velocities geralmente são ignoradas pelo sintetizadores.) Isso pode variar de 0 a 127. Reduza o segundo argumento para MidiMessage.StartNote para tornar a observação mais suave. (Discutirei o terceiro argumento daqui a pouco.)
As duas chamadas para thread.Sleep suspender o thread para 1.000 milissegundos. Isso é uma maneira muito simples de tempo as mensagens, mas deve ser evitado em um thread de interface do usuário. A segunda chamada de suspensão é necessária para permitir que a anotação the antes que seja truncado por chamada fechar abruptamente.
E quanto Polyphony?
É como você pode reproduzir uma observação. E várias anotações ao mesmo tempo? Isso também é possível. Por exemplo, se você quiser executar um acorde C principal em vez de apenas uma única nota C, você pode substituir a primeira mensagem enviar pelo seguinte:
midiOut.Send(MidiMessage.StartNote(60, 127, 0).RawData);
midiOut.Send(MidiMessage.StartNote(64, 127, 0).RawData);
midiOut.Send(MidiMessage.StartNote(67, 127, 0).RawData);
midiOut.Send(MidiMessage.StartNote(72, 127, 0).RawData);
Em seguida, substitua a segunda mensagem de envio com:
midiOut.Send(MidiMessage.StopNote(60, 0, 0).RawData);
midiOut.Send(MidiMessage.StopNote(64, 0, 0).RawData);
midiOut.Send(MidiMessage.StopNote(67, 0, 0).RawData);
midiOut.Send(MidiMessage.StopNote(72, 0, 0).RawData);
Se você desejar que as notas de vários para iniciar e interromper em diversos momentos, você provavelmente desejará abandonar o uso de thread.Sleep e envolver um cronômetro real, principalmente se você estiver jogando a música em um thread de interface do usuário. Obter mais informações sobre isso em breve.
Há um formato de arquivo MIDI que combina MIDI mensagens com informações de tempo, mas esses arquivos requerem um software especializado para criar e eu não ser discuti-los aqui.
Instrumentos e canais
Até agora eu já foi tocando sons apenas piano. Você pode alternar o sintetizador para reproduzir sons de outros instrumentos usando a mensagem de alteração do programa de MIDI, implementada no NAudio com o método ChangePatch:
midiOut.Send(MidiMessage.ChangePatch(47, 0).RawData);
O primeiro argumento para ChangePatch é um código numérico que varia de 0 a 127 para indicar um instrumento determinado som.
Novamente nos primórdios do MIDI, os sons reais saindo de sintetizadores de foram inteiramente controlados pelo executor através de ligações e cabos de correção. (Por isso, uma configuração de sintetizador específico ou instrumento de som é normalmente conhecido como “ patch ”.) No futuro, criadores de arquivos MIDI queriam um conjunto padrão de instrumentos para que os arquivos seriam som quase a mesma, independentemente do sintetizador que eles executados na. Isso levou a um padrão chamado MIDI geral.
Uma boa referência para MIDI geral é a Wikipedia entrada en.wikipedia.org/wiki/General_midi . Sob os título “ melódico sons ” são sons instrumento 128 com os códigos que varia de 1 a 128. Usar códigos com base em zero no método ChangePatch, portanto o código 47 no exemplo anterior é instrumento 48 nesta lista, que é um som Timpani.
Mencionei no início que o sintetizador MIDI é equivalente a uma faixa de 16 partes. O sintetizador MIDI oferece suporte a 16 canais de . A qualquer momento, cada canal está associado a um instrumento específico com base na mensagem de alteração do programa mais recente. O número de canal varia de 0 a 15 e é especificado no argumento final dos métodos StartNote, StopNote e ChangePatch.
O Channel 9 é especial. Esse é um canal de percussão. (Ele é conhecido como Channel 10, mas é se os canais são numerados começando em 1.) Para o channel 9, os códigos passados a métodos StartNote e StopNote Consulte sons de percussão não tons específico em vez de constrangedoras. Na entrada da Wikipedia sobre MIDI geral, consulte a lista sob o título “ Percussão. ” Por exemplo, a seguinte chamada tocará um som cowbell, que é indicado por um código de 56:
midiOut.Send(MidiMessage.StartNote(56, 127, 9).RawData);
Há muito mais para MIDI, mas aqueles são os conceitos básicos.
MIDI baseado em XAML
Acompanhando o espírito do WPF e XAML, pensei que seria divertido para desenvolver um formato com base na seqüência de caracteres para incorporação curtos pedaços de música diretamente nos arquivos XAML e reproduzi-los. Chamo esse formato de uma seqüência MIDI — uma seqüência de caracteres de texto de anotações e informações de tempo. Todos os símbolos são separados por espaços em branco.
As anotações são maiúsculas letras à G, seguido de qualquer número de + sinais ou # signs (cada gera o uma semitone densidade) ou – sinais ou a letra b (para reduzir o uma semitone densidade) seguido de um número oitava opcional onde início oitava a C intermediária é oitava quatro. (Isso é uma maneira padrão de numeração de oitavas.) Portanto, translation from VPE for Csharp abaixo middle C é:
TRANSLATION FROM VPE FOR CSHARP 3
A letra R por si só é uma pausa. Uma observação ou um resto pode ser opcionalmente seguido por uma duração, que indica o período de tempo até a próxima nota. Por exemplo, isso é um quarto Observação, que também é o padrão se nenhuma duração for indicada:
1/4
As durações são auto-adesivas — ou seja, se uma duração não segue uma observação, a duração do última será usada. Se a duração começar com uma barra, o numerador é equivalente a 1.
A duração indica o tempo até a próxima nota. Esta duração também é usada para o tamanho da anotação — ou seja, o tempo até que a anotação está ativada desativado. Um som mais staccato, convém comprimento da nota a ser menor do que sua duração. Ou você poderá sucessivas anotações para sobrepor um pouco. Você indicar o comprimento da nota da mesma forma como a duração, mas com um sinal de menos:
–3/16
Durações e comprimentos sempre aparecem após a Observação para que elas se aplicam, mas não importa a ordem. Os comprimentos não são auto-adesivos. Se não for exibido um comprimento de observação, a duração é usada para o comprimento.
Anotações também podem ser precedidas por tokens. Para definir um voz de instrumento, siga a letra I pelo número de patches baseada em zero. Por exemplo, isso indica um violin para as notas sucessivas:
I40
O piano é o patch do padrão.
Para definir um novo volume (isto é, uma velocidade) para sucessivas anotações usar V, como:
V64
I e V, o número que segue deve variar de zero a 127.
Por padrão, o tempo será 60 trimestre anotações por minuto. Para definir um novo tempo para as seguintes observações, utilize T seguido pelo número de notas de trimestre por minuto, por exemplo:
T120
Se você quiser que um grupo de notas para ser executado com os mesmos parâmetros, você pode colocá-los entre parênteses. Aqui está um acorde C principais:
(C4 E4 G4 C5)
Somente anotações podem aparecer em parênteses. A barra vertical | separa canais. Os canais são reproduzidos simultaneamente e são totalmente independentes, incluindo os tempos.
Se um canal em particular contém um P maiúsculo em qualquer lugar dentro, esse canal se torna o canal de percussão. Esse canal pode conter anotações ou é posicionado na notação normal, mas também permite que as vozes Percussão ser indicados numericamente. Por exemplo, esta é a cowbell:
P56
Se você for para en.wikipedia.org/wiki/Charge_(fanfare) , você verá o ajuste de “ carga! ” geralmente executado em eventos esportivos. Que pode ser expresso no formato de seqüência MIDI como:
"T100 I56 G4 C5 E5 G5/12 -3 DE 3/16/32 E5 /16 G5 2"
O MidiStringPlayer
O MidiStringPlayer é a classe pública apenas no projeto de biblioteca de Petzold.Midi incluído no código-fonte que pode ser baixado. Ele deriva de FrameworkElement, portanto, você pode incorporá-lo na árvore visual em um arquivo XAML, mas ela tem nenhum aspecto visual. Defina a propriedade MidiString como uma seqüência de caracteres no formato mostrado no exemplo anterior e chamar Play (e opcionalmente, parar para parar a seqüência antes de ele ser concluído).
MidiStringPlayer também tem uma propriedade PlayOnLoad para executar uma seqüência quando carrega o elemento e uma propriedade somente obtenção do IsPlaying. O elemento gera um evento concluído quando tiver concluído uma seqüência de execução e um evento de falha que é acionado se ocorrer um erro na sintaxe da seqüência MIDI. O evento inclui uma compensação na seqüência de texto que indica o token problemático e uma explicação de texto do erro.
Dois programas do WPF também estão incluídos no código para download. O programa MusicComposer permite interativamente juntar uma seqüência MIDI. O programa WpfMusicDemo codifica algumas seqüências simples em um arquivo MIDI, conforme mostrado no 1 Figura .
Figura 1 de WpfMusicDemo.xaml codifica MIDI simples várias seqüências
<Window x:Class="WpfMusicDemo.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:midi="clr-namespace:Petzold.Midi;assembly=Petzold.Midi"
Title="WPF Music Demo"
Height="300" Width="300">
<Grid>
<midi:MidiStringPlayer Name="player"
PlayOnLoad="True"
MidiString="{Binding ElementName=chargeButton, Path=Tag}" />
<UniformGrid Rows="2"
ButtonBase.Click="OnButtonClick">
<UniformGrid.Resources>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=player, Path=IsPlaying}"
Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</UniformGrid.Resources>
<Button Name="chargeButton"
Content="Charge!"
Tag="T100 I56 G4 /12 C5 E5 G5 3/16 -3/32 E5 /16 G5 /2" />
<Button Content="Bach D-Minor Toccata"
Tag="T24 I19 A5 /64 G5 A5 5/32 R /32 G5 /64 F5 E5 D5 C#5 /32 D5 /16 R 4/16 A4 /64 G4 A4 5/32 R /32 E4 F4 C#4 D4 /16 R 4/16 | T24
I19 A4 /64 G4 A4 5/32 R /32 G4 /64 F4 E4 D4 C#4 /32 D4 /16 R 4/16 A3 /64 G3 A3 5/32 R /32 E3 F3 C#3 D3 /16 R 4/16"/>
<Button Content="Shave & a Haircut"
Tag="T130 I58 C5 G4 /8 G4 Ab4 /4 G4 R I75 B4 C5" />
<Button Content="Beethoven Fifth"
Tag="T200 I71 R /8 G4 G4 G4 Eb4 7/8 R /8 F4 F4 F4 D4 5/4 | T200 I40 R /8 G4 G4 G4 Eb4 7/8 R /8 F4 F4 F4 D4 5/4 | T200 I40 R /8 G4
G4 G4 Eb4 7/8 R /8 F4 F4 F4 D4 5/4 | T200 I41 R /8 G3 G3 G3 Eb3 7/8 R /8 F3 F3 F3 D3 5/4 | T200 I43 R /8 G2 G2 G2 Eb2 7/8 R /8 F2 F2 F2 D2
5/4 | T200 I43 R /8 G2 G2 G2 Eb2 7/8 R /8 F2 F2 F2 D2 5/4"/>
</UniformGrid>
</Grid>
</Window>
Uma parte essencial de qualquer software de música sendo tocada é o cronômetro, mas para MidiStringPlayer usei o DispatcherTimer muito simples, que é executado no thread da interface do usuário. Isso certamente não é ideal. Se outro programa está sobrecarregando a CPU, a reprodução de música passará a ser irregular. DispatcherTimer também não é possível gerar eventos Tick mais rápidos do que aproximadamente 60 por segundo, que é satisfatória para partes simples, mas não oferece a precisão necessária dos música rhythmically mais complexa.
A API do Win32 inclui um timer de alta resolução especificamente para a reprodução MIDI seqüências, mas isso não ainda tornou à biblioteca NAudio. Talvez em algum momento posterior que substituirá o DispatcherTimer com algo um pouco mais precisa e regular, mas para agora fico feliz que ele funciona, assim como faz com essa solução simples.
Charles Petzold é editor colaborador antigo para MSDN Magazine *.*Seu livro mais recente é “ O Turing Annotated: A Guided Tour por meio de papel histórico de Alan Turing sobre Computability e a máquina de Turing ” (Wiley, 2008). Blogs Petzold no seu site da charlespetzold.com .
Graças ao especialista técnico seguir para revisar este artigo: Mark Heath