From 4e147586e429e6c9827bebd88666d0a52292342b Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 08:08:28 +0200 Subject: [PATCH] Audio Interface ud Mixer Klasse --- src/schule/ngb/zm/media/Audio.java | 70 ++++++++++++++++ src/schule/ngb/zm/media/Mixer.java | 129 +++++++++++++++++++++++++++++ src/schule/ngb/zm/media/Music.java | 12 ++- src/schule/ngb/zm/media/Sound.java | 4 +- 4 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 src/schule/ngb/zm/media/Audio.java create mode 100644 src/schule/ngb/zm/media/Mixer.java diff --git a/src/schule/ngb/zm/media/Audio.java b/src/schule/ngb/zm/media/Audio.java new file mode 100644 index 0000000..e2f5137 --- /dev/null +++ b/src/schule/ngb/zm/media/Audio.java @@ -0,0 +1,70 @@ +package schule.ngb.zm.media; + +/** + * Interface für Audio-Medien. + */ +public interface Audio { + + /** + * Prüft, ob das Medium gerade abgespielt wird. + * + * @return {@code true}, wenn das Medium abgespielt wird, {@code false} + * sonst. + */ + boolean isPlaying(); + + /** + * Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn + * {@code isLooping() == true}, dann muss auch immer + * {@code isPlaying() == true} gelten. + * + * @return @return {@code true}, wenn das Medium in einer Schleife + * abgespielt wird, {@code false} sonst. + */ + boolean isLooping(); + + /** + * Legt die Lautstärke des Mediums beim Abspielen fest. + *

+ * Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton + * und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des + * Mediums. + * + * @param volume Die neue Lautstärke zwischen 0 und 1. + * @see https://stackoverflow.com/a/40698149 + */ + void setVolume( double volume ); + + /** + * Startet die Wiedergabe des Mediums und beendet die Methode. Das + * Audio-Medium wird einmal abgespielt und stoppt dann. + */ + void playOnce(); + + /** + * Startet die Wiedergabe des Mediums und blockiert das Programm, bis die + * Wiedergabe beendet ist. + */ + void playOnceAndWait(); + + /** + * Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode + * startet die Wiedergabe und beendet dann direkt die Methode. Um die + * Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden. + */ + void loop(); + + /** + * Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt + * {@code isPlaying() == false}, dann passiert nichts. + */ + void stop(); + + /** + * Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium + * verwendet werden, frei. + */ + void dispose(); + +} diff --git a/src/schule/ngb/zm/media/Mixer.java b/src/schule/ngb/zm/media/Mixer.java new file mode 100644 index 0000000..91b2064 --- /dev/null +++ b/src/schule/ngb/zm/media/Mixer.java @@ -0,0 +1,129 @@ +package schule.ngb.zm.media; + +import schule.ngb.zm.Constants; +import schule.ngb.zm.tasks.TaskRunner; + +import java.util.ArrayList; +import java.util.List; + +/** + * Ein Mixer ist eine Sammlung mehrerer {@link Audio Audio-Medien}, die + * gemeinsam kontrolliert werden können. + *

+ * Im einfachsten Fall kann die Audio-Gruppe gemeinsam gestartet und gestoppt + * werden. Ein Mixer kann die Lautstärke der Medien in Relation zueinander + * setzen. Dazu wird jedem Medium ein Faktor mitgegeben. Ein Medium mit dem + * Faktor 0.5 ist dann halb so laut wie eines, mit dem Faktor 1.0. + *

+ * Darüber hinaus kann ein Mixer Effekte wie einen + * {@link #fade(double, int) fadeIn} auf die Medien anwenden. + */ +public class Mixer implements Audio { + + private List audios; + + private float volume = 0.8f; + + class AudioWrapper { + + Audio audio; + + float volumeFactor; + + public AudioWrapper( Audio audio, float volumeFactor ) { + this.audio = audio; + this.volumeFactor = volumeFactor; + } + + } + + public Mixer() { + this.audios = new ArrayList<>(4); + } + + public void add( Audio pAudio ) { + add(pAudio, 1f); + } + + public void add( Audio pAudio, double pVolumeFactor ) { + audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor)); + pAudio.setVolume(pVolumeFactor * volume); + } + + @Override + public boolean isPlaying() { + return audios.stream().anyMatch(aw -> aw.audio.isPlaying()); + } + + @Override + public boolean isLooping() { + return audios.stream().anyMatch(aw -> aw.audio.isLooping()); + } + + @Override + public void setVolume( double pVolume ) { + volume = (float) pVolume; + audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume)); + } + + @Override + public void playOnce() { + audios.stream().forEach(aw -> aw.audio.playOnce()); + } + + @Override + public void playOnceAndWait() { + audios.stream().forEach(aw -> aw.audio.playOnce()); + while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) { + try { + Thread.sleep(10); + } catch( InterruptedException e ) { + // Just keep waiting + } + } + } + + @Override + public void loop() { + audios.stream().forEach(aw -> aw.audio.loop()); + } + + @Override + public void stop() { + audios.stream().forEach(aw -> aw.audio.stop()); + } + + @Override + public void dispose() { + if( isPlaying() ) { + stop(); + } + audios.stream().forEach(aw -> aw.audio.dispose()); + } + + public void fade( final double to, final int time ) { + TaskRunner.run(new Runnable() { + @Override + public void run() { + 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; + + try { + Thread.sleep(1000 / Constants.framesPerSecond); + } catch( InterruptedException e ) { + // Keep waiting + } + } while( t < 1.0 ); + setVolume(to); + } + }); + } + +} diff --git a/src/schule/ngb/zm/media/Music.java b/src/schule/ngb/zm/media/Music.java index 01ae8f2..1c53ba7 100644 --- a/src/schule/ngb/zm/media/Music.java +++ b/src/schule/ngb/zm/media/Music.java @@ -9,7 +9,7 @@ import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; -public class Music { +public class Music implements Audio { // size of the byte buffer used to read/write the audio stream private static final int BUFFER_SIZE = 4096; @@ -25,16 +25,18 @@ public class Music { private SourceDataLine audioLine; - private float volume = 1.0f; + private float volume = 0.8f; public Music( String source ) { this.audioSource = source; } + @Override public boolean isPlaying() { return playing; } + @Override public boolean isLooping() { if( !playing ) { looping = false; @@ -42,6 +44,7 @@ public class Music { return looping; } + @Override public void setVolume( double volume ) { this.volume = (float) volume; if( audioLine != null ) { @@ -58,6 +61,7 @@ public class Music { gainControl.setValue(vol); } + @Override public void playOnce() { if( openLine() ) { TaskRunner.run(new Runnable() { @@ -69,23 +73,27 @@ public class Music { } } + @Override public void playOnceAndWait() { if( openLine() ) { stream(); } } + @Override public void loop() { looping = true; playOnce(); } + @Override public void stop() { playing = false; looping = false; dispose(); } + @Override public void dispose() { if( audioLine != null ) { if( audioLine.isRunning() ) { diff --git a/src/schule/ngb/zm/media/Sound.java b/src/schule/ngb/zm/media/Sound.java index 004207f..ceda917 100644 --- a/src/schule/ngb/zm/media/Sound.java +++ b/src/schule/ngb/zm/media/Sound.java @@ -7,7 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; -public class Sound { +public class Sound implements Audio { private boolean playing = false; @@ -19,7 +19,7 @@ public class Sound { private boolean disposeAfterPlay = false; - private float volume = 1.0f; + private float volume = 0.8f; public Sound( String source ) { this.audioSource = source;