Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 48 - Sound

## 48.3 Midi

- 48.3 Midi
  - 48.3.1 Was ist Midi?
  - 48.3.2 Grundlegende Klassen des Midi-APIs
  - 48.3.3 Alle meine Entchen - Erster Versuch
  - 48.3.4 Alle meine Entchen mit dem Sequenzer
  - 48.3.5 Zugriff auf Midi-Dateien
    - Lesen und Abspielen einer Midi-Datei
    - Abspeichern einer Sequenz in einer Midi-Datei

### 48.3.1 Was ist Midi?

In den siebziger Jahren standen Musiker, die sich mit elektronischer Musik beschäftigten, vor einigen Herausforderungen. Zwar gab es gut klingende Synthesizer, die unzählige Sounds und Erweiterungsmöglichkeiten boten. Doch schwierig wurde es, wenn zwei von ihnen miteinander verbunden werden sollten. Es gab nämlich keinen einheitlichen Standard zur Übertragung der Daten zwischen ihnen. Mit den ersten digitialen Synthesizern der 80er Jahre wurde dieses Problem durch die Schaffung des Midi-Standards behoben. Midi steht für Musical Instrument Digital Interface und bezeichnet einen Standard, der die Übertragung von Daten zwischen zwei oder mehr elektronischen Musikinstrumenten beschreibt. Neben der Standardisierung der Hardware (Kabel und Stecker) wurde dabei insbesondere festgelegt, welche Daten übertragen und wie sie kodiert werden sollten.

Midi war ursprünglich eine serielle Schnittstelle, auf der die Daten byteweise übertragen werden. Drückte der Musiker auf seinem Keyboard die Taste C, wurde diese Information in eine drei Byte lange Nachricht verpackt (Status-/Kanalinformation, Tonhöhe, Lautstärke) und in Echtzeit an die angeschlossenen Synthesizer verschickt. Auch beim Loslassen der Taste wurde eine entsprechende Nachricht verschickt. Die angeschlossenen Synthesizer wurden also über die Midi-Schnittstelle ferngesteuert. Neben Notendaten können dabei auch Statusinformationen und Einstellungen von Reglern (Lautstärke, Effekte, Pitch-Bend etc.) übertragen werden. Auch die Übertragung proprietärer Daten ist vorgesehen, um die Kommunikation nichtstandardisierter, gerätespezifischer Informationen in kontrollierter Weise zu ermöglichen.

|  | Es ist wichtig zu verstehen, daß beim Midi-Protokoll nicht die Audiosignale an sich übertragen werden, sondern lediglich die Ereignisse, die zur ihrer Entstehung führen. Midi-Daten können also in einem gewissen Sinne als die Partitur eines Stückes angesehen werden. Was dann tatsächlich erklingt, wird durch die dadurch angesteuerten Synthesizer und ihre klanglichen Eigenschaften bestimmt. |  |
Hinweis

Zunächst war Midi ein reines »Wire«-Protokoll, das die Übertragung von Echtzeit-Daten über eine elektrische Verbindung beschrieb. Später wollte man Midi-Datenströme auch aufzeichnen und in Dateien speichern können, und man entwickelte dazu die Midi-Dateiprotokolle. Darin werden die eigentlichen Midi-Nachrichten mit Zeitstempeln versehen, um sie später mit Hilfe eines Sequenzers in ihrer exakten zeitlichen Abfolge wiedergeben zu können. Im Sound-API werden Midi-Daten ohne Zeitstempel als Midi-Nachrichten (Midi-Messages) und solche mit Zeitstempel als Midi-Ereignisse (Midi-Events) bezeichnet. Der Inhalt einer Midi-Datei wird üblicherweise als Sequenz bezeichnet. Eine Sequenz enthält eine Reihe von Spuren, die ihrerseits die Midi-Events enthalten. Meist repräsentieren die Spuren die unterschiedlichen Instrumente eines Stücks, so daß etwa in Spur eins das Piano liegt, in Spur zwei der Bass usw. Die verschiedenen Spuren werden innerhalb der Midi-Events durch Kanäle repräsentiert, von denen es maximal 16 pro Midi-Schnittstelle gibt.

### 48.3.2 Grundlegende Klassen des Midi-APIs

Ähnlich wie im Sampling-API gibt es eine Reihe von Klassen und Interfaces, mit denen die zuvor beschriebenen Konzepte innerhalb des Midi-APIs umgesetzt werden. Sie befinden sich im zweiten großen Bestandteil des Java Sound-APIs, dem Paket `javax.sound.midi`. Wir wollen die wichtigsten von ihnen kurz vorstellen:

- Analog zur Klasse `AudioSystem` gibt es im Paket `javax.sound.midi` eine Klasse `MidiSystem`, mit der Midi-Geräte und Midi-Dateien beschafft und Informationen über sie abgefragt werden können.
- Ein Midi-Gerät wird durch die Klasse `MidiDevice` repräsentiert. Ein `MidiDevice` kann entweder geöffnet oder geschlossen sein und besitzt eine Reihe von Ein- und Ausgabeelementen. Die Eingabeelemente werden durch das Interface `Receiver`, die Ausgabeelemente durch das Interface `Transmitter` dargestellt. Ein `MidiDevice` kennt die Anzahl seiner `Receiver` und `Transmitter` und stellt diese auf Anfrage zur Verfügung.
- Die wichtigsten Unterklassen von `MidiDevice` sind `Synthesizer` und `Sequencer`. Gegenüber seiner Vaterklasse besitzt ein `Synthesizer` zusätzlich Informationen über die verfügbaren Instrumente und ihre Zuordnung auf die verschiedenen Kanäle. Ein `Sequencer` besitzt dagegen Methoden, um eine Sequenz abzuspielen oder anzuhalten und Parameter wie Tempo, Synchronisierung oder Positionierung zu beeinflussen.
- Eine Midi-Nachricht wird durch die Klasse `MidiMessage` und deren Unterklassen `ShortMessage`, `MetaMessage` und `SysexMessage` repräsentiert. Ein Midi-Ereignis wird durch die Klasse `MidiEvent` dargestellt, die zusätzlich zur `MidiMessage` einen Zeitstempel enthält. Eine Sequenz wird durch die Klasse `Sequence` repräsentiert. Sie enthält neben einigen globalen Informationen zum Timing der Daten eine Reihe von `Tracks`, die ihrerseits die `MidiEvents` enthalten.

Weitere Details zu den genannten Klassen werden in den folgenden Abschnitten vorgestellt.

### 48.3.3 Alle meine Entchen - Erster Versuch

In diesem Abschnitt wollen wir uns die Aufgabe stellen, das allseits bekannte "Alle meine Entchen" mit Hilfe des Midi-APIs wiederzugeben. Zuerst wollen wir einen sehr einfachen Ansatz wählen, bei dem die Midi-Nachrichten in Echtzeit an einen Synthesizer geschickt werden, wobei das Timing mit Hilfe von `Thread.sleep`-Aufrufen manuell gesteuert wird.

Zunächst wird also ein Synthesizer benötigt, den wir von der Klasse `MidiSystem` beziehen können:

    public static Synthesizer getSynthesizer()
      throws MidiUnavailableException
| javax.sound.midi.MidiSystem

`getSynthesizer` liefert den Default-Synthesizer der installierten Sound-Hardware, typischerweise den auf der Soundkarte eingebauten. Ist mehr als ein Synthesizer vorhanden, muß die Liste aller verfügbaren Synthesizer durch Aufruf von `getMidiDeviceInfo` durchsucht und mit `getMidiDevice` der gewünschte ausgewählt werden. Wir wollen zunächst davon ausgehen, daß ein Default-Synthesizer vorhanden ist, der unseren Ansprüchen genügt.

Nachdem der `Synthesizer` verfügbar ist, muß er geöffnet und zur Übergabe von Midi-Nachrichten ein `Receiver` beschafft werden:

    public void open()
      throws MidiUnavailableException

    public void close()

    public boolean isOpen()

    public int getMaxReceivers()

    public Receiver getReceiver()
      throws MidiUnavailableException
| javax.sound.midi.Synthesizer

Das Öffnen und Schließen eines Midi-Geräts wird mit `open` und `close` erledigt, und mit `isOpen` kann sein aktueller Status herausgefunden werden. Ein `Receiver` kann durch Aufruf von `getReceiver` beschafft werden, die Gesamtzahl aller vorhandenen `Receiver` kann mit `getMaxReceivers` abgefragt werden.

Um an ein Midi-Gerät Daten zu senden, werden diese einfach an einen seiner `Receiver` geschickt. Dazu besitzt dieser eine Methode `send`, an die beim Aufruf die gewünschte `MidiMessage` übergeben wird:

    public void send(MidiMessage message, long timeStamp)
| javax.sound.midi.Receiver

Das zweite Argument `timeStamp` ist zur Feinsynchronisierung der Midi-Nachrichten vorgesehen. Damit soll ein `Synthesizer` in der Lage sein, leichte Timing-Schwankungen beim Anliefern der Daten auszugleichen. Ob ein Gerät dieses Feature unterstützt, kann durch Aufruf von `getMicrosecondPosition` bestimmt werden. Ist dessen Rückgabewert -1, werden derartige Timestamps nicht unterstützt:

    public long getMicrosecondPosition()
| javax.sound.midi.MidiDevice

Aber auch, wenn diese Timestamps unterstützt werden, sollte man keine Wunder von ihnen erwarten. Die Spezifikation weist ausdrücklich darauf hin, daß damit nur kleinere Timing-Schwankungen ausgeglichen werden können. Liegt ein Zeitstempel dagegen weit in der Zukunft (oder gar in der Vergangenheit), ist das Midi-Gerät nicht verpflichtet, diesen korrekt zu behandeln. Wird -1 an das `timeStamp`-Argument von `send` übergeben, ignoriert das entsprechende Gerät den Zeitstempel und bearbeitet die Midi-Nachricht, so schnell es kann.

Um eine `MidiMessage` zu konstruieren, wird diese zunächst mit `new` erzeugt und durch Aufruf von `setMessage` mit Daten gefüllt. Da wir weder Meta- noch Sysex-Daten benötigen, wollen wir uns lediglich die Konstruktion einer `ShortMessage` mit Hilfe der folgenden `setMessage`-Methode ansehen:

    public void setMessage(
      int command,
      int channel,
      int data1,
      int data2
    )
      throws InvalidMidiDataException
| javax.sound.midi.ShortMessage

Als erstes Argument muß das gewünschte Midi-Kommando übergeben werden. Die für uns relevanten Kommandos sind `NOTE_ON` (Taste wird gedrückt), `NOTE_OFF` (Taste wird losgelassen) und `PROGRAM_CHANGE` (Instrumentenwechsel). Sie werden als Konstanten in der Klasse `ShortMessage` definiert. Als zweites Argument wird der Kanal angegeben, auf den sich das Kommando auswirken soll. Anschließend folgen zwei Datenbytes, die kommandospezifisch sind. Das `NOTE_ON`-Kommando erwartet darin beispielsweise die Tonhöhe (die verfügbaren Noten sind als Ganzzahlen durchnumeriert) und die relative Lautstärke (Anschlagsdynamik) der Note. `NOTE_OFF` erwartet die Tonhöhe als erstes Datenbyte und ignoriert das zweite. `PROGRAM_CHANGE` erwartet die gewünschte Programmnummer und ignoriert das zweite Datenbyte.

Nach diesen Vorbemerkungen wollen wir uns nun ein Beispielprogramm ansehen:

    001 /* Listing4802.java */
    002
    003 import javax.sound.midi.*;
    004
    005 public class Listing4802
    006 {
    007   private static void playAlleMeineEntchen()
    008   throws Exception
    009   {
    010     //Partitur {{Tonhoehe, DauerInViertelNoten, AnzahlWdh},...}
    011     final int DATA[][] = {
    012       {60,  1, 1}, //C
    013       {62,  1, 1}, //D
    014       {64,  1, 1}, //E
    015       {65,  1, 1}, //F
    016       {67,  2, 2}, //G,G
    017       {69,  1, 4}, //A,A,A,A
    018       {67,  4, 1}, //G
    019       {69,  1, 4}, //A,A,A,A
    020       {67,  4, 1}, //G
    021       {65,  1, 4}, //F,F,F,F
    022       {64,  2, 2}, //E,E
    023       {62,  1, 4}, //D,D,D,D
    024       {60,  4, 1}  //C
    025     };
    026     //Synthesizer öffnen und Receiver holen
    027     Synthesizer synth = MidiSystem.getSynthesizer();
    028     synth.open();
    029     Receiver rcvr = synth.getReceiver();
    030     //Melodie spielen
    031     ShortMessage msg = new ShortMessage();
    032     for (int i = 0; i < DATA.length; ++i) {
    033       for (int j = 0; j < DATA[i][2]; ++j) { //Anzahl Wdh. je Note
    034         //Note an
    035         msg.setMessage(ShortMessage.NOTE_ON, 0, DATA[i][0], 64);
    036         rcvr.send(msg, -1);
    037         //Pause
    038         try {
    039           Thread.sleep(DATA[i][1] * 400);
    040         } catch (Exception e) {
    041           //nothing
    042         }
    043         //Note aus
    044         msg.setMessage(ShortMessage.NOTE_OFF, 0, DATA[i][0], 0);
    045         rcvr.send(msg, -1);
    046       }
    047     }
    048     //Synthesizer schließen
    049     synth.close();
    050   }
    051
    052   public static void main(String[] args)
    053   {
    054     try {
    055       playAlleMeineEntchen();
    056     } catch (Exception e) {
    057       e.printStackTrace();
    058       System.exit(1);
    059     }
    060     System.exit(0);
    061   }
    062 }
| Listing4802.java

Listing 48.2: Alle meine Entchen - Erster Versuch

Ab Zeile 027 wird ein `Synthesizer` aktiviert und zur Übergabe von Midi-Nachrichten auf einen seiner `Receiver` zugegriffen. Anschließend wird die Melodie durch wiederholten Aufruf seiner `send`-Methode abgespielt. Die Melodie ist in dem Array `DATA` in Zeile 011 versteckt. Für jede einzelne Note wird dort die Tonhöhe, die Tondauer in Viertelnoten und die Anzahl der Wiederholungen angegeben. Die jeweilige Noteninformation wird mit `setMessage` an die vorinstanzierte `ShortMessage` übergeben und als `NOTE_ON`-Nachricht an den Synthesizer geschickt. In Zeile 039 pausiert das Programm, um die Note entsprechend ihrer Länge erklingen zu lassen (in unserem Beispiel 400 ms. je Viertelnote). Anschließend wird die `NOTE_OFF`-Nachricht geschickt, um den Ton zu beenden.

|  |  |  | Wenn wir das Programm mit dem Java-Interpreter starten und eine passende Sound-Hardware vorhanden ist, hören wir tatschlich "Alle meine Entchen". Bei unveränderter Standard-Instrumentierung sollte die Melodie auf einem Klavier gespielt werden. Wenn wir genau hinhören, stellen wir allerdings ein Problem fest: Das Timing des Stücks ist nicht präzise, sondern schwankt während der Darbietung. Manche Noten werden etwas zu kurz, andere dagegen zu lang gespielt. Das liegt daran, daß die mit `Thread.sleep` erzeugten Notenlängen bei weitem nicht präzise genug sind. Die beim Aufruf erzeugten Schwankungen sind für das menschliche Ohr sehr gut hörbar und führen dazu, daß das Musikstück ungenießbar wird. Obwohl das Verfahren prinzipiell funktioniert, benötigen wir also ein präziseres Wiedergabewerkzeug. Und das ist das Thema des nächsten Abschnitts. |  |
Warnung

### 48.3.4 Alle meine Entchen mit dem Sequenzer

Neben dem `Synthesizer` ist der `Sequencer` das zweite wichtige `MidiDevice`. Er dient dazu, Midi-Sequenzen entsprechend der darin enthaltenen Timing-Information präzise wiederzugeben. Das hört sich zunächst einmal einfach an, ist es aber nicht. Einerseits benötigt ein Sequencer einen Zeitgeber, der genauer ist als die üblicherweise vom Betriebssystem zur Verfügung gestellten Timer. Zweitens muß dieser möglichst immun gegen Schwankungen der CPU-Last sein, d.h. der Sequencer sollte auch dann noch stabil arbeiten, wenn im Hintergrund CPU-intensive Operationen ablaufen. Drittens muß ein Sequenzer nicht nur, wie in unserem Beispiel, eine einzige Spur mit verhältnismäßig langsamen Viertelnoten abspielen, sondern möglicherweise ein Dutzend von ihnen, mit Achteln, Sechzehnteln oder noch kürzeren Noten, wie sie beispielsweise bei Schlagzeugspuren auftreten. Zudem enthalten die Spuren oft immense Mengen an Controller-Daten (z.B. Pitch-Bend), die ebenfalls präzise wiedergegeben werden müssen. Zu guter Letzt besitzt ein Sequenzer Zusatzfunktionen, wie das Ändern der Abspielgeschwindigkeit, das Ausblenden einzelner Spuren oder das Synchronisieren mit externen Taktgebern, und er besitzt in aller Regel einen Aufnahmemodus, mit dem Mididaten in Echtzeit aufgenommen werden können.

Wir sehen also, daß die Implementierung eines guten Sequenzers gar nicht so einfach ist. Glücklicherweise stellt das `MidiSystem` einen eigenen `Sequencer` zur Verfügung, dessen Standardimplementierung durch einen Aufruf von `getSequencer` beschafft werden kann:

    public static Sequencer getSequencer()
      throws MidiUnavailableException
| javax.sound.midi.MidiSystem

Beim Abspielen einer Melodie mit dem `Sequencer` werden die Midi-Nachrichten nicht mehr direkt an den `Synthesizer` geschickt, sondern zunächst in eine `Sequence` verpackt. Diese kann wie folgt konstruiert werden:

    public Sequence(float divisionType, int resolution)
      throws InvalidMidiDataException
| javax.sound.midi.Sequence

Das erste Argument gibt die Art und Weise an, wie das Timing erzeugt werden soll. Hier gibt es im wesentlichen die Möglichkeiten, einen internen Timer zu verwenden, der auf Bruchteilen von Viertelnoten basiert, oder mit externer Synchronisation zu arbeiten, bei der die Timing-Informationen im SMPTE-Format zur Verfügung gestellt werden. Wir wollen die erste Variante wählen und übergeben dazu die Konstante `Sequence.PPQ` als erstes Argument. Das zweite Argument `resoultion` bezieht sich auf das erste und gibt die Auflösung des Timers an. In unserem Fall würde es also die Anzahl der Zeitimpulse je Viertelnote angeben.

Um eine `Sequence` mit Daten zu füllen, wird mindestens ein `Track` benötigt, der durch Aufruf von `createTrack` erzeugt werden kann:

    public Track createTrack()
| javax.sound.midi.Sequence

Ein `Track`-Objekt ist zunächst leer und wird durch wiederholte Aufrufe von `add` mit Midi-Ereignissen versehen:

    public boolean add(MidiEvent event)
| javax.sound.midi.Track

Nachdem die `Sequence` fertiggestellt ist, kann sie durch Aufruf von `setSequence` an den `Sequencer` übergeben werden:

    public void setSequence(Sequence sequence)
      throws InvalidMidiDataException

    public void setTempoInBPM(float bpm)

    public void start()
    public void stop()
    public boolean isRunning()
| javax.sound.midi.Sequencer

Mit `setTempoInBPM` kann das Abspieltempo in »Beats Per Minute« (kurz BPM), also in Viertelnoten pro Minute, angegeben werden. `start` startet das Abspielen der Sequenz, `stop` beendet es. Mit `isRunning` kann geprüft werden, ob der Sequencer gerade läuft.

Bevor eine `Sequence` abgespielt werden kann, müssen `Synthesizer` und `Sequencer` miteinander verbunden werden. Dies geschieht, indem ein `Transmitter` des Sequenzers an einen `Receiver` des Synthesizers angeschlossen wird. Der `Transmitter` verfügt dazu über eine Methode `setReceiver`, an die der empfangende `Receiver` übergeben wird:

    public void setReceiver(Receiver receiver)
| javax.sound.midi.Transmitter

Nach diesen Vorbemerkungen können wir uns nun ansehen, wie "Alle meine Entchen" mit Hilfe eines Sequenzers wiedergegeben werden kann.

    001 /* Listing4803.java */
    002
    003 import javax.sound.midi.*;
    004
    005 public class Listing4803
    006 {
    007   private static void playAlleMeineEntchen()
    008   throws Exception
    009   {
    010     //Partitur {{Tonhoehe, DauerInViertelNoten, AnzahlWdh},...}
    011     final int DATA[][] = {
    012       {60,  1, 1}, //C
    013       {62,  1, 1}, //D
    014       {64,  1, 1}, //E
    015       {65,  1, 1}, //F
    016       {67,  2, 2}, //G,G
    017       {69,  1, 4}, //A,A,A,A
    018       {67,  4, 1}, //G
    019       {69,  1, 4}, //A,A,A,A
    020       {67,  4, 1}, //G
    021       {65,  1, 4}, //F,F,F,F
    022       {64,  2, 2}, //E,E
    023       {62,  1, 4}, //D,D,D,D
    024       {60,  4, 1}  //C
    025     };
    026     //Sequence bauen
    027     final int PPQS = 16;
    028     final int STAKKATO = 4;
    029     Sequence seq = new Sequence(Sequence.PPQ, PPQS);
    030     Track track = seq.createTrack();
    031     long currentTick = 0;
    032     ShortMessage msg;
    033     //Kanal 0 auf "EnsembleStrings" umschalten
    034     msg = new ShortMessage();
    035     msg.setMessage(ShortMessage.PROGRAM_CHANGE, 0, 48, 0);
    036     track.add(new MidiEvent(msg, currentTick));
    037     //Partiturdaten hinzufügen
    038     for (int i = 0; i < DATA.length; ++i) {
    039       for (int j = 0; j < DATA[i][2]; ++j) { //Anzahl Wdh. je Note
    040         msg = new ShortMessage();
    041         msg.setMessage(ShortMessage.NOTE_ON, 0, DATA[i][0], 64);
    042         track.add(new MidiEvent(msg, currentTick));
    043         currentTick += PPQS * DATA[i][1] - STAKKATO;
    044         msg = new ShortMessage();
    045         msg.setMessage(ShortMessage.NOTE_OFF, 0, DATA[i][0], 0);
    046         track.add(new MidiEvent(msg, currentTick));
    047         currentTick += STAKKATO;
    048       }
    049     }
    050     //Sequencer und Synthesizer initialisieren
    051     Sequencer sequencer = MidiSystem.getSequencer();
    052     Transmitter trans = sequencer.getTransmitter();
    053     Synthesizer synth = MidiSystem.getSynthesizer();
    054     Receiver rcvr = synth.getReceiver();
    055     //Beide öffnen und verbinden
    056     sequencer.open();
    057     synth.open();
    058     trans.setReceiver(rcvr);
    059     //Sequence abspielen
    060     sequencer.setSequence(seq);
    061     sequencer.setTempoInBPM(145);
    062     sequencer.start();
    063     while (true) {
    064       try {
    065         Thread.sleep(100);
    066       } catch (Exception e) {
    067         //nothing
    068       }
    069       if (!sequencer.isRunning()) {
    070         break;
    071       }
    072     }
    073     //Sequencer anhalten und Geräte schließen
    074     sequencer.stop();
    075     sequencer.close();
    076     synth.close();
    077   }
    078
    079   public static void main(String[] args)
    080   {
    081     try {
    082       playAlleMeineEntchen();
    083     } catch (Exception e) {
    084       e.printStackTrace();
    085       System.exit(1);
    086     }
    087     System.exit(0);
    088   }
    089 }
| Listing4803.java

Listing 48.3: Alle meine Entchen mit dem Sequenzer

Die Partiturdaten stimmen mit denen des vorigen Beispiels überein, werden allerdings anders verwendet. Zunächst wird ab Zeile 027 eine `Sequence` mit einem einzelnen `Track` angelegt, deren Auflösung 16 Ticks per Viertelnote beträgt. Ab Zeile 038 werden die Daten in `ShortMessage`-Objekte übertragen und dem `Track` hinzugefügt. Anders als im vorigen Beispiel lassen wir die Note allerdings nicht während der kompletten Notenlänge an, sondern beenden sie `STAKKATO` Ticks früher als vorgesehen. Diese Zeit zählen wir in Zeile 047 zu der anschließenden Pause hinzu. Durch diese Maßnahme gehen die Noten nicht direkt ineinander über, sondern werden mit einem hörbaren Abstand gespielt. In Zeile 034 wird eine `ShortMessage` zur Programmumschaltung generiert, um Kanal 0 auf Instrument 48 umzuschalten. Dadurch erklingt die Sequenz nicht mit einem Piano- sondern mit einem Streichersound.

Ab Zeile 051 werden `Sequencer` und `Synthesizer` initialisiert und miteinander verbunden. Anschließend wird die `Sequence` an den Sequenzer übergeben, die Abspielgeschwindigkeit eingestellt und das Stück gespielt. In der nachfolgenden Schleife wartet das Programm durch wiederholten Aufruf von `isRunning`, bis die Sequenz vollständig abgespielt ist. Anschließend stoppt es den Sequenzer und schließt alle Geräte.

### 48.3.5 Zugriff auf Midi-Dateien

#### Lesen und Abspielen einer Midi-Datei

Als letztes Beispiel zum Thema Midi wollen wir uns ansehen, wie eine Midi-Datei gelesen und abgespielt werden kann. Dazu benötigen wir nur noch eine zusätzliche Methode, nämlich `getSequence` aus der Klasse `MidiSystem`:

    public static Sequence getSequence(File file)
      throws InvalidMidiDataException, IOException
| javax.sound.midi.MidiSystem

`getSequence` erwartet ein `File`-Objekt als Argument, mit dem die abzuspielende Midi-Datei angegeben wird. Es liefert als Rückgabewert eine `Sequence`, die an den Sequenzer übergeben und von diesem abgespielt werden kann. Ein Beispielprogramm zur Widergabe einer Midi-Datei ist nun sehr einfach zu konstruieren. Mit Ausnahme der expliziten Konstruktion der `Sequence` entspricht es vollkommen dem Beispielprogramm des vorigen Abschnitts:

    001 /* Listing4804.java */
    002
    003 import java.io.*;
    004 import javax.sound.midi.*;
    005
    006 public class Listing4804
    007 {
    008   private static void playMidiFile(String name)
    009   throws Exception
    010   {
    011     //Sequencer und Synthesizer initialisieren
    012     Sequencer sequencer = MidiSystem.getSequencer();
    013     Transmitter trans = sequencer.getTransmitter();
    014     Synthesizer synth = MidiSystem.getSynthesizer();
    015     Receiver rcvr = synth.getReceiver();
    016     //Beide öffnen und verbinden
    017     sequencer.open();
    018     synth.open();
    019     trans.setReceiver(rcvr);
    020     //Sequence lesen und abspielen
    021     Sequence seq = MidiSystem.getSequence(new File(name));
    022     sequencer.setSequence(seq);
    023     sequencer.setTempoInBPM(145);
    024     sequencer.start();
    025     while (true) {
    026       try {
    027         Thread.sleep(100);
    028       } catch (Exception e) {
    029         //nothing
    030       }
    031       if (!sequencer.isRunning()) {
    032         break;
    033       }
    034     }
    035     //Sequencer anhalten und Geräte schließen
    036     sequencer.stop();
    037     sequencer.close();
    038     synth.close();
    039   }
    040
    041   public static void main(String[] args)
    042   {
    043     try {
    044       playMidiFile(args[0]);
    045     } catch (Exception e) {
    046       e.printStackTrace();
    047       System.exit(1);
    048     }
    049     System.exit(0);
    050   }
    051 }
| Listing4804.java

Listing 48.4: Abspielen einer Midi-Datei

Das Programm erwartet in der Kommandozeile den Namen der abzuspielenden Midi-Datei. Wird es mit der (ebenfalls im Verzeichnis der Beispieldateien befindlichen) Datei ame.mid als Argument aufgerufen, ertönt das altbekannte »Alle meine Entchen«.

#### Abspeichern einer Sequenz in einer Midi-Datei

Das Abspeichern einer `Sequence` in einer Midi-Datei ist fast so einfach wie ihr Abspielen. Die Klasse `MidiSystem` besitzt dazu eine Methode `write`, die drei Parameter erwartet:

    public static int write(Sequence in, int type, File out)
      throws IOException
| javax.sound.midi.MidiSystem

Als erstes Argument wird die zu speichernde `Sequence` übergeben, als zweites der Midi-Dateityp und als letztes ein `File`-Objekt mit dem Namen der Ausgabedatei. Für einfache Experimente kann als Midi-Dateityp einfach 0 übergeben werden. Ein Array mit allen unterstützten Dateitypen kann durch Aufruf von `getMidiFileTypes` beschafft werden.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de