
com.threerings.openal.Sound Maven / Gradle / Ivy
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import org.lwjgl.openal.AL10;
/**
* Represents an instance of a sound clip which can be positioned in 3D space, gain and pitch
* adjusted and played or looped.
*/
public class Sound
{
/**
* Used to await notification of the starting of a sound which may be delayed in loading.
*/
public interface StartObserver
{
/**
* Called when the specified sound has started playing. If sound is null then the sound
* failed to play but soundStarted was called anyway to perform whatever actions were
* waiting on the sound.
*/
public void soundStarted (Sound sound);
}
/**
* Returns a reference to the group to which the sound belongs.
*/
public SoundGroup getGroup ()
{
return _group;
}
/**
* Returns the buffer of audio data associated with this sound.
*/
public ClipBuffer getBuffer ()
{
return _buffer;
}
/**
* Sets the position of the sound.
*/
public void setPosition (float x, float y, float z)
{
if (_source != null) {
_source.setPosition(x, y, z);
}
_px = x;
_py = y;
_pz = z;
}
/**
* Sets the velocity of the sound.
*/
public void setVelocity (float x, float y, float z)
{
if (_source != null) {
_source.setVelocity(x, y, z);
}
_vx = x;
_vy = y;
_vz = z;
}
/**
* Sets the gain of the sound (which will be multiplied by the base gain).
*/
public void setGain (float gain)
{
_gain = gain;
updateSourceGain();
}
/**
* Sets whether or not the position, velocity, etc., of the sound are relative to the
* listener.
*/
public void setSourceRelative (boolean relative)
{
if (_source != null) {
_source.setSourceRelative(relative);
}
_sourceRelative = relative;
}
/**
* Sets the minimum gain.
*/
public void setMinGain (float gain)
{
if (_source != null) {
_source.setMinGain(gain);
}
_minGain = gain;
}
/**
* Sets the maximum gain.
*/
public void setMaxGain (float gain)
{
if (_source != null) {
_source.setMaxGain(gain);
}
_maxGain = gain;
}
/**
* Sets the reference distance for attenuation.
*/
public void setReferenceDistance (float distance)
{
if (_source != null) {
_source.setReferenceDistance(distance);
}
_referenceDistance = distance;
}
/**
* Sets the rolloff factor for attenuation.
*/
public void setRolloffFactor (float rolloff)
{
if (_source != null) {
_source.setRolloffFactor(rolloff);
}
_rolloffFactor = rolloff;
}
/**
* Sets the maximum distance for attenuation.
*/
public void setMaxDistance (float distance)
{
if (_source != null) {
_source.setMaxDistance(distance);
}
_maxDistance = distance;
}
/**
* Sets the pitch multiplier.
*/
public void setPitch (float pitch)
{
if (_source != null) {
_source.setPitch(pitch);
}
_pitch = pitch;
}
/**
* Sets the direction of the sound.
*/
public void setDirection (float x, float y, float z)
{
if (_source != null) {
_source.setDirection(x, y, z);
}
_dx = x;
_dy = y;
_dz = z;
}
/**
* Sets the inside angle of the sound cone.
*/
public void setConeInnerAngle (float angle)
{
if (_source != null) {
_source.setConeInnerAngle(angle);
}
_coneInnerAngle = angle;
}
/**
* Sets the outside angle of the sound cone.
*/
public void setConeOuterAngle (float angle)
{
if (_source != null) {
_source.setConeOuterAngle(angle);
}
_coneOuterAngle = angle;
}
/**
* Sets the gain outside of the sound cone.
*/
public void setConeOuterGain (float gain)
{
if (_source != null) {
_source.setConeOuterGain(gain);
}
_coneOuterGain = gain;
}
/**
* Plays this sound from the beginning. While the sound is playing, an audio channel will be
* locked and then freed when the sound completes.
*
* @param allowDefer if false, the sound will be played immediately or not at all. If true,
* the sound will be queued up for loading if it is currently flushed from the cache and
* played once loaded.
*
* @return true if the sound could be played and was started (or queued up to be loaded and
* played ASAP if it was specified as deferrable) or false if the sound could not be played
* either because it was not ready and deferral was not allowed or because too many other
* sounds were playing concurrently.
*/
public boolean play (boolean allowDefer)
{
return play(allowDefer, false, null);
}
/**
* Loops this sound, starting from the beginning of the audio data. It will continue to loop
* until {@link #pause}d or {@link #stop}ped. While the sound is playing an audio channel will
* be locked.
*
* @return true if a channel could be obtained to play the sound (and the sound was thus
* started) or false if no channels were available.
*/
public boolean loop (boolean allowDefer)
{
return play(allowDefer, true, null);
}
/**
* Plays this sound from the beginning, notifying the supplied observer when the audio starts.
*
* @param loop whether or not to loop the sampe until {@link #stop}ped.
*/
public boolean play (StartObserver obs, boolean loop)
{
return play(true, loop, obs);
}
/**
* Pauses this sound. A subsequent call to {@link #play} will resume the sound from the
* precise position that it left off. While the sound is paused, its audio channel will remain
* locked.
*/
public void pause ()
{
_stateDesired = AL10.AL_PAUSED;
if (_source != null) {
_source.pause();
}
}
/**
* Stops this sound and rewinds to its beginning. The audio channel being used to play the
* sound will be released.
*/
public void stop ()
{
_stateDesired = AL10.AL_STOPPED;
if (_source != null) {
_source.stop();
}
}
/**
* Called to check if this sound is currently playing.
*/
public boolean isPlaying ()
{
return _source != null && _source.isPlaying();
}
/**
* Called to check if this sound wants to start playing.
*/
public boolean isPending ()
{
return _stateDesired == AL10.AL_PLAYING;
}
protected Sound (SoundGroup group, ClipBuffer buffer)
{
_group = group;
_buffer = buffer;
}
protected boolean play (boolean allowDefer, final boolean loop, final StartObserver obs)
{
// if we were unable to get our buffer, fail immediately
if (_buffer == null) {
if (obs != null) {
obs.soundStarted(null);
}
_stateDesired = AL10.AL_INVALID;
return false;
}
// if we're not ready to go...
if (!_buffer.isPlayable()) {
if (allowDefer) {
// save the desired state, which may be overridden by calls to play/pause/stop
_stateDesired = AL10.AL_PLAYING;
_loopDesired = loop;
// resolve the buffer and instruct it to play once it is resolved
_buffer.resolve(new ClipBuffer.Observer() {
public void clipLoaded (ClipBuffer buffer) {
if (_stateDesired == AL10.AL_STOPPED) {
return;
}
play(false, _loopDesired, obs);
if (_stateDesired == AL10.AL_PAUSED) {
pause();
}
}
public void clipFailed (ClipBuffer buffer) {
// well, let's pretend like the sound started so that the observer isn't
// left hanging
if (obs != null && _stateDesired != AL10.AL_STOPPED) {
obs.soundStarted(Sound.this);
_stateDesired = AL10.AL_INVALID;
}
}
});
return true;
} else {
// sorry charlie...
if (obs != null) {
obs.soundStarted(null);
}
_stateDesired = AL10.AL_INVALID;
return false;
}
}
// let the observer know that (as far as they're concerned), we're started
if (obs != null) {
obs.soundStarted(this);
}
// if we do not already have a source, obtain one
if (_source == null) {
_source = _group.acquireSource(this);
if (_source == null) {
_stateDesired = AL10.AL_INVALID;
return false;
}
// bind our clip buffer to the source and notify it
_source.setBuffer(_buffer.getBuffer());
_buffer.sourceBound();
// configure the source with our ephemera
_source.setPosition(_px, _py, _pz);
_source.setVelocity(_vx, _vy, _vz);
updateSourceGain();
_source.setSourceRelative(_sourceRelative);
_source.setMinGain(_minGain);
_source.setMaxGain(_maxGain);
_source.setReferenceDistance(_referenceDistance);
_source.setRolloffFactor(_rolloffFactor);
_source.setMaxDistance(_maxDistance);
_source.setPitch(_pitch);
_source.setDirection(_dx, _dy, _dz);
_source.setConeInnerAngle(_coneInnerAngle);
_source.setConeOuterAngle(_coneOuterAngle);
_source.setConeOuterGain(_coneOuterGain);
}
// configure whether or not we should loop
_source.setLooping(loop);
// and start that damned thing up!
_source.play();
return true;
}
/**
* Updates the source gain according to our configured gain and the base gain.
*/
protected void updateSourceGain ()
{
if (_source != null) {
_source.setGain(_gain * _group.getInheritedBaseGain());
}
}
/**
* Called by the {@link SoundGroup} when it wants to reclaim our source.
*
* @return false if we have no source to reclaim or if we're still busy playing our sound,
* true if we gave up our source.
*/
protected boolean reclaim ()
{
if (_source != null && _source.isStopped()) {
_source.setBuffer(null);
_buffer.sourceUnbound();
_source = null;
return true;
}
return false;
}
/** The sound group with which we are associated. */
protected SoundGroup _group;
/** The OpenAL buffer from which we get our sound data. */
protected ClipBuffer _buffer;
/** The source via which we are playing our sound currently. */
protected Source _source;
/** The desired state of the sound (stopped, playing, paused) after resolution. */
protected int _stateDesired = AL10.AL_INVALID;
/** Whether or not looping is desired after resolution. */
protected boolean _loopDesired;
/** The position of the sound. */
protected float _px, _py, _pz;
/** The velocity of the sound. */
protected float _vx, _vy, _vz;
/** The gain of the sound. */
protected float _gain = 1f;
/** Whether or not the sound's position, velocity, etc. are relative to the listener. */
protected boolean _sourceRelative;
/** The minimum gain. */
protected float _minGain;
/** The maximum gain. */
protected float _maxGain = 1f;
/** The reference distance for attenuation. */
protected float _referenceDistance = 1f;
/** The attenuation rolloff factor. */
protected float _rolloffFactor = 1f;
/** The maximum distance for attenuation. */
protected float _maxDistance = Float.MAX_VALUE;
/** The pitch multiplier. */
protected float _pitch = 1f;
/** The direction of the sound. */
protected float _dx, _dy, _dz;
/** The inside angle of the sound cone. */
protected float _coneInnerAngle = 360f;
/** The outside angle of the sound cone. */
protected float _coneOuterAngle = 360f;
/** The gain outside the sound cone. */
protected float _coneOuterGain;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy