Word-Doc

Werbung
__________________________________________________________________________________________
Sound-Programmierung in Java
-1-
Einführung
Dieses Tutorial soll eine kurze Einführung in die Möglichkeiten darstellen, die die Sprache Java
bietet, um mit ihr Sounds, bzw Musik wiederzugeben, oder auch eigenständig aufnehmen zu können.
Das Tutorial ist in drei Teile gegliedert.
Teil I und II beschäftigen sich ausschliesslich mit der Java Sound API V0.99, die als Teil des Java
Media Frameworks (JMF) vertrieben wird.
Teil III bietet darüber hinaus eine knappe Einführung in das Java Media Framework.
Zum Zeitpunkt der Textbearbeitung, befand sich die Java Sound API schon in der Version 1.0, die
fester Bestandteil des Java SDK 1.3 RC1 ist. Nach Angaben von Sun sollen aber keine gravierenden
Änderungen zwischen der Version 0.99 und 1.0 stattgefunden haben; daher sind die Aussagen, die
hier getroffen werden auch für die aktuellere Version gültig.
Das Anliegen dieses Tutorials ist nicht, die gesamte API, bzw. das gesamte JMF mit all ihren Klassen
und Methoden zu beschreiben, sondern es soll dem Leser als Erleichterung dienen, seine Programme
um die Möglichkeiten der Java Sound API bzw. des JMF zu erweitern. Zu nennen wäre hier
beispielsweise das Abspielen von Sounddateien in Programmen (z.B. die korrekte Aussprache einer
Vokabel in einem Vokabeltrainer) oder aber auch die Möglichkeit, eigene Sounddateien zu kreieren.
Für eine komplette Übersicht über alle Klassen und Methoden der Java Sound API und des Java
Media Framework sei der Leser auf die Java Sound Page von Sun [1] verwiesen.
Das Tutorial ist so angelegt, neben unausweichlichen Theorieaspekten der Soundgenerierung, ohne
die man die Klassengliederung der API und des JMF nicht nachvollziehen kann, vor allem an
möglichst einfach gehaltenen Beispielprogrammen das Einbinden von Sound in die eigenen
Programme zu zeigen. Hierfür gibt es die sogenannten „Wie kann man ...“ - Abschnitte im Text, die
sich einfachen Fragestellungen widmen (zum Beispiel Wie kann man ... Sounddateien wiedergeben,
die sich auf der Festplatte befinden?) und dem Leser hoffentlich bei Standardproblemen bzw. fragen helfen. Man kann diese Abschnitte also als eine Sammlung von „Kochrezepten“ verstehen, die
bei auftretenden Problemen helfen sollen.
Im Text wird auch außerdem auf Probleme mit der API hingewiesen, die uns beim Verfassen des
Textes aufgefallen sind. Einige Klassen befinden sich noch in einem - nach unserer Meinung - nicht
ganz ausgereiftem Stadium und erfüllen nicht alle Aufgaben so, wie sie von Sun in der
Dokumentation spezifiziert wurden.
__________________________________________________________________________________________
Sound-Programmierung in Java
-2-
Die Java Sound API gliedert sich in die vier Unterpakete:
 javax.sound.sampled
 javax.sound.sampled.spi
 javax.sound.midi
 javax.sound.midi.spi
In diesem Text werden nur die beiden Pakete javax.sound.sampled , javax.sound.midi näher beschrieben.
Die beiden anderen Unterpakete und die darin enthaltenen Klassen, sind für Hersteller gedacht, um
mit ihnen – z.B. eigene Audiokomprimierungsverfahren - einfach ihre Erweiterungen in die Java
Sound API einbinden zu können.
Wer nähere Informationen zu diesen beiden Paketen benötigt, findet eine kurze Einführung in der
offiziellen Sun-Dokumentation zur Sound API, die man unter [1] finden kann.
Teil I dieses Tutorials widmet sich dem Paket javax.sound.sampled und den dazu passenden
Problem- und Fragestellungen.
Teil II hingegen beschäftigt sich mit dem javax.sound.midi - Paket und dessen Anwendung in der
Praxis.
Teil III bespricht die audiophilen Aspekte des Java Media Framework.
__________________________________________________________________________________________
Sound-Programmierung in Java
-3-
TEIL I
javax.sound.sampled
__________________________________________________________________________________________
Sound-Programmierung in Java
-4-
Kapitel 1 - Einführung in Sampled Audio
Das Paket javax.sound.sampled richtet seinen Hauptaugenmerk auf den Transport von AudioDaten, d.h. in erster Linie auf das Abspielen und Aufnehmen von digitalem Sound.
Die wichtigste Aufgabe ist hierbei, wie die AudioBytes in das System hinein- und wieder heraus
transportiert werden. Die Java Sound API kann den Audio-Datentransport auf zwei verschiedene
Arten bewerkstelligen. Es gibt auf der einen Seite den gepufferten und gestreamten Weg und auf der
anderen den in-memory ungepufferten. Der erste bietet sich bei kleinen Sounddateien an (um auf
unser Vokabeltrainerbeispiel zurückzukommen wären dies die Vokabel-Sounddateien), der zweite bei
sehr grossen Dateien (zum Beispiel bei gegrabbten Audio-Tracks einer CD).
Beide Ansätze werden genauer im Kapitel 2 - Abspielen von Audio-Files besprochen.
Kommen wir jetzt zu den grundlegenden Eigenschaften der API.
Was benötigt man um Soundfiles mit der API abspielen zu können ?
Um Sound wiederzugeben oder aufzunehmen werden 3 wichtige Dinge benötigt:
in einem bestimmten Format vorliegende Audio-Daten (z.B. den Audio-Track einer CD als Wave Datei), einen sogenannten Mixer und eine Line.
Was sind formatierte Audio-Daten ?
Als formatierte Audio-Daten werden Sounds bezeichnet, die in einem Standard-Sounddateiformat
vorliegen; dazu zählen beispielsweise WAV - Dateien oder AU - Dateien.
Die Java Sound API unterscheidet zwischen Datenformaten und Fileformaten.
Das Datenformat einer Sounddatei bestimmt, wie das Programm, das die Sounddatei bzw. dessen
Audiodaten wiedergibt, diese interpretieren soll. Repräsentiert werden diese Informationen z.B. durch
die Sample-Rate (d.h. wie viele Samples der Original-Audiodaten pro Sekunde gespeichert werden)
oder durch die Anzahl der Kanäle (d.h. 1 Kanal = Mono-, 2 Kanäle = Stereoqualität usw.). Das
Datenformat einer Sounddatei wird in der Java Sound API durch die Klasse AudioFormat
spezifiziert.
Das Fileformat einer Sounddatei bestimmt die innere Struktur einer Audiodatei, z.B. die Angabe in
welchem Audioformat die Sounddatei vorliegt.
__________________________________________________________________________________________
Sound-Programmierung in Java
-5-
Standard-Audiofileformate gibt es einige. Zu nennen wären hier WAVE (die Windows .wav-Dateien),
AIFF, oder AU. Diese verschiedenen Formate besitzen alle eine unterschiedliche interne Strukturen
die die Informationen über die Sounddatei widerspiegeln. In der Java Sound API wird das Fileformat
durch die Klasse AudioFileFormat spezifiziert. Sie enthält Informationen über das Audiofileformat
der verknüpften Sounddatei, deren Länge in Bytes usw.
Was ist ein Mixer ?
In der Java Sound API werden Audiogeräte, die sich in oder an der Maschine befinden, durch die
sogenannte Mixer Klasse repräsentiert. Der Anspruch dieser Klasse liegt darin, die AudioIn - und
Outputströme in das jeweilige Gerät zu koordinieren oder zu beeinflussen (beispielsweise
dieLaustärke bei der Ausgabe); der Mixer fungiert also in ähnlicher Weise wie ein Mischpult.
In der Java Sound API sind den Eingängen und Ausgängen einer Soundkarte keine eigenen Mixer
zugeordnet, sondern sogenannte Ports die in der Klasse Port zusammengefaßt werden. Diese Ports
(z.B. der Mikrofoneingang an der Soundkarte) werden durch den Mixer gesteuert.
Da in den hier besprochenen Standardproblemen die Mixer nicht benötigt werden, finden sie in
diesem Tutorial auch keine weitere Beachtung und sollen an dieser Stelle nur der Vollständigkeit
halber erwähnt werden. Wichtig sind die Mixerobjekte erst dann, wenn ein anderes installiertes Gerät
als die Standardsoundkarte zur Sounderzeugung bzw. –aufnahme benutzt wird.
Anzumerken ist hier, daß sich die Portobjekte in der Version 0.99 (und nach den Angaben in der Java
Sound Mailingliste[4] auch in der Version 1.0) noch in nicht nutzbarem Stadium befinden. Es ist nicht
möglich, etwa den Mikrofoneingang und dessen Einstellungen über ein Portobjekt zu beeinflussen
oder ihn auf stumm zu schalten.
Was ist eine Line ?
Als Line wird in der Java Sound API der Pfad beschrieben, auf dem Audiodaten in das System
hineingelangen oder hinausgelangen. Ein Beispiel für den Pfad in das System wäre das Mikrofon, ein
Pfad aus dem System heraus die angeschlossenen Lautsprecher.
Java Sound API – Objekte, die als Line bezeichnet werden, sind Ports, TargetDataLine,
SourceDataLine
und Clip.
Kommen wir nun nach den theoretischen Grundlagen der API zur Anwendung dieser Klassen und
damit zunächst zur Soundgenerierung.
__________________________________________________________________________________________
Sound-Programmierung in Java
-6-
Kapitel 2 - Die Soundgenerierung
Die wichtigste Klasse in dem Paket javax.sound.sampled ist die statische Klasse AudioSystem.
Mit Hilfe dieser Klasse hat man Zugriff auf die installierten Audiogeräte in einem System.
Dies bedeutet im einzelnen, daß mit der Klasse AudioSystem :
-
Mixer - Objekte erzeugt kann mit denen man auf bestimmte Geräte im System speziell zugreifen
kann
-
das man Lineobjekte erzeugen kann um den Strom von Audiodaten in das System herein und
wieder heraus zu ermöglichen
-
das
man
mit
ihrer
Hilfe
Audiodaten
konvertieren
kann
zwischen
verschiedenen
Audiodatenformaten
-
streams zu Audiodaten geöffnet werden, um diese zu lesen oder zu schreiben.
Wichtigster Punkt ist aber, daß man mit ihr direkt auf die Soundkarte zugreifen kann ohne, vorher
umständlich ein Mixer - Objekte anzulegen und dann über diese die Soundkarte und die Soundgenerierung zu steuern. Da die Klasse AudioSystem für unseren Ansatz vollkommen ausreicht,
werden in diesem Tutorial keine Mixer – Objekte benutzt.
Bevor wir mit dem Generieren von Sound beginnen, müssen wir noch auf die verschiedenen
Sicherheitsaspekte hinweisen, wenn der Java Security Manager im Einsatz ist. Folgende Zugriffe sind
per default erlaubt und sollten beim Programmieren mit der API berücksichtigt werden:
 Applets Soundwiedergabe möglich, Soundaufnahme nicht möglich
 Applikationen, die mit keinem security manager laufen, können Sound sowohl aufnehmen als auch
wiedergeben
 Applikationen, die mit dem default security manager laufen, können Sound wiedergeben, aber
keinen Sound aufnehmen
Beim Generieren von Sound kommen wir zunächst wieder zum schon besprochenen Lineobjekt
zurück.
Wir haben zwei Ansatzmöglichkeiten den Sound mit der API wiederzugeben. Zum Einen können
Audio-files vor dem Abspielen komplett in den Arbeitsspeicher des Rechners geladen werden (was
__________________________________________________________________________________________
Sound-Programmierung in Java
-7-
nur bei sehr kleinen Dateien zu empfehlen ist), zum anderen können jeweils nur kleine Teile des
Audiofiles in den
Speicher geladen und dann abgespielt werden. Dies spiegelt sich in den Klassen wider, die zum Abspielen benutzt werden können: die Clip Klasse, die den ersten Ansatz verfolgt, und die
SourceDataLine Klasse, die den zweiten Ansatz realisiert. Beide werden zu den Lineobjekten
gezählt.
Wie kann man ... Audiofiles als Clip abspielen ?
Erster Schritt beim Abspielen einer Sounddatei mit Hilfe der Clip-Klasse sollte sein, ein File Objekt zu erzeugen, das auf die wiederzugebende Sounddatei verweist.
File file = new File("ding.wav");
In unserem Beispiel soll eine Wave-Datei mit dem Namen „ding.wav“ wiedergegeben werden.
Als nächsten Schritt müssen wir von dem Java Sound System einen sogenannten AudioInputStream
anfordern, der später die Daten der Sounddatei für uns in den Speicher des Rechners liest.
AudioInputStream stream =
AudioSystem.getAudioInputStream(file);
Als Übergabeparameter geben wir der getAudioInputStream - Methode das File - Objekt.
Danach benötigt man ein Line - Objekt, das den Eingang für das Java Sound System spezifiziert. D.h.
welche Eingangsquelle das Java Sound System bei der Soundgenerierung benutzen soll. In unserem
Fall, handelt es sich um ein Clip - Objekt, da wir die Audiodaten vor der Wiedergabe komplett in
den Hauptspeicher lesen und von dort aus die Wiedergabe durchführen wollen. Um an dieses Clip Objekt über das Java Audiosystem zu gelangen, muss im voraus ein sogenanntes LineInfo - Objekt
erzeugt werden. Dieses definiert dem Java AudioSystem welche Art von Dateneingangsquelle wir
bei der Wiedergabe unseres Sound benutzen wollen. Ein solches Objekt (in der Java Sound API
repräsentiert durch die Line.info Klasse) kann aber nicht direkt erzeugt werden, sondern nur ein
Objekt seiner Unterklassen Port.info oder DataLine.info. Da die Port - Objekte in der
aktuellen Version des Paketes noch nicht vollständig implementiert sind, werden hier nur die
DataLine - Objekte (Clip, SourceDataLine und TargetDataLine) benutzt und
besprochen.
Wir werden also nun ein DataLine - Objekt erzeugen:
DataLine.Info info =
newDataLine.Info(Clip.class,stream.getFormat());
Als Parameter erwartet der Konstruktor den Klassentyp, der zur Wiedergabe benutzt werden soll (also
Clip - Klasse oder SourceDataLine - Klasse) und das Audiodatenformat der Sounddatei, die
__________________________________________________________________________________________
Sound-Programmierung in Java
-8-
abgespielt werden soll. Das Format unserer Datei bekommen wir einfach, indem wir die
AudioInputStream - Methode getFormat() benutzen.
Nachdem wir diese Vorarbeiten geleistet haben, fordern wir unser Clip - Objekt von dem
Soundsystem an. Hierzu wird dem AudioSystem die vorbereitete DataLine.info Klasse
übergeben:
Clip clip = (Clip) AudioSystem.getLine(info);
Als nächstes müssen wir für unsere Applikation dieses Clip – Objekt reservieren, damit kein anderes
Programm dieses Objekt währendessen nutzen kann. Dies geschieht durch eine open - Anweisung:
clip.open(stream);
Der Anweisung muß der AudioInputStream übergeben werden, der mit der wiederzugebenden
AudioDatei verknüpft ist. Nachdem der Clip geöffnet und so für unsere Anwendung reserviert wurde,
können wir die Wiedergabe starten. Dies geschieht, indem wir eine start - Anweisung benutzen:
clip.start();
Nun wird der Clip bzw. unsere Sounddatei wiedergegeben.
Um die Wiedergabe zu unterbrechen genügt eine einfache stop - Anweisung:
clip.stop();
Gibt man nun wieder eine start - Anweisung, wird die Wiedergabe exakt an der Stelle fortgesetzt, an
der vorher die stop - Anweisung gegeben wurde.
Ist die Wiedergabe des Clips beendet und man der Clip bzw. die Datei soll nicht noch einmal wiedergeben werden, gibt man durch eine close - Anweisung alle gebundenen Resourcen wieder frei:
clip.close();
Dies sind die typischen Schritte bei der Wiedergabe einer Sounddatei über den Clip - Ansatz.
Hier nun ein komplettes Beispielprogramm PlaySound.java :
import javax.sound.sampled.*;
import java.io.*;
class PlaySound
{
static File file = null;
static AudioInputStream stream = null;
static Clip clip = null;
public static void main (String args[])
{
System.out.println("Spiele wav ...");
//bestimme welche Sounddatei abgespielt werden soll
__________________________________________________________________________________________
Sound-Programmierung in Java
-9-
file = new File("ding.wav");
//versuche EingabeStream auf die abzuspielende Sounddatei zu
//bekommen
try
{
stream = AudioSystem.getAudioInputStream(file);
}catch(UnsupportedAudioFileExceptione){System.out.println("Kein
unterstuetztes AudioFormat!");System.exit(0);}
catch(IOException e2){System.out.println("Fehler beim Oeffnen
der Sounddatei!");System.exit(1);}
//erzeuge LineObjekt das spezifiziert welche Line vom AudioSystem zur
//Wiedergabe benutzt werden soll
DataLine.Info info =
new DataLine.Inf(Clip.class,stream.getFormat());
//versuche gewuenschte Eingangs-Line beim AudioSystem anzumelden
try{
clip = (Clip) AudioSystem.getLine(info);
}catch(LineUnavailableException e){System.out.println("Line
konnte nicht benutzt werden");System.exit(1);}
//oeffne das Clipobjekt und reserviere es somit fuer diese
//Applikation
try{
clip.open(stream);
}catch(LineUnavailableException e){System.out.println("Fehler
beim Öffnen des AudioStreams");System.exit(1);}
catch(IOException e){}
//beginne die Wiedergabe
clip.start();
//warte solange wie das AudioSystem den Clip wiedergibt
while(clip.isActive()){}
//stoppe Clip nachdem er komplett wiedergegeben wurde
clip.stop();
//schliesse den Clip und gib somit die gebundenen Resourcen wieder
//frei
clip.close();
//beende ordnungsgemaess das Programm
System.exit(0);
}
}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 10 -
Dieses Programm spielt eine Wave - Datei in der vorher besprochenen Weise ab. Hinzugekommen ist
dabei noch die Clip - Methode
[Clip].isActive().
Diese gibt ein false zurück, wenn die Wiedergabe des Soundclips gestoppt wurde, entweder weil
das Ende der Audiodatei erreicht wurde, oder weil eine stop - Anweisung vorher erfolgt ist. An dieser
Stelle wird gut sichtbar, welche Methoden Java Exceptions auslösen können und um welche es sich
dabei handelt. Für eine komplette Übersicht der bisher behandelten Methoden, die Exceptions
auslösen können, sei auf das Ende dieses Kapitels verwiesen.
Weitere Clip – Methoden, die hier nicht weiter besprochen werden, aber in manchen Anwendungen
hilfreich sein können, sind:
void loop(int count)
- startet die Wiedergabe eines Clips und wiederholt diesen count - mal
long getMicrosecondLength()
- gibt die Länge des Sounds in Mikrosekunden an
long getMicrosecondPosition()
- gibt die aktuelle Position bei der Wiedergabe in Mikrosekunden an
Nun zeigen wir, wie man den zweiten Ansatz vollziehen kann bei der Wiedergabe einer Sounddatei,
nämlich ...
Wie kann man ... Audiofiles als SourceDataLine abspielen ?
Zunächst erzeugt man wie beim ersten Ansatz ein File Objekt. Danach wird wieder ein Objekt vom
Typ
AudioInputStream
generiert.
Als
nächster
Schritt
liegt
die
Erzeugung
eines
DataLine.info Objekt vor. Num kommen wir zum ersten Unterschied zum ersten Ansatz. Dem
Konstruktor der DataLine.info Klasse wird nun nämlich mitgeteilt das eine SourceDataLine
gewünscht wird. Man ersetzt also Clip.class durch SourceDataLine.class und erhält:
DataLine.Info info
= new DataLine.Info(SourceDataLine.class,
stream.getFormat());
Als nächste wird vom AudioSystem ein Objekt vom Typ SourceDataLine angefordert:
SourceDataLine sl
= (SourceDataLine)AudioSystem.getLine(info);
Dies geschieht auf gleichem Wege wie bei dem Clip - Ansatz.
Die nächsten Schritte sind ebenfalls gleich, da nun eine open - Anweisung - gefolgt von einer start Anweisung - gemacht werden muß.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 11 -
Vor dem eigentlich Lesen der Audiodaten aus der Sounddatei muss noch ein Byte-Array angelegt
werden, in dem die gelesenen Audiodaten gepuffert werden. Ist dies erledigt, folgt das Auslesen der
Audioinformationen aus der Sounddatei. Da die Klasse AudioInputStream eine von der Klasse
InputStream abgeleitete Klasse darstellt, wird hierzu die bekannte read - Methode benutzt.
Es wird solange die Anweisung
int numBytesRead = stream.read(ba,0,1024);
wie numBytesRead nicht den Wert -1 annimmt.
Wird dieser Wert nämlich zurückgeliefert, ist das Ende der Sounddatei beim Auslesen der Audioinformationen erreicht. Die Methode read schreibt in das vorher angelegte ByteArray ba die ausgelesenen Audiodaten der Quelldatei. Hierbei werden die Arrayfelder 0 bis 1024 benutzt.
Nach der read - Anweisung wird das ByteArray der SourceDataLine - Methode write übergeben, was die Wiedergabe der darin enthaltenen Audiodaten veranlasst.
Nach dem Verlassen der read - write -Schleife, also nachdem das Ende der Quelldatei erreicht und
alle Audiodaten ausgelesen wurden, wird die SourceDataLine - Methode drain() aufgerufen,
die solange blockt, bis alle sich noch im internen Puffer befindlichen Audiodaten verarbeitet wurden;
somit ist gewährleistet, daß die Sounddatei komplett wiedergegeben wurde.
Danach erfolgen - wie schon von dem Clip - Ansatz her bekannt - noch stop - und close Anweisungen. Damit wären alle Schritte abgehandelt, die nötig sind, um mit Hilfe einer
SourceDataLine eine Sounddatei wiederzugeben.
Nun wollen wir dazu ein komplettes Beispiel zeigen, nämlich PlaySoundStreamed.java:
import javax.sound.sampled.*;
import javax.sound.midi.*;
import java.io.*;
class PlaySoundStreamed
{
static File file = null;
//die zu streamende Datei
static AudioInputStream stream = null;//ueber diesen Stream
//werden die AudioDaten aus der
//Datei "file" gelesen
static SourceDataLine sl = null;
//ueber diese DataLine wird die
//Datei "file" abgespielt
static byte
ba[] = null;
// Array enthaelt die
//Sounddateibytes die aktuell aus
//dem AudioInputStream "stream"
__________________________________________________________________________________________
Sound-Programmierung in Java
- 12 -
//gelesen wurden und dann ueber
//die SourceDataLine "sl"
//abgespielt werden
static int numBytesRead = 0;
//Anzahl der gelesenen Bytes aus
//dem InputStream
static PlaySoundStreamed pss = null;
public PlaySoundStreamed(File fi)
{
//erzeuge den InputStream auf die Datei "file"
try
{
stream = AudioSystem.getAudioInputStream(file);
}catch(UnsupportedAudioFileException e)
{System.out.println("AudioFormat wird nicht
unterstuetzt!");}
catch(IOException e2){System.out.println("Fehler beim Oeffnen
der Quelldatei!");}
//erzeuge DataLine.info die spezifiziert welche Art von Eingabe man
//haben will beim AudioSystem
DataLine.Info info
= new DataLine.Info (SourceDataLine.class,
stream.getFormat());
//versuche mit Hilfe von "info" die gewünschte Eingabe beim
//AudioSystem zu bekommen
try{
sl = (SourceDataLine) AudioSystem.getLine(info);
}catch(LineUnavailableException e){System.out.println("Line
konnte nicht benutzt werden");System.exit(1);}
}
public static void main (String args[])
{
file = new File("1-welcome.wav");
pss = new PlaySoundStreamed(file);
System.out.println("Spiele wav ...");
// oeffne und reserviere somit fuer diese Applikation die
//SourceDataLine
try{
sl.open();
}catch(LineUnavailableException e){System.out.println("Fehler
beim Öffnen des AudioStreams");System.exit(1);}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 13 -
// starte die Line zum Abspielen des Sounds
sl.start();
// lege den Buffer an in den die Sounddateidaten eingelesen werden
//ueber die AudioInputStream
ba = new byte[1024];
//Spiele Sound
while(true)
{
//lese SounddateiDaten in Buffer ein
try
{
numBytesRead = stream.read(ba,0,1024);
}catch(IOException e){System.out.println("Fehler
beim Lesen der Sounddatei-Bytes");
System.exit(1);}
//wenn Ende der Sounddatei bzw. Streams erreicht beende Schleife
if (numBytesRead == -1)
break;
//Ende der Sounddatei erreicht!
// schreibe gelesene Sounddateidaten in SourceDataLine und veranlasse
//somit das Abspielen der Daten
sl.write(ba,0,ba.length);
}
// blockiere bis die letzten Daten abgespielt wurden
sl.drain();
// halte die Line an und schliesse die Line
sl.stop();
sl.close();
sl=null;
System.out.println("
... Fertig!");
System.exit(0);
}
}
Wie man sieht, gibt es bis auf die while - Schleife keine großen Unterschiede zur PlaySound
Applikation. In dieser Schleife werden jeweils kleine Pakete von Sounddaten aus der Quelldatei
gelesen und dann auf die SourceDataLine geschrieben. Dieses Schreiben veranlasst, daß Java Sound
System, die empfangenen Sounddaten über die Soundkarte abzuspielen. Ungewöhnlich erscheint
vielleicht der
__________________________________________________________________________________________
Sound-Programmierung in Java
- 14 -
Aufruf der Methode
[SourceDataLine].drain()
nach dem Verlassen der Schleife. Diese Methode ist wichtig, damit beim Austritt aus der Schleife
nicht direkt die stop - Anweisung folgt und dadurch die Wiedergabe gestoppt wird, obwohl sich
eventuell noch nicht wiedergegebene Sounddaten im System befinden. Die Methode blockt also
solange,
bis
alle
Daten korrekt abgespielt wurden.
Eine verfeinerte Version des obigen Programms findet man im Anhang A, bei dem man jederzeit die
Wiedergabe durch Tastendruck beenden kann (siehe PlaySoundStreamedT.java).
Zum Schluß dieses Kapitels, fassen wir in einer Tabelle noch einmal die Methoden der Java Sound
API zusammen, die die Möglichkeit haben, Java Exceptions zu erzeugen:
Methode
AudioSystem.getAudioInputStream( )
AudioSystem.getLine( )
[Line – Objekt].open( )
[AudioInputStream – Objekt].read( )
[AudioOutputStream – Objekt].write( )
erzeugte Exception
UnsupportedAudioFileException,
IOException,
LineUnavailableException,
SecurityException,
IllegalArgumentException
LineUnavailableException
IOException
IOException
__________________________________________________________________________________________
Sound-Programmierung in Java
- 15 -
Kapitel 3 - Die Aufnahme
Die Aufnahme einer Sounddatei gestaltet sich in großen Abschnitten ähnlich wie das Abspielen von
Dateien. Neu hinzugekommen sind Objekte vom Typ TargetDataLine.
Was ist eine TargetDataLine ?
Über eine TargetDataLine hat man die Möglichkeit Audiodaten, die in das Java Sound System
hinein gelangen, zu lesen. Daher stellen diese Line - Objekte die wichtigsten Werkzeuge dar, mit
denen bei der Soundaufnahme gearbeitet wird. TargetDataLine bekommt seine Daten direkt von
einem „vorgeschaltenen“ Mixer - Objekte oder von der AudioSystem - Klasse der Java Sound
API.
Diese beiden Objekte können Audiodaten, die sie eventuell vorher noch bearbeitet haben
(beispielsweise Hall hinzufügen oder die Balance zwischen linken und rechtem Kanal beinflussen)
direkt in den internen Puffer einer TargetDataLine hineinschreiben. An diese Daten gelangt man
dann über eine read - Methode, deren Übergabeparameter gleich denen der read - Methode bei der
Klasse InputStream sind. Dies resultiert aus der Tatsache, das TargetDataLine eine von
InputStream abgeleitete Klasse darstellt.
Diese zu verarbeitenden Audioinformationen können beispielsweise über ein angeschlossenes
Mikrofon oder über den Line-In - Eingang der Soundkarte in das Java Sound System gelangen.
Wie kann man ... selber Audiodaten aufnehmen ?
Man entscheidet zunächst, in welchem Format und mit welcher Qualität die Daten aufgezeichnet
werden sollen. Hierzu wird ein AudioFormat - Objekt erzeugt, das diese Punkte genau spezifiziert.
AudioFormat af = new AudioFormat((float)11025.0,16,2,
true,false);
Danach erfolgt das schon bekannte Erzeugen eines DataLine.info - Objektes.
DataLine.info info
= new DataLine.Info (TargetDataLine.class,af);
Zu beachten ist jetzt hierbei, das dem Konstruktor als erster Parameter TargetDataLine.class
übergeben wird, da wir ja solch ein Line - Objekt im folgenden Schritt vom AudioSystem erhalten
wollen:
__________________________________________________________________________________________
Sound-Programmierung in Java
- 16 -
TargetDataLine tl
= (TargetDataLine) AudioSystem.getLine(info);
Als nächstes müssen wir unsere TargetDataLine tl für unsere Applikation reservieren, damit
keine andere Anwendung diese für sich beanspruchen kann. Das geschieht durch eine einfache open Anweisung:
tl.open(af);
Als Übergabeparamter haben wir hier nicht den AudioInputStream wie bei dem Verfahren zur
Soundwiedergabe
gesehen,
sondern
das
gewünschte
Audioformat,
in
dem
über
die
TargetDataLine nachher die Audioaufnahmen gemacht werden sollen.
Nun wird die start() - Methode der TargetDataLine aufgerufen:
tl.start();
Alle Audiodaten, die in das AudioSystem fliessen, können nun mit der eben schon erwähnten read
- Methode über die TargetDataLine gelesen werden. Zur Verdeutlichung, sei an dieser Stelle
folgendes Beispielprogramm mit dem Namen CaptureAudio.java eingebracht:
import javax.sound.sampled.*;
import java.io.*;
class CaptureAudio extends Thread
{
static AudioInputStream stream = null; //uber diesen Stream werden
//die aufgenommenen AudioDaten
//ausgelesen
static AudioFormat af = null;
//gibt das AudioFormat an in dem
//die Aufnahme gemacht werden soll
static byte
ba[] = null,
// Array enthaelt die Sounddateibytes
//die aktuell aus dem AudioInputStream
//"stream" gelesen wurden und dann ueber
//die SourceDataLine "sl" abgespielt
//werden
caB[] = null;
static
int numBytesRead = 0;// Anzahl der gelesenen Bytes
static CaptureAudio ca = null;
static boolean capture = true;
static SourceDataLine sl = null;
static TargetDataLine tl = null;
static ByteArrayOutputStream baOut = null;
static ByteArrayInputStream baIn = null;
static DataLine.Info info;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 17 -
public CaptureAudio()
{
// bestimmt das Soundformat in dem die Aufnahme gemacht werden soll
af = new AudioFormat((float)11025.0,16,2,true,false);
// bestimmt welche Art von Line vom AudioSystem angefordert wird
info = new DataLine.Info (TargetDataLine.class,af);
// versuche Line von AudioSystem anzufordern
try
{
tl
= (TargetDataLine) AudioSystem.getLine(info);
}catch(LineUnavailableException e)
{System.out.println("AudioSystem hat keine Line frei");
System.exit(1);}
//oeffne und reserviere somit fuer diese Applikation die
//TargetDataLine
try
{
tl.open(af);
}catch(LineUnavailableException e){System.out.println("Fehler
beim Öffnen des AudioStreams");System.exit(1);}
}
public void run()
{
baOut = new ByteArrayOutputStream();
int numBytesRead = 0;
//Puffer in den die aufgenommenen Daten eingelesen werden
ba = new byte [64];
// starte die Line zum Aufnehmen des Sounds
tl.start();
// starte Aufnahme
while(capture)
{
//lese SounddateiDaten in Buffer ein
numBytesRead = tl.read(ba,0,ba.length);
//schreibe in aktuelle AudioDatenbytes auf OutputStream
baOut.write(ba,0,numBytesRead);
}
// blockiere bis die letzten Daten geschrieben wurden
__________________________________________________________________________________________
Sound-Programmierung in Java
- 18 -
tl.drain();
// halte die Line an und schliesse die Line
tl.stop();
tl.close();
tl=null;
System.out.println("
... Fertig mit Aufnahme");
System.out.println("...STOP");
System.out.println("Spiele aufgenommenen Sound ab ...");
//lese aufgenommene Audiodaten aus
caB = baOut.toByteArray();
System.out.println("Anzahl der gecaptureten
Bytes:"+caB.length);
//uebergebe alle gelesenen Audiodaten an Inputstream
baIn = new ByteArrayInputStream(caB);
//oeffne InputStream auf gelesene Audiodaten
stream = new AudioInputStream(baIn, af,caB.length /
af.getFrameSize());
//erzeuge DataLine.info die spezifiziert welche Art von Eingabe man
//haben will beim AudioSystem
info = new DataLine.Info(SourceDataLine.class,
stream.getFormat());
//versuche mit Hilfe von "info" die gewünschte Eingabe beim
//AudioSystem zu bekommen
try{
sl = (SourceDataLine) AudioSystem.getLine(info);
}catch(LineUnavailableException e){System.out.println("Line
konnte nicht benutzt werden");System.exit(1);}
try{
// öffne Line zum Abspielen des Sounds
sl.open();
}catch(LineUnavailableException e){System.out.println("Fehler
beim Öffnen des AudioStreams");System.exit(1);}
// starte die Line zum Abspielen der aufgenommenen Audiodaten
sl.start();
while(true)
{
//lese SounddateiDaten in Buffer ein
try
__________________________________________________________________________________________
Sound-Programmierung in Java
- 19 -
{
numBytesRead = stream.read(ba,0,64);
}catch(IOException e){System.out.println("Fehler beim
Lesen der SounddateiDaten");System.exit(1);}
//wenn Ende der Sounddatei bzw. Streams erreicht beende Schleife
if (numBytesRead == -1)
break;
//Ende der Sounddatei erreicht!
// schreibe gelesene Sounddateidaten in
//SourceDataLine und veranlasse somit das Abspielen
//der Daten
sl.write(ba,0,ba.length);
}
// blockiere bis die lezten Daten abgespielt wurden
sl.drain();
// halte die Line an und schliesse die Line
sl.stop();
sl.close();
System.out.println("
... Fertig!");
System.exit(0);
}
public static void main (String args[])
{
ca = new CaptureAudio();
//starte den Aufnahmethread
ca.start();
System.out.println("Aufnahme ...");
System.out.println("Druecke <RETURN> fuer Aufnahme-STOP");
try
{
int c = System.in.read();
}catch(IOException e){System.exit(1);}
capture = false;
}
}
Dieses Beispielprogramm dem Nutzer, eine Audioaufnahme zu machen, die das Programm
anschließend noch einmal abspielt.
Das Programm beginnt zunächst damit, im Konstruktor die oben beschriebenen Schritte
durchzuführen bzw. abzuarbeiten. Es wird ein Audioformat gewählt (für eine genaue Beschreibung,
__________________________________________________________________________________________
Sound-Programmierung in Java
- 20 -
welche Formate man wählen kann und wie man sie erstellt s. Kapitel 4 - Audioformate): In unserem
Beispiel soll in einer Qualität von 11025Hz, 16Bit und Stereo aufgenommen werden.
Als nächstes wird eine TargetDataLine erzeugt und geöffnet, die Line wird gestartet und die
Aufnahme kann beginnen. Dies wird in der run - Methode des Threads erledigt. Man durchläuft hier
solange eine Schleife, bis der Nutzer die Return-Taste betätigt und dadurch der boolschen Variable
capture den Wert false zuweist. In dieser Schleife wird die TargetDataLine - Methode
read()mit einem vorher angelegten Bytearray aufgerufen. Dieses Array stellt als erster Parameter
einen Puffer dar. Der zweite Paramter benennt die Position des Arrays, an der begonnen werden soll
es mit gelesenen Audiodaten zu füllen; der dritte Parameter gibt den Index an, bis zu dem dies
durchgeführt werden soll. Dieses Vorgehen entspricht dem bei einem InputStream und sollte daher
dem Leser nicht neu vorkommen. Die read - Methode liefert die Anzahl der beim letzten Aufruf
gelesenen Bytes zurück.
Als
nächsten
Schritt
in
der
while
-Schleife
wird
das
gelesene
Array
ba
in
den
ByteArrayOutputStream baOut geschrieben. An dieser Stelle könnte auch ein anderer Stream
auftreten, zum Beispiel einer, der die Daten auf die Festplatte schreibt.
Dieser read - write - Zyklus wird, wie schon erwähnt, so lange wiederholt, bis der User die Return Taste betätigt. Geschieht dies, wird nach der Schleife die TargetDataLine - Methode drain()
aufgerufen. Damit wird dem System Zeit gegeben, eventuell noch im internen Puffer der
TargetDataLine - Klasse befindliche Audiodaten korrekt auszulesen und zu schreiben (in diesem
Beispiel auf den baOut - Stream). Die Methode blockt solange, bis dies vollständig erledigt ist.
Zuletzt wird die TargetDataLine noch gestoppt und dann geschlossen, damit alle damit
gebundenen Resourcen dem System wieder zur Verfügung gestellt werden können.
Nun wird in das ByteArray caB die bei der Aufnahmen gelesenen Audiodaten mit der
toByteArray() - Methode geschrieben. Das Array caB
ByteArrayInputStream
dient als Eingabe für den
baIn. Dieser wiederum ist der erste Paramter um einen
AudioInputStream zu erstellen. Der zweite Parameter, ist das Audioformat in dem die
Audiodaten vorliegen, welche durch den AudioInputStream gelesen werden sollen. Als dritten
Parameter benötigt der Konstruktor die Grösse der zu lesenden Datei in Frames.
Was ist ein Frame ?
Eine Sounddatei ist nicht nur durch die Länge in Bytes charakterisiert, sondern sie ist auch in eine
Abfolge von sogenannten Frames unterteilt. Ein Frame enthält die Audiodaten für alle Kanäle, die die
__________________________________________________________________________________________
Sound-Programmierung in Java
- 21 -
Datei zu einem bestimmten Zeitpunkt benutzt. Bei den Wave - Dateien entspricht die Anzahl der
Frames, in die die Audiodatei aufgeteilt ist, der Anzahl der Samples pro Sekunde.
Kommen wir jetzt wieder zu unserem Beispielprogramm zurück. Nachdem wir also unseren
AudioInputStream erzeugt haben, fahren wir in der schon bekannten Weise (siehe Kapitel 2 –
Soundgenerierung) fort. Wir erzeugen ein DataLine.info - Objekt und beziehen damit vom
AudioSystem eine SourceDataLine
sl. Was nun folgt ist schon aus dem Beispiel
PlaySoundStreamed aus dem vorhergehenden Kapitel bekannt. In einer while – Schleife, die
ver-lassen wird, wenn das Ende des Streams erreicht wurde (typisch hier der bei read zurückgelieferte
Wert -1), wird mit read auf dem AudioInputStream gelesen und mit write auf der
SourceDataLine geschrieben.
Zum Schluss ...
Wie kann man ... aufgenommene Sounds auf Platte speichern ?
Bei dieser Aufgabenstellung ist die Methode write() der AudioSystem Klasse der Java Sound
API die entsprechende Lösung. Man ruft die Methode mit dem ersten Parameter - einem
AudioInputStream - auf, als zweiten spezifiert man das gewünschte Zielaudioformat der
Soundatei und als dritten und letzten Paramter erwartet die Methode ein File Objekt. Um dieses
Verfahren zu verdeutlichen verändern wir die run - Methode der im vorherigen Abschnitt
besprochenen Applikation CaptureAudio.java wie folgt:
public void run()
{
baOut = new ByteArrayOutputStream();
int numBytesRead = 0;
ba = new byte [64];
// starte die Line zum Aufnehmen des Sounds
tl.start();
// starte Aufnahme
while(capture)
{
//lese SounddateiDaten in Buffer ein
numBytesRead = tl.read(ba,0,ba.length);
__________________________________________________________________________________________
Sound-Programmierung in Java
- 22 -
baOut.write(ba,0,numBytesRead);
}
// blockiere bis die lezten Daten abgespielt wurden
tl.drain();
// halte die Line an und schliesse die Line
tl.stop();
tl.close();
tl=null;
System.out.println("
... Fertig mit Aufnahme");
System.out.println("...STOP");
System.out.println("Schreibe aufgenommenen Sound ...");
caB = baOut.toByteArray();
System.out.println("Anzahl der gecaptureten
Bytes:"+caB.length);
File file = new File("my.wav");
baIn = new ByteArrayInputStream(caB);
stream = new AudioInputStream(baIn,
af,caB.length / af.getFrameSize());
try
{
AudioSystem.write(stream,
AudioFileFormat.Type.WAVE,file);
}catch(IOException e){System.out.println(
"Kann Datei nicht schreiben!");}
System.out.println("
... Fertig!");
System.exit(0);
}
Man sieht, daß hier die write Methode mit dem vorher erzeugten AudioInputStream stream
aufgerufen wird. Das zweite übergebene Objekt AudioFileFormat.Type.WAVE bestimmt, daß
die zu schreibende Datei, im WAVE - Audioformat erstellt werden soll. Näheres zu den
verschiedenen Audioformaten, die vom Java Sound System unterstützt werden, findet man im Kapitel
4 – Audioformate. Als dritter Parameter ist noch das vorher erzeugte File Objekt zu nennen.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 23 -
Soviel also zu den Verfahren, mit denen typischerweise Audioaufzeichnungen mit der Java Sound
API durchgeführt werden. Weitere Informationen findet man natürlich wieder in der Java Sound API
Dokumentation[2] oder dem offiziellen Java Sound API Programmers Guide[3].
Kapitel 4 - Audioformate
__________________________________________________________________________________________
Sound-Programmierung in Java
- 24 -
Dieses Kapitel beschäftigt sich mit den verschiedenen Audioformaten, die von der Java Sound API
unterstützt werden und wie man von einem Format in ein anderes konvertiert.
Doch zunächst müssen wir uns folgende Frage stellen.
Was sind überhaupt formatierte Audiodaten ?
Formatierte Audiodaten beziehen sich auf Sounds, die sich in einem bestimmten Standard Audioformat befinden. Die Java Sound API unterscheidet zwei große Gruppen von formatierten
Audiodaten:
Zum einen die Gruppe der Datenformate und zum andere Seite die Gruppe der Fileformate.
Was sind Datenformate ?
Das Datenformat einer Sounddatei beschreibt, wie die Abfolge der „rohen“ Bytes in einer Sounddatei
zu interpretieren ist. Es ist beispielsweise wichtig zu wissen, aus wie vielen Bits sich ein Sample (die
kleinste Einheit, in die man Audiodaten aufteilen kann) zusammensetzt, oder wie die sogenannte
Samplerate (gibt an, wie schnell auf ein Sample ein nächstes folgt) lautet.
In der Java Sound API wird das Datenformat einer Sounddatei durch ein AudioFormat Objekt
spezifiziert. In diesem Objekt sind folgende Eigenschaften eines Soundformates gespeichert:
 encoding Technik: zu nennen sind hier das PCM - Verfahren (das üblichste) und das
ULAW/ALAW - Verfahren
 Anzahl der Kanäle d.h. wieviele Kanäle benutzt werden: 1 entspricht hierbei Mono und 2
Stereo
 Samplerate: die Anzahl der Samples pro Sekunde und pro benutzten Kanal
 Anzahl der Bits pro Sample: also wieviel Bits benutzt werden, um die Informationen
eines Samples zu speichern
 Framerate
 Grösse eines Frames in Bytes
 Byte – Order: d.h. sind die Audiodaten als little Endian oder big Endian zu lesen
An dieser Stelle soll nun nicht weiter auf die einzelnen Felder der Klasse eingegangen werden. Hierzu
sei auf das Sun Tutorial[3] verwiesen, das die Klassen näher und ausführlicher beschreibt.
Wie kann man ... nun ein geeignetes Audioformat finden ?
__________________________________________________________________________________________
Sound-Programmierung in Java
- 25 -
Diese Frage stellt sich beispielsweise, wenn man in seinem Programm die Möglichkeit einbauen
möchte, daß der User eigene Sounds aufnehmen kann (etwa bei einem selbstprogrammierten Audio Recorder). Wie wir schon bei der CaptureAudio.java Anwendung aus dem vorhergehenden
Kapitel gesehen haben, wird bei der Konstruktion eines AudioInputStreams ein AudioFormat
Objekt benötigt, um zu bestimmen, in welcher Qualität der aufgenommene Sound später vorliegen
soll.
Typischerweise nutzt man folgendes Objekt mit eventuellen Veränderungen:
AudioFormat af = new AudioFormat(11025.0,16,2,true,false);
Wichtig sind hierbei die drei ersten Werte, da sie die Qualität bestimmen, in der später der Sound
vorliegt. Um CD - Qualität zu erlangen würde man 11025.0 (bestimmt die Samplerate) durch den
Wert 44100.0 ersetzen, da dies der Samplerate einer CD entspricht. Der Wert 16 im Konstruktor
bestimmt, daß für ein Sample 16 Bit benutzt werden sollen. Hier könnte man den Wert durch eine 8
ersetzen und somit festlegen, daß eine geringere Qualität verlangt wird, um die Sounddatei möglichst
klein zu halten. Eine weitere Möglichkeit die Dateigröße zu minimieren, wäre, den Wert 2 (bestimmt
eine Stereoqualität) durch eine 1 zu ersetzen und dadurch die Aufnahme nur in Monoqualität
durchzuführen.
Die beiden letzten Werte können auf einem Windows - Rechner so übernommen werden. Für eine
nähere Beschreibung sei auf die Java Sound API Dokumentation[2] oder an den Java Sound
Programmer´s Guide[3] verwiesen.
Wie kann man ... von einem Audiodatenformat in ein anderes konvertieren ?
Grundsätzlich ist eine Konvertierung von Audiodaten zulässig und sogar vorgesehen in der Java
Sound API. Leider mussten wir beim Verfassen dieses Tutorials feststellen, daß ähnlich dem
unfertigen Zustand der Port Objekte, auch die Konvertierung noch nicht endgültig implementiert
wird. In [3] wird zwar beschrieben, wie man eine Konvertierung in eigene Programme einbauen kann,
doch realisieren lässt sie sich so nicht. Es werden bisher nur einige wenige Konvertierungen
unterstützt und der vielleicht wichtigste Aspekt, nämlich das Herunterkonvertieren von einer guten,
speicherplatzzehrenden Formatierung auf ein schlechteres aber dafür weitaus weniger speicherplatzbenötigendes Format kann nicht durchgeführt werden. Mit Hilfe des Java Sound Boards (ein
wichtiger Anlaufpunkt bei Problemen mit der API wie wir festgestellt haben)[4] konnten wir folgende
unterstützten Konvertierungen zusammentragen:
__________________________________________________________________________________________
Sound-Programmierung in Java
- 26 -
Quellformat
Zielformat
16 Bit, PCM signed/unsigned
8 Bit, ULAW/ALAW
8 Bit, ULAW/ALAW
16 Bit, PCM signed/unsigned
PCM signed
PCM unsigned
PCM unsigned
PCM signed
PCM little Endian
PCM big Endian
PCM big Endian
PCM little Endian
Die Tabelle zeigt, welche Werte jeweils bei der Konvertierung von einem Format in ein anderes
verändert werden dürfen. Alle anderen Parameter des Audioformates dürfen nicht verändert werden.,
weshalb eine „Herunterkonvertierung“, wie oben beschrieben, auch nicht möglich ist.
An einem kleinen Beispiel wollen wir nun zeigen wie man generell eine Konvertierung durchführen
kann. Hier der Beispielcode der AudioDataConverter.java Klasse:
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
class AudioDataConverter
{
static AudioFormat sAudioFormat = null;
static AudioFormat tAudioFormat = null;
static AudioInputStream highResStream = null;
static AudioInputStream lowResStream = null;
static File sFile = null;
public static void main (String args[])
{
// pruefe ob Datei angegeben wurde die konvertiert werden sollte
if (args.length == 0)
throw new IllegalArgumentException ("Syntax:
AudioDataConverter <path_to_wav-File>");
// erzeuge FileObject mit Referenz auf Quelldatei
sFile = new File(args[0]);
try
__________________________________________________________________________________________
Sound-Programmierung in Java
- 27 -
{
// erstelle AudioInputStream auf Quelldatei
highResStream = AudioSystem.getAudioInputStream(sFile);
// ermittle AudioFormat der Quelldatei
sAudioFormat = highResStream.getFormat();
// bestimme Werte des Originalformates
AudioFormat.Encoding enc = sAudioFormat.getEncoding();
float sampleRate = sAudioFormat.getSampleRate();
int sampleBits = sAudioFormat.getSampleSizeInBits();
int channels = sAudioFormat.getChannels();
int frameSize = sAudioFormat.getFrameSize();
float frameRate = sAudioFormat.getFrameRate();
boolean endian = sAudioFormat.isBigEndian();
// erstelle gewuenschtes Zielformat
tAudioFormat = new AudioFormat(enc ,sampleRate,
sampleBits, channels, frameSize,
frameRate,(!endian));
// pruefe ob das Soundsystem auf der Maschine die Konvertierung
// unterstuetzt
if(AudioSystem.isConversionSupported(tAudioFormat,
sAudioFormat))
{
// erstelle AudioInputStream auf die Quelldatei mit vorher festgelegtem
//Ziel-AudioDatenFormat
lowResStream
= AudioSystem.getAudioInputStream(tAudioFormat,
highResStream);
AudioSystem.write(lowResStream,
AudioFileFormat.Type.WAV,new File("convert.wav"));
lowResStream.close();
highResStream.close();
}
else
{
System.out.println("Die Konvertierung wird vom
AudioSystem nicht unterstuetzt.");
}
}catch(UnsupportedAudioFileException e){System.out.println("Die
von Ihnen spezifizierte Sounddatei hat ein vom
AudioSystem nicht unterstuetztes Format.");}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 28 -
catch(IOException e){System.out.println("Fehler beim Lesen der
Quelldatei.");}
System.exit(0);
}
}
Unser Programm erwartet beim Aufruf den Pfad zu der zu konvertierenden Audiodatei als
Übergabeparameter. Als nächsten Schritt erzeugen wir einen AudioInputStream auf diese Datei,
genauso als wollten wir sie wiedergeben. Daraufhin wird das Audioformat unserer Quelldatei
bestimmt, in der Variablen sAudioFormat zwischengespeichert sind und dann die einzelnen
Audioformat-parameter bestimmt. Dies ist notwendig, da wir ja nur die in der obigen Tabelle
angegebenen Werte bei der Konvertierung verändern dürfen und alle anderen unberührt bleiben
müssen. Unser Programm soll hierbei den ausgelesen Zustand des bigEndian - Parameters der
Quelldatei invertieren, d.h. eine Datei, die vor dem Aufruf im bigEndian - Format geschrieben wurde,
wird durch unser Programm in das littleEndian - Format konvertiert und als „convert.wav“
geschrieben.
Nachdem wir die Parameter der Quelldatei ausgelesen haben, erzeugen wir unser Zielformat
tAudioFormat, bei dem alle Parameter bis auf den invertierten bigEndian - Wert gleich bleiben.
Wenn wir also Quell - und Zielaudioformat bestimmt sind, wird das AudioSystem befragt, ob diese
Konvertierung überhaupt von dem System unterstützt wird. Die geschieht mit dem Aufruf von
boolean AudioSystem.isConversionSupported(Zielformat,
Quellformat);
Diese Methode ist sehr wichtig bei der Konvertierung, da man damit vorher prüfen kann, ob die
Konvertierungsart überhaupt vom System unterstützt wird.
Um die Zieldatei zu schreiben und die Konvertierung durchzuführen, wird die schon bekannte
write() - Methode der AudioSystem - Klasse benutzt
int AudioSystem.write(AudioInputStream,Zieldatenformat,
Zielfile - Objekt);
Nachdem wir uns also mit den (bisher noch unzulänglichen) Möglichkeiten der Audiodatenkonvertierung beschäftigt haben, wollen wir uns nun den Audiofileformaten zuwenden, die von der
Java Sound API unterstützt werden.
Was sind Audiofileformate ?
__________________________________________________________________________________________
Sound-Programmierung in Java
- 29 -
Audiofileformate spezifizieren, wie die rohen Audiodaten in einem Soundfile angeordnet sind. In der
Java Sound API sind die verschiedenen Audioformate durch die Klasse AudioFormat repräsentiert,
und ein Audiofile durch die Klasse AudioFileFormat. Die API unterstützt folgende Standardaudioformate, definiert als AudioFileFormat.Type.X - Objekte, wobei X für
 AIFC
 AIFF
 SND
 AU
 WAVE
steht.
Die Java Sound API unterstützt die Konvertierung zwischen oben genannten Standardaudioformaten.
Wie kann man ... zwischen verschiedenen Audiofileformaten konvertieren ?
Die Konvertierung zwischen verschiedenen Audiofileformaten erweist sich als einfacher und vor
allem als vollständig in die API implementiert. Das Vorgehen bei dieser Aufgabe wollen wir uns an
der entscheidenden Methode des Beispielprogramms FileConverter.java, dessen kompletten
Quellcode in Anhang A zu finden ist, ansehen. Hierbei handelt es sich um die Methode write():
private static void write (AudioInputStream in,AudioFileFormat.Type
fileType,File file)
{
System.out.println("Konvertiere Quelldatei nach
<"+file.getName()+">");
int
writtenBytes = 0;
try
{
writtenBytes = AudioSystem.write(in, fileType, file);
}
catch(IllegalArgumentException e){System.out.println("Das von
Ihnen gewaehlte Soundformat wird nicht unterstuetzt!");
System.exit(0);}
catch (IOException e){System.out.println("Fehler beim Schreiben
in Zieldatei!");System.exit(1);}
System.out.println("... Konvertierung erfolgreich beendet");
System.out.println("\nGeschrieben : "+writtenBytes+" Bytes als
__________________________________________________________________________________________
Sound-Programmierung in Java
- 30 -
"+fileType.getExtension());
}
Die Methode erwartet einen - bereits auf die Quelldatei geöffneten - AudioInputStream, das
Zielformat, in welches die Quelldatei konvertiert werden soll und schliesslich ein File Objekt, daß
die Zieldatei spezifiziert. Die eigentliche Formatkonvertierung führt die uns schon bekannte
write() - Methode der AudioSystem Klasse durch.
Zum Abschluss noch die neu hinzugekommenen Methoden und die von ihnen erzeugten Exceptions:
Methode
AudioSystem.write( )
erzeugte Exception
IllegalArgumentException,
IOException
Kapitel 5 - Kontrollen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 31 -
Dieses Kapitel soll sich mit den noch verbleibenden Aspekten des Paketes javax.sound.sampled
beschäftigen.
Erwähnenswert sind beispielsweise noch die sogenannten Control Objekte der API. Sie dienen
dazu, etwa die Lautstärke der Wiedergabe zu regeln, oder die Balance zwischen rechtem und linken
Kanal zu verändern. Es gibt vier verschiedene Subklassen der Control Objekte, die man in seinen
Programmen nutzen kann:
 BooleanControl - repräsentiert Kontrollen, die durch 2 verschiedene Zustände charakterisiert
werden können; Beispiel: Stummschalten einer Line an/aus
 FloatControl - erlaubt Kontrolle über Objekte, die mit Fließkommazahlen arbeiten; Beispiel:
Lautstärke der Wiedergabe eines Sounds
 EnumControl - wird benutzt, wenn man die Wahl zwischen verschiedenen Kontrollobjekten
implementieren möchte; Beispiel: User hat die Wahl zwischen verschiedenen festgelegten
Lautstärkeeinstellungen
 CompoundControl - ermöglicht den gleichzeitigen Zugriff auf Control Objekte, die zu einer
Gruppe zusammengefaßt sind; Beispiel: ein Equalizer
In diesem Tutorial beschränken wir uns darauf, eine kurze Einführung in die FloatControl
Objekte zu geben, da sie uns in der Praxis am wichtigsten erscheinen. Für eine weiterführende
Beschreibung der anderen Klassen, sei der Leser auf [2] und [3] verwiesen.
Die von der Java Sound API zur Verfügung gestellten FloatControl Objekte werden durch
folgende FloatControl.Type.X Objekte repräsentiert:
X steht für:
 AUX_RETURN
 AUX_SEND
 BALANCE
 MASTER_GAIN
 PAN
 REVERB_RETURN
 REVERB_SEND
 SAMPLE_RATE
 VOLUME
__________________________________________________________________________________________
Sound-Programmierung in Java
- 32 -
Möchte man dem User in einem Programm z.B. die Möglichkeit geben, die Lautstärke, in der eine
Sounddatei wiedergegeben wird, selber zu regeln, geschieht dies darin, sich ein FloatControl Objekt zu generieren, das vom Typ FloatControl.Type.MASTER_GAIN ist.
Damit kommen wir zu der Frage ...
Wie kann man ... auf FloatControl Objekte erzeugen ?
Zunächst einmal ist die Frage zu klären, welches FloatControl Objekt man überhaupt benutzen
möchte. In unserem folgenden Beispiel soll die Lautstärke geregelt werden, in der die Soundausgabe
erfolgt. Wir benötigen also ein Objekt vom Typ FloatControl.Type.MASTER_GAIN.
Als ersten Schritt müssen wir prüfen, ob wir auf der Line, auf der wir die Ausgabe durchführen
wollen, überhaupt die Möglichkeit haben, das von uns gewünschte FloatControl Objekt zu
nutzen.
Hierzu
rufen
wir
die
Line
-
Methode
isControlSupported(FloatControl.Type.X) auf. Sie gibt einen boolschen Wert zurück,
an dem wir erkennen können, ob unser gewünschtes Control - Objekt verfügbar ist. Schauen wir
uns diesen Schritt an einem beispielhaften Auszug aus der Klasse PlayBack.java an (das
vollständige Programm AudioPlayer und seine Klassen steht in Anhang A):
private void createClip()throws LineUnavailableException
{
DataLine.Info info = new DataLine.Info(Clip.class,
audioFormat);
clip = (Clip) AudioSystem.getLine(info);
if (clip.isControlSupported
(FloatControl.Type.MASTER_GAIN))
{
clipGainControl = (FloatControl)
clip.getControl(FloatControl.Type.MASTER_GAIN);
}
if (clip.isControlSupported(FloatControl.Type.PAN))
{
clipPanControl = (FloatControl)
clip.getControl(FloatControl.Type.PAN);
}
}
In dieser Methode createClip() wird zunächst ein Clip Objekt als Line erzeugt auf der die
Soundausgabe erfolgen soll. Im nächsten Schritt wird geprüft, ob die Line eine Lautstärkeregelung
__________________________________________________________________________________________
Sound-Programmierung in Java
- 33 -
zulässt. Hierzu wird die Methode isControlSupported() benutzt. Wenn sie ein true
zurückliefert, wird das gewünschte Control Objekt erzeugt, in dem man die Line - Methode
getControl(X) aufgerufen wird. Wichtig hierbei ist der vorgestellte Cast - Operator, der das
zurückgelieferte Control - Objekt, zu einem FloatControl - Objekt castet.
Zum Schluss wird versucht, Zugriff auf die Balance - Steuerung zwischen rechtem und linkem Kanal
zu erhalten, in dem ein Control Objekt vom Typ FloatControl.Type.PAN erzeugt wird.
Nachdem wir nun also gesehen haben, wie wir Control Objekte erzeugen können, die die von uns
gewünschten Aufgaben erfüllen bleibt noch ...
Wie kann man ... Werte bei FloatControl Objekten verändern ?
Kommen wir noch einmal auf unser Beispiel zurück, bei dem der User eines Programmes selbst
bestimmen können soll, wie laut oder leise die Wiedergabe einer Sounddatei erfolgen soll. Die
Eingabe des neuen Wertes könnte beispielsweise über Swing Elemente ( zum Beispiel über ein
Slider Objekt) erfolgen. Dieser Ansatz wird in dem Beispielprogramm AudioPlayer, das im
Anhang A zu finden ist, durchgeführt. Über ein Slider Objekt kann der User die Lautstärke und die
Balance zwischen linkem und rechtem Kanal beeinflussen. Der gewünschte Wert muss nun über ein
FloatControl Objekt auf die benutzte Line angewendet werden. Im vorherigen Abschnitt haben
wir ja schon gezeigt, wie wir bei dem AudioPlayer Programm, die FloatControl Elemente
erzeugen.
Nehmen wir beispielsweise das PAN Objekt. Um hierbei die gewünschte Balance einzustellen, wird
die setValue() - Methode der FloatControl Klasse benutzt. Dies geschieht wie folgt:
void [FloatControl-Objekt].setValue(float neuerWert);
Der so gesetzte neue Wert, wird sofort auf die benutzte Line angewendet.
In unserem Beispielprogramm sieht dies wie folgt aus:
clipPanControl.setValue(fPan);
Zum Schluss seien noch folgende FloatControl - Methoden genannt, die bei der Arbeit mit den
Control Objekten nützlich sein können,:
float [FloatControl-Objekt].getMaximum();
- gibt den maximalen Wert zurück, den das Element annehmen kann
float [FloatControl-Objekt].getMinimum();
- gibt den minimalsten Wert zurück, den das Element annehmen kann
float [FloatControl-Objekt].getValue();
- gibt den aktuellen Wert des Elementes an
Zum Ende dieses Kapitels sei noch eine letzte nützliche Klasse genannt, die sogenannten
LineListener. Mit Hilfe dieser Klasse kann der Status einer Line - also der Status eines Clips,
__________________________________________________________________________________________
Sound-Programmierung in Java
- 34 -
einer Source- bzw. TargetDataLine oder eines Ports - überwacht werden und kann daher auch zu den
Kontrollen gezählt werden, die von der Java Sound API zur Verfügung gestellt werden.
Line - Objekte erzeugen sogenannte LineEvent - Objekte, wenn sie sich in einem bestimmten
Zustand befinden. Diese Zustände sind START, STOP, OPEN, CLOSE. Wird zum Beispiel die
Wiedergabe eines Sounds gestartet, so wird ein Start - LineEvent - Objekt erzeugt und an die
LineListener übergeben, die an die gestartete Line gebunden sind.
Wie kann man ... LineListener benutzen ?
Man geht hierbei ähnlich vor, wie bei AWT - Events. Typischerweise benutzt man eine innere Klasse.
Schauen wir uns folgendes Beispiel an:
clip.addLineListener(new LineListener () {
public void update (LineEvent e){
if (e.getType() == LineEvent.Type.STOP)
{
//schliesse den Clip und gib somit die gebundenen Resourcen wieder frei
clip.close();
}
if(e.getType() == LineEvent.Type.CLOSE)
{
//beende Programm
System.exit(0);
}
}});
In diesem Beispiel wird ein LineListener an ein Line Objekt clip gebunden, um dessen Status
zu überwachen. Hierbei sollen STOP und CLOSE Zustände behandelt werden.
Man implementiert also eine innere Klasse LineListener und schreibt die Methode update().
Diese Methode wird immer dann aufgerufen, wenn das Line Objekt, das an diesen LineListener
gebunden ist, ein LineEvent Objekt erzeugt. Das erzeugte Objekt wird anschließend der update Methode übergeben. Um festzustellen, um welchen Event es sich handelt, wird die LineEvent
Methode getType() benutzt.
Der gelieferte Wert wird dann mit den folgenden möglichen LineEvent.Type Objekten
verglichen:
__________________________________________________________________________________________
Sound-Programmierung in Java
- 35 -
 LineEvent.Type.START
 LineEvent.Type.STOP
 LineEvent.Type.OPEN
 LineEvent.Type.CLOSE
und es wird entsprechend reagiert.
Zum Ende des Kapitels, wieder alle neu hinzugekommenen Methoden, die die Möglichkeit haben,
Exceptions zu erzeugen:
Methode
[Line – Objekt].getControl()
[FloatControl – Objekt].setValue( )
erzeugte Exception
IllegalArgumentException
IllegalArgumentException
__________________________________________________________________________________________
Sound-Programmierung in Java
- 36 -
TEIL II
javax.sound.midi
Kapitel 6 - MIDI - Wiedergabe
__________________________________________________________________________________________
Sound-Programmierung in Java
- 37 -
Dieses Kapitel beschäftigt sich mit der Wiedergabe von Midi - Audiodateien mit Hilfe der Java
Sound API. Es werden einige Midi - spezifische Audrücke benutzt werden müssen, die in diesem
Tutorial nicht ausführlich besprochen werden können. Für alle, die mehr über die Midi - Spezifikation
erfahren wollen, sei die offizielle Midi – Seite, die man unter [5] findet, empfohlen.
Kommen wir nun zu unserer ersten Frage - bzw. Problemstellung.
Wie kann mann ... mit der Java Sound API Midi - Dateien wiedergeben ?
Die wichtigste Klasse im Paket javax.sound.midi ist die sogenannte statische Klasse MidiSystem.
Sie kann mit der statischen Klasse AudioSystem aus dem sampled - Paket der Java Sound API
verglichen werden. Das MidiSystem bzw. die Klasse erstellt die beiden wichtigsten Klassen, die
bei der Wiedergabe von Mididateien eine Rolle spielen, nämlich einmal die Sequencer Klasse und
zum anderen die Synthesizer Klasse.
Ein Sequencer kann man sich als Steuerung für den Synthesizer vorstellen. Er „liest“ die
Noten aus einer Midi - Datei aus und spielt sie auf dem richtigen Instrument auf dem Synthesizer ab.
Daher liegt es nahe, als ersten Schritt bei der Erstellung eines Programms, das Midi - Dateien
bearbeiten soll, ein Sequencer und Synthesizer Objekt zu erzeugen. Dies geschieht über die
MidiSystem - Methoden getSequencer() und getSynthesizer() wie folgt:
Sequencer sequencer = MidiSystem.getSequencer();
Synthesizer synthesizer = MidiSystem.getSynthesizer();
Als nächsten Schritt öffnet man den Sequencer und den Synthesizer mit einfachen open() - Methoden:
sequencer.open();
synthesizer.open();
Somit werden die beiden Objekte exklusiv für diese Anwendung reserviert und kein anderes
Programm kann sie benutzen.
Um nun eine Verbindung zwischen dem Synthesizer und dem Sequencer herzustellen und damit die
Wiedergabe zu ermöglichen, werden sogenannte Receiver und Transmitter Objekte aus der
API benötigt. Ein Transmitter wird immer mit einem Receiver verbunden damit der Receiver die vom
Transmitter ausgesendeten Daten empfängt und an seine Klasse weitergibt, welche die Daten dann
verarbeitet. In unserem Beispiel wird für den Sequencer ein Transmitter benötigt (da der
Sequencer ja später den Synthesizer steuern soll) und für den Synthesizer ein Receiver Objekt,
damit dieses die Daten vom Sequencer aufnimmt und an ihn weitergibt:
Transmitter trans = sequencer.getTransmitter();
Receiver receive = synthesizer.getReceiver();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 38 -
Im nächsten Schritt soll dem erstellten Transmitter Objekt ein Receiver Objekt zugewiesen, so
daß dieses später den Empfänger der „auszusendenden“ Daten kennt. Dies wird durch den Aufruf der
Transmitter Methode setReceiver() gewährleistet und wie folgt in die Praxis umgesetzt:
trans.setReceiver(receive);
Nach solchen vorbereitenden Massnahmen müssen wir uns vor der Wiedergabe der Datei noch mit
dieser selbst auseinandersetzen. Nachdem wir ein File Objekt erzeugt haben, das sich auf die
wiederzugebende Midi - Datei bezieht, muss damit ein sogenanntes Sequence Objekt erzeugt werden.
In der Java Sound API werden die Midi - Informationen einer Midi - Datei durch Sequence Objekte
dargestellt. Wir verfahren also nun weiter, indem wir
File file = new File (Pfad zu einer Midi - Datei);
Sequence sequence = new Sequence (file);
Objekte erzeugen. Der letzte Schritt, den wir vor dem Abspielen der Midi - Datei file durchführen
müssen, ist, die erstellte sequence dem Sequencer zu übergeben, damit dieser die sequence
abarbeiten kann und dadurch die Midi - Datei auf dem Synthesizer abgespielt wird. Folgende
Anweisung übernimmt diese Aufgabe:
sequencer.setSequence(sequence);
Um die Wiedergabe der Midi - Datei file zu starten, ist nur noch eine start - Anweisung nötig und
der Sequencer beginnt die Musikwiedergabe auf dem Synthesizer:
sequencer.start();
Diese Vorgehensweise wollen wir uns nun an einem konkreten Beispiel ansehen, nämlich an dem
Programm PlayMidi.java:
import javax.sound.midi.*;
import java.io.IOException;
import java.io.File;
class PlayMidi
{
static PlayMidi playMidi = null;
static Synthesizer synth = null;
static Sequencer sequencer = null;
static Receiver synthReceiver = null;
static Transmitter seqTransmitter =null;
public static void main(String args[])
{
if (args.length == 0)
throw new IllegalArgumentException ("Syntax: java
__________________________________________________________________________________________
Sound-Programmierung in Java
- 39 -
PlayMidi <midifile>");
try
{
//erzeuge Sequencer und öffne diesen
sequencer = MidiSystem.getSequencer();
sequencer.open();
//erzeuge Transmitter
seqTransmitter = sequencer.getTransmitter();
//erzeuge Synthesizer und öffne diesen
synth = MidiSystem.getSynthesizer();
synth.open();
//erzeuge Receiver, der mit dem Transmitter verbunden wird, um Daten//uebertragung zwischen Synthesizer und Sequenzer zu erlauben
synthReceiver = synth.getReceiver();
seqTransmitter.setReceiver(synthReceiver);
//erzeuge File Objekt auf uebergebenen Midi-Datei-Pfad
File midiFile = new File(args[0]);
//erzeuge Sequence mit der Midi-Datei
Sequence sequence = MidiSystem.getSequence(midiFile);
//uebergib dem Sequencer die Sequence, die er abspielen soll
sequencer.setSequence(sequence);
//lass Sequencer von MetaL. ueberwachen, um festzustellen, wann
//Ende der Midi-Datei bzw. Sequence erreicht
sequencer.addMetaEventListener(new MetaEventListener()
{
public void meta(MetaMessage event)
{
if (event.getType() == 47)
{
sequencer.stop();
//gib dem Synthesizer Zeit, die letzte Note klingen zu lassen
try
{
Thread.sleep(1000);
}catch(InterruptedException e){}
sequencer.close();
synthesizer.close();
System.exit(0);
}
}});
}catch(MidiUnavailableException e)
{System.out.println("Kein MidiGeraet verfuegbar.");
System.exit(0);}
catch(InvalidMidiDataException e){System.out.println("Die Datei
__________________________________________________________________________________________
Sound-Programmierung in Java
- 40 -
"+args[0]+" enthaelt keine gueltigen MidiDaten.");
System.exit(0);}
catch(IOException e){System.out.println("Konnte Datei
"+args[0]+" nicht oeffnen."); System.exit(1);}
System.out.println("Spiele Midi-Datei ...");
System.out.println("Druecke <RETURN> fuer STOP");
//starte den Sequencer und somit die Wiedergabe
sequencer.start();
//warte bis Nutzer Return drueckt oder Ende der Midi-Datei erreicht wird
try
{
int c = System.in.read();
}catch(IOException e){System.exit(1);}
sequencer.stop();
System.out.println("...STOP");
//gib die gebundenen Resourcen wieder frei
sequencer.close();
synthesizer.close();
System.exit(0);
}
}
In diesem Beispielcode findet man alle oben beschriebenen Schritte wieder. Wichtigste Neuerung ist
hier die Einbindung einer sogenannten MetaEventListener Klasse. Vergleichbar ist diese Klasse
mit der LineListener Klasse aus dem sampled - Paket in Teil II dieses Tutorials. Sie dient
ebenfalls zur Überwachung der Wiedergabe einer Datei. Der Unterschied besteht allerdings darin, daß
keine LineEvent Objekte erzeugt werden, wenn bestimmte Situationen während der Wiedergabe
auftreten, sondern sogenannte MetaEvent Objekte. Diese MetaEvents können von einem
Sequencer Objekt interpretiert werden. MetaEvent Objekten sind bestimmte Nummern nach der
Midi - Spezifikation zugeordnet. In unserem Beispiel wird ein Event mit der Nummer 47 abgefragt.
Dies bedeutet Ende der Sequence und der Sequencer selbst kann durch eine stop() - Anweisung
beendet werden. Nach der stop() - Anweisung sollte dem Synthesizer noch eine gewisse
Zeitspanne gegeben werden, um die letzte Note auch vollkommen verklingen zu lassen. Stoppt man
einfach den Sequencer und beendet das Programm, wirkt die Wiedergabe der letzten Note unnatürlich
abgehakt.
Daher in unserem Beispiel die
Thread.sleep(1000);
Anweisung damit das Programm nach einer Wartezeit von einer Sekunde beendet wird.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 41 -
Wenn die Geräte Sequencer und Synthesizer nicht mehr benötigt werden, sollten ihre Resourcen,
durch einfache close() - Anweisungen dem System wieder zur Verfügung gestellt werden, damit
andere Programme nun auch diese Geräte nutzen können:
sequencer.close();
synthesizer.close();
Mit Hilfe der Java Sound API ist es darüber hinaus möglich, nicht nur den eingebauten Synthesizer zu
benutzen, sondern auch andere, an das System angeschlossene, Midi - Geräte. Die führt zu der Frage
...
Wie kann man ... andere Midi - Geräte ansprechen ?
Um herauszufinden, welche Midi - Geräte überhaupt in einem System zur Verfügung stehen, kann die
statische Klasse MidiSystem befragt werden. Sie kann eine Liste mit Informationen über alle
verfügbaren Geräte erstellen, auf die man über die Java Sound API Zugriff hat. Informationen über
die Eigenschaften eines Midi - Gerätes, werden in sogenannten MidiDevice.info Objekten
gespeichert.
Dies geschieht wie folgt:
MidiDevice.info info[] = MidiSystem.getMidiDeviceInfo();
Im Array info sind nun Informationen über alle verfügbaren Geräte gespeichert. Jedem Gerät ist ein
MidiDevice.info Objekt zugeordnet.
Um an ein bestimmtes Midi - Gerät zu gelangen, muss ein MidiDevice - Objekt erzeugt werden.
Dies geschieht, indem man dem MidiSystem ein MidiDevice.info Objekt übergibt, das die
Informationen des gewünschten Objektes enthält.
Nehmen wir an, in unserem Array info hätten wir unter Index 2 unser gesuchtes Midi - Gerät
gefunden. Um
Zugriff auf dieses Gerät zu erhalten, fragen wir beim MidiSystem an. Dies
geschieht mit folgender Anweisung:
MidiDevice unserDevice = MidiSystem.getMidiDevice(info[2]);
Wenn wir Zugriff auf dieses Gerät haben, müssen wir wieder - wie schon im letzten Abschnitt - eine
open() Anweisung benutzen, um das Gerät exklusiv für unsere Anwendung zu reservieren:
unserDevice.open();
Um nun überhaupt mit dem Gerät arbeiten zu können, benötigen wir von ihm entweder ein
Receiver Objekt oder ein Transmitter Objekt. Dies geschieht wiederum mit den schon
bekannten Methoden
getReceiver() oder getTransmitter(). Nehmen wir einfach an, daß unser Midi - Gerät
unserDevice eine Referenz auf einen Synthesizer darstellt. Dann benötigen wir natürlich ein
__________________________________________________________________________________________
Sound-Programmierung in Java
- 42 -
Receiver - Objekt von ihm, damit ein anderer Transmitter Daten an unseren Receiver senden
kann. Dies erfolgt wie im vorangegangenen Abschnitt:
Receiver unserReceiver = unserDevice.getReceiver();
Sollten wir unser Gerät nicht mehr weiter benötigen, geben wir die Resourcen wieder durch eine
close() - Anweisung frei:
unserDevice.close();
Dies waren die entscheidenden Schritte, wie man das Midi - System nutzt, um Midi - Geräte von ihm
anzufordern und auf einfache Weise eine Midi - Datei auf diesen wiederzugeben. Im nächsten Kapitel
beschäftigen wir uns mit der Dateienwiedergabe, sondern damit selbst einzelne Noten auf einem Midi
- Gerät wiederzugeben.
Zum Schluss, in einer Tabelle zusammengefaßt, alle neuen Methoden, die die Möglichkeit haben,
Java Exceptions auszulösen:
Methode
MidiSystem.getSequencer( )
MidiSystem.getSequencer( )
[MidiDevice – Objekt].open( )
[MidiDevice – Objekt].getTransmitter ( )
MidiDevice – Objekt].getReceiver ( )
Sequence – Konstruktor
[Sequencer – Objekt].setSequence( )
MidiSystem.getMidiDevice( )
erzeugte Exception
MidiUnavailableException
MidiUnavailableException
MidiUnavailableException
MidiUnavailableException
MidiUnavailableException
InvalidMidiDataException
InvalidMidiDataException,
IOException
MidiUnavailableException,
IllegalArgumentException
Kapitel 7 - Tonwiedergabe
__________________________________________________________________________________________
Sound-Programmierung in Java
- 43 -
Dieses Kapitel beschäftigt sich mit der Wiedergabe einzelner Töne bzw. Noten auf Midi - Geräten.
Eine praktische Anwendung hierfür wäre ein virtuelles Keyboard. Der User könnte z.B. , über eine
Swing - Anwendung, die ein Keyboard zeichnet, auf diesem spielen. Die Problemstellung wäre also
zu erkennen, welche Noten gespielt werden sollen, und wie man das Midi - System dazu veranlasst,
diese auch wiederzugeben. Die erste Frage soll hier nicht weiter beantwortet werden, da es sich dabei
um ein Swing bzw. AWT - Problem handelt. Wir kommen daher zu ...
Wie kann man ... einzelne Noten wiedergeben mit dem Midi - System ?
Zunächst fordert man ein Synthesizer Objekt bei dem MidiSystem an, oder man erzeugt sich
ein MidiDevice wie im vorigen Kapitel beschrieben. Der Einfachheit halber, werden wir hier nur
ein Synthesizer vom MidiSystem beziehen.
Synthesizer synthesizer = MidiSystem.getSynthesizer();
Haben wir dies erledigt, müssen wir noch den Synthesizer für unsere Anwendung reservieren, indem
wir die open() - Methode benutzen.
Nun kommt ein uns neues Java Sound API Objekt, das sogenannte MidiChannel Objekt, hinzu.
Nach der MIDI - Spezifikation kann ein Synthesizer verschiedene Kanäle - sogenannte Channels besitzen, auf denen er Töne generiert bzw. wiedergibt. Ein MidiChannel Objekt ist also die
Umsetzung der Spezifikation in die API Umgebung. Um an die von unserem Synthesizer
unterstützten Kanäle zu gelangen, müssen wir folgenden Schritt in unser Programm implementieren:
MidiChannel channel[] = synthesizer.getChannels();
Mit Hilfe der Methode
[Synthesizer-Objekt].getChannels();
erhält man ein Array von MidiChannel Objekten, die die verfügbaren Kanäle des Synthesizers
repräsentieren.
Um eine Note wiederzugeben, müssen wir den Kanal, auf dem die Note beim Synthesizer gespielt
werden soll, angeben, welche Note gespielt und wie stark diese Note „angeschlagen“ werden soll. Das
„Anschlagen“ muß man sich wie bei einem realen Klavier vorstellen. Je stärker die einzelne
Klaviertaste gespielt wird, desto lauter erklingt der entsprechende Ton. In der Java Sound API wird
dies als Velocity bezeichnet
In unserem Programm sieht dieses Vorgehen wie folgt aus:
channels[0].noteOn(zu spielende note, velocity);
__________________________________________________________________________________________
Sound-Programmierung in Java
- 44 -
Die zu spielende Note muss als Integer - Wert zwischen 0 und 127 angegeben werden. Dies richtet
sich nach der schon erwähnten Midi - Spezifikation. Für eine genaue Auflistung der einzelnen Noten
und deren entsprechenden Integer - Werte sei auf [5] verwiesen.
Ebenso wie die zu spielende Note, wird der Velocity - Wert als Integer übergeben.
Als Gegenstück zum noteOn - Befehl gibt es noch den noteOff - Befehl, den man einem Synthesizer Objekt geben kann:
channels[0].noteOff(zu spielende Note);
Dieser Befehl signalisiert dem Synthesizer, das die „virtuelle“ Taste nicht mehr gedrückt ist, und er
entsprechend reagieren soll.
An einem einfachen Beispielprogramm, wollen wir uns nun diese Schritte in der Praxis ansehen. Bei
dem Demo handelt es sich um die PlayMidiSelf.java Applikation:
import
javax.sound.midi.*;
public class PlayMidiSelf
{
static int note = 0;
//nimmt zu spielende Note auf
static int velocity = 0;
//nimmt Tastenanschlagstaerke auf
static int duration = 0;
//nimmt Zeit auf, fuer die die Taste
//gedrueckt bleiben soll
static int instrument = 0;//nimmt die Nummer des Instrumentes auf,
//auf dem Note gespielt werden soll
static Synthesizer synth = null;
static MidiChannel[]channels = null;
static Soundbank sb = null;//Referenz auf die benutzte Soundbank
static Instrument inst [] = null;//Liste der unterstuetzten
//Instrumente durch die Soundbank
public static void main(String[] args)
{
if (args.length < 4)
throw new IllegalArgumentException("Syntax: java PlayMidiSelf
<note number> <velocity> <duration> <instrument>");
try
{
synth = MidiSystem.getSynthesizer();
}
catch (MidiUnavailableException e)
{
}
try
__________________________________________________________________________________________
Sound-Programmierung in Java
- 45 -
{
synth.open();
}
catch (MidiUnavailableException e)
{
e.printStackTrace();
System.exit(1);
}
//bestimme die verfuegbaren Midi-Kanaele
channels = synth.getChannels();
//bestimme die Soundbank die der Synthesizer benutzt
sb = synth.getDefaultSoundbank();
//bestimme welche Instrumente die Soundbank zur Verfuegung stellt
inst = sb.getInstruments();
//passe die ueber Konsole gelesenen Werte an
note = Integer.parseInt(args[0]);
note = Math.min(127, Math.max(0, note));
velocity = Integer.parseInt(args[1]);
velocity = Math.min(127, Math.max(0, velocity));
duration = Integer.parseInt(args[2]);
duration = Math.max(0, duration);
instrument = Integer.parseInt(args[3]);
instrument = Math.max(0,Math.min(instrument,inst.length));
System.out.println("Spiele auf Instrument Nr. "+instrument+"
mit der Bezeichnung :"+inst[instrument-1].getName());
//wechsle auf das gewuenschte Instrument
channels[0].programChange(instrument);
//spiele die gewuenschte Note
channels[0].noteOn(note, velocity);
//halte die Note ueber die gewuenschte Zeit
try
{
Thread.sleep(duration);
}
catch (InterruptedException e)
{
}
//signalisiere, das Taste nicht mehr gedrueckt
channels[0].noteOff(note);
//gib Synthesizer genug Zeit die Note ausklingen zu lassen bevor
/Programm beendet wird
try
__________________________________________________________________________________________
Sound-Programmierung in Java
- 46 -
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
}
System.exit(0);
}
}
Das Programm verlangt vom Nutzer vor dem Start auf Konsolenebene als Programmparameter eine
Note, die es spielen soll, die Velocity, mit der die Note angespielt wird und wie lange die „virtuelle“
Taste auf dem Synthesizer gehalten werden soll (die duration). Es ist möglich, dem Synthesizer
vorzuschreiben, auf welchem virtuellen Instrument er die Note spielen soll. Dieses Instrument kann
aus einer Liste, der sogenannten Soundbank, ausgewählt werden; die gewählte Nummer stellt den
vierten Parameter dar. Es ist möglich, mit der API eine andere Soundbank in einen Synthesizer zu
laden oder nur einzelne Instrumente oder Geräuschefekte aus einer bereits im Synthesizer
befindlichen Bank auszutauschen. Diese Punkte gehen aber weit über den knappen Rahmen dieses
Tutorials hinaus. Von daher sei auf [2] oder [3] verwiesen.
Im weiteren Verlauf benutzen wir, daher nur die bereits im Synthesizer befindliche Soundbank und
deren Instrumente und Geräuschefekte. Nun aber wieder zu unserem Programm und dessen Aufbau.
Zu Beginn wird ein Synthesizer Objekt vom MidiSystem angefordert auf dem später dann, die
vom User bestimmte Note gespielt wird. Dann wird dieses Synthesizer Objekt durch eine open() Anweisung für unsere Anwendung reserviert und anschließend bestimmt, welche Kanäle zur
Verfügung gestellt werden. Als neuen Schritt finden wir die getDefaultSoundbank() Anweisung, die das Format
Soundbank unsereSB = [Synthesizer-Objekt].getDefaultSoundbank();
hat. Mit Hilfe dieser Methode bestimmen wir die aktuell im Synthesizer befindliche Soundbank. Mit
der Soundbank unsereSB haben wir nun die Möglichkeit, uns eine Liste von den Instrumenten
erstellen zu lassen, die momentan in unserem Synthesizer verfügbar sind. Die Instrumente einer
Soundbank werden in der Java Sound API durch die Instrument Objekte repräsentiert.
Wir erstellen also nun durch
inst = sb.getInstruments();
ein Array von Instrument Objekten mit den Namen inst.
In den darauffolgenden Zeilen werden die von der Konsolenebene übergebenen Parameter in ein
bestimmtes Format gebracht.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 47 -
Wenn dies erfolgt ist, wird der Syntesizer angewiesen, auf dem Kanal, über den später die Note
gespielt werden soll, auf das vom Nutzer gewünschte Instrument zu wechseln:
channels[0].programChange(instrument);
Man benutzt also die programChange Methode eines MidiChannel Objektes, die folgendes Format
hat:
[MidiChannel - Objekt].programChange(int-Wert der Nummer von
gewünschten Instrument entspricht);
Hat man das Programm des Synthesizers auf das gewünschte Instrument umgestellt, erfolgen die
schon besprochenen noteOn() und noteOff() - Anweisungen an den Synthesizer, die das
Anspielen der Note auslösen.
An diesem kleinen Beispiel lässt sich erkennen, wie eine größere Applikation aussehen könnte, die
ein Midi - Keyboard simuliert, auf dem gespielt und zwischen verschiedenen Instrumenten gewählt
werden kann.
Kapitel 8 - Aufzeichnung von Midi-Informationen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 48 -
Dieses Kapitel beschäftigt sich mit der Aufnahme von Midistücken und dem Sichern des Stückes
auf der Festplatte.
Wie kann man ... eigene Midistücke als eigenen Miditrack aufzeichnen ?
Wir wollen im folgenden zeigen, wie man eine eigene Sequence aufzeichnet, die dann von jedem
anderen Midi - Gerät abgespielt werden kann. Als einleitenden Schritt fordern wir beim
MidiSystem zunächst ein Sequencer Objekt an. Mit einer getReceiver() Anweisung
erzeugen wir ein Receiver Objekt, das unsere Midi – Daten, die aufgezeichnet werden sollen, an
den Sequencer weitergibt. Wie man vermuten kann, ist der Sequencer bei dieser Problemstellung also
unsere zentrale Klasse und spielt die wichtigste Rolle bei der Aufzeichnung einer Sequence. Bis jetzt
haben wir also folgende Schritte in unserem Programm abzuarbeiten:
Sequencer sequencer = MidiSystem.getSequencer();
Receiver seqReceiver = sequencer.getReceiver();
Nach diesen - für uns nicht neuen - Schritten, müssen wir nun ein eigenes Sequence - Objekt
erzeugen. Sequence Objekte kennen wir schon aus dem Kapitel 6, bei dem wir eine Midi - Datei
bzw. die darin enthaltene Sequence an Midi - Informationen wiedergegeben haben. Neu ist hierbei
allerdings, daß wir ja unsere eigene Sequence erzeugen wollen. Wir müssen also zunächst ein „leeres“
Sequence Objekt konstruieren, das wir über folgende Anweisung durchführen:
Sequence unsereSeq = new Sequence(Sequence.PPQ, 10);
Dem Konstruktor müssen wir zwei Midi - spezifische Argumente übergeben: einmal eine sogenannte
timing - resolution und andererseits noch ein divisionType. Es sei hier nur soviel erwähnt, daß es
zwei verschiedene Verfahren gibt, wie man Midi - Sequencen in Zeiteinheiten aufteilt: das hier
verwendete PPQ (Zeiteinheit in Takte pro Viertelnote) oder das SMPTE (Takte pro Frame, ein
Format, das aus der Filmindustrie stammt) - Verfahren. Man übergibt dem Sequence - Konstruktor
also entweder als ersten Parameter ein Sequence.PPQ oder ein Sequence.X (X ist entweder:
SMPTE_24, SMPTE_25, SMPTE_30, oder SMPTE_30DROP) Objekt. Für eine genauere
Beschreibung dieser beiden Begriffe sei auch an dieser Stelle wieder auf [3] oder [5] verwiesen.
Der zweite Wert, den wir dem Sequence - Konstruktor übergeben müssen, bezieht sich darauf,
wieviele Takte pro gewählter Zeiteinheit benutzt werden sollen. In unserem Fall also 10 Takte pro
Viertelnote.
Auch der nun folgende Schritt ist neu für uns. Midi - Sequencen können verschiedene sogenannte
Tracks besitzen, die jeweils parallel von einem Synthesizer abgearbeitet werden. Diese Tracks
enthalten unter anderen Informationen darüber, wann der Synthesizer welche Note spielen soll.
Um einen Track in einer Sequence zu erzeugen, wird
Track track = unsereSeq.createTrack();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 49 -
ausgeführt.
Jetzt wird der Sequencer angewiesen, unsere Sequence zu bearbeiten, in dem wir
sequencer.setSequence(unsereSeq);
ausführen. Wenn wir nun
sequencer.recordEnable(track, -1);
in unserem Programm abarbeiten lassen, versetzen wir den Sequencer in Aufnahmebereitschaft. Um
dies zu erreichen, müssen wir ihm mitteilen, auf welchem Track er in der aktuellen Sequence
bearbeitet die Aufnahme durchführen soll und auf welchem Kanal die zu speichernden Midi Informationen herein-kommen werden. Übergibt man hier eine -1, wird jeder zur Verfügung stehende
Kanal benutzt, d.h. alle Midi – Informationen, die der Sequencer auf seinen Kanälen erhält, werden in
die Sequence geschrieben; gibt man die Nummer eines bestimmten Kanals an, werden nur die
Informationen, die den Sequencer dort erreichen, auch wirklich in die Sequence geschrieben.
Nach diesen umfangreicheren Befehlen, erfolgt nun der wesentlich einfachere
sequencer.startRecording();
und die Aufnahme beginnt, d.h. alle Informationen, die nun den Sequencer erreichen, werden auch in
die Sequence geschrieben.
Um die Aufnahme zu stoppen, wird einfach
sequencer.stopRecording();
ausgeführt.
Zuletzt bleibt noch, die Sequence auch wirklich auf einer Platte zu sichern. Hierzu bedient man sich
der write() - Methode, die die MidiSystem - Klasse zur Verfügung stellt.
MidiSystem.write(unsereSeq, 0, File - Objekt);
Als ersten Parameter, erwartet die Methode die zu schreibende Sequence, als zweiten einen Integer Wert, der den Midifile - Typ bestimmt (für Näheres über die verschiedenen Midifiletypen siehe [5])
und schliesslich ein File Objekt, das die Zieldatei spezifiziert.
Nachdem wir gesehen haben, wie man eine Sequence erzeugen und diese als Midi - File schreiben
kann, bleibt die Frage, wie man überhaupt die Informationen über die zu spielenden Noten in eine
Sequence eingebracht werden. Daher kommen wir zu ...
Wie kann man ... eigene Noten in eine Sequence schreiben ?
Um diese Frage zu lösen, benötigen wir sogenannte MidiMessages. Diese MidiMessages sind nach
der Midi - Spezifikation wie folgt definiert.
Es gibt drei verschiedene Gruppen von Messages, nämlich:
1. Short - Message
__________________________________________________________________________________________
Sound-Programmierung in Java
- 50 -
2. Sysex - Message
3. Meta - Message
Wir werden hier nur die erste Art benutzen. Für eine Übersicht über die anderen Arten sei auch hier
wieder auf [5] und in knapper Form auf [3] verwiesen.
Kommen wir zu den Short - Messages. In einer solchen Nachricht sind Midi - Daten (z.B. „spiele
Note x mit dem Anschlag y“) gekapselt. Diese Eigenschaft werden wir ausnutzen und somit eine
Möglichkeit haben, in unsere Sequence eine Abfolge von Short - Messages zu schreiben, in denen
unsere Noten gekapselt sind.
Eine Short - Message, wird in der Java Sound API durch sogenannte ShortMessage Objekte
repräsentiert. Bei der Erstellung einer Short - Message verfährt man wie folgt:
ShortMessage meineNote = new ShortMessage();
erzeugt zunächst einmal ein „leeres“ ShortMessage Objekt, das noch keinerlei Midi - Informationen
enthält. Diese gelangen erst durch
meineNote.setMessage(ShortMessage.NOTE_ON, 0, 60, 93);
in das Objekt. setMessage erwartet als ersten Parameter ein Kommando (die wichtigsten wären
ShortMessage.NOTE_ON, ShortMessage.NOTE_OFF), als zweiten die Nummer des Midi –
Kanals, auf den die Message bei einem Gerät gesendet werden soll, drittens die zu spielende Note als
Integer - Wert (hier die 60, die einem mittleren C entspricht) und schliesslich einen Integer, der
bestimmt, wie stark die Note auf dem virtuellen Keyboad angeschlagen werden soll, also die Velocity.
Um nun eine solche Midi - Nachricht an ein Midi - Gerät zu schicken, benötigen wir ein dem Gerät
zugeordnetes Receiver - Objekt und benutzen die Receiver - Methode send(). Konkret sieht dies wie
folgt aus:
[Receiver-Obj. des Gerätes zu dem Nachricht geschickt
wird].send(shortM,time);
shortM ist hierbei die zu sendende Short - Message bzw. das entsprechende Java Objekt. Noch neu
für uns ist der Wert time. Dieser im long - Format vorliegende Wert gibt den sogenannten
Zeitstempel der Midi - Nachricht an. Zeitstempel bedeutet, daß einer Midi - Nachricht eine Zeit
zugeordnet werden kann
zu der das Midi - Gerät die Nachricht verarbeiten soll. Setzt man diesen Wert auf eine -1, wird dem
Gerät überlassen, wann es die Nachricht verarbeitet; eine genaue Zeit wird also nicht vorgeschrieben.
Näheres zu den Zeitstempeln bei Midi - Messages findet man wieder unter [5] und [3]. In unseren
einfachen Beispielen werden wir nur den Wert -1 benutzen und es somit dem Gerät überlassen, wann
es die Nachricht verarbeitet.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 51 -
Nach diesen theoretischen Aspekten kommen wir nun zu einem praktischen Beispiel, in dem alle - in
diesem Kapitel neu hinzugekommenen - Sachverhalte angewendet werden. Bei diesem Programm
handelt es sich um die RecMidi.java Applikation:
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
class RecMidi
{
static Sequencer sequencer = null;
static Receiver seqReceiver = null;
static ShortMessage message = null;
static Sequence newSequence = null;
static Track track = null;
static void main(String args[])
{
try
{
sequencer = MidiSystem.getSequencer();
sequencer.open();
seqReceiver = sequencer.getReceiver();
}catch(MidiUnavailableException e)
{System.out.println("Synthesizer nicht verfuegbar!");
System.exit(0);}
try
{
//erzeuge unsere eigene Sequence in die wir später unsere Noten schreiben
newSequence = new Sequence(Sequence.PPQ, 10);
//erzeuge einen Track in dieser Sequence der unsere Noten aufnimmt
track = newSequence.createTrack();
//übergib dem Sequencer unsere Sequence als seine aktuelle Sequence
sequencer.setSequence(newSequence);
}catch(InvalidMidiDataException e){System.out.println("Kein
gueltiges MidiFormat!");System.exit(0);}
//Sequencer vorbereiten für Aufnahme
sequencer.recordEnable(track, 0);
//beginne die Aufnahme
sequencer.startRecording();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 52 -
// erzeuge nun die MidiMessages die aufgenommen werden sollen
// und schicke diese zum Sequencer damit dieser sie aufnimmt in Sequence
message = new ShortMessage();
try
{
message.setMessage(ShortMessage.NOTE_ON, 0, 60, 93);
seqReceiver.send(message, -1);
message.setMessage(ShortMessage.NOTE_OFF, 0, 60, 93);
seqReceiver.send(message, -1);
message.setMessage(ShortMessage.NOTE_ON, 0, 62, 93);
seqReceiver.send(message, -1);
message.setMessage(ShortMessage.NOTE_OFF, 0, 62, 93);
seqReceiver.send(message, -1);
message.setMessage(ShortMessage.NOTE_ON, 0, 64, 93);
seqReceiver.send(message, -1);
message.setMessage(ShortMessage.NOTE_OFF, 0, 64, 93);
seqReceiver.send(message, -1);
}catch(InvalidMidiDataException e)
{System.out.println("MidiDaten nicht korrekt!");
System.exit(0);}
//Aufnahme beendet
sequencer.stopRecording();
//Erzeuge File - Objekt das Zieldatei bestimmt
File file = new File("my.mid");
//schreibe Midi - File auf Platte
try
{
MidiSystem.write(newSequence, 0, file);
}catch(IOException e){System.out.println("Fehler beim Schreiben
des MidiFiles!");System.exit(1);}
//stoppe den Sequencer
sequencer.stop();
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
}
System.exit(0);
}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 53 -
}
Das Programm erzeugt eine kleine Midi - Datei „my.mid“. In dieser Datei ist eine kleine Sequence
enthalten, die eine Abfolge von drei Tönen auf einem Track beinhaltet. Begonnen wird mit dem
mittleren C (die Note 60), dann die Note 62 und schliesslich 64. Schauen wir uns das Programm nun
genauer an.
Zu Beginn werden die schon vertrauten Dinge veranlasst. Man fordert einen Sequencer bei dem
MidiSystem an, fordert von diesem wiederum einen Receiver an, über den wir später unsere Midi Messages an den Sequencer schicken können. Daraufhin erzeugen wir eine Sequence, in die der Track
eingebettet ist, der unsere Noten aufnimmt:
newSequence = new Sequence(Sequence.PPQ, 10);
Wir können sehen, daß in dem Beispiel das „Takte - Pro - Viertelnote - Zeiteinheiten“ - Verfahren
benutzt wird (PPQ), und daß pro Viertelnote 10 Takte zu erfolgen haben.
Im Anschluss daran, wird ein Track in unserer Sequence erzeugt
track = newSequence.createTrack();
und die Sequence an den Sequencer übergeben:
sequencer.setSequence(newSequence);
Die nächsten Schritte versetzen den Sequencer in Aufnahmebereitschaft:
sequencer.recordEnable(track, 0);
sequencer.startRecording();
Er wird angewiesen, nur die Midi – Informationen, die auf seinem Kanal 0 eintreffen (bestimmt durch
den zweiten Integer - Parameter), in unsere Sequence und speziell dort im Track track
aufzunehmen.
Im nächsten Anweisungsblock, werden unsere ShortMessage - Objekte erzeugt, die über den
Receiver seqReceiver und dessen send() - Methode an den Sequencer geschickt werden und
von diesem in unsere Sequence bzw. in unseren Track übernommen werden.
Zum Schluss wird die Aufnahme durch
sequencer.stopRecording();
gestoppt; was noch bleibt, ist, unser kleines Midi - Stück in eine Datei mit dem Namen „my.mid“ zu
schreiben. Dies übernehmen folgende Anweisungen:
File file = new File("my.mid");
MidiSystem.write(newSequence, 0, file);
Zum Schluss wird dem System noch die Zeit gegeben, die Aufgabe zu erfüllen, bevor der Sequencer
gestoppt und das Programm beendet wird.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 54 -
Dieses kleine Beispielprogramm sollte zeigen, wie man eigene Midi - Stücke aufnehmen kann und
diese anschließend in eine Midi - Datei schreibt. Eine Anwendung hierfür wäre beispielsweise unsere
schon erwähnte Swing - Applikation, bei der ein User auf einem „virtuellen“ Keyboard spielen kann.
Vor-stellbar wäre diesen Ansatz um die Möglichkeit zu erweitern, sein Spiel auf dem Keyboard als
Midi - Datei auf der Festplatte zu konservieren.
Bei den in diesem Kapitel neu hinzugekommenen Methoden die die Möglichkeit haben, Java
Exceptions zu erzeugen, handelt es sich um:
Methode
erzeugte Exception
[Sequencer – Objekt].recordEnable( )
MidiSystem.write( )
IllegalArgumentException
IllegalArgumentException,
IOException
IllegalArgumentException
IllegalStateException
[MidiMessage – Objekt].setMessage( )
[Receiver – Objekt].send( )
Mit dem Schluss des Teils II haben wir die kurze Einführung zum Thema Java Sound API beendet. In
Teil III wird kurz auf eine weitere Möglichkeit eingegangen Java zur Soundgenerierung zu benutzen:
das sogenannte Java Media Framework
TEIL III
Java Media Framework
__________________________________________________________________________________________
Sound-Programmierung in Java
- 55 -
(javax.media.*)
Kapitel 9 - Musik - Player : Die Grundfunktion
Das Java Media Framework ist durch die Unterstützung von fast allen gängigen Audio- und
Videoformaten relativ einfach als universeller Player nutzbar. Wenn man im Internet nach Beispielen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 56 -
für die Nutzung des Java Media Framework sucht, wird man fast nur Applets oder mit Unterstützung
von Swing erstellte Programme finden. Der Grund liegt darin, daß es direkt im Java Media
Framework Klassen für eine grafische Benutzeroberfläche gibt.
Im folgenden wird vor allem die Funktion von Java Media Framework als Musik - Player betrachtet.
Dies wird aber der Übersichtlichkeit halber nicht mit Hilfe der grafischen Benutzeroberfläche gezeigt.
In den Kapiteln 9.1 bis 9.5 wird das Grundgerüst des in Kapitel 9.6 abgedruckten Players schrittweise
beschrieben.
Im Kapitel 10 wird beschrieben, wie mit dem Java Media Framework eigene Musikstücke
aufgenommen werden können.
Der Umgang mit der grafischen Benutzeroberfläche wird mit Hilfe eines Beispielprogrammes von
SUN in Kapitel 11 erläutert.
Einige interessante Möglichkeiten, die das Java Media Framework noch bietet, werden kurz im
Kapitel 12 beschrieben.
In Anhang B finden sich die hier im Text benutzten Quellen.
Kapitel 9.1 - Initialisierung
Um einen Player zu erzeugen, muß man zuerst eine Variable vom Type Player definieren
Player wplayer = null
und danach den Player initialisieren
wplayer = Manager.createPlayer(url)
Falls der Player hier aus irgendeinem Grund nicht initialisiert werden
konnte, wird
NoPlayerException zurückgegeben. Außerdem kann es eine IOException auftreten, da der
Player
versucht, die in einer Url stehende Datei zu öffnen.
Wie man sehen kann, muß der Player mit einer Url aufgerufen werden. Um die Url zu erzeugen,
benötigt man nur einen Dateinamen oder direkt eine Internet-Adresse. Wir gehen im folgenden von
der Situation aus, daß wir eine Datei von der Festplatte abspielen möchten. Bei der Eingabe eines
Dateinamens mit Pfadangabe, ist es nur wichtig zu wissen, daß anstatt des in DOS üblichen Backslash
nur ein Slash benutzt wird. Da die Datei auf der Festplatte liegt, muß vor dem Dateinamen noch ein
__________________________________________________________________________________________
Sound-Programmierung in Java
- 57 -
"file:" eingefügt werden. Nun muß die Url (hier als src bezeichnet) nur noch in ein für den Player
gültiges Format geändert werden
url = new URL(src)
Hierbei kann es zu einer MalformedURLException kommen, falls die übergebene Adresse falsch
war. Bevor der Player gestartet werden kann, müssen noch die Befehle wplayer.realize() und
wplayer.prefetch() ausgeführt werden. Diese Befehle sorgen dafür, daß die Variablen, Puffer,
usw. für den Player eingerichtet werden. Mit Hilfe des in Kapitel 12 beschriebenen
ControllerListener ist es möglich, zu erfahren, wann realize und prefetch abgeschlossen
sind, da RealizeCompleteEvent und PrefetchCompleteEvent beim Beenden von realize
und prefetch ausgelöst werden.
Neben der hier beschriebenen Methode createPlayer() gibt es auch noch die Methode
createProcessor(), die es erlaubt, Musik nicht nur abzuspielen, sondern vorher auch noch zu
bearbeiten. Solange das Java Media Framework nur zum Abspielen von Musik benutzt wird, sollte
createPlayer() reichen.
Kapitel 9.2 - Play, Stop
Nachdem der Player initialisiert wurde, kann jetzt begonnen werden, das bei der Initialisierung
angegebene
Musikstück
abzuspielen.
Um
den
Player
zu
starten,
genügt
es
einfach
wplayer.start() aufrufen. Um das Abspielen zu stoppen, reicht wplayer.stop(). Damit der
Player nicht unnötig viele Ressourcen nutzt, ist es besser nach dem Befehl wplayer.stop() die
belegten Ressourcen mit wplayer.deallocate() wieder frei zu geben. Dies sollte aber auf jeden
Fall beim Beenden des Players geschehen (siehe Kapitel 9.5).
Wenn der Player mit wplayer.stop() angehalten wurde, kann mit dem Befehl wplayer.start()
an der gleichen Stelle im Musikstück das Abspielen fortgesetzt werden.
Kapitel 9.3 - Aktuelle Spielzeit auslesen, neue Spielzeit
setzen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 58 -
Mit
Time AktuelleZeit = wplayer.getMediaTime()
kann die aktuelle Spielzeit vom Musikstück ausgelesen werden. Mit
wplayer.setMediaTime(new Time(0))
kann man z.B. die aktuelle Spielzeit auf den Anfang des Musikstückes setzen. Die Befehle
setMediaTime und getMediaTime können aufgerufen werden, während der Player ein Musikstück
abspielt. Bei setMediaTime springt der Player dann sofort zu der angegebenen Stelle und spielt die
Musik weiter ab.
Um die Gesamtspiellänge zu ermitteln, kann man
Time Gesamtlaenge = wplayer.getDuration()
benutzen.
Kapitel 9.4 - Schneller Vorlauf
Bei
einigen Formaten ist Java Media Framework sogar in der Lage, Dateien in doppelter
Geschwindigkeit abzuspielen. Die aktuelle Spielgeschwindigkeit kann man mit getRate() abgefragt
werden. Wenn man aber die Geschwindigkeit mit setRate((int) Geschwindigkeit) setzen
will, muß vorher sichergestellt sein, daß der Player gerade kein Stück abspielt. Das Abspielen mit
doppelter Geschwindigkeit ist z.B. bei *.wav ,*.au ,*.aif möglich. Nicht möglich ist es dagegen bei
*.mp3.
Kapitel 9.5 - Player schließen
Um den Player zu schließen, sollte man am besten folgendermaßen vorgehen :
-
Player stoppen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 59 -
-
Ressourcen freigeben
Player schließen
Wie schon in Kapitel 1.2 beschrieben, wird der Player mit stop() gestoppt und die Ressourcen
werden mit deallocate() freigegeben. Um den Player endgültig zu beenden, genügt der Befehl
close().
Kapitel 9.6 - Ein Beispielprogramm
Dieses Beispielprogramm zeigt einen Musik-Player, der die Grundfunktionen Start, Stop,
Zurücksetzen der aktuellen Zeit auf Null und das Einstellen der Wiedergabe mit doppelter
Geschwindigkeit beherrscht.
Das Programm wurde mit folgenden Dateitypen getestet : *.wav, *.mp3, *.au, *.aif
Das Programm ist wie folgt aufzurufen : java WavPlayer <Musikdatei>
import javax.media.*;
// JMF
import javax.media.protocol.*;
// JMF
import java.io.*;
// für Bildschirmausgabe...
import java.net.*;
// URL...
public class WavPlayer {
Player wplayer = null;
public static void main (String args[]) {
if (args.length == 0)
throw new IllegalArgumentException ("Fehler : Keine Datei
angegeben! z.B. WavPlayer test.wav");
WavPlayer wplay = new WavPlayer(args[0]);
}
private void Menu()
__________________________________________________________________________________________
Sound-Programmierung in Java
- 60 -
{
System.out.println("Funktionen :");
System.out.println("
e = Ende");
System.out.println("
s = Stop");
System.out.println("
a = Start");
System.out.println("
o = Startposition 0");
System.out.println("
r = Rate 1 oder 2");
}
public WavPlayer(String datei) {
String src = "file:"+datei;// Dateinamen für lokale Festplatte ändern
URL url = null;
// Datei - Adresse ändern ;
Player initialisieren
try {
// Den gesamten Dateinamen in url - Format umändern
if ((url = new URL(src)) == null) {
System.out.println("Fehler URL=null ;"+src+";"+url);
System.out.println("Bitte die Schreibweise überprüfen");
System.out.println("z.B. WavPlayer test.wav oder
WavPlayer e:/test.wav");
return;
}
try {
// Player erzeugen
wplayer = Manager.createPlayer(url);
} catch (NoPlayerException e) {
System.out.println("Fehler - create Player");
}
} catch (MalformedURLException e) {
// Fehler bei url - Erstellung
System.out.println("Fehler URL : "+datei);
} catch (IOException e) {
System.out.println("Fehler IO");
}
wplayer.realize();
wplayer.prefetch();
// Player ist bereit ...
char Taste = 'b';
// dummy - Taste zur Initialisierung
float Rate;
// Programmschleife
Menu();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 61 -
do
{
try {
Taste = (char) System.in.read();
} catch (IOException e) {
System.out.println("Fehler beim einlesen der Taste");
System.exit(2);
}
switch(Taste)
{
case 's' : wplayer.stop();wplayer.deallocate();break;
case 'a' : {
wplayer.start();
break;
}
case 'r' : {
wplayer.stop();
//Rate kann nicht geändert werden, wenn der player läuft
Rate = wplayer.getRate();
System.out.println("aktuelle - Rate : " +
Rate);
if (Rate == 1.0) { Rate = (float) 2.0; } else
{ Rate = (float) 1.0; }
System.out.println("neue - Rate : " +
wplayer.setRate(Rate));
System.out.println("Falls sich der Player
jetzt ausgeschaltet hat, bitte a für
Start eingeben");
break;
}
case 'o' : wplayer.setMediaTime(new Time(0));break;
}
} while (Taste != 'e');
// Player - Ende
wplayer.stop();
// falls der Player nicht manuell beendet wurde
wplayer.deallocate();
wplayer.close();
System.exit(0);
}
}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 62 -
Kapitel 10 - Aufnahme von Musikstücken
Dieses Kapitel bezieht sich auf das Programm SoundRecorder.java (und den dazugehörigen
Programmteil StateHelper.java) im Anhang A.
Es ist noch zu erwähnen, daß dieses Programm aus den Hilfe-Dateien von SUN [6] herauskopiert ist.
Leider war es nicht möglich, das Programm auszuführen.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 63 -
Zuerst muß man ein Eingabegerät mit
Vector deviceList = CaptureDeviceManager.getDeviceList (new
AudioFormat(AudioFormat.LINEAR, 44100, 16, 2))
auswählen. Hier werden die Geräte ausgesucht, die eine Samplerate von 44 kHz mit 16 Bit und Stereo
beherrschen. Als nächstes muß ein Gerät ausgesucht werden. Für das Mikrofon als Eingabegerät muß
nun mit
CaptureDeviceInfo di = (CaptureDeviceInfo) deviceList.firstElement()
ein Player, wie in Kapitel 9.1 beschrieben, initialisiert werden. Anstatt der Url gibt man hier den oben
erzeugten MediaLocator ein. Der Aufruf sieht also wie folgt aus :
Player p = Manager.createPlayer(di.getLocator())
Mit p.start() beginnt der Player mit der Aufnahme und endet mit p.stop().
Es stellt sich nun die Frage , wie die Aufnahme gespeichert werden kann. Für diesen Fall gibt es
DataSink. Mit Hilfe von DataSink kann man aufgenommene Musikstücke relativ einfach auf der
Festplatte speichern.
Um die Daten zu speichern, die der Player jetzt vom Mikrofon bekommt, muß zuerst der Dateityp mit
p.setContentDescriptor(new
FileTypeDescriptor(FileTypeDescriptor.WAVE))
festgelegt werden (hier *.wav). Danach wird die Ausgabe des Players mit
DataSource source = p.getDataOutput()
geholt. Als letzte Variable fehlt jetzt noch der Dateiname, unter dem die Daten gespeichert werden
sollen, und der mit
MediaLocator dest = newMediaLocator(Dateiname)
gesetzt wird. Nun wird der DataSink mit
DataSink filewriter = Manager.createDataSink(source, dest)
erzeugt. Nachdem der DataSink mit open() geöffnet wurde, kann man mit start() die Aufnahme
beginnen und mit stop() beenden. Mit close() wird der DataSink wieder geschlossen.
Um das Erzeugen zu großer Dateien zu verhindern kann mit
StreamWriterControl swc = (StreamWriterControl) p.getControl
("javax.media.control.StreamWriterControl");
If (swc != null) swc.setStreamSizeLimit(größe in Byte);
die maximale Dateigröße festgelegt werden.
__________________________________________________________________________________________
Sound-Programmierung in Java
- 64 -
Kapitel 11 - Musik-Player und Swing
Kapitel 11.1 - Grafische Oberfläche
__________________________________________________________________________________________
Sound-Programmierung in Java
- 65 -
Wie bereits in Kapitel 9 beschrieben, gibt es die Möglichkeit mit einer grafischen Oberfläche zu
arbeiten.
Das Aussehen läßt sich mit Hilfe von java.awt.* (AWT Layout Manager) steuern. Die
Einstellungen lassen sich durch
Component visual = wplayer.getVisualComponent()
verändern; so kann z.B. durch visual.width und visual.height die Breite und Höhe des Players
verändert werden.
Weiterhin besteht die Möglichkeit, ein Control Panel einzurichten. Dafür muß man
Component control = wplayer.getControlPanelComponent()
mit
getContentPane().add("South", control)
zum Fenster hinzufügen. In diesem Control Panel sind standardmäßig Buttons für Start, Stop, Pause
und eine Laufleiste für die aktuelle Wiedergabe - Position im Musikstück vorhanden. Falls ein
Musikstück z.B. zuerst noch aus dem Internet heruntergeladen werden muß, kann mit
getCachingControl und getProgressBar noch eine Anzeige des Download – Fortschritts
erzeugt werden. Es ist außerdem möglich, dem Fenster mit getGainControl einen Lautstärkeregler
hinzuzufügen.
Kapitel 11.2 - Ein Beispielprogramm
MDIApp.java
: Dies ist ein Beispielprogramm, das aus den Internet-Seiten von SUN [7]
entnommen ist. Es zeigt einen Sound-Player unter Swing.
import javax.media.*;
import com.sun.media.ui.*;
import javax.media.protocol.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 66 -
import java.util.Vector;
public class MDIApp extends Frame {
/*************************************************************************
* MAIN PROGRAM / STATIC METHODS
*************************************************************************/
public static void main(String args[]) {
MDIApp mdi = new MDIApp();
}
static void Fatal(String s) {
MessageBox mb = new MessageBox("JMF Error", s);
}
/*************************************************************************
* VARIABLES
*************************************************************************/
JMFrame jmframe = null;
JDesktopPane desktop;
FileDialog fd = null;
CheckboxMenuItem cbAutoLoop = null;
Player player = null;
Player newPlayer = null;
String filename;
/*************************************************************************
* METHODS
*************************************************************************/
public MDIApp() {
super("Java Media Player");
// Add the desktop pane
setLayout( new BorderLayout() );
desktop = new JDesktopPane();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 67 -
desktop.setDoubleBuffered(true);
add("Center", desktop);
setMenuBar(createMenuBar());
setSize(640, 480);
setVisible(true);
try {
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
} catch (Exception e) {
System.err.println("Could not initialize java.awt Metal lnf");
}
addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent we) {
System.exit(0);
}
} );
Manager.setHint(Manager.LIGHTWEIGHT_RENDERER, new Boolean(true));
}
private MenuBar createMenuBar() {
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
String command = ae.getActionCommand();
if (command.equals("Open")) {
if (fd == null) {
fd = new FileDialog(MDIApp.this, "Open File",
FileDialog.LOAD);
fd.setDirectory("/movies");
}
fd.show();
if (fd.getFile() != null) {
String filename = fd.getDirectory() + fd.getFile();
openFile("file:" + filename);
}
} else if (command.equals("Exit")) {
dispose();
System.exit(0);
}
}
};
__________________________________________________________________________________________
Sound-Programmierung in Java
- 68 -
MenuItem item;
MenuBar mb = new MenuBar();
// File Menu
Menu mnFile = new Menu("File");
mnFile.add(item = new MenuItem("Open"));
item.addActionListener(al);
mnFile.add(item = new MenuItem("Exit"));
item.addActionListener(al);
// Options Menu
Menu mnOptions = new Menu("Options");
cbAutoLoop = new CheckboxMenuItem("Auto replay");
cbAutoLoop.setState(true);
mnOptions.add(cbAutoLoop);
mb.add(mnFile);
mb.add(mnOptions);
return mb;
}
/**
* Open a media file.
*/
public void openFile(String filename) {
String mediaFile = filename;
Player player = null;
// URL for our media file
URL url = null;
try {
// Create an url from the file name and the url to the
// document containing this applet.
if ((url = new URL(mediaFile)) == null) {
Fatal("Can't build URL for " + mediaFile);
return;
}
// Create an instance of a player for this media
try {
player = Manager.createPlayer(url);
} catch (NoPlayerException e) {
Fatal("Error: " + e);
}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 69 -
} catch (MalformedURLException e) {
Fatal("Error:" + e);
} catch (IOException e) {
Fatal("Error:" + e);
}
if (player != null) {
this.filename = filename;
JMFrame jmframe = new JMFrame(player, filename);
desktop.add(jmframe);
}
}
}
class JMFrame extends JInternalFrame implements ControllerListener {
Player mplayer;
Component visual = null;
Component control = null;
int videoWidth = 0;
int videoHeight = 0;
int controlHeight = 30;
int insetWidth = 10;
int insetHeight = 30;
boolean firstTime = true;
public JMFrame(Player player, String title) {
super(title, true, true, true, true);
getContentPane().setLayout( new BorderLayout() );
setSize(320, 10);
setLocation(50, 50);
setVisible(true);
mplayer = player;
mplayer.addControllerListener((ControllerListener) this);
mplayer.realize();
addInternalFrameListener( new InternalFrameAdapter() {
public void internalFrameClosing(InternalFrameEvent ife) {
mplayer.close();
}
} );
}
public void controllerUpdate(ControllerEvent ce) {
if (ce instanceof RealizeCompleteEvent) {
__________________________________________________________________________________________
Sound-Programmierung in Java
- 70 -
mplayer.prefetch();
} else if (ce instanceof PrefetchCompleteEvent) {
if (visual != null)
return;
if ((visual = mplayer.getVisualComponent()) != null) {
Dimension size = visual.getPreferredSize();
videoWidth = size.width;
videoHeight = size.height;
getContentPane().add("Center", visual);
} else
videoWidth = 320;
if ((control = mplayer.getControlPanelComponent()) != null) {
controlHeight = control.getPreferredSize().height;
getContentPane().add("South", control);
}
setSize(videoWidth + insetWidth,
videoHeight + controlHeight + insetHeight);
validate();
mplayer.start();
} else if (ce instanceof EndOfMediaEvent) {
mplayer.setMediaTime(new Time(0));
mplayer.start();
}
}
}
Kapitel 12 - Weitere ausgewählte Funktionen
Es gibt noch einige interessante Funktionen des Java Media Framework, die hier zumindest noch
Erwähnung finden sollten.
Wenn beispielsweise der Player nach einer bestimmten Zeit aufhören soll, ein Musikstück
wiederzugeben, so kann dazu setStopTime(Zeit) benutzt werden.
Manchmal ist es auch nötig mehrere Musikstücke gleichzeitig abzuspielen; in diesem Fall ist es
ratsam, die beiden Player zu synchronisieren. Dies geschieht mit getTimeBase, setTimeBase und
syncStart.
In Kapitel 9.1 wurde schon vom ControllerListener gesprochen. Nun zu der Frage, wofür der
denn da ist.
Nicht nur realize und prefetch lösen Events aus, sondern fast alles hier
__________________________________________________________________________________________
Sound-Programmierung in Java
- 71 -
Beschrieben kann Events auslösen. Wenn ein Musikstück zu ende ist, wird z.B. EndOfMediaEvent
ausgelöst. Dies kann dazu benutzt werden, wieder an den Anfang zurückzuspringen.
Weitere Events sind : CachingControlEvent (siehe oben in diesem Kapitel); StartEvent (nach
dem Start der Wiedergabe); StopByRequestEvent (falls versucht wird, einen bereits gestoppten
Player noch mal zu stoppen);
ControllerColsedEvent (falls der Controller mit close()
geschlossen wurde); uvm.
Anhang A
Programmbeispiele
__________________________________________________________________________________________
Sound-Programmierung in Java
- 72 -
In diesem Anhang finden sich alle Beispiele, die aus Platzmangel im Fliesstext keine
Berücksichtigung finden konnten.
Das erste Beispielprogramm, ist das Programm PlaySoundStreamedT.java aus Kapitel 2 Soundgenerierung
import javax.sound.sampled.*;
import javax.sound.midi.*;
import java.io.*;
class PlaySoundStreamedT extends Thread
{
static File file = null;
// die zu streamende Datei
static AudioInputStream stream = null;//ueber diesen Stream werden
//die AudioDaten aus der Datei
//"file" gelesen
static AudioFormat af = null; // gibt das AudioFormat der Datei "file"
__________________________________________________________________________________________
Sound-Programmierung in Java
- 73 -
//an
static SourceDataLine sl = null;// ueber diese DataLine wird die
//Datei "file" abgespielt
static byte
ba[] = null;// Array enthaelt die Sounddateibytes die
//aktuell aus dem AudioInputStream "stream"
//gelesen wurden und dann ueber die
//SourceDataLine "sl" abgespielt werden
static int numBytesRead = 0;// Anzahl der gelesenen Bytes aus dem
//Soundfile
static long frames = 0, // Anzahl der Frames aus der die
//Sounddatei besteht die in "file"
//angegeben ist
bSize = 0;
// Groesse der Sounddatei die in "file"
//angegeben ist in Bytes
static PlaySoundStreamedT pss = null;
static boolean play = true;
public PlaySoundStreamedT(File fi)
{
// Erzeuge den InputStream auf die Datei "file"
try
{
stream = AudioSystem.getAudioInputStream(file);
}catch(UnsupportedAudioFileException e){System.out.println("Die
gewaehlte Sounddatei wird nicht unterstuetzt!");
System.exit(1);}
catch(IOException e2){System.out.println("Fehler beim Erzeugen
des AudioInputStreams");System.exit(1);}
//bestimme das Format der abzuspielenden Datei
af = stream.getFormat();
// bestimmt die Länge der Sounddatei in Frames
frames = stream.getFrameLength();
// bestimmt die Länge der Sounddatei in Bytes
bSize = frames * af.getFrameSize();
System.out.println("Die Dateigroesse betraegt:"+bSize+" in
Bytes");
//erzeuge DataLine.info die spezifiziert welche Art von Eingabe man haben
//will beim AudioSystem
__________________________________________________________________________________________
Sound-Programmierung in Java
- 74 -
DataLine.Info info =
new DataLine.Info (SourceDataLine.class,af);
//versuche mit Hilfe von "info" die gewünschte Eingabe beim AudioSystem zu
//bekommen
try{
sl = (SourceDataLine) AudioSystem.getLine(info);
}catch(LineUnavailableException e){System.out.println("Line
konnte nicht benutzt werden");System.exit(1);}
}
//die Hauptmethode der Applikation
public void run()
{
//oeffne und reserviere somit fuer diese Applikation die SourceDataLine
try{
sl.open();
}catch(LineUnavailableException e){System.out.println("Fehler
beim Öffnen des AudioStreams");System.exit(1);}
//starte die Line zum Abspielen des Sounds
sl.start();
int ava=0;
//lege den Buffer an in den die Sounddateidaten eingelesen werden ueber
//die AudioInputStream
ba = new byte[1024];
//gib dem User an wieviele Bytes an Audiodaten zu lesen sind
try
{
ava = stream.available();
}catch(IOException e){System.exit(1);}
System.out.println("Bytes zu lesen:"+ava);
//Spiele Sound
while(play)
{
//lese SounddateiDaten in Buffer ein
try
{
numBytesRead = stream.read(ba,0,1024);
}catch(IOException e){System.out.println("Fehler
beim Lesen der SounddateiDaten");
System.exit(1);}
//wenn Ende der Sounddatei bzw. Streams erreicht beende Schleife
__________________________________________________________________________________________
Sound-Programmierung in Java
- 75 -
if (numBytesRead == -1)
break;//Ende der Sounddatei erreicht!
//schreibe gelesene Sounddateidaten in SourceDataLine und veranlasse somit
//das Abspielen der Daten
sl.write(ba,0,ba.length);
}
// blockiere bis die lezten Daten abgespielt wurden
sl.drain();
// halte die Line an und schliesse die Line
sl.stop();
sl.close();
sl=null;
System.out.println("
... Fertig!");
return;
}
// main - Methode der Applikation
public static void main (String args[])
{
if(args.length == 0)
throw new IllegalArgumentException ("Syntax: java
PlaySoundStreamedT <file>");
file = new File(args[0]);
pss = new PlaySoundStreamedT(file);
pss.start();
System.out.println("Spiele wav ...");
System.out.println("Druecke <RETURN> fuer STOP");
try
{
int c = System.in.read();
}catch(IOException e){System.exit(1);}
play = false;
System.out.println("...STOP");
System.exit(0);
}
}
Dieses Programm erwartet beim Start als Parameter den Name oder den Pfad der wiederzugebenden
Sounddatei. Das Abspielen der Datei kann jederzeit, durch den Druck auf die RETURN – Taste,
__________________________________________________________________________________________
Sound-Programmierung in Java
- 76 -
beendet werden. Dieses Beispielprogramm soll einen einfachen auf Konsolenebene laufenden Audio Player realisieren.
Nach dem schon gezeigten einfachen Kommandozeilenplayer, kommen wir nun zu einer Swing Applikation, die einen Player realisiert, der sich vollständig über GUI - Elemente steuern läßt. Dieses
Beispielprogramm wurde in Kapitel 5 - Kontrollen erwähnt, in dem die Control – Objekte der
Java Sound API vorgestellt wurden. Das Programm gliedert sich in folgende drei Klassen:
 AudioPlayer.java - die Klasse, welche die Applikation startet
 AudioPlayerPanel.java - die Klasse, die die GUI aufbaut
 PlayBack.java - die Klasse, die die Wiedergabe der Sounddateien durchführt
Hier nun zuerst den Quellcode der AudioPlayer.java - Klasse:
import
java.awt.BorderLayout;
import
java.awt.GridLayout;
import
java.awt.FlowLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.awt.event.WindowEvent;
import
java.awt.event.WindowAdapter;
import
java.io.BufferedInputStream;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.InputStream;
import
java.io.IOException;
import
javax.sound.sampled.UnsupportedAudioFileException;
import
javax.sound.sampled.LineUnavailableException;
import
javax.swing.JFrame;
import
javax.swing.JCheckBox;
import
javax.swing.JButton;
import
javax.swing.JPanel;
import
javax.swing.JLabel;
import
javax.swing.JSlider;
import
javax.swing.JFileChooser;
import
javax.swing.event.ChangeEvent;
import
javax.swing.event.ChangeListener;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 77 -
public class AudioPlayer extends
JFrame
{
private JButton
loadB;
private JLabel
fileName;
private JFileChooser
fileChooser;
private AudioPlayerPanel
panel;
public AudioPlayer()
{
super("AudioPlayer1.0");
//überwache das Schliessen des Applikationsfensters
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
JPanel
filePanel = new JPanel();
filePanel.setLayout(new FlowLayout());
//erstelle einen LOAD - Button mit einem entsprechenden Listener
loadB = new JButton("Load..:");
loadB.addActionListener(new ActionListener()
{
public voidactionPerformed
(ActionEvent ae)
{
loadAudioFile();
}
});
filePanel.add(loadB);
fileName = new JLabel("Kein AudioFile geladen!");
filePanel.add(fileName);
panel = new AudioPlayerPanel(filePanel);
this.getContentPane().add(panel);
}
//Methode wird aufgerufen wenn Load-Button gedrueckt wurde
private void loadAudioFile()
{
//erzeuge ein FileChooser Element um Auswahl der Datei die abgespielt
//werden soll zu vereinfachen
__________________________________________________________________________________________
Sound-Programmierung in Java
- 78 -
if (fileChooser == null)
{
fileChooser = new JFileChooser();
}
int
nOption = fileChooser.showOpenDialog(this);
if (nOption != JFileChooser.APPROVE_OPTION)
{
return;
}
//bestimme die vom User gewählte Datei
File audioFile = fileChooser.getSelectedFile();
if (panel.setAudioFile(audioFile))
{
fileName.setText(audioFile.getName());
}
}
public static void main(String[] args)
{
AudioPlayer ap = new AudioPlayer();
ap.pack();
ap.show();
}
}
Nun folgt der Quellcode für AudioPlayerPanel.java:
import
java.awt.BorderLayout;
import
java.awt.GridLayout;
import
java.awt.FlowLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.awt.event.WindowEvent;
import
java.awt.event.WindowAdapter;
import
java.io.BufferedInputStream;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.InputStream;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 79 -
import
java.io.IOException;
import
javax.sound.sampled.UnsupportedAudioFileException;
import
javax.sound.sampled.LineUnavailableException;
import
javax.swing.JFrame;
import
javax.swing.JCheckBox;
import
javax.swing.JButton;
import
javax.swing.JPanel;
import
javax.swing.JLabel;
import
javax.swing.JSlider;
import
javax.swing.JFileChooser;
import
javax.swing.JOptionPane;
import
javax.swing.event.ChangeEvent;
import
javax.swing.event.ChangeListener;
public class AudioPlayerPanel extends
JPanel
{
protected JButton
loadB;
protected JLabel
fileName;
protected JButton
startB;
protected JButton
stopB;
protected JButton
pauseB;
protected JButton
resumeB;
protected JSlider
gainS;
protected JSlider
panS;
private PlayBack
playBack;
private File
audioFile;
private File
dataFile;
public AudioPlayerPanel(JPanel northPanel)
{
playBack = new PlayBack();
this.setLayout(new BorderLayout());
this.add("North", northPanel);
Jpanel controlPanel = new JPanel();
controlPanel.setLayout(new GridLayout(0, 1));
this.add("South", controlPanel);
Jpanel subControlPanel1 = new JPanel();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 80 -
subControlPanel1.setLayout(new FlowLayout());
controlPanel.add(subControlPanel1);
//ueber START - Button ist das Starten der Wiedergabe der
//gelesenen Audiodatei möglich
startB = new JButton("Start");
startB.addActionListener(new ActionListener()
{
public void
actionPerformed(ActionEvent ae)
{
//starte die Wiedergabe
startPlayback();
}
});
subControlPanel1.add(startB);
//ueber den STOP-Button haelt man die Wiedergabe an
stopB = new JButton("Stop");
stopB.addActionListener(new ActionListener()
{
public void
actionPerformed(ActionEvent ae)
{
//stoppe die Wiedergabe
stopPlayback();
}
});
subControlPanel1.add(stopB);
JPanel
subControlPanel2 = new JPanel();
subControlPanel2.setLayout(new FlowLayout());
controlPanel.add(subControlPanel2);
//PAUSE-Button veranlasst die Wiedergabe zu pausieren
pauseB = new JButton("Pause");
pauseB.addActionListener(new ActionListener()
{
public void
actionPerformed(ActionEvent ae)
{
//pausiere mit der Wiedergabe
pausePlayback();
}
});
subControlPanel2.add(pauseB);
//RESUME-Button ermöglicht es die pausierte Wiedergabe
__________________________________________________________________________________________
Sound-Programmierung in Java
- 81 -
//fortzusetzen
resumeB = new JButton("Resume");
resumeB.addActionListener(new ActionListener()
{
public void
actionPerformed(ActionEvent ae)
{
//setze pausierte Wiedergabe fort
resumePlayback();
}
});
subControlPanel2.add(resumeB);
startB.setEnabled(false);
stopB.setEnabled(false);
pauseB.setEnabled(false);
resumeB.setEnabled(false);
//mit Volume laest sich Wiedergabelautstaerke regeln
subControlPanel1.add(new JLabel("Volume"));
gainS = new JSlider(JSlider.HORIZONTAL, -90, 24, 0);
gainS.addChangeListener(new ChangeListener()
{
public void
stateChanged(ChangeEvent ce)
{
//passe Lautstaerke an
changeGain();
}
});
subControlPanel1.add(gainS);
//mit Balance laesst sich Verhaeltnis rechter/linker Kanal
//beeinflussen
subControlPanel2.add(new JLabel("Balance"));
panS = new JSlider(JSlider.HORIZONTAL, -100, 100, 0);
panS.addChangeListener(new ChangeListener()
{
public void
stateChanged(ChangeEvent ce)
{
//passe Balance an
changePan();
}
});
subControlPanel2.add(panS);
__________________________________________________________________________________________
Sound-Programmierung in Java
- 82 -
}
//Methode bestimmt aktuelles Audiofile das wiedergegeben werden soll
public boolean setAudioFile(File file)
{
dataFile = file;
//erzeuge ein Playback Objekt mit dem die Datei wiedergegeben wird
try
{
playBack = new PlayBack(dataFile, this);
}
catch (UnsupportedAudioFileException e)
{
JOptionPane.showMessageDialog(null, "Das Audioformat wird
nicht unterstuetzt.");
return false;
}
catch (IllegalArgumentException e)
{
JOptionPane.showMessageDialog(null, "Das Audioformat wird
nicht unterstuetzt.");
return false;
}
catch (LineUnavailableException e)
{
JOptionPane.showMessageDialog(null, "Keine Line zum
Abspielen vorhanden!");
return false;
}
catch (IOException e)
{
JOptionPane.showMessageDialog(null, "IOFehler!.");
return false;
}
audioFile = dataFile;
startB.setEnabled(true);
stopB.setEnabled(false);
pauseB.setEnabled(false);
resumeB.setEnabled(false);
return true;
}
//startet die Wiedergabe bei einem Playback Objekt
private void startPlayback()
{
__________________________________________________________________________________________
Sound-Programmierung in Java
- 83 -
playBack.start();
startB.setEnabled(false);
stopB.setEnabled(true);
pauseB.setEnabled(true);
resumeB.setEnabled(false);
}
//stoppt die Wiedergabe bei einem Playback Objekt
protected void stopPlayback()
{
playBack.stop();
startB.setEnabled(true);
stopB.setEnabled(false);
pauseB.setEnabled(false);
resumeB.setEnabled(false);
}
//pausiert die Wiedergabe bei einem Playback Objekt
private void pausePlayback()
{
playBack.pause();
startB.setEnabled(false);
stopB.setEnabled(true);
pauseB.setEnabled(false);
resumeB.setEnabled(true);
}
//setzt die Wiedergabe bei einem Playback Objekt fort
private void resumePlayback()
{
playBack.resume();
startB.setEnabled(false);
stopB.setEnabled(true);
pauseB.setEnabled(true);
resumeB.setEnabled(false);
}
//aendert die Wiedergabelautstaerke bei einem Playback Objekt
private void changeGain()
{
int
nValue = gainS.getValue();
float fGain = (float) nValue;
playBack.setGain(fGain);
__________________________________________________________________________________________
Sound-Programmierung in Java
- 84 -
}
//aendert die Balance bei einem Playback Objekt
private void changePan()
{
int
nValue = panS.getValue();
float fPan = nValue * 0.01F;
playBack.setPan(fPan);
}
}
Zuletzt noch die wichtigste Klasse der Applikation, nämlich PlayBack.java:
import
java.io.File;
import
java.io.IOException;
import
javax.sound.sampled.AudioFormat;
import
javax.sound.sampled.AudioInputStream;
import
javax.sound.sampled.Clip;
import
javax.sound.sampled.AudioSystem;
import
javax.sound.sampled.DataLine;
import
javax.sound.sampled.LineUnavailableException;
import
javax.sound.sampled.SourceDataLine;
import
javax.sound.sampled.UnsupportedAudioFileException;
import
javax.sound.sampled.FloatControl;
public class PlayBack implements
Runnable
{
private static boolean isRunning;
private static boolean paused = false;
private static Thread
thread = null;
private static File
audioFile;
private static AudioInputStream
stream;
private static FloatControl
clipGainControl;
private static FloatControl
clipPanControl;
private static AudioFormat audioFormat;
private static Clip clip;
private static AudioPlayerPanel parent = null;
public PlayBack()
{
__________________________________________________________________________________________
Sound-Programmierung in Java
- 85 -
audioFile = null;
}
public PlayBack(File file,AudioPlayerPanel parent)throws
UnsupportedAudioFileException, LineUnavailableException,IOException
{
this();
audioFile = file;
this.parent = parent;
initAudioInputStream(audioFile);
}
private void initAudioInputStream(File file)throws
UnsupportedAudioFileException,LineUnavailableException,IOException
{
//erzeuge AudioInputStream auf Quelldatei
try
{
stream = AudioSystem.getAudioInputStream(file);
}
catch (IOException e)
{
throw new IllegalArgumentException("Kann keinen
AudioInputStream erzeugen auf: " + file);
}
if (stream == null)
{
throw new IllegalArgumentException("Kann keinen
AudioInputStream erzeugen auf: " + file);
}
//bereite den Clip vor um die Wiedergabe zu starten
initClip();
}
//Methode erstellt alle nötigen Voraussetzungen um Clip abzuspielen
private void initClip()throws LineUnavailableException,IOException
{
DataLine.Info info =
new DataLine.Info(Clip.class,audioFormat);
clip = (Clip) AudioSystem.getLine(info);
//pruefe welche Control Objekte zur Verfügung stehen
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN))
__________________________________________________________________________________________
Sound-Programmierung in Java
- 86 -
{
clipGainControl = (FloatControl)
clip.getControl(FloatControl.Type.MASTER_GAIN);
}
if (clip.isControlSupported(FloatControl.Type.PAN))
{
clipPanControl = (FloatControl)
clip.getControl(FloatControl.Type.PAN);
}
//reserviere Clip fuer diese Anwendung
clip.open(stream);
}
//starte die Wiedergabe in dem Thread erzeugt wird
public void start()
{
thread = new Thread(this);
thread.start();
}
//stoppe die Wiedergabe und setze entsprechend die Button
//eigenschaften bei Panel
public void stop()
{
clip.stop();
clip.setFramePosition(0);
parent.startB.setEnabled(true);
parent.stopB.setEnabled(false);
parent.pauseB.setEnabled(false);
parent.resumeB.setEnabled(false);
isRunning = false;
}
//pausiere Wiedergabe
public void pause()
{
clip.stop();
isRunning = false;
paused = true;
}
//setzte Wiedergabe fort
public void resume()
{
isRunning = true;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 87 -
paused = false;
thread = new Thread(this);
thread.start();
}
//im Thread wird Wiedergabe gestartet
public void run()
{
isRunning = true;
clip.start();
while(clip.isActive() && isRunning){}
if(paused)
clip.stop();
else
stop();
}
//setzt den neuen Lautstaerkewert
public void setGain(float fGain)
{
if(clipGainControl != null)
clipGainControl.setValue(fGain);
}
//setzt den neuen Balance-Wert
public void setPan(float fPan)
{
if(clipPanControl != null)
clipPanControl.setValue(fPan);
}
}
SoundRecorder.java : Ein Beispielprogramm zur Aufnahme von Sound-Dateien, kopiert aus
den Hilfe-Dateien von SUN [6]. Aufruf : java SoundRecorder
import
import
import
import
import
import
import
javax.media.*;
javax.media.protocol.*;
javax.media.format.*;
javax.media.control.*;
java.io.*;
java.net.*;
java.util.*;
public class SoundRecorder {
public static void main (String args[]) {
SoundRecorder srec = new SoundRecorder();
__________________________________________________________________________________________
Sound-Programmierung in Java
- 88 -
}
public SoundRecorder() {
CaptureDeviceInfo di = null;
Processor p = null;
StateHelper sh = null;
Vector deviceList = CaptureDeviceManager.getDeviceList(new
AudioFormat(AudioFormat.LINEAR, 44100, 16, 2));
if (deviceList.size() > 0)
di = (CaptureDeviceInfo)deviceList.firstElement();
else
// Exit if we can't find a device that does linear,
// 44100Hz, 16 bit,
// stereo audio.
System.exit(-1);
try {
p = Manager.createProcessor(di.getLocator());
sh = new StateHelper(p);
} catch (IOException e) {
System.exit(-1);
} catch (NoProcessorException e) {
System.exit(-1);
}
// Configure the processor
if (!sh.configure(10000))
System.exit(-1);
// Set the output content type and realize the processor
p.setContentDescriptor(new
FileTypeDescriptor(FileTypeDescriptor.WAVE));
if (!sh.realize(10000))
System.exit(-1);
// get the output of the processor
DataSource source = p.getDataOutput();
// create a File protocol MediaLocator with the location of the
// file to which the data is to be written
MediaLocator dest = new MediaLocator("file://foo.wav");
// create a datasink to do the file writing & open the sink to
// make sure we can write to it.
DataSink filewriter = null;
try {
filewriter = Manager.createDataSink(source, dest);
filewriter.open();
} catch (NoDataSinkException e) {
System.exit(-1);
} catch (IOException e) {
System.exit(-1);
} catch (SecurityException e) {
System.exit(-1);
}
// if the Processor implements StreamWriterControl, we can
// call setStreamSizeLimit
// to set a limit on the size of the file that is written.
StreamWriterControl swc = (StreamWriterControl)
p.getControl("javax.media.control.StreamWriterControl");
//set limit to 5MB
if (swc != null)
swc.setStreamSizeLimit(5000000);
// now start the filewriter and processor
try {
filewriter.start();
} catch (IOException e) {
System.exit(-1);
}
// Capture for 5 seconds
__________________________________________________________________________________________
Sound-Programmierung in Java
- 89 -
sh.playToEndOfMedia(5000);
sh.close();
// Wait for an EndOfStream from the DataSink and close it...
filewriter.close();
}
}
StateHelper.java : Dieses Programm wird für SoundRecorder.java benötigt und ist
ebenfalls aus den Hilfe-Dateien von SUN [6] kopiert.
import javax.media.*;
public class StateHelper implements javax.media.ControllerListener {
Player player = null;
boolean configured = false;
boolean realized = false;
boolean prefetched = false;
boolean eom = false;
boolean failed = false;
boolean closed = false;
public StateHelper(Player p) {
player = p;
p.addControllerListener(this);
}
public boolean configure(int timeOutMillis) {
long startTime = System.currentTimeMillis();
synchronized (this) {
if (player instanceof Processor)
((Processor)player).configure();
else
return false;
while (!configured && !failed) {
try {
wait(timeOutMillis);
} catch (InterruptedException ie) {
}
if(System.currentTimeMillis()- startTime > timeOutMillis)
break;
__________________________________________________________________________________________
Sound-Programmierung in Java
- 90 -
}
}
return configured;
}
public boolean realize(int timeOutMillis) {
long startTime = System.currentTimeMillis();
synchronized (this) {
player.realize();
while (!realized && !failed) {
try {
wait(timeOutMillis);
} catch (InterruptedException ie) {
}
if(System.currentTimeMillis()- startTime > timeOutMillis)
break;
}
}
return realized;
}
public boolean prefetch(int timeOutMillis) {
long startTime = System.currentTimeMillis();
synchronized (this) {
player.prefetch();
while (!prefetched && !failed) {
try {
wait(timeOutMillis);
} catch (InterruptedException ie) {
}
if(System.currentTimeMillis()- startTime > timeOutMillis)
break;
}
}
return prefetched && !failed;
}
public boolean playToEndOfMedia(int timeOutMillis) {
long startTime = System.currentTimeMillis();
eom = false;
synchronized (this) {
player.start();
while (!eom && !failed) {
__________________________________________________________________________________________
Sound-Programmierung in Java
- 91 -
try {
wait(timeOutMillis);
} catch (InterruptedException ie) {
}
if (System.currentTimeMillis() - startTime > timeOutMillis)
break;
}
}
return eom && !failed;
}
public void close() {
synchronized (this) {
player.close();
while (!closed) {
try {
wait(100);
} catch (InterruptedException ie) {
}
}
}
player.removeControllerListener(this);
}
public synchronized void controllerUpdate(ControllerEvent ce) {
if (ce instanceof RealizeCompleteEvent) {
realized = true;
} else if (ce instanceof ConfigureCompleteEvent) {
configured = true;
} else if (ce instanceof PrefetchCompleteEvent) {
prefetched = true;
} else if (ce instanceof EndOfMediaEvent) {
eom = true;
} else if (ce instanceof ControllerErrorEvent) {
failed = true;
} else if (ce instanceof ControllerClosedEvent) {
closed = true;
} else {
return;
}
notifyAll();
}
}
__________________________________________________________________________________________
Sound-Programmierung in Java
- 92 -
Anhang B
Quellenangaben
[1] http://java.sun.com/products/java-media/sound/
[2] http://java.sun.com/products/jdk/1.3/docs/guide/sound API - Doku
[3] http://java.sun.com/products/jdk/1.3/docs/guide/sound/
[4] http://archives.java.sun.com/archives/javasound-interest.html
[5] http://www.midi.org
[6] http://java.sun.com/products/java-media/jmf/2.0/specdownload.html
[7] http://java.sun.com/products/java-media/jmf/2.0/solutions/SwingJMF.html
__________________________________________________________________________________________
Sound-Programmierung in Java
- 93 -
Herunterladen