com.github.mathiewz.slick.openal.SoundStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modernized-slick Show documentation
Show all versions of modernized-slick Show documentation
The main purpose of this libraryis to modernize and maintain the slick2D library.
The newest version!
package com.github.mathiewz.slick.openal;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import org.lwjgl.BufferUtils;
import org.lwjgl.Sys;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.OpenALException;
import com.github.mathiewz.slick.SlickException;
import com.github.mathiewz.slick.util.Log;
import com.github.mathiewz.slick.util.ResourceLoader;
/**
* Responsible for holding and playing the sounds used in the game.
*
* @author Kevin Glass
* @author Rockstar setVolume cleanup
*/
public class SoundStore {
/** The single instance of this class */
private static SoundStore store = new SoundStore();
/** True if sound effects are turned on */
private boolean sounds;
/** True if music is turned on */
private boolean music;
/** True if sound initialisation succeeded */
private boolean soundWorks;
/** The number of sound sources enabled - default 8 */
private int sourceCount;
/** The map of references to IDs of previously loaded sounds */
private final HashMap loaded = new HashMap<>();
/** The ID of the buffer containing the music currently being played */
private int currentMusic = -1;
/** The OpenGL AL sound sources in use */
private IntBuffer sources;
/** True if the sound system has been initialise */
private boolean inited = false;
/** The MODSound to be updated */
private MODSound mod;
/** The stream to be updated */
private OpenALStreamPlayer stream;
/** The global music volume setting */
private float musicVolume = 1.0f;
/** The global sound fx volume setting */
private float soundVolume = 1.0f;
/** The volume given for the last current music */
private float lastCurrentMusicVolume = 1.0f;
/** True if the music is paused */
private boolean paused;
/** True if we're returning deferred versions of resources */
private boolean deferred;
/** The buffer used to set the velocity of a source */
private final FloatBuffer sourceVel = BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });
/** The buffer used to set the position of a source */
private final FloatBuffer sourcePos = BufferUtils.createFloatBuffer(3);
/** The maximum number of sources */
private int maxSources = 64;
/**
* Create a new sound store
*/
private SoundStore() {
}
/**
* Clear out the sound store contents
*/
public void clear() {
store = new SoundStore();
}
/**
* Disable use of the Sound Store
*/
public void disable() {
inited = true;
}
/**
* True if we should only record the request to load in the intention
* of loading the sound later
*
* @param deferred
* True if the we should load a token
*/
public void setDeferredLoading(boolean deferred) {
this.deferred = deferred;
}
/**
* Check if we're using deferred loading
*
* @return True if we're loading deferred sounds
*/
public boolean isDeferredLoading() {
return deferred;
}
/**
* Inidicate whether music should be playing
*
* @param music
* True if music should be played
*/
public void setMusicOn(boolean music) {
if (soundWorks) {
this.music = music;
if (music) {
restartLoop();
setMusicVolume(musicVolume);
} else {
pauseLoop();
}
}
}
/**
* Check if music should currently be playing
*
* @return True if music is currently playing
*/
public boolean isMusicOn() {
return music;
}
/**
* Set the music volume
*
* @param volume
* The volume for music
*/
public void setMusicVolume(float volume) {
if (volume < 0) {
volume = 0;
}
if (volume > 1) {
volume = 1;
}
musicVolume = volume;
if (soundWorks) {
AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
}
}
/**
* Get the volume scalar of the music that is currently playing.
*
* @return The volume of the music currently playing
*/
public float getCurrentMusicVolume() {
return lastCurrentMusicVolume;
}
/**
* Set the music volume of the current playing music. Does NOT affect the global volume
*
* @param volume
* The volume for the current playing music
*/
public void setCurrentMusicVolume(float volume) {
if (volume < 0) {
volume = 0;
}
if (volume > 1) {
volume = 1;
}
if (soundWorks) {
lastCurrentMusicVolume = volume;
AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
}
}
/**
* Set the sound volume
*
* @param volume
* The volume for sound fx
*/
public void setSoundVolume(float volume) {
if (volume < 0) {
volume = 0;
}
soundVolume = volume;
}
/**
* Check if sound works at all
*
* @return True if sound works at all
*/
public boolean soundWorks() {
return soundWorks;
}
/**
* Check if music is currently enabled
*
* @return True if music is currently enabled
*/
public boolean musicOn() {
return music;
}
/**
* Get the volume for sounds
*
* @return The volume for sounds
*/
public float getSoundVolume() {
return soundVolume;
}
/**
* Get the volume for music
*
* @return The volume for music
*/
public float getMusicVolume() {
return musicVolume;
}
/**
* Get the ID of a given source
*
* @param index
* The ID of a given source
* @return The ID of the given source
*/
public int getSource(int index) {
if (!soundWorks) {
return -1;
}
if (index < 0) {
return -1;
}
return sources.get(index);
}
/**
* Indicate whether sound effects should be played
*
* @param sounds
* True if sound effects should be played
*/
public void setSoundsOn(boolean sounds) {
if (soundWorks) {
this.sounds = sounds;
}
}
/**
* Check if sound effects are currently enabled
*
* @return True if sound effects are currently enabled
*/
public boolean soundsOn() {
return sounds;
}
/**
* Set the maximum number of concurrent sound effects that will be
* attempted
*
* @param max
* The maximum number of sound effects/music to mix
*/
public void setMaxSources(int max) {
maxSources = max;
}
/**
* Initialise the sound effects stored. This must be called
* before anything else will work
*/
public void init() {
if (inited) {
return;
}
Log.info("Initialising sounds..");
inited = true;
AccessController.doPrivileged((PrivilegedAction) () -> {
try {
AL.create();
soundWorks = true;
sounds = true;
music = true;
Log.info("- Sound works");
} catch (Exception e) {
Log.error("Sound initialisation failure.");
Log.error(e);
soundWorks = false;
sounds = false;
music = false;
}
return null;
});
if (soundWorks) {
sourceCount = 0;
sources = BufferUtils.createIntBuffer(maxSources);
while (AL10.alGetError() == AL10.AL_NO_ERROR) {
IntBuffer temp = BufferUtils.createIntBuffer(1);
try {
AL10.alGenSources(temp);
if (AL10.alGetError() == AL10.AL_NO_ERROR) {
sourceCount++;
sources.put(temp.get(0));
if (sourceCount > maxSources - 1) {
break;
}
}
} catch (OpenALException e) {
// expected at the end
break;
}
}
Log.info("- " + sourceCount + " OpenAL source available");
if (AL10.alGetError() != AL10.AL_NO_ERROR) {
sounds = false;
music = false;
soundWorks = false;
Log.error("- AL init failed");
} else {
FloatBuffer listenerOri = BufferUtils.createFloatBuffer(6).put(new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f });
FloatBuffer listenerVel = BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });
FloatBuffer listenerPos = BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });
listenerPos.flip();
listenerVel.flip();
listenerOri.flip();
AL10.alListener(AL10.AL_POSITION, listenerPos);
AL10.alListener(AL10.AL_VELOCITY, listenerVel);
AL10.alListener(AL10.AL_ORIENTATION, listenerOri);
Log.info("- Sounds source generated");
}
}
}
/**
* Stop a particular sound source
*
* @param index
* The index of the source to stop
*/
void stopSource(int index) {
AL10.alSourceStop(sources.get(index));
}
/**
* Play the specified buffer as a sound effect with the specified
* pitch and gain.
*
* @param buffer
* The ID of the buffer to play
* @param pitch
* The pitch to play at
* @param gain
* The gain to play at
* @param loop
* True if the sound should loop
* @return source The source that will be used
*/
int playAsSound(int buffer, float pitch, float gain, boolean loop) {
return playAsSoundAt(buffer, pitch, gain, loop, 0, 0, 0);
}
/**
* Play the specified buffer as a sound effect with the specified
* pitch and gain.
*
* @param buffer
* The ID of the buffer to play
* @param pitch
* The pitch to play at
* @param gain
* The gain to play at
* @param loop
* True if the sound should loop
* @param x
* The x position to play the sound from
* @param y
* The y position to play the sound from
* @param z
* The z position to play the sound from
* @return source The source that will be used
*/
int playAsSoundAt(int buffer, float pitch, float gain, boolean loop, float x, float y, float z) {
gain *= soundVolume;
if (gain == 0) {
gain = 0.001f;
}
if (soundWorks) {
if (sounds) {
int nextSource = findFreeSource();
if (nextSource == -1) {
return -1;
}
AL10.alSourceStop(sources.get(nextSource));
AL10.alSourcei(sources.get(nextSource), AL10.AL_BUFFER, buffer);
AL10.alSourcef(sources.get(nextSource), AL10.AL_PITCH, pitch);
AL10.alSourcef(sources.get(nextSource), AL10.AL_GAIN, gain);
AL10.alSourcei(sources.get(nextSource), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);
sourcePos.clear();
sourceVel.clear();
sourceVel.put(new float[] { 0, 0, 0 });
sourcePos.put(new float[] { x, y, z });
sourcePos.flip();
sourceVel.flip();
AL10.alSource(sources.get(nextSource), AL10.AL_POSITION, sourcePos);
AL10.alSource(sources.get(nextSource), AL10.AL_VELOCITY, sourceVel);
AL10.alSourcePlay(sources.get(nextSource));
return nextSource;
}
}
return -1;
}
/**
* Check if a particular source is playing
*
* @param index
* The index of the source to check
* @return True if the source is playing
*/
boolean isPlaying(int index) {
int state = AL10.alGetSourcei(sources.get(index), AL10.AL_SOURCE_STATE);
return state == AL10.AL_PLAYING;
}
/**
* Find a free sound source
*
* @return The index of the free sound source
*/
private int findFreeSource() {
for (int i = 1; i < sourceCount - 1; i++) {
int state = AL10.alGetSourcei(sources.get(i), AL10.AL_SOURCE_STATE);
if (state != AL10.AL_PLAYING && state != AL10.AL_PAUSED) {
return i;
}
}
return -1;
}
/**
* Play the specified buffer as music (i.e. use the music channel)
*
* @param buffer
* The buffer to be played
* @param pitch
* The pitch to play the music at
* @param gain
* The gaing to play the music at
* @param loop
* True if we should loop the music
*/
void playAsMusic(int buffer, float pitch, float gain, boolean loop) {
paused = false;
setMOD(null);
if (soundWorks) {
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
AL10.alSourcei(sources.get(0), AL10.AL_BUFFER, buffer);
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
AL10.alSourcei(sources.get(0), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);
currentMusic = sources.get(0);
if (!music) {
pauseLoop();
} else {
AL10.alSourcePlay(sources.get(0));
}
}
}
/**
* Get the OpenAL source used for music
*
* @return The open al source used for music
*/
private int getMusicSource() {
return sources.get(0);
}
/**
* Set the pitch at which the current music is being played
*
* @param pitch
* The pitch at which the current music is being played
*/
public void setMusicPitch(float pitch) {
if (soundWorks) {
AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
}
}
/**
* Pause the music loop that is currently playing
*/
public void pauseLoop() {
if (soundWorks && currentMusic != -1) {
paused = true;
AL10.alSourcePause(currentMusic);
}
}
/**
* Restart the music loop that is currently paused
*/
public void restartLoop() {
if (music && soundWorks && currentMusic != -1) {
paused = false;
AL10.alSourcePlay(currentMusic);
}
}
/**
* Check if the supplied player is currently being polled by this
* sound store.
*
* @param player
* The player to check
* @return True if this player is currently in use by this sound store
*/
boolean isPlaying(OpenALStreamPlayer player) {
return stream == player;
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param ref
* The refernece to the mod to load
* @return The sound for play back
* @throws IOException
* Indicates a failure to read the data
*/
public Audio getMOD(String ref) throws IOException {
return getMOD(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param in
* The stream to the MOD to load
* @return The sound for play back
* @throws IOException
* Indicates a failure to read the data
*/
public Audio getMOD(InputStream in) throws IOException {
return getMOD(in.toString(), in);
}
/**
* Get a MOD sound (mod/xm etc)
*
* @param ref
* The stream to the MOD to load
* @param in
* The stream to the MOD to load
* @return The sound for play back
* @throws IOException
* Indicates a failure to read the data
*/
public Audio getMOD(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new SlickException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.MOD);
}
return new MODSound(this, in);
}
/**
* Get the Sound based on a specified AIF file
*
* @param ref
* The reference to the AIF file in the classpath
* @return The Sound read from the AIF file
* @throws IOException
* Indicates a failure to load the AIF
*/
public Audio getAIF(String ref) throws IOException {
return getAIF(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified AIF file
*
* @param in
* The stream to the MOD to load
* @return The Sound read from the AIF file
* @throws IOException
* Indicates a failure to load the AIF
*/
public Audio getAIF(InputStream in) throws IOException {
return getAIF(in.toString(), in);
}
/**
* Get the Sound based on a specified AIF file
*
* @param ref
* The reference to the AIF file in the classpath
* @param in
* The stream to the AIF to load
* @return The Sound read from the AIF file
* @throws IOException
* Indicates a failure to load the AIF
*/
public Audio getAIF(String ref, InputStream in) throws IOException {
in = new BufferedInputStream(in);
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new SlickException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.AIF);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = loaded.get(ref).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
AiffData data = AiffData.create(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
loaded.put(ref, new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
IOException x = new IOException("Failed to load: " + ref);
x.initCause(e);
throw x;
}
}
if (buffer == -1) {
throw new IOException("Unable to load: " + ref);
}
return new AudioImpl(this, buffer);
}
/**
* Get the Sound based on a specified WAV file
*
* @param ref
* The reference to the WAV file in the classpath
* @return The Sound read from the WAV file
* @throws IOException
* Indicates a failure to load the WAV
*/
public Audio getWAV(String ref) throws IOException {
return getWAV(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified WAV file
*
* @param in
* The stream to the WAV to load
* @return The Sound read from the WAV file
* @throws IOException
* Indicates a failure to load the WAV
*/
public Audio getWAV(InputStream in) throws IOException {
return getWAV(in.toString(), in);
}
/**
* Get the Sound based on a specified WAV file
*
* @param ref
* The reference to the WAV file in the classpath
* @param in
* The stream to the WAV to load
* @return The Sound read from the WAV file
* @throws IOException
* Indicates a failure to load the WAV
*/
public Audio getWAV(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new SlickException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.WAV);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = loaded.get(ref).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
WaveData data = WaveData.create(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);
loaded.put(ref, new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
IOException x = new IOException("Failed to load: " + ref);
x.initCause(e);
throw x;
}
}
if (buffer == -1) {
throw new IOException("Unable to load: " + ref);
}
return new AudioImpl(this, buffer);
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref
* The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException
* Indicates a failure to load the OGG
*/
public Audio getOggStream(String ref) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
setMOD(null);
setStream(null);
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
currentMusic = sources.get(0);
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref
* The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException
* Indicates a failure to load the OGG
*/
public Audio getOggStream(URL ref) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
setMOD(null);
setStream(null);
if (currentMusic != -1) {
AL10.alSourceStop(sources.get(0));
}
getMusicSource();
currentMusic = sources.get(0);
return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref
* The reference to the OGG file in the classpath
* @return The Sound read from the OGG file
* @throws IOException
* Indicates a failure to load the OGG
*/
public Audio getOgg(String ref) throws IOException {
return getOgg(ref, ResourceLoader.getResourceAsStream(ref));
}
/**
* Get the Sound based on a specified OGG file
*
* @param in
* The stream to the OGG to load
* @return The Sound read from the OGG file
* @throws IOException
* Indicates a failure to load the OGG
*/
public Audio getOgg(InputStream in) throws IOException {
return getOgg(in.toString(), in);
}
/**
* Get the Sound based on a specified OGG file
*
* @param ref
* The reference to the OGG file in the classpath
* @param in
* The stream to the OGG to load
* @return The Sound read from the OGG file
* @throws IOException
* Indicates a failure to load the OGG
*/
public Audio getOgg(String ref, InputStream in) throws IOException {
if (!soundWorks) {
return new NullAudio();
}
if (!inited) {
throw new SlickException("Can't load sounds until SoundStore is init(). Use the container init() method.");
}
if (deferred) {
return new DeferredSound(ref, in, DeferredSound.OGG);
}
int buffer = -1;
if (loaded.get(ref) != null) {
buffer = loaded.get(ref).intValue();
} else {
try {
IntBuffer buf = BufferUtils.createIntBuffer(1);
OggDecoder decoder = new OggDecoder();
OggData ogg = decoder.getData(in);
AL10.alGenBuffers(buf);
AL10.alBufferData(buf.get(0), ogg.channels > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16, ogg.data, ogg.rate);
loaded.put(ref, new Integer(buf.get(0)));
buffer = buf.get(0);
} catch (Exception e) {
Log.error(e);
Sys.alert("Error", "Failed to load: " + ref + " - " + e.getMessage());
throw new IOException("Unable to load: " + ref);
}
}
if (buffer == -1) {
throw new IOException("Unable to load: " + ref);
}
return new AudioImpl(this, buffer);
}
/**
* Set the mod thats being streamed if any
*
* @param sound
* The mod being streamed
*/
void setMOD(MODSound sound) {
if (!soundWorks) {
return;
}
currentMusic = sources.get(0);
stopSource(0);
mod = sound;
if (sound != null) {
stream = null;
}
paused = false;
}
/**
* Set the stream being played
*
* @param stream
* The stream being streamed
*/
void setStream(OpenALStreamPlayer stream) {
if (!soundWorks) {
return;
}
currentMusic = sources.get(0);
this.stream = stream;
if (stream != null) {
mod = null;
}
paused = false;
}
/**
* Poll the streaming system
*
* @param delta
* The amount of time passed since last poll (in milliseconds)
*/
public void poll(int delta) {
if (!soundWorks) {
return;
}
if (paused) {
return;
}
if (music) {
if (mod != null) {
try {
mod.poll();
} catch (OpenALException e) {
Log.error("Error with OpenGL MOD Player on this this platform");
Log.error(e);
mod = null;
}
}
if (stream != null) {
try {
stream.update();
} catch (OpenALException e) {
Log.error("Error with OpenGL Streaming Player on this this platform");
Log.error(e);
mod = null;
}
}
}
}
/**
* Check if the music is currently playing
*
* @return True if the music is playing
*/
public boolean isMusicPlaying() {
if (!soundWorks) {
return false;
}
int state = AL10.alGetSourcei(sources.get(0), AL10.AL_SOURCE_STATE);
return state == AL10.AL_PLAYING || state == AL10.AL_PAUSED;
}
/**
* Get the single instance of this class
*
* @return The single instnace of this class
*/
public static SoundStore get() {
return store;
}
/**
* Stop a playing sound identified by the ID returned from playing. This utility method
* should only be used when needing to stop sound effects that may have been played
* more than once and need to be explicitly stopped.
*
* @param id
* The ID of the underlying OpenAL source as returned from playAsSoundEffect
*/
public void stopSoundEffect(int id) {
AL10.alSourceStop(id);
}
/**
* Retrieve the number of OpenAL sound sources that have been
* determined at initialisation.
*
* @return The number of sources available
*/
public int getSourceCount() {
return sourceCount;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy