Files
zeichenmaschine/src/main/java/schule/ngb/zm/media/Mixer.java
J. Neugebauer 20772da813 {@inheritDoc} Kommentare entfernt
Javadoc Kommentare, die nur {@inheritDoc} enthalten, sind redundant und wurden entfernt.
2022-12-10 11:06:51 +01:00

246 lines
6.1 KiB
Java

package schule.ngb.zm.media;
import schule.ngb.zm.Constants;
import schule.ngb.zm.util.events.EventDispatcher;
import schule.ngb.zm.util.tasks.TaskRunner;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Ein Mixer ist eine Sammlung mehrerer {@link Audio Audio-Medien}, die
* gemeinsam kontrolliert werden können.
* <p>
* 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.
* <p>
* Darüber hinaus kann ein Mixer Effekte wie einen
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
*/
@SuppressWarnings( "unused" )
public class Mixer implements Audio, AudioListener {
private List<AudioWrapper> audios;
private float volume = 0.8f;
EventDispatcher<Audio, AudioListener> eventDispatcher;
public Mixer() {
this.audios = new ArrayList<>(4);
}
public String getSource() {
return "";
}
private AudioWrapper findWrapper( Audio pAudio ) {
for( AudioWrapper aw: audios ) {
if( aw.audio == pAudio ) {
return aw;
}
}
return null;
}
public boolean contains( Audio pAudio ) {
return findWrapper(pAudio) != null;
}
public void add( Audio pAudio ) {
add(pAudio, 1);
}
/**
* Fügt ein Audio-Objekt dem Mixer mit dem angegebenen Lautstärke-Faktor
* hinzu.
* <p>
* Der Lautstärke-Faktor setzt die Lautstärke des Audio-Objektes relativ zur
* Lautstärke des Mixers. Bei einem Faktor von 1.0 wird die Lautstärke des
* Mixers übernommen. Bei einem Wert von 0.5 wird das Objekt halb so laut
* abgespielt. Auf diese Weise lässt sich die Lautstärke aller Audio-Objekte
* des Mixers gleichzeitig anpassen, während ihre relative Lautstärke
* zueinander gleich bleibt.
*
* @param pAudio Ein Audio-Objekt.
* @param pVolumeFactor Der Lautstärke-Faktor.
*/
public void add( Audio pAudio, double pVolumeFactor ) {
if( !contains(pAudio) ) {
audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
} else {
findWrapper(pAudio).volumeFactor = (float) pVolumeFactor;
}
pAudio.setVolume(pVolumeFactor * volume);
}
/**
* Entfernt die das angegebene Audio-Objekt aus dem Mixer. Ist das Objekt
* nicht Teil des Mixers, passiert nichts.
*
* @param pAudio Ein Audio-Objekt.
*/
public void remove( Audio pAudio ) {
Iterator<AudioWrapper> it = audios.listIterator();
while( it.hasNext() ) {
AudioWrapper aw = it.next();
if( aw.audio == pAudio ) {
it.remove();
break;
}
}
}
public void removeAll() {
audios.clear();
}
@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 volume ) {
this.volume = volume < 0 ? 0f : (float) volume;
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * volume));
}
@Override
public double getVolume() {
return volume;
}
@Override
public void play() {
audios.stream().forEach(aw -> aw.audio.play());
}
@Override
public void playAndWait() {
audios.stream().forEach(aw -> aw.audio.play());
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());
}
/**
* Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen
* Zeit schrittweise, bis die angegebene Lautstärke erreicht ist.
* <p>
* 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
public void run() {
final long start = System.currentTimeMillis();
double t = 0.0;
double from = volume;
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);
}
});
}
@Override
public void playbackStarted( Audio source ) {
if( eventDispatcher != null ) {
eventDispatcher.dispatchEvent("start", Mixer.this);
}
}
@Override
public void playbackStopped( Audio source ) {
if( !isPlaying() ) {
if( eventDispatcher != null ) {
eventDispatcher.dispatchEvent("stop", Mixer.this);
}
}
}
@Override
public void addAudioListener( AudioListener listener ) {
initializeEventDispatcher().addListener(listener);
}
@Override
public void removeAudioListener( AudioListener listener ) {
initializeEventDispatcher().removeListener(listener);
}
/**
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
* aufgerufen, sobald sich auch ein Listener registrieren möchte.
*
* @return
*/
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
if( eventDispatcher == null ) {
eventDispatcher = new EventDispatcher<>();
eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
}
return eventDispatcher;
}
class AudioWrapper {
Audio audio;
float volumeFactor;
public AudioWrapper( Audio audio, float volumeFactor ) {
this.audio = audio;
this.volumeFactor = volumeFactor;
}
}
}