Dieser Artikel wurde maschinell übersetzt.

Die Benutzeroberfläche und ihre Grenzen

MIDI-Musik in WPF-Anwendungen

Charles Petzold

Beispielcode herunterladen

Auf jedem PC stehen 16 Instrumente zum Abspielen von Musik zur Verfügung. Die Mitglieder dieses Band fühlen wahrscheinlich viel Vernachlässigte, für Sie, vielleicht die am häufigsten underutilized-Komponente des Arrays von sound und video-Features von Windows unterstützt werden darstellen.

Diese 16 Stück Band ist ein elektronisches Synthesizer in Hardware oder Software, die gemäß der standardmäßigen bekannt als MIDI-implementiert – Musical Instrument Digital Interface. Wiedergeben von Musik über die MIDI-Synthesizer wird über Funktionen, die beginnend mit der MidiOut Wörter in der Win32-API unterstützt.

MIDI-Unterstützung ist nicht Teil von .NET Framework jedoch, wenn Sie diese MIDI-Synthesizer in einer Windows Forms- oder Windows Presentation Foundation (WPF) zugreifen möchten, Sie P/Invoke oder eine externe Bibliothek verwenden müssen.

Ich war sehr erfreut, MIDI-Unterstützung in der NAudio Bibliothek sound auf CodePlex verfügbar finden, die ich in meinem letzten Artikel erläutert. Sie können die Bibliothek mit Quellcode von codeplex.com/naudio downloaden. Für diesen Artikel verwendet habe ich NAudio Version 1.3.8.

Beispiel für einen Brief

Sie können als High-Level-Schnittstelle in Waveform-Audiodateien von MIDI-vorstellen, mit denen Sie Musikinstrumenten und Notizen arbeiten.

Der MIDI-Standard wurde in den frühen 80er Jahren entwickelt. Hersteller von elektronisches Synthesizern wollte eine Standardmethode mit Synthesizern elektronisches-Controller (z. B. Tastaturen) herstellen, und diese mit einem System zum Übertragen von kleiner Meldungen (meist eine, zwei oder drei Byte Länge) über ein Kabel mit einem 5-Pin-Verbinder Rate Pokey 3,125 Bytes pro Sekunde von waren.

Zwei der wichtigsten dieser Meldungen heißen Hinweis ein und Notiz aus. Wenn ein Musiker ein MIDI-Tastatur drückt, generiert die Tastatur eine Note On-Nachricht ab, der angibt, der Notiz, die gedrückt wurde und die Geschwindigkeit des Schlüssels. Der Synthesizer reagiert durch Wiedergeben der Notiz im Allgemeinen lauter für höhere wichtigsten Velocities. Wenn die Musiker die Taste loslässt, die Tastatur generiert eine Notiz aus Nachricht und der Synthesizer reagiert, indem Sie die Notiz zu deaktivieren. Keine tatsächlichen Audiodaten durchläuft die MIDI-Kabel.

Obwohl MIDI-Verbindung elektronische Musik Hardware noch verwendet wird, kann es auch vollständig in einem PC über Software verwendet werden. Sound-Karten können MIDI-Synthesizern enthalten, und Windows selbst einen MIDI-Synthesizer vollständig in Software emuliert.

Zuzugreifen, wird die WinForms oder WPF-Anwendung, die unter Verwendung der NAudio-Bibliothek-Synthesizer, fügen als Verweis NAudio.dll und 
this using-Direktive im Quellcode enthalten:

using NAudio.Midi;

Genommen Sie an, Sie möchten die Anwendung für eine einzelne Notiz einsekündigen Wiedergabe, Sounds, das Mitte C ein Klavier werden. Sie können dies tun, mit dem folgenden Code:

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

Ein PC haben möglicherweise Zugriff auf mehrere MIDI-Synthesizern, das Argument für die MidiOut-Konstruktor ist eine numerische ID, den Sie zum Öffnen auswählen. Der Konstruktor wird eine Ausnahme ausgelöst, wenn das MIDI-Ausgabegerät bereits verwendet wird.

Ein Programm kann Informationen über den MIDI-Synthesizern erhalten, indem Sie erste statische MidiOut.NumberOfDevices-Eigenschaft ermitteln, wie viele Synthesizern vorhanden sind. Die numerischen IDs zwischen 0 und eins kleiner als die Anzahl der Geräte. Die statische MidiOut.DeviceInfo-Methode akzeptiert eine numerische ID und gibt ein Objekt des Typs MidiOutCapabilities, die den Synthesizer zu beschreiben. (Ich wird nicht diese Features verwenden. Für den Rest dieses Artikels wird einfach den Standard-MIDI-Synthesizer verfügbar mit einer Kennung von 0 (null) verwendet.)

Die Send-Methode der MidiOut-Klasse sendet eine Nachricht an den MIDI-Synthesizer. Eine MIDI-Nachricht umfasst eine, zwei oder drei Bytes, aber die Win32-API (und NAudio) möchte diese in eine einzelne 32-Bit-Ganzzahl gepackt. Die Methoden MidiMessage.StartNote und MidiMessage.StopNote erledigen diese Verpackung für Sie. Sie können die beiden Argumente zu senden bzw. mit 0x007F3C90 und 0x00003C80, ersetzen.

Das erste Argument für StartNote und StopNote ist ein Code, der im Bereich von 0 bis 127, die die aktuelle Notiz angibt, wo ist der Wert 60 Mitte c. Eine höhere Octave ist 72. Eine niedrigere Octave ist 48. Das zweite Argument ist die Geschwindigkeit, dass die Taste gedrückt oder losgelassen wird. (Version Velocities werden normalerweise von Synthesizern ignoriert.) Diese reichen von 0 bis 127. Verringern Sie das zweite Argument für MidiMessage.StartNote, um weichere notieren. (Das dritte Argument kurz erörtert.)

Die beiden Aufrufen von Thread.Sleep Anhalten des Threads für 1.000 Millisekunden. Dies ist eine sehr einfache Möglichkeit, Anzeigedauern für die Nachrichten, aber es sollte vermieden werden, in einem Benutzeroberflächen-Thread. Der zweite Aufruf der Ruhezustand ist erforderlich, können die Notiz, die die deaktivieren, bevor es durch den Aufruf von Close plötzlich abgeschnitten.

Was ist mit Polyphony?

Das ist ein Hinweis wiedergegeben werden können. Was mehrere Notizen gleichzeitig? Das ist ebenfalls möglich. Wenn Sie eine C#-Hauptversion Sehne, sondern nur eine einzelne C Notiz wiederzugeben, können Sie z. B. die erste Nachricht senden mit den folgenden Code ersetzen:

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

Ersetzen Sie die zweite Nachricht senden mit:

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

Sie ggf. verschiedene Notizen zum Starten und beenden zu verschiedenen Zeiten sollten wahrscheinlich Sie die Verwendung von Thread.Sleep verwerfen und einen tatsächlichen Zeitgeber, auch nicht besonders, wenn Sie die Musik auf ein Benutzeroberflächen-Thread spielen sind. Mehr dazu in Kürze.

Ein MIDI-Dateiformat, in der MIDI-Nachrichten mit Zeitinformationen, kombiniert ist, aber diese Dateien erfordern speziellen Software zum Erstellen, und ich wird nicht werden besprechen Sie hier.

Instrumente und Kanälen

Bisher habe ich nur Klavier Sounds wiedergegeben wurde. Wechseln Sie zur Wiedergabe von Sounds, der andere Instrumente anhand der Meldung MIDI-Programm ändern, die in NAudio mit der ChangePatch-Methode implementiert den Synthesizer:

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

Das erste Argument für ChangePatch ist ein numerischer Code im Bereich von 0 bis 127, um einen bestimmten Instrument Sound anzugeben.

Wieder in den frühen Tagen des MIDI wurden die tatsächlichen Klängen aus dem Synthesizern vollständig durch Interpreten über wählt und Patch-Kabel gesteuert. (Daher einen bestimmten Synthesizer-Setup oder Instrument Sound wird häufig als “ Patch. ” bezeichnet) Ersteller von MIDI-Dateien wollte später auf einen Standardsatz von Instrumenten, damit die Dateien ziemlich ähnlich unabhängig von der Synthesizer, die Sie wiedergegeben Sound würden, auf. Dies führte dazu, dass ein Aufruf Allgemeine MIDI-Standard.

Anhaltspunkt für die allgemeine MIDI-ist im Wikipedia Eintrag en.wikipedia.org/wiki/General_midi . Unter der Überschrift “ Melodic Sounds ” sind 128 Instrument Sound mit Codes, die im Bereich von 1 bis 128. Sie verwenden nullbasierte Codes in der ChangePatch-Methode, damit Code 47 im vorherigen Beispiel Instrument 48 in dieser Liste ist, also ein Timpani-Sound.

Erwähnt am Anfang, dass der MIDI-Synthesizer ein Band 16 Stück entspricht. Der MIDI-Synthesizer unterstützt Kanäle 16 . Zu jedem Zeitpunkt ist jeden Kanal zugeordnet ein bestimmtes Instrument basierend auf der letzten Nachricht Programm ändern. Die Kanalnummer liegt im Bereich von 0 bis 15 und in das letzte Argument der Methoden StartNote, StopNote und ChangePatch angegeben ist.

Channel 9 ist ein Sonderfall. Dies ist ein Kanal Schlagzeug. (Es wird häufig als Channel 10 bezeichnet, aber das ist, wenn die Kanäle mit 1 beginnend nummeriert sind.) Channel 9 finden Pitches, anstatt bestimmte nicht Tonwert Schlagzeug Sounds die Codes der StartNote und StopNote-Methode übergeben. Finden Sie im Wikipedia-Eintrag auf Allgemeine MIDI-in der Liste unter der Überschrift “ Schlagzeug ”. Der folgende Aufruf spielt z. B. einen Cowbell-Sound, der durch einen 56-Code angezeigt wird:

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

MIDI-viel mehr, aber dies sind die wesentlichen Aspekte.

MIDI-XAML-basierte

Im Einklang mit den Geist von WPF und XAML dachte ich, wäre es Spaß, eine zeichenfolgenbasierte Format für kurze Musikstücke direkt in XAML-Dateien eingebettet und wieder abspielen zu entwickeln. Ich bezeichne dieses Format eine MIDI-Zeichenfolge – eine Textzeichenfolge mit Notizen und Zeitinformationen. Alle Tokens werden durch Leerzeichen voneinander getrennt.

Notizen sind Großbuchstaben A bis G, gefolgt von einer beliebigen Anzahl von + Anzeichen oder (jede löst die Zeichenbreite einer Semitone) # signs oder – Anzeichen oder den Buchstaben b (um die Zeichenbreite einer Semitone zu reduzieren), gefolgt von einer optionalen Octave-Zahl, wobei Octave vier ist der Octave mit Mitte C beginnt. (Dies ist eine Standardmethode Nummerierung Octaves.) Daher ist die c# unten Mitte C:

C# 3

Der Buchstaben "R" allein ist ein Rest. Eine Notiz oder einen Rest kann optional eine Dauer folgen, womit den Zeitraum, bis die nächste Notiz. Dies ist z. B. ein Quartal Hinweis: auch die Standardeinstellung ist, wenn keine Dauer angegeben ist:

1/4

Dauer Kurznotiz sind – d. h., wenn eine Dauer keine Notiz entspricht, die letzte Dauer verwendet werden. Wenn die Dauer mit einem Schrägstrich beginnt, wird der Zähler der Wert 1 angenommen.

Die Dauer gibt die Zeit bis die nächste Notiz. Diese Dauer wird auch verwendet, für die Länge der Notiz – d. h. die Zeit, bis die Notiz aktiviert ist deaktiviert. Für einen Sound mehr staccato sollten Sie die Notiz Länge kleiner als die Dauer des Vorgangs sein. Oder Sie aufeinander folgende Notizen etwas überlappen. Sie angeben eine Notiz Länge die gleiche Weise wie die Dauer, jedoch mit einem Minuszeichen Folgendes:

–3/16

Dauer und Längen werden nach den Hinweis auf die Sie gelten jedoch die Reihenfolge spielt keine Rolle, immer angezeigt. Sind keine servergebundenen. Wenn Sie eine Notiz Länge nicht angezeigt wird, wird die Dauer für die Länge verwendet.

Notizen können auch Token vorangestellt sein. Ein Instrument Stimme festlegen möchten, folgen Sie den Buchstaben I durch die nullbasierte Patch-Anzahl. Dies gibt z. B. ein Violin für aufeinander folgende Hinweise:

I40

Die Piano handelt es sich um die Standard-Patch.

Notizen verwenden V, um ein neues Volume (d. h., eine Geschwindigkeit) für nachfolgende festlegen wie z. b.:

V64

Ich sowohl, für V muss die Zahl von 0 bis 127 liegen.

Standardmäßig ist das Tempo 60 Quartal Notizen pro Minute. Um eine neue Tempo für die folgenden Hinweise festzulegen, verwenden Sie T gefolgt von der Anzahl der Quartal Notizen pro Minute, z. B.:

T120

Wenn Sie eine Gruppe von Notizen mit den gleichen Parametern wiedergegeben werden soll, können Sie in Klammern platziert werden. Hier ist eine C#-Hauptversion Sehne:

(C4 E4 G4 C5)

Nur Notizen können in Klammern angezeigt. Die vertikale Leiste | trennt Kanäle. Die Kanäle gleichzeitig wiedergegeben werden, und Sie sind vollkommen unabhängig, einschließlich des Tempos.

Wenn ein bestimmter Kanal ein großes P an einer beliebigen Stelle innerhalb enthält, wird dieser Kanal Schlagzeug-Kanal. Dieser Kanal kann enthalten Notizen oder in der normalen Schreibweise hält, ermöglicht aber auch Schlagzeug stimmen numerisch angegeben werden. Dies ist z. B. die Cowbell:

P56

Wenn Sie in en.wikipedia.org/wiki/Charge_(fanfare) wechseln, sehen Sie die “ entspricht ”! Melodie oft am Sportereignisse Ereignisse wiedergegeben. Kann das Format der MIDI-Zeichenfolge als ausgedrückt werden:

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

Die MidiStringPlayer

Die MidiStringPlayer ist die nur öffentliche Klasse im downloadbaren Quellcode enthaltene Petzold.Midi Bibliothek-Projekt. Es wird von FrameworkElement abgeleitet, so können Sie es in der visuellen Struktur in einer XAML-Datei einbetten, aber es keine visuelle Darstellung wurde. Legen Sie die MidiString-Eigenschaft auf eine Zeichenfolge in das Format, das im vorherigen Beispiel gezeigte und Aufrufen von Play (und, optional, beenden, um die Reihenfolge zu beenden, bevor er abgeschlossen ist).

MidiStringPlayer verfügt auch über eine PlayOnLoad-Eigenschaft, um eine Sequenz wiedergeben, wenn das Element geladen und eine nur-Get-IsPlaying-Eigenschaft. Das Element wird ein abgeschlossen-Ereignis nach Abschluss der Wiedergabe einer Sequenz und einer fehlgeschlagen-Ereignis, das ausgelöst wird, wenn ein Fehler in der Syntax der MIDI-Zeichenfolge, generiert. Das Ereignis enthält einen Offset in der Textzeichenfolge ab, der angibt, das problematische Token und eine Text-Erklärung des Fehlers.

Zwei WPF-Programme sind auch in der herunterladbare Code enthalten. Das Programm MusicComposer ermöglicht es Ihnen, eine MIDI-Zeichenfolge interaktiv zusammengestellt. Das Programm WpfMusicDemo codiert einige einfachen Sequenzen in einer MIDI-Datei, wie in Abbildung 1 .

Abbildung 1 WpfMusicDemo.xaml verschlüsselt mehrere Simple MIDI-Zeichenfolgen

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

Ein maßgeblicher Teil der jedes Datenelement Software Musik wiedergegeben wird der Zeitgeber, aber für MidiStringPlayer verwendet die DispatcherTimer sehr einfach, die auf dem Benutzeroberflächenthread ausgeführt wird. Dies ist durchaus nicht optimale. Wenn ein anderes Programm die CPU-hogging ist, wird die Wiedergabe der Musik unregelmäßig werden. DispatcherTimer kann nicht auch Tick-Ereignissen, die schneller ist als ca. 60 pro Sekunde, generieren, die für einfache Stück zufrieden stellend ist, jedoch nicht die erforderliche Genauigkeit für mehr rhythmically komplexe Musik.

Der Win32-API enthält einen hochauflösenden Zeitgeber speziell für die Wiedergabe von MIDI-Sequenzen, aber diese wurde noch nicht gemacht es an die NAudio-Bibliothek. Vielleicht führt, dass es sich ebenso wie es funktioniert zu einem späteren Zeitpunkt ich die DispatcherTimer mit etwas mehr präzise und regulären jedoch für ersetzen werden jetzt bin ich glücklich mit dieser einfache Lösung.

Charles Petzold wohne seit redaktionelle an MSDN Magazine*.* Sein neuestes Buch heißt "The Annotated Turing: A Guided Tour through Alan Turing’s Historic Paper on Computability and the Turing Machine" (Wiley, 2008). Petzold Blogs auf seiner Website charlespetzold.com .

Dank an den folgenden technischen Experten für die Überprüfung dieses Artikels:  Mark Health