Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 51 - Sound |
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ücks 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 Echtzeitdaten ü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-API umgesetzt werden. Sie befinden sich im zweiten großen Bestandteil des Java Sound-API, 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-API 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 /* Listing5102.java */ 002 003 import javax.sound.midi.*; 004 005 public class Listing5102 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 } |
Listing5102.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 Standardinstrumentierung 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 /* Listing5103.java */ 002 003 import javax.sound.midi.*; 004 005 public class Listing5103 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 } |
Listing5103.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 /* Listing5104.java */ 002 003 import java.io.*; 004 import javax.sound.midi.*; 005 006 public class Listing5104 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 } |
Listing5104.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, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |