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

48.3 Midi



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:

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 tatsächlich "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.1
 <<    <     >    >>   API  © 1998-2003 Guido Krüger, http://www.javabuch.de