All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gurkenlabs.litiengine.sound.SoundEngine Maven / Gradle / Ivy

The newest version!
package de.gurkenlabs.litiengine.sound;

import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.ILaunchable;
import de.gurkenlabs.litiengine.IUpdateable;
import de.gurkenlabs.litiengine.entities.IEntity;
import de.gurkenlabs.litiengine.resources.Resources;
import de.gurkenlabs.litiengine.tweening.TweenFunction;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.LineUnavailableException;

/**
 * This {@code SoundEngine} class provides all methods to play back sounds and music in your game.
 * It allows to define the 2D coordinates of the sound or even pass in the source entity of the
 * sound which will adjust the position according to the position of the entity.
 *
 * 

* The sound engine supports .wav, .mp3 and .ogg by default. If you need other file extensions, you * have to write an own SPI implementation and inject it in your project. */ public final class SoundEngine implements IUpdateable, ILaunchable { public static final int DEFAULT_MAX_DISTANCE = 150; static final ExecutorService EXECUTOR = Executors.newCachedThreadPool( new ThreadFactory() { private int id = 0; @Override public Thread newThread(Runnable r) { return new Thread(r, "Sound Playback Thread " + ++id); } }); private static final Logger log = Logger.getLogger(SoundEngine.class.getName()); private Point2D listenerLocation; private UnaryOperator listenerLocationCallback = old -> Game.world().camera().getFocus(); private int maxDist = DEFAULT_MAX_DISTANCE; private MusicPlayback music; private final Collection allMusic = ConcurrentHashMap.newKeySet(); private final Collection sounds = ConcurrentHashMap.newKeySet(); /** * You should never call this manually! Instead use the {@code Game.audio()} instance. * * @see Game#audio() */ public SoundEngine() { if (Game.audio() != null) { throw new UnsupportedOperationException( "Never initialize a SoundEngine manually. Use Game.audio() instead."); } } /** * Gets the maximum distance from the listener at which a sound source can still be heard. * * @return The maximum distance at which a sound can be heard. */ public int getMaxDistance() { return maxDist; } /** * Sets the currently playing track to a {@code LoopedTrack} with the sound defined by the * specified music name. This has no effect if the specified track is already playing. * * @param musicName The name of the {@code Sound} to be played. * @return The playback of the music */ public MusicPlayback playMusic(String musicName) { return playMusic(Resources.sounds().get(musicName)); } /** * Sets the currently playing track to a {@code LoopedTrack} with the specified music * {@code Sound}. This has no effect if the specified track is already playing. * * @param music The {@code Sound} to be played. * @return The playback of the music */ public MusicPlayback playMusic(Sound music) { return playMusic(new LoopedTrack(music)); } /** * Sets the currently playing track to the specified track. This has no effect if the specified * track is already playing. * * @param track The track to play * @return The playback of the music */ public MusicPlayback playMusic(Track track) { return playMusic(track, null, false, true); } /** * Sets the currently playing track to the specified track. * * @param track The track to play * @param restart Whether to restart if the specified track is already playing, determined by * {@link Object#equals(Object)} * @return The playback of the music */ public MusicPlayback playMusic(Track track, boolean restart) { return playMusic(track, null, restart, true); } /** * Plays the specified track. * * @param track The track to play * @param restart Whether to restart if the specified track is already playing, determined by * {@link Object#equals(Object)} * @param stop Whether to stop an existing track if present * @return The playback of the music */ public MusicPlayback playMusic(Track track, boolean restart, boolean stop) { return playMusic(track, null, restart, stop); } /** * Plays the specified track, optionally configuring it before starting. * * @param track The track to play * @param config A call to configure the playback prior to starting, which can be {@code null} * @param restart Whether to restart if the specified track is already playing, determined by * {@link Object#equals(Object)} * @param stop Whether to stop an existing track if present * @return The playback of the music */ public synchronized MusicPlayback playMusic( Track track, Consumer config, boolean restart, boolean stop) { if (!restart && music != null && music.isPlaying() && music.getTrack().equals(track)) { return music; } try { MusicPlayback playback = new MusicPlayback(track); if (config != null) { config.accept(playback); } if (stop) { stopMusic(); } allMusic.add(playback); playback.start(); music = playback; return playback; } catch (LineUnavailableException | IllegalArgumentException e) { resourceFailure(e); return null; } } /** * Fades out all music volume to 0 over the specified time. * * @param duration The fade duration in milliseconds */ public void fadeMusic(int duration) { this.allMusic.forEach(m -> m.fade(duration)); } /** * Fades all music volume to a given value over the specified time. * * @param duration The fade duration in milliseconds * @param target The target volume. * @param easeType The easing Function used for Tweening the volume */ public void fadeMusic(int duration, float target, TweenFunction easeType) { this.allMusic.forEach(m -> m.fade(duration, target, easeType)); } /** * Gets the "main" music that is playing. This usually means the last call to {@code playMusic}, * though if the music has been stopped it will be {@code null}. * * @return The main music, which could be {@code null}. */ public synchronized MusicPlayback getMusic() { return music; } /** * Gets a list of all music playbacks. * * @return A list of all music playbacks. */ public synchronized Collection getAllMusic() { return Collections.unmodifiableCollection(allMusic); } /** * Plays the specified sound and updates its volume and pan by the current entity location in * relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param sound The sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final IEntity entity) { return playSound(sound, entity, false); } /** * Plays a {@code Sound} with the specified name and updates its volume and pan by the current * entity location in relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param soundName The name of the sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, final IEntity entity) { return playSound(Resources.sounds().get(soundName), entity, false); } /** * Plays the specified sound and updates its volume and pan by the current entity location in * relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final IEntity entity, boolean loop) { return playSound(sound, entity, loop, getMaxDistance(), 1f); } /** * Plays the specified sound and updates its volume and pan by the current entity location in * relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final IEntity entity, boolean loop, int range) { return playSound(sound, entity::getCenter, loop, range, 1f); } /** * Plays the specified sound and updates its volume and pan by the current entity location in * relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @param volume The volume modifier for the sound playback instance. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound( final Sound sound, final IEntity entity, boolean loop, int range, float volume) { return playSound(sound, entity::getCenter, loop, range, volume); } /** * Plays a {@code Sound} with the specified name and updates its volume and pan by the current * entity location in relation to the listener location. * * @param entity The entity at which location the sound should be played. * @param soundName The name of the sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, final IEntity entity, boolean loop) { return playSound(Resources.sounds().get(soundName), entity, loop); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param location The location at which to play the sound. * @param sound The sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final Point2D location) { return playSound(sound, location, false); } /** * Plays a {@code Sound} with the specified name at the specified location and updates the volume * and pan in relation to the listener location. * * @param location The location at which to play the sound. * @param soundName The name of the sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, final Point2D location) { return playSound(Resources.sounds().get(soundName), location, false); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param x The x-coordinate of the location at which to play the sound. * @param y The y-coordinate of the location at which to play the sound. * @param sound The sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, double x, double y) { return playSound(sound, new Point2D.Double(x, y), false); } /** * Plays a {@code Sound} with the specified name at the specified location and updates the volume * and pan in relation to the listener location. * * @param x The x-coordinate of the location at which to play the sound. * @param y The y-coordinate of the location at which to play the sound. * @param soundName The name of the sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, double x, double y) { return playSound(Resources.sounds().get(soundName), new Point2D.Double(x, y), false); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param location The location at which to play the sound. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final Point2D location, boolean loop) { return playSound(sound, () -> location, loop, getMaxDistance(), 1f); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param location The location at which to play the sound. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final Point2D location, boolean loop, int range) { return playSound(sound, () -> location, loop, range, 1f); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param location The location at which to play the sound. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @param volume The volume modifier for the sound playback instance. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound( final Sound sound, final Point2D location, boolean loop, int range, float volume) { return playSound(sound, () -> location, loop, range, volume); } /** * Plays a {@code Sound} with the specified name at the specified location and updates the volume * and pan in relation to the listener location. * * @param location The location at which to play the sound. * @param soundName The name of the sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, final Point2D location, boolean loop) { return playSound(Resources.sounds().get(soundName), location, loop); } /** * Plays the specified sound at the specified location and updates the volume and pan in relation * to the listener location. * * @param x The x-coordinate of the location at which to play the sound. * @param y The y-coordinate of the location at which to play the sound. * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, final double x, final double y, boolean loop) { return playSound(sound, new Point2D.Double(x, y), loop); } /** * Plays a {@code Sound} with the specified name at the specified location and updates the volume * and pan in relation to the listener location. * * @param x The x-coordinate of the location at which to play the sound. * @param y The y-coordinate of the location at which to play the sound. * @param soundName The name of the sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound( final String soundName, final double x, final double y, boolean loop) { return playSound(Resources.sounds().get(soundName), new Point2D.Double(x, y), loop); } /** * Plays the specified sound with the volume configured in the SOUND config with a center pan. * * @param sound The sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound) { return playSound(sound, false); } /** * Plays a {@code Sound} with the specified name with the volume configured in the SOUND config * with a center pan. * * @param soundName The name of the sound to play. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName) { return playSound(Resources.sounds().get(soundName), false); } /** * Plays the specified sound with the volume configured in the SOUND config with a center pan. * * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, boolean loop) { return playSound(sound, () -> null, loop, getMaxDistance(), 1f); } /** * Plays the specified sound with the volume configured in the SOUND config with a center pan. * * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, boolean loop, int range) { return playSound(sound, () -> null, loop, range, 1f); } /** * Plays the specified sound with the volume configured in the SOUND config with a center pan. * * @param sound The sound to play. * @param loop Determines whether this playback should be looped or not. * @param range the range in pixels for which this sound can be heard * @param volume The volume modifier for the sound playback instance. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final Sound sound, boolean loop, int range, float volume) { return playSound(sound, () -> null, loop, range, volume); } /** * Plays a {@code Sound} with the specified name with the volume configured in the SOUND config * with a center pan. * * @param soundName The name of the sound to play. * @param loop Determines whether this playback should be looped or not. * @return An {@link SFXPlayback} instance that allows to further process and control the played * sound. */ public SFXPlayback playSound(final String soundName, boolean loop) { return playSound(Resources.sounds().get(soundName), loop); } /** * Sets the default maximum distance from the listener at which a sound source can still be heard. * If the distance between the sound source and the listener is greater than the specified value, * the volume is set to 0. * * @param radius The maximum distance at which sounds can still be heard. */ public void setMaxDistance(final int radius) { maxDist = radius; } /** * Stops the playback of the current background music. */ public synchronized void stopMusic() { for (MusicPlayback track : allMusic) { track.cancel(); } } /** * Creates an {@code SFXPlayback} object that can be configured prior to starting. Also allows for * a custom source supplier. * *

* Unlike the {@code playSound} methods, the {@code SFXPlayback} objects returned by this method * must be started using the {@link SoundPlayback#start()} method. However, necessary resources * are acquired immediately upon calling this method, and will remain in use until the * playback is either cancelled or finalized. * * @param sound The sound to play * @param supplier A function to get the sound's current source location (the sound is statically * positioned if the location is {@code null}) * @param loop Whether to loop the sound * @param range the range in pixels for which this sound can be heard * @param volume The volume modifier for the sound playback instance. * @return An {@code SFXPlayback} object that can be configured prior to starting, but will need * to be manually started. */ public SFXPlayback createSound(Sound sound, Supplier supplier, boolean loop, int range, float volume) { try { return new SFXPlayback(sound, supplier, loop, range, volume); } catch (LineUnavailableException | IllegalArgumentException e) { resourceFailure(e); return null; } } /** * This method allows to set the callback that is used by the SoundEngine to determine where the * listener location is. * *

* If not explicitly set, the SoundEngine uses the camera focus (center of the screen) as listener * location. * * @param callback The callback that determines the location of the sound listener. */ public void setListenerLocationCallback(UnaryOperator callback) { listenerLocationCallback = callback; } @Override public void start() { listenerLocation = Game.world().camera().getFocus(); } @Override public void terminate() { if (music != null && music.isPlaying()) { music.cancel(); music = null; } EXECUTOR.shutdown(); synchronized (sounds) { for (SFXPlayback playback : sounds) { playback.cancel(); } sounds.clear(); } } @Override public void update() { listenerLocation = listenerLocationCallback.apply(listenerLocation); Iterator iter = sounds.iterator(); while (iter.hasNext()) { SFXPlayback s = iter.next(); if (s.isPlaying()) { s.updateLocation(listenerLocation); } else { iter.remove(); } } Iterator iter2 = allMusic.iterator(); while (iter.hasNext()) { MusicPlayback s = iter2.next(); if (s.isPlaying()) { s.setMusicVolume(Game.config().sound().getMusicVolume()); } else { iter.remove(); } } if (music != null) { if (music.isPlaying()) { music.setMusicVolume(Game.config().sound().getMusicVolume()); } else { music = null; } } } Point2D getListenerLocation() { return this.listenerLocation == null ? new Point2D.Double(0, 0) : (Point2D) this.listenerLocation.clone(); } void addSound(SFXPlayback playback) { this.sounds.add(playback); } private SFXPlayback playSound( Sound sound, Supplier supplier, boolean loop, int range, float volume) { if (sound == null) { return null; } SFXPlayback playback = createSound(sound, supplier, loop, range, volume); if (playback == null) { return null; } playback.start(); return playback; } private static void resourceFailure(Throwable e) { log.log(Level.WARNING, "could not open a line", e); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy