From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+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 den Systemen. 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, dass 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.
+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 dass 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.
+
+
+
+
+
+Ä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.
+
+
+
+
+
+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:
+
+
+getSynthesizer
+liefert den Default-Synthesizer der installierten Sound-Hardware,
+typischerweise den auf der Soundkarte eingebauten. Ist mehr als ein
+Synthesizer vorhanden, muss 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, dass ein Default-Synthesizer vorhanden ist, der unseren
+Ansprüchen genügt.
+
+
+Nachdem der Synthesizer
+verfügbar ist, muss er geöffnet und zur Übergabe von
+Midi-Nachrichten ein Receiver
+beschafft werden:
+
+
+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:
+
+
+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:
+
+
+Aber auch, wenn diese Timestamps unterstützt werden, sollte man
+keine Wunder von ihnen erwarten. Die Spezifikation weist ausdrücklich
+darauf hin, dass 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:
+
+
+Als erstes Argument muss 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 durchnummeriert) 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:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 49 - Sound
+
+
+
+
+
+49.3 Midi
+
+
+
+
+
+
+
+
+49.3.1 Was ist Midi?
+
+
+
+
+
+
+
+
+![]()
+
+
+
+![]()
+
+
+
+
+
+ Hinweis
+
+
49.3.2 Grundlegende Klassen des Midi-APIs
+
+
+
+
+49.3.3 Alle meine Entchen - Erster Versuch
+
+
+
+
+
+
+
+
+
+
+public static Synthesizer getSynthesizer()
+ throws MidiUnavailableException
+
+
+
+javax.sound.midi.MidiSystem
+
+
+
+
+
+
+
+
+
+public void open()
+ throws MidiUnavailableException
+
+public void close()
+
+public boolean isOpen()
+
+public int getMaxReceivers()
+
+public Receiver getReceiver()
+ throws MidiUnavailableException
+
+
+
+javax.sound.midi.Synthesizer
+
+
+
+
+
+
+
+
+
+public void send(MidiMessage message, long timeStamp)
+
+
+
+javax.sound.midi.Receiver
+
+
+
+
+
+
+
+
+
+public long getMicrosecondPosition()
+
+
+
+javax.sound.midi.MidiDevice
+
+
+
+
+
+
+
+
+
+public void setMessage(
+ int command,
+ int channel,
+ int data1,
+ int data2
+)
+ throws InvalidMidiDataException
+
+
+
+javax.sound.midi.ShortMessage
+
+
+
+Listing 49.2: Alle meine Entchen - Erster Versuch
+
+
+
+
+
+001 /* Listing4902.java */
+002
+003 import javax.sound.midi.*;
+004
+005 public class Listing4902
+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 }
+
+
+Listing4902.java
+
+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, dass 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, dass 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. |
+
+
|
+![]() |
+
+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 muss +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 muss 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, dass 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 /* Listing4903.java */
+002
+003 import javax.sound.midi.*;
+004
+005 public class Listing4903
+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 }
+
+ |
++Listing4903.java | +
+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. + + + + +
+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 /* Listing4904.java */
+002
+003 import java.io.*;
+004 import javax.sound.midi.*;
+005
+006 public class Listing4904
+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 }
+
+ |
++Listing4904.java | +
+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«. + + + + +
+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, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |