diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java index 1922946..fa64162 100644 --- a/src/main/java/schule/ngb/zm/media/Audio.java +++ b/src/main/java/schule/ngb/zm/media/Audio.java @@ -18,8 +18,8 @@ public interface Audio { * {@code isLooping() == true}, dann muss auch immer * {@code isPlaying() == true} gelten. * - * @return {@code true}, wenn das Medium in einer Schleife - * abgespielt wird, {@code false} sonst. + * @return {@code true}, wenn das Medium in einer Schleife abgespielt wird, + * {@code false} sonst. */ boolean isLooping(); @@ -36,17 +36,28 @@ public interface Audio { */ void setVolume( double volume ); + /** + * Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück. + *
+ * Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton + * und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des + * Mediums. + * + * @return Die Lautstärke als linear skalierter Wert. + */ + double getVolume(); + /** * Startet die Wiedergabe des Mediums und beendet die Methode. Das * Audio-Medium wird einmal abgespielt und stoppt dann. */ - void playOnce(); + void play(); /** * Startet die Wiedergabe des Mediums und blockiert das Programm, bis die * Wiedergabe beendet ist. */ - void playOnceAndWait(); + void playAndWait(); /** * Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode diff --git a/src/main/java/schule/ngb/zm/media/Mixer.java b/src/main/java/schule/ngb/zm/media/Mixer.java index 91b2064..f94ec4f 100644 --- a/src/main/java/schule/ngb/zm/media/Mixer.java +++ b/src/main/java/schule/ngb/zm/media/Mixer.java @@ -50,30 +50,53 @@ public class Mixer implements Audio { pAudio.setVolume(pVolumeFactor * volume); } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return audios.stream().anyMatch(aw -> aw.audio.isPlaying()); } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { return audios.stream().anyMatch(aw -> aw.audio.isLooping()); } + /** + * {@inheritDoc} + */ @Override public void setVolume( double pVolume ) { volume = (float) pVolume; audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume)); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public double getVolume() { + return volume; } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public void play() { + audios.stream().forEach(aw -> aw.audio.play()); + } + + /** + * {@inheritDoc} + */ + @Override + public void playAndWait() { + audios.stream().forEach(aw -> aw.audio.play()); while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) { try { Thread.sleep(10); @@ -83,16 +106,25 @@ public class Mixer implements Audio { } } + /** + * {@inheritDoc} + */ @Override public void loop() { audios.stream().forEach(aw -> aw.audio.loop()); } + /** + * {@inheritDoc} + */ @Override public void stop() { audios.stream().forEach(aw -> aw.audio.stop()); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( isPlaying() ) { @@ -101,6 +133,18 @@ public class Mixer implements Audio { audios.stream().forEach(aw -> aw.audio.dispose()); } + /** + * Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen + * Zeit schrittweise, bis die angegebene Lautstärke erreicht ist. + *
+ * Zu beachten ist, dass die Lautstärke des Mixers angepasst wird. Das + * bedeutet, dass die Lautstärke der hinzugefügten Medien mit ihrem + * Lautstärkefaktor multipliziert werden. Die Medien haben am Ende also + * nicht unbedingt die Lautstärke {@code to}. + * + * @param to Der Zielwert für die Lautstärke. + * @param time Die Zeit, nach der die Änderung abgeschlossen sein soll. + */ public void fade( final double to, final int time ) { TaskRunner.run(new Runnable() { @Override @@ -108,9 +152,6 @@ public class Mixer implements Audio { final long start = System.currentTimeMillis(); double t = 0.0; double from = volume; - if( !isPlaying() ) { - playOnce(); - } do { setVolume(Constants.interpolate(from, to, t)); t = (double) (System.currentTimeMillis() - start) / (double) time; diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java index fa03cf3..72ad34c 100644 --- a/src/main/java/schule/ngb/zm/media/Music.java +++ b/src/main/java/schule/ngb/zm/media/Music.java @@ -1,43 +1,82 @@ package schule.ngb.zm.media; import schule.ngb.zm.tasks.TaskRunner; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; +/** + * Ein Musikstück, dass im Projekt abgespielt werden soll. + *
+ * Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien, + * die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher + * nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle + * gestreamt und wiedergegeben. + */ public class Music implements Audio { // size of the byte buffer used to read/write the audio stream private static final int BUFFER_SIZE = 4096; + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet + * wurde. Sonst {@code null}. + */ private AudioInputStream audioStream; + /** + * Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst + * {@code null}. + */ private SourceDataLine audioLine; + /** + * Die Lautstärke der Musik. + */ private float volume = 0.8f; + /** + * Erstellt eine Musik aus der angegebenen Datei oder Webadresse. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Music( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return playing; } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { if( !playing ) { @@ -46,6 +85,9 @@ public class Music implements Audio { return looping; } + /** + * {@inheritDoc} + */ @Override public void setVolume( double volume ) { this.volume = (float) volume; @@ -54,6 +96,18 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN); @@ -63,8 +117,11 @@ public class Music implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { + public void play() { if( openLine() ) { TaskRunner.run(new Runnable() { @Override @@ -75,19 +132,28 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { + public void playAndWait() { if( openLine() ) { stream(); } } + /** + * {@inheritDoc} + */ @Override public void loop() { looping = true; - playOnce(); + play(); } + /** + * {@inheritDoc} + */ @Override public void stop() { playing = false; @@ -95,6 +161,9 @@ public class Music implements Audio { dispose(); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( audioLine != null ) { @@ -112,7 +181,8 @@ public class Music implements Audio { if( audioStream != null ) { audioStream.close(); } - } catch( IOException ex ) {} + } catch( IOException ex ) { + } audioLine = null; audioStream = null; @@ -126,15 +196,14 @@ public class Music implements Audio { int bytesRead = -1; try { - while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) { + while( playing && (bytesRead = audioStream.read(bytesBuffer)) != -1 ) { audioLine.write(bytesBuffer, 0, bytesRead); } audioLine.drain(); audioLine.stop(); } catch( IOException ex ) { - LOGGER.warning("Error while playing Music source <" + audioSource + ">"); - LOGGER.throwing("Music", "stream", ex); + LOG.warn(ex, "Error while playing Music source <%s>", audioSource); } // Wait for the remaining audio to play @@ -163,7 +232,7 @@ public class Music implements Audio { final int ch = format.getChannels(); final float rate = format.getSampleRate(); - AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch*2, rate, false); + AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); @@ -174,14 +243,14 @@ public class Music implements Audio { audioStream = AudioSystem.getAudioInputStream(outFormat, inStream); return true; } else { - LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found."); + LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource); } } catch( UnsupportedAudioFileException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource); } catch( LineUnavailableException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource); } catch( IOException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource); } return false; } @@ -201,7 +270,6 @@ public class Music implements Audio { } } - //private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music"); - private static final Logger LOGGER = Logger.getLogger(Music.class.getName()); + private static final Log LOG = Log.getLogger(Music.class); } diff --git a/src/main/java/schule/ngb/zm/media/Sound.java b/src/main/java/schule/ngb/zm/media/Sound.java index ceda917..ece650d 100644 --- a/src/main/java/schule/ngb/zm/media/Sound.java +++ b/src/main/java/schule/ngb/zm/media/Sound.java @@ -1,35 +1,84 @@ package schule.ngb.zm.media; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; +/** + * Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden. + *
+ * In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds, + * die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn + * die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden + * komplett in den Speicher geladen und können dadurch immer wieder, als + * Schleife oder auch nur Abschnittsweise abgespielt werden. + *
+ * Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die + * KLasse {@link Music} an. + */ +@SuppressWarnings( "unused" ) public class Sound implements Audio { + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der Clip, falls er schon geladen wurde, sonst {@code null}. + */ private Clip audioClip; + /** + * Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen + * freigegeben werden sollen. + */ private boolean disposeAfterPlay = false; + /** + * Die Lautstärke des Clips. + */ private float volume = 0.8f; + /** + * Erstellt einen Sound aus der angegebene Quelle. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Sound( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ + @Override public boolean isPlaying() { // return audioClip != null && audioClip.isRunning(); return playing; } + /** + * {@inheritDoc} + */ + @Override public boolean isLooping() { if( !playing ) { looping = false; @@ -37,6 +86,10 @@ public class Sound implements Audio { return looping; } + /** + * {@inheritDoc} + */ + @Override public void setVolume( double volume ) { this.volume = (float) volume; if( audioClip != null ) { @@ -44,6 +97,18 @@ public class Sound implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN); @@ -53,6 +118,10 @@ public class Sound implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ + @Override public void stop() { looping = false; if( audioClip.isRunning() ) { @@ -61,6 +130,10 @@ public class Sound implements Audio { playing = false; } + /** + * {@inheritDoc} + */ + @Override public void play() { if( this.openClip() ) { audioClip.start(); @@ -68,16 +141,10 @@ public class Sound implements Audio { } } - public void playOnce() { - disposeAfterPlay = true; - play(); - } - - public void playOnceAndWait() { - disposeAfterPlay = true; - playAndWait(); - } - + /** + * {@inheritDoc} + */ + @Override public void playAndWait() { this.play(); @@ -94,26 +161,73 @@ public class Sound implements Audio { audioClip.close(); } + /** + * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips + * frei. + *
+ * Der Aufruf ist effektiv gleich zu + *
+ * clip.playAndWait();
+ * clip.dispose();
+ *
+ * allerdings wird der aufrufende Thread nicht blockiert und
+ * {@link #dispose()} automatisch am Ende aufgerufen.
+ */
+ public void playOnce() {
+ disposeAfterPlay = true;
+ play();
+ }
+
+ /**
+ * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
+ * frei.
+ * + * Der Aufruf entspricht + *
+ * clip.playAndWait();
+ * clip.dispose();
+ *
+ */
+ public void playOnceAndWait() {
+ disposeAfterPlay = true;
+ playAndWait();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void loop() {
loop(Clip.LOOP_CONTINUOUSLY);
}
+ /**
+ * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt
+ * die Wiedergabe dann.
+ * @param count Anzahl der Wiederholungen.
+ */
public void loop( int count ) {
- int loopCount = count;
- if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
- if( loopCount <= 0 ) {
- return;
+ if( count > 0 ) {
+ int loopCount = count;
+ if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
+ if( loopCount <= 0 ) {
+ return;
+ }
+ // Adjust Number of loops
+ loopCount -= 1;
+ }
+ if( openClip() ) {
+ looping = true;
+ audioClip.loop(loopCount);
+ playing = true;
}
- // Adjust Number of loops
- loopCount -= 1;
- }
- if( openClip() ) {
- looping = true;
- audioClip.loop(loopCount);
- playing = true;
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void dispose() {
if( audioClip != null ) {
if( audioClip.isRunning() ) {
@@ -150,17 +264,14 @@ public class Sound implements Audio {
applyVolume();
return true;
} else {
- LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found.");
+ LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource);
}
} catch( UnsupportedAudioFileException ex ) {
- LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported.");
- LOGGER.throwing("Sound", "openClip", ex);
+ LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource);
} catch( LineUnavailableException ex ) {
- LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable.");
- LOGGER.throwing("Sound", "openClip", ex);
+ LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource);
} catch( IOException ex ) {
- LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file.");
- LOGGER.throwing("Sound", "openClip", ex);
+ LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource);
}
return false;
}
@@ -180,6 +291,11 @@ public class Sound implements Audio {
}
}*/
+ /**
+ * Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
+ * Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
+ * nach {@link #playOnce()} beendet wurde.
+ */
private void playbackStopped() {
playing = false;
if( disposeAfterPlay ) {
@@ -188,17 +304,6 @@ public class Sound implements Audio {
}
}
- /*
- public void addLineListener( LineListener listener ) {
- if( audioClip == null ) {
- openClip();
- }
- if( audioClip != null ) {
- audioClip.addLineListener(listener);
- }
- }
- */
-
- private static final Logger LOGGER = Logger.getLogger(Sound.class.getName());
+ private static final Log LOG = Log.getLogger(Sound.class);
}