/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.shavenpuppy.jglib.sound; import org.lwjgl.openal.AL10; import org.lwjgl.openal.OpenALException; import com.shavenpuppy.jglib.interpolators.LinearInterpolator; import com.shavenpuppy.jglib.openal.ALBuffer; import com.shavenpuppy.jglib.openal.ALSource; import com.shavenpuppy.jglib.openal.ALStream; import com.shavenpuppy.jglib.openal.ALStreamInstance; import com.shavenpuppy.jglib.util.PriorityPooled; import static org.lwjgl.openal.AL10.*; /** * A sound effect */ public final class SoundEffect implements PriorityPooled { final ALSource source = new ALSource(); private int priority; private Object owner; private SoundEffect link; private boolean locked; private boolean updatePosition; private boolean updateVelocity; private boolean updateGain; private boolean updatePitch; private boolean updateState; private boolean updateBuffer; private boolean updateLooping; private boolean updateAttenuated; private float x, y, z, dx, dy, dz, gain, pitch; boolean looped; boolean attenuated; int fadeTick; int fadeDuration; float initialGain, finalGain; ALBuffer buffer; ALStreamInstance stream; int fadeType; private static final int FADE_NONE = 0; private static final int FADE_IN = 1; private static final int FADE_OUT = 2; private int state = -1; private static final int DO_PLAY = 0; private static final int DO_PAUSE = 1; private static final int DO_REWIND = 2; private static final int DO_STOP = 3; private final SoundPlayer player; // Constructor SoundEffect(SoundPlayer player) { this.player = player; } private void doInit() { x = 0; y = 0; z = 0; dx = 0; dy = 0; dz = 0; state = DO_PLAY; fadeType = FADE_NONE; fadeTick = 0; attenuated = true; updatePosition = true; updateVelocity = true; updateGain = true; updatePitch = true; updateState = true; updateLooping = true; updateAttenuated = true; source.set(AL_REFERENCE_DISTANCE, 1024.0f); source.set(AL_ROLLOFF_FACTOR, 1.0f); source.set(AL_MIN_GAIN, 0.0f); source.set(AL_MAX_GAIN, 1.0f); if (link != null) { link.deactivate(); link = null; } } /** * Initialise the sound effect with a stream * * @param stream * The sound stream */ void init(ALStream stream) throws Exception { this.buffer = null; this.stream = stream.getInstance(source); pitch = stream.getPitch(); gain = stream.getGain(); looped = stream.isLooped(); doInit(); } /** * Initialise this sound effect * * @param buf * The sound buffer */ void init(ALBuffer buf) { this.buffer = buf; this.stream = null; updateBuffer = true; pitch = buffer.getPitch(); gain = buffer.getGain(); looped = buffer.isLooped(); doInit(); } /** * Update is called every frame by Player.play(), and takes note of any * changes the user has specified. */ void update() { try { // If this is a stream and we're no longer the owner, quietly // relinquish the // stream if (stream != null && stream.getOwner() != source) { System.out.println(this + " lost ownership of stream " + stream + " to " + stream.getOwner()); stream = null; deactivate(); return; } // Do fades switch (fadeType) { case FADE_IN: gain = LinearInterpolator.instance.interpolate(initialGain, finalGain, (float) fadeTick / (float) fadeDuration); fadeTick++; updateGain = true; if (fadeTick >= fadeDuration) { fadeType = FADE_NONE; } break; case FADE_OUT: gain = LinearInterpolator.instance.interpolate(initialGain, finalGain, (float) fadeTick / (float) fadeDuration); fadeTick++; updateGain = true; if (fadeTick >= fadeDuration) { fadeType = FADE_NONE; state = DO_STOP; updateState = true; } break; case FADE_NONE: break; default: assert false; } if (updatePosition) { source.set(AL_POSITION, x, y, z); updatePosition = false; } if (updateVelocity) { source.set(AL_VELOCITY, dx, dy, dz); updateVelocity = false; } if (updatePitch) { source.set(AL_PITCH, pitch); updatePitch = false; } if (updateLooping) { source.setLooped(looped); updateLooping = false; } if (updateAttenuated) { source.set(AL_ROLLOFF_FACTOR, attenuated ? 1.0f : 0.0f); updateAttenuated = false; } if (updateGain) { source.set(AL10.AL_GAIN, gain); updateGain = false; } if (updateBuffer) { if (buffer != null) { source.attach(buffer); } updateBuffer = false; } if (updateState) { switch (state) { case SoundEffect.DO_PLAY: if (stream != null) { stream.setPlaying(true); player.registerStream(stream); } else { source.play(); } break; case SoundEffect.DO_PAUSE: source.pause(); break; case SoundEffect.DO_REWIND: source.rewind(); break; case SoundEffect.DO_STOP: if (stream != null) { player.deregisterStream(stream); stream.setPlaying(false); deactivate(); return; } else { source.stop(); } break; default: assert false; } } // Query the source to see if it has finished playing if (!updateState) { if (buffer != null) { int sourceState = source.getInt(AL_SOURCE_STATE); if (sourceState == AL_STOPPED || sourceState == AL_INITIAL) { // System.out.println("Deactivating "+buffer+" state is "+AL.recode(sourceState)); deactivate(); } } else if (stream != null && !stream.isPlaying()) { deactivate(); } } } catch (OpenALException e) { deactivate(); } updateState = false; } public void setPosition(float x, float y, float z, Object owner) { if (!isOwnedBy(owner)) { return; } this.x = x; this.y = y; this.z = z; updatePosition = true; } public void setVelocity(float dx, float dy, float dz, Object owner) { if (!isOwnedBy(owner)) { return; } this.dx = dx; this.dy = dy; this.dz = dz; updateVelocity = true; } public void setGain(float gain, Object owner) { if (!isOwnedBy(owner)) { return; } this.gain = gain; updateGain = true; } public void setPitch(float pitch, Object owner) { if (!isOwnedBy(owner)) { return; } this.pitch = pitch; updatePitch = true; } public void play(Object owner) { if (!isOwnedBy(owner)) { return; } state = SoundEffect.DO_PLAY; updateState = true; } public void pause(Object owner) { if (!isOwnedBy(owner)) { return; } state = SoundEffect.DO_PAUSE; updateState = true; } public void stop(Object owner) { if (!isOwnedBy(owner)) { return; } state = SoundEffect.DO_STOP; updateState = true; } public void rewind(Object owner) { if (!isOwnedBy(owner)) { return; } state = SoundEffect.DO_REWIND; updateState = true; } public void setLooped(boolean looped, Object owner) { if (!isOwnedBy(owner)) { return; } this.looped = looped; updateLooping = true; } public void setAttenuated(boolean attenuated, Object owner) { if (!isOwnedBy(owner)) { return; } this.attenuated = attenuated; updateAttenuated = true; } public void setFade(int duration, float finalGain, boolean stopAtEnd, Object owner) { if (!isOwnedBy(owner)) { return; } this.fadeDuration = duration; this.initialGain = gain; this.finalGain = finalGain; fadeTick = 0; if (stopAtEnd) { fadeType = FADE_OUT; } else { fadeType = FADE_IN; } } public boolean isOwnedBy(Object owner) { return this.owner == owner; } @Override public String toString() { if (buffer != null) { return "SoundEffect[source=" + source + " buffer=" + buffer + " owner=" + owner + "]"; } else if (stream != null) { return "SoundEffect[source=" + source + " stream=" + stream + " owner=" + owner + "]"; } else { return "SoundEffect[source=" + source + " owner=" + owner + "]"; } } /** * @see com.shavenpuppy.jglib.util.PriorityPooled#isActive() */ @Override public boolean isActive() { return locked || (buffer != null || stream != null) && owner != null; } /* * (non-Javadoc) * * @see com.shavenpuppy.jglib.util.PriorityPooled#lock() */ @Override public void lock() { locked = true; } /* * (non-Javadoc) * * @see com.shavenpuppy.jglib.util.PriorityPooled#isLocked() */ @Override public boolean isLocked() { return locked; } /* * (non-Javadoc) * * @see com.shavenpuppy.jglib.util.PriorityPooled#unlock() */ @Override public void unlock() { locked = false; } /** * @see com.shavenpuppy.jglib.util.PriorityPooled#allocate(java.lang.Object) */ @Override public void allocate(Object owner) { this.owner = owner; } /** * @see com.shavenpuppy.jglib.util.PriorityPooled#getOwner() */ @Override public Object getOwner() { return owner; } /** * @see com.shavenpuppy.jglib.util.PriorityPooled#deactivate() */ @Override public void deactivate() { try { buffer = null; if (stream != null) { player.deregisterStream(stream); stream.setPlaying(false); stream = null; } owner = null; source.stop(); } catch (OpenALException e) { // Silently ignore } unlock(); if (link != null) { link.deactivate(); link = null; } } /** * @see com.shavenpuppy.jglib.util.PriorityPooled#tick() */ @Override public void tick() { update(); } /** * Get the buffer * * @return the buffer */ public ALBuffer getBuffer() { return buffer; } /** * Get the stream * * @return the stream */ public synchronized ALStreamInstance getStream() { return stream; } /** * Get the source * * @return the source */ ALSource getSource() { return source; } /** * @param link * the linked to set */ void setLink(SoundEffect link) { this.link = link; if (link != null) { link.lock(); } } /* * (non-Javadoc) * * @see com.shavenpuppy.jglib.util.PriorityPooled#getPriority() */ @Override public int getPriority() { return priority; } /* * (non-Javadoc) * * @see com.shavenpuppy.jglib.util.PriorityPooled#setPriority(int) */ @Override public void setPriority(int newPriority) { this.priority = newPriority; } }