From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001 From: Sven Eisenhauer Date: Fri, 10 Nov 2023 15:11:48 +0100 Subject: add new repo --- .../hjp5/html/k100310.html | 1007 ++++++++++++++++++++ 1 file changed, 1007 insertions(+) create mode 100644 Master/Reference Architectures and Patterns/hjp5/html/k100310.html (limited to 'Master/Reference Architectures and Patterns/hjp5/html/k100310.html') diff --git a/Master/Reference Architectures and Patterns/hjp5/html/k100310.html b/Master/Reference Architectures and Patterns/hjp5/html/k100310.html new file mode 100644 index 0000000..c737794 --- /dev/null +++ b/Master/Reference Architectures and Patterns/hjp5/html/k100310.html @@ -0,0 +1,1007 @@ + + + +Handbuch der Java-Programmierung, 5. Auflage + + + + + + + + + +
 Titel  + Inhalt  + Suchen  + Index  + DOC  +Handbuch der Java-Programmierung, 5. Auflage +
 <<  +  <   +  >   + >>  + API  +Kapitel 49 - Sound +
+
+ + + + +

49.3 Midi

+
+ +
+ + + + +

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

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

49.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. + + + + +

49.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, 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: +

+ + + + + +
+ +
+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, 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: +

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

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

+ + + + + +
+ +
+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
+ +Listing 49.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, 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.

+ + + + +
 Warnung 
+
+ + + + +

49.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 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
+ +Listing 49.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. + + + + +

49.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 /* 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
+ +Listing 49.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, 5. Auflage, Addison +Wesley, Version 5.0.1 +
 <<  +  <   +  >   + >>  + API  +© 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de +
+ + + -- cgit v1.2.3