Este artículo proviene de un motor de traducción automática.

Las fronteras de las UI

Música MIDI en aplicaciones WPF

Charles Petzold

Descargar el código de ejemplo

Cada PC contiene una banda de 16 piezas integrada lista para reproducir algún tipo de música. Los miembros de esta banda probablemente sienten mucho sin atender para representan quizás el componente más subutilizado del arreglo de discos de las características de sonido y vídeo compatibles con Windows.

Esta banda 16 fragmento es un sintetizador de música electrónica implementado en hardware o software que se ajusta al estándar conocido como MIDI, Musical Instrument Digital Interface. En la API de Win32, se admite la reproducción de música a través de sintetizador MIDI a través de funciones a partir de midiOut palabras.

Compatibilidad con MIDI no es parte de .NET Framework, sin embargo, por lo si desea tener acceso a este sintetizador MIDI en una aplicación de formularios Windows Forms o Windows Presentation Foundation (WPF), deberá utilizar P/Invoke o una biblioteca externa.

Estaba muy satisfechos de encontrar el soporte de MIDI en la biblioteca de sonido NAudio disponible en CodePlex que hablé en mi última columna. Puede descargar dicha biblioteca con código fuente desde codeplex.com/naudio de . En este artículo he usado NAudio versión 1.3.8.

Un ejemplo de Brief

Se puede considerar como una interfaz de alto nivel para audio de forma de onda en el que está trabajando con instrumentos musicales y notas de MIDI.

El estándar MIDI se desarrolló en los años ochenta anticipado. Los fabricantes de sintetizadores de música electrónica deseaban una forma estándar para conectar los controladores de música electrónica (por ejemplo, teclados) con sintetizadores y surgió con un sistema para transmitir mensajes pequeños (principalmente uno, dos o tres bytes de longitud) a través de un cable con un conector de 5 pines a un ritmo pokey de 3,125 bytes por segundo.

Dos de la más importante de estos mensajes se denominan Nota On y OFF nota. Cuando un músico presiona una tecla en un teclado MIDI, el teclado genera un mensaje de nota al que indica la nota que se presionó y velocidad de la clave. El sintetizador responde reproducir esa nota generalmente más fuerte para velocidades de claves superiores. Cuando el músico suelta la tecla, el teclado genera un mensaje de nota OFF y responde el sintetizador desactivando la nota. Los datos de audio no atraviesa el cable de MIDI.

Aunque MIDI todavía se utiliza para conectar el hardware de música electrónica, también se puede usar completamente dentro de un PC a través del software. Las placas de sonido pueden incluir sintetizadores de MIDI y propio Windows emula a un sintetizador MIDI completamente en software.

Para tener acceso a ese sintetizador de la aplicación de formularios Windows Forms o WPF mediante la biblioteca NAudio, agregue NAudio.dll como una referencia e incluya 
this utiliza la directiva en el código de origen:

using NAudio.Midi;

Suponga que desea que la aplicación para reproducir una sola nota de un segundo que suena como C medio de un piano. Puede hacerlo con el código siguiente:

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

Un PC puede tener acceso a múltiples sintetizadores de MIDI; el argumento al constructor MidiOut es un identificador numérico para seleccionar uno de abrir. El constructor producirá una excepción si el dispositivo de salida MIDI ya está en uso.

Un programa puede obtener información acerca de sintetizadores MIDI usando primero la propiedad MidiOut.NumberOfDevices estática para descubrir cuántos sintetizadores están presentes. La numérica identificadores comprendidos 0 y uno menos que el número de dispositivos. El método estático de MidiOut.DeviceInfo acepta un identificador numérico y devuelve un objeto de tipo MidiOutCapabilities que describen el sintetizador. (No esté utilizando estas características. Para el resto de este artículo simplemente usaré el sintetizador de MIDI predeterminada disponible con un ID de cero.)

El método Send de la clase MidiOut envía un mensaje para el sintetizador MIDI. Un mensaje MIDI comprende uno, dos o tres bytes, pero la API de Win32 (y NAudio) desea ellos se empaquetan en un único entero de 32 bits. Los métodos MidiMessage.StartNote y MidiMessage.StopNote hacer este embalaje por usted. Puede reemplazar los dos argumentos para enviar con 0x007F3C90 y 0x00003C80, respectivamente.

El primer argumento para StartNote y StopNote es un código comprendido entre 0 y 127 que indica la nota real, donde el valor 60 es medio C. Una octava superior es 72. Una octava inferior es 48. El segundo argumento es la velocidad que se presione o suelte la tecla. (Versión velocidades normalmente se pasan por alto por sintetizadores.) Puede oscilar entre 0 a 127. Reducir el segundo argumento MidiMessage.StartNote anote más suave. (Analizaré el tercer argumento en breve.)

Las dos llamadas a Thread.Sleep suspensión el subproceso para 1.000 milisegundos. Esto es una forma muy sencilla de agotar el tiempo de los mensajes, pero debería evitarse en un subproceso de interfaz de usuario. La segunda llamada de Sleep es necesaria para permitir que la nota die antes se trunque abruptamente por la llamada de cerrar.

¿Qué sucede con la polifonía?

Eso es cómo se puede reproducir una nota. ¿Qué ocurre con varias notas al mismo tiempo? Que es posible también. Por ejemplo, si desea reproducir una cuerda de principales de C en lugar de una sola C nota, reemplace el primer mensaje de envío con lo siguiente:

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

A continuación, reemplace el segundo mensaje de envío con:

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

Si desea las notas diferentes para iniciar y detener en diversos momentos, probablemente deseará abandonar el uso de Thread.Sleep y incorporarme a un temporizador real, especialmente si está reproduciendo la música en un subproceso de interfaz de usuario. Obtener más información sobre esto en breve.

Hay un formato de archivo MIDI que combina los mensajes MIDI con información de temporización, pero estos archivos requieren un software especializado para crear y que no se tratan ellos aquí.

Instrumentos y canales

Hasta ahora he sido reproducir sonidos de piano sólo. Puede cambiar el sintetizador para reproducir sonidos de otros instrumentos utilizando el mensaje de MIDI programa cambio implementado en NAudio con el método ChangePatch:

midiOut.Send(MidiMessage.ChangePatch(47, 0).RawData);

El primer argumento ChangePatch es un código numérico comprendido entre 0 y 127 para indicar un sonido particular instrumento.

Hacia atrás en los comienzos de MIDI, los sonidos reales procedentes de los sintetizadores totalmente fueron controlados por el ejecutante a través de diales y cables de parche. (Eso, un programa de instalación particular de sintetizador o instrumento sonido a menudo se conoce como “ revisión ”.) Posterior en, los creadores de archivos MIDI querían un conjunto estándar de instrumentos por lo que sería de sonido prácticamente la misma independientemente del sintetizador que reproducen en los archivos. Esto dio lugar a un estándar denominado general MIDI.

Una buena referencia para general MIDI es la de en.wikipedia.org/wiki/General_midi de de Wikipedia entrada. Bajo los encabezado “ sonidos melódico ” son 128 sonidos de instrumento con códigos comprendido entre 1 y 128. Utilizar códigos de base cero en el método ChangePatch, por lo que el código 47 en el ejemplo anterior es el instrumento 48 en esta lista, que es un sonido Timpani.

He mencionado al principio que el sintetizador MIDI es equivalente a una banda de la pieza de 16. Sintetizador MIDI admite 16 canales de . En cualquier momento, cada uno de los canales está asociado con un instrumento determinado según el mensaje de cambiar de programa más reciente. El número de canal que va de 0 a 15 y se especifica en el argumento final de los métodos StartNote, StopNote y ChangePatch.

Channel 9 es especial. Se trata de un canal de percusión. (A menudo se conoce como Channel 10, pero eso es si los canales se numeran a partir de 1). Para channel 9, los códigos que se pasan a los métodos StartNote y StopNote consulte sonidos de percusión no tonal concreto en lugar de tonos. En la entrada de Wikipedia en general MIDI, ver la lista bajo el encabezado “ percusión. ” Por ejemplo, la llamada siguiente reproduce un sonido cowbell, que se indica mediante un código de 56:

midiOut.Send(MidiMessage.StartNote(56, 127, 9).RawData);

Hay mucho más en MIDI, pero éstos son los fundamentos.

MIDI basada en XAML

Espíritu de XAML y WPF, pensé que sería divertido para desarrollar un formato basado en cadena para incrustar cortos piezas de música directamente en archivos XAML y reproducción de. Llamo a este formato de una cadena de MIDI: una cadena de texto de notas e información de temporización. Todos los símbolos (tokens) está separados por espacios en blanco.

Las notas son capital letras A G, seguido de cualquier número de + signos o # signs (cada provoca el un semitono tono) o bien – signos o la letra b (para reducir el un semitono tono) seguido por un número opcional octava, donde la octava, comenzando por medio C es la octava cuatro. (Esto es una forma estándar de numeración octavas). Por lo tanto, C# por debajo de medio C es:

C# 3

La letra R por sí mismo es un resto. Una nota o un resto puede ir seguido opcionalmente por una duración, que indica el período de tiempo hasta que la nota siguiente. Por ejemplo, esto es un trimestre nota, que es también el valor predeterminado si no se indica ninguna duración:

1/4

Las duraciones son rápidas, es decir, si una duración no sigue una nota, la duración de la última se utilizará. Si la duración comienza con una barra diagonal, se supone que el numerador es 1.

Esa duración indica el tiempo hasta que la nota siguiente. Esta duración también se utiliza para la longitud de la Nota: es decir, el tiempo hasta que se activa la nota desactivado. Para un sonido más staccato, es conveniente longitud de la nota sea menor que su duración. O bien puede notas sucesivas a se superpongan ligeramente. Indicar la longitud de la nota de la misma manera que la duración, pero con un signo menos:

–3/16

Las duraciones y las longitudes siempre aparecen después de la nota que se aplican, pero no importa el orden. Las longitudes no son rápidas. Si no aparece una longitud de la nota, la duración se utiliza para la longitud.

Notas también pueden ir precedidas por símbolos (tokens). Para establecer una voz de instrumento, siga la letra I por el número de revisión basado en cero. Por ejemplo, esto indica un violín para las notas sucesivas:

I40

El piano es la revisión de seguridad predeterminada.

Para establecer un nuevo volumen (es decir, una velocidad) para sucesivas notas utilice V, tales como:

V64

Para I y V, el número que sigue debe oscilar entre cero y 127.

De forma predeterminada, el tempo es 60 trimestre notas por minuto. Para establecer un nuevo tempo para las notas siguientes, utilice T seguido del número de notas de trimestre por minuto, por ejemplo:

T120

Si desea que un grupo de notas que se va a reproducir con los mismos parámetros, puedes incluirlos entre paréntesis. Ésta es una cuerda C principales:

(C4 E4 G4 C5)

Sólo las notas pueden aparecen entre paréntesis. La barra vertical | separa canales. Los canales se reproducen simultáneamente y son totalmente independientes, incluida las tempos.

Si un canal concreto contiene una P mayúscula en cualquier lugar dentro de, dicho canal se convierte en el canal de percusión. Ese canal puede contener notas o se sitúe en la notación normal, pero también permite que las voces de percusión indicarse numéricamente. Por ejemplo, esto es el cowbell:

P56

Si va a en.wikipedia.org/wiki/Charge_(fanfare) de , verá el examen de ajuste de “ cargo! ” a menudo se reproducen con material deportivo eventos. Que se puede expresar en el formato de cadena MIDI como:

"T100 I56 G4 C5 E5 G5/12 -3 DE 3/16/32 E5 /16 G5 2"

El MidiStringPlayer

El MidiStringPlayer es la clase pública sólo en el proyecto de biblioteca Petzold.Midi incluida con el código fuente descargable. Se deriva de FrameworkElement por lo que se pueden incrustar en el árbol visual en un archivo XAML, pero no tiene apariencia visual. Establezca la propiedad MidiString en una cadena en el formato mostrado en el ejemplo anterior llamado Play (y, opcionalmente, detener para detener la secuencia antes de que haya terminado).

MidiStringPlayer también tiene una propiedad PlayOnLoad para reproducir una secuencia cuando se carga el elemento y una propiedad de IsPlaying get-only. El elemento genera un evento terminado cuando ha completado su reproducción una secuencia y un evento de error que se desencadena si se produce un error en la sintaxis de la cadena de MIDI. El evento incluye un desplazamiento en la cadena de texto que indica el símbolo (token) de problemático y una explicación de texto del error.

Dos programas WPF también se incluyen en el código descargable. El programa de MusicComposer permite reunir interactivamente una cadena de MIDI. El programa WpfMusicDemo codifica algunas secuencias simples en un archivo MIDI, como se muestra en de figura 1.

Figura 1 de WpfMusicDemo.xaml codifica MIDI simple varias cadenas

<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 &amp; 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>

Una parte fundamental de cualquier parte del software de reproducción de música es el temporizador, pero para MidiStringPlayer usé DispatcherTimer muy simple, que se ejecuta en el subproceso de interfaz de usuario. Esto ciertamente no es óptimo. Si otro programa está acaparando la CPU, la reproducción de música será irregular. También DispatcherTimer no puede generar eventos de Tick más rápidos que aproximadamente el 60 por segundo, lo cual es satisfactoria para fragmentos simples, pero no ofrece la precisión necesaria de música rítmicamente más compleja.

La API de Win32 incluye un temporizador de alta resolución específicamente para la reproducción de secuencias de MIDI, pero esto no todavía ha para la biblioteca NAudio. Quizás en algún momento posterior que sustituirá el DispatcherTimer con algo un poco más precisas y regular, pero para ahora estoy satisfecho que funciona, así como hace con esta solución simple.

Charles Petzold   es editor colaborador desde hace mucho tiempo a MSDN Magazine *.*Su libro más reciente es “The Annotated Turing: A Guided Tour through Alan Turing’s Historic Paper on Computability and the Turing Machine” (Wiley, 2008). Blogs de Petzold en su de charlespetzold.com de sitio Web de .

Gracias al siguiente técnico experto para revisar este artículo:   Estado de Mark