Behold, Nenya, Ring of Water and repository for our media and animation related
goodies, both Java 2D and LWJGL/JME 3D. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* A blank sound used when we cannot initialize the sound system.
|
||||
*/
|
||||
public class BlankSound extends Sound
|
||||
{
|
||||
protected BlankSound ()
|
||||
{
|
||||
super(null, null);
|
||||
}
|
||||
|
||||
protected boolean play (boolean allowDefer, boolean loop)
|
||||
{
|
||||
return false; // nothing doing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Contains data for a single sampled sound.
|
||||
*/
|
||||
public class Clip
|
||||
{
|
||||
/** The OpenAL format of this clip: {@link AL10#AL_FORMAT_MONO8}, etc. */
|
||||
public int format;
|
||||
|
||||
/** The frequency of this clip in samples per second. */
|
||||
public int frequency;
|
||||
|
||||
/** The audio data. */
|
||||
public ByteBuffer data;
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.IOException;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
/**
|
||||
* Represents a sound that has been loaded into the OpenAL system.
|
||||
*/
|
||||
public class ClipBuffer
|
||||
{
|
||||
/** Used to notify parties interested in when a clip is loaded. */
|
||||
public static interface Observer
|
||||
{
|
||||
/** Called when a clip has completed loading and is ready to be
|
||||
* played. */
|
||||
public void clipLoaded (ClipBuffer buffer);
|
||||
|
||||
/** Called when a clip has failed to prepare itself for one reason
|
||||
* or other. */
|
||||
public void clipFailed (ClipBuffer buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a key that uniquely identifies this combination of clip
|
||||
* provider and path.
|
||||
*/
|
||||
public static Comparable makeKey (ClipProvider provider, String path)
|
||||
{
|
||||
// we'll just use a string, amazing!
|
||||
return provider + path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new clip buffer with the specified path that will obtain
|
||||
* its clip data from the specified source. The clip will
|
||||
* automatically queue itself up to be loaded into memory.
|
||||
*/
|
||||
public ClipBuffer (SoundManager manager, ClipProvider provider, String path)
|
||||
{
|
||||
_manager = manager;
|
||||
_provider = provider;
|
||||
_path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique key for this clip buffer.
|
||||
*/
|
||||
public Comparable getKey ()
|
||||
{
|
||||
return makeKey(_provider, _path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provider used to load this clip.
|
||||
*/
|
||||
public ClipProvider getClipProvider ()
|
||||
{
|
||||
return _provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path that identifies this sound clip.
|
||||
*/
|
||||
public String getPath ()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this buffer is loaded and ready to go.
|
||||
*/
|
||||
public boolean isPlayable ()
|
||||
{
|
||||
return (_state == LOADED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier for this clip's buffer or -1 if it is not
|
||||
* loaded.
|
||||
*/
|
||||
public int getBufferId ()
|
||||
{
|
||||
return (_bufferId == null) ? -1 : _bufferId.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size (in bytes) of this clip as reported by OpenAL.
|
||||
* This value will not be valid until the clip is bound.
|
||||
*/
|
||||
public int getSize ()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs this buffer to resolve its underlying clip and be ready
|
||||
* to be played ASAP.
|
||||
*/
|
||||
public void resolve (Observer observer)
|
||||
{
|
||||
// if we were waiting to unload, cancel that
|
||||
if (_state == UNLOADING) {
|
||||
_state = LOADED;
|
||||
_manager.restoreClip(this);
|
||||
}
|
||||
|
||||
// if we're already loaded, this is easy
|
||||
if (_state == LOADED) {
|
||||
if (observer != null) {
|
||||
observer.clipLoaded(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// queue up the observer
|
||||
if (observer != null) {
|
||||
_observers.add(observer);
|
||||
}
|
||||
|
||||
// if we're already loading, we can stop here
|
||||
if (_state == LOADING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create our OpenAL buffer and then queue ourselves up to have
|
||||
// our clip data loaded
|
||||
_bufferId = BufferUtils.createIntBuffer(1);
|
||||
AL10.alGenBuffers(_bufferId);
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
Log.warning("Failed to create buffer [key=" + getKey() +
|
||||
", errno=" + errno + "].");
|
||||
_bufferId = null;
|
||||
// queue up a failure notification so that we properly return
|
||||
// from this method and our sound has a chance to register
|
||||
// itself as an observer before we jump up and declare failure
|
||||
_manager.queueClipFailure(this);
|
||||
|
||||
} else {
|
||||
_state = LOADING;
|
||||
_manager.queueClipLoad(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees up the internal audio buffers associated with this clip.
|
||||
*/
|
||||
public void dispose ()
|
||||
{
|
||||
if (_bufferId != null) {
|
||||
// if there are sources bound to this buffer, we must wait
|
||||
// for them to be unbound
|
||||
if (_bound > 0) {
|
||||
_state = UNLOADING;
|
||||
return;
|
||||
}
|
||||
|
||||
// free up our buffer
|
||||
AL10.alDeleteBuffers(_bufferId);
|
||||
_bufferId = null;
|
||||
_state = UNLOADED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by the background sound loading thread and
|
||||
* actually loads the sound data from wherever it cometh.
|
||||
*/
|
||||
protected Clip load ()
|
||||
throws IOException
|
||||
{
|
||||
return _provider.loadClip(_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called back on the main thread and instructs this
|
||||
* buffer to bind the clip data to this buffer's OpenAL buffer.
|
||||
*
|
||||
* @return true if the binding succeeded, false if we were unable to
|
||||
* load the sound data into OpenAL.
|
||||
*/
|
||||
protected boolean bind (Clip clip)
|
||||
{
|
||||
AL10.alBufferData(
|
||||
_bufferId.get(0), clip.format, clip.data, clip.frequency);
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
Log.warning("Failed to bind clip [key=" + getKey() +
|
||||
", errno=" + errno + "].");
|
||||
failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
_state = LOADED;
|
||||
_size = AL10.alGetBufferi(_bufferId.get(0), AL10.AL_SIZE);
|
||||
_observers.apply(new ObserverList.ObserverOp() {
|
||||
public boolean apply (Object observer) {
|
||||
((Observer)observer).clipLoaded(ClipBuffer.this);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_observers.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we fail in some part of the process in resolving our
|
||||
* clip data. Notifies our observers and resets the clip to the
|
||||
* UNLOADED state.
|
||||
*/
|
||||
protected void failed ()
|
||||
{
|
||||
if (_bufferId != null) {
|
||||
AL10.alDeleteBuffers(_bufferId);
|
||||
_bufferId = null;
|
||||
}
|
||||
_state = UNLOADED;
|
||||
|
||||
_observers.apply(new ObserverList.ObserverOp() {
|
||||
public boolean apply (Object observer) {
|
||||
((Observer)observer).clipFailed(ClipBuffer.this);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_observers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the buffer that a source has been bound to it.
|
||||
*/
|
||||
protected void sourceBound ()
|
||||
{
|
||||
_bound++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the buffer that a source has been unbound from it.
|
||||
*/
|
||||
protected void sourceUnbound ()
|
||||
{
|
||||
// dispose of the buffer when the last source is unbound
|
||||
if (--_bound == 0 && _state == UNLOADING) {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected SoundManager _manager;
|
||||
protected ClipProvider _provider;
|
||||
protected String _path;
|
||||
protected int _state;
|
||||
protected IntBuffer _bufferId;
|
||||
protected int _size;
|
||||
protected ObserverList _observers =
|
||||
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||
protected int _bound;
|
||||
|
||||
protected static final int UNLOADED = 0;
|
||||
protected static final int LOADING = 1;
|
||||
protected static final int LOADED = 2;
|
||||
protected static final int UNLOADING = 3;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a generic mechanism for loading sound data.
|
||||
*/
|
||||
public interface ClipProvider
|
||||
{
|
||||
/** Loads the specified clip from the appropriate source. */
|
||||
public Clip loadClip (String path) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id: Log.java 3099 2004-08-27 02:21:06Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by
|
||||
* this package.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
public static com.samskivert.util.Log log =
|
||||
new com.samskivert.util.Log("narya.openal");
|
||||
|
||||
/** Convenience function. */
|
||||
public static void debug (String message)
|
||||
{
|
||||
log.debug(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void info (String message)
|
||||
{
|
||||
log.info(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void warning (String message)
|
||||
{
|
||||
log.warning(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void logStackTrace (Throwable t)
|
||||
{
|
||||
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.jmex.sound.openAL.objects.util.OggInputStream;
|
||||
|
||||
/**
|
||||
* An audio stream read from one or more Ogg Vorbis files.
|
||||
*/
|
||||
public class OggFileStream extends Stream
|
||||
{
|
||||
/**
|
||||
* Creates a new Ogg stream for the specified file.
|
||||
*
|
||||
* @param loop whether or not to play the file in a continuous loop
|
||||
* if there's nothing on the queue
|
||||
*/
|
||||
public OggFileStream (SoundManager soundmgr, File file, boolean loop)
|
||||
throws IOException
|
||||
{
|
||||
super(soundmgr);
|
||||
_file = new OggFile(file, loop);
|
||||
_istream = new OggInputStream(new FileInputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the queue of files to play.
|
||||
*
|
||||
* @param loop if true, play this file in a loop if there's nothing else
|
||||
* on the queue
|
||||
*/
|
||||
public void queueFile (File file, boolean loop)
|
||||
{
|
||||
_queue.add(new OggFile(file, loop));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected int getFormat ()
|
||||
{
|
||||
return (_istream.getFormat() == OggInputStream.FORMAT_MONO16) ?
|
||||
AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected int getFrequency ()
|
||||
{
|
||||
return _istream.getRate();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected int populateBuffer (ByteBuffer buf)
|
||||
throws IOException
|
||||
{
|
||||
int read = _istream.read(buf, buf.position(), buf.remaining());
|
||||
while (buf.hasRemaining() && (!_queue.isEmpty() || _file.loop)) {
|
||||
if (!_queue.isEmpty()) {
|
||||
_file = _queue.remove(0);
|
||||
}
|
||||
_istream = new OggInputStream(new FileInputStream(_file.file));
|
||||
read = Math.max(0, read);
|
||||
read += _istream.read(buf, buf.position(), buf.remaining());
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
/** The file currently being played. */
|
||||
protected OggFile _file;
|
||||
|
||||
/** The underlying Ogg input stream for the current file. */
|
||||
protected OggInputStream _istream;
|
||||
|
||||
/** The queue of files to play after the current one. */
|
||||
protected ArrayList<OggFile> _queue = new ArrayList<OggFile>();
|
||||
|
||||
/** A file queued for play. */
|
||||
protected class OggFile
|
||||
{
|
||||
/** The file to play. */
|
||||
public File file;
|
||||
|
||||
/** Whether or not to play the file in a loop when there's nothing
|
||||
* in the queue. */
|
||||
public boolean loop;
|
||||
|
||||
public OggFile (File file, boolean loop)
|
||||
{
|
||||
this.file = file;
|
||||
this.loop = loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.nio.FloatBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
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 the buffer of audio data associated with this sound.
|
||||
*/
|
||||
public ClipBuffer getBuffer ()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the location of this sound in 3D space. This will not
|
||||
* affect an already playing sound but will take effect the next time
|
||||
* it is played.
|
||||
*/
|
||||
public void setLocation (float x, float y, float z)
|
||||
{
|
||||
if (_position == null) {
|
||||
_position = BufferUtils.createFloatBuffer(3);
|
||||
}
|
||||
_position.put(x).put(y).put(z);
|
||||
_position.flip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the velocity of this sound in 3D space, in delta
|
||||
* location per second (see {@link #setLocation}). This will not
|
||||
* affect an already playing sound but will take effect the next time
|
||||
* it is played.
|
||||
*/
|
||||
public void setVelocity (float dx, float dy, float dz)
|
||||
{
|
||||
if (_velocity == null) {
|
||||
_velocity = BufferUtils.createFloatBuffer(3);
|
||||
}
|
||||
_velocity.put(dx).put(dy).put(dz);
|
||||
_velocity.flip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the sounds's pitch. This value can range from 0.5 to
|
||||
* 2.0. This will not affect an already playing sound but will take
|
||||
* effect the next time it is played.
|
||||
*/
|
||||
public void setPitch (float pitch)
|
||||
{
|
||||
_pitch = pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the sound's gain (volume). This value can range from 0.0
|
||||
* to 1.0 with 1.0 meaning no attenuation, each division by two
|
||||
* corresponding to a -6db attentuation and each multiplication by two
|
||||
* corresponding to +6db amplification. This will not affect an
|
||||
* already playing sound but will take effect the next time it is
|
||||
* played.
|
||||
*
|
||||
* <p><em>Note:</em> this value is multiplied by the base gain configured
|
||||
* in the sound manager.
|
||||
*/
|
||||
public void setGain (float gain)
|
||||
{
|
||||
_gain = 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 void play (StartObserver obs, boolean loop)
|
||||
{
|
||||
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 ()
|
||||
{
|
||||
if (_sourceId != -1) {
|
||||
AL10.alSourcePause(_sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this sound and rewinds to its beginning. The audio channel
|
||||
* being used to play the sound will be released.
|
||||
*/
|
||||
public void stop ()
|
||||
{
|
||||
if (_sourceId != -1) {
|
||||
AL10.alSourceStop(_sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we're not ready to go...
|
||||
if (!_buffer.isPlayable()) {
|
||||
if (allowDefer) {
|
||||
// resolve the buffer and instruct it to play once it is
|
||||
// resolved
|
||||
_buffer.resolve(new ClipBuffer.Observer() {
|
||||
public void clipLoaded (ClipBuffer buffer) {
|
||||
play(false, loop, obs);
|
||||
}
|
||||
public void clipFailed (ClipBuffer buffer) {
|
||||
// well, let's pretend like the sound started so that
|
||||
// the observer isn't left hanging
|
||||
if (obs != null) {
|
||||
obs.soundStarted(Sound.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
// sorry charlie...
|
||||
if (obs != null) {
|
||||
obs.soundStarted(null);
|
||||
}
|
||||
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 (_sourceId == -1) {
|
||||
_sourceId = _group.acquireSource(this);
|
||||
if (_sourceId == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// bind our clip buffer to the source and notify it
|
||||
AL10.alSourcei(_sourceId, AL10.AL_BUFFER, _buffer.getBufferId());
|
||||
_buffer.sourceBound();
|
||||
}
|
||||
|
||||
// configure the source with our ephemera
|
||||
AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch);
|
||||
AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain * _group.getBaseGain());
|
||||
if (_position != null) {
|
||||
AL10.alSource(_sourceId, AL10.AL_POSITION, _position);
|
||||
}
|
||||
if (_velocity != null) {
|
||||
AL10.alSource(_sourceId, AL10.AL_VELOCITY, _velocity);
|
||||
}
|
||||
|
||||
// configure whether or not we should loop
|
||||
AL10.alSourcei(_sourceId, AL10.AL_LOOPING,
|
||||
loop ? AL10.AL_TRUE : AL10.AL_FALSE);
|
||||
|
||||
// and start that damned thing up!
|
||||
AL10.alSourcePlay(_sourceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (_sourceId != -1 &&
|
||||
AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE) ==
|
||||
AL10.AL_STOPPED) {
|
||||
AL10.alSourcei(_sourceId, AL10.AL_BUFFER, 0);
|
||||
_buffer.sourceUnbound();
|
||||
_sourceId = -1;
|
||||
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 int _sourceId = -1;
|
||||
|
||||
/** The pitch adjustment. */
|
||||
protected float _pitch = 1;
|
||||
|
||||
/** The gain adjustment. */
|
||||
protected float _gain = 1;
|
||||
|
||||
/** The starting position in 3D space. */
|
||||
protected FloatBuffer _position;
|
||||
|
||||
/** The velocity vector. */
|
||||
protected FloatBuffer _velocity;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Manages a group of sounds, binding them to OpenAL sources as they are
|
||||
* played and freeing up those sources for use by other sounds when the
|
||||
* sounds are finished.
|
||||
*/
|
||||
public class SoundGroup
|
||||
{
|
||||
/**
|
||||
* Queues up the specified sound clip for pre-loading into the cache.
|
||||
*/
|
||||
public void preloadClip (String path)
|
||||
{
|
||||
if (_manager.isInitialized()) {
|
||||
_manager.getClip(_provider, path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an "instance" of the specified sound which can be
|
||||
* positioned, played, looped and otherwise used to make noise.
|
||||
*/
|
||||
public Sound getSound (String path)
|
||||
{
|
||||
ClipBuffer buffer = null;
|
||||
if (_manager.isInitialized()) {
|
||||
buffer = _manager.getClip(_provider, path);
|
||||
}
|
||||
return (buffer == null) ? new BlankSound() : new Sound(this, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this sound group, freeing up the OpenAL sources with which
|
||||
* it is associated. All sounds obtained from this group will no
|
||||
* longer be usable and should be discarded.
|
||||
*/
|
||||
public void dispose ()
|
||||
{
|
||||
if (_sourceIds != null) {
|
||||
// make sure any bound sources are released
|
||||
for (Source source : _sources) {
|
||||
if (source.holder != null) {
|
||||
source.holder.stop();
|
||||
source.holder.reclaim();
|
||||
}
|
||||
}
|
||||
_sources.clear();
|
||||
AL10.alDeleteSources(_sourceIds);
|
||||
_sourceIds = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected SoundGroup (
|
||||
SoundManager manager, ClipProvider provider, int sources)
|
||||
{
|
||||
_manager = manager;
|
||||
_provider = provider;
|
||||
|
||||
// if we were unable to initialize the sound system at all, just
|
||||
// stop here and we'll behave as if we have no available sources
|
||||
if (!_manager.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create our sources
|
||||
_sourceIds = BufferUtils.createIntBuffer(sources);
|
||||
AL10.alGenSources(_sourceIds);
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
Log.warning("Failed to create sources [cprov=" + provider +
|
||||
", sources=" + sources + ", errno=" + errno + "].");
|
||||
_sourceIds = null;
|
||||
// we'll have no sources which means all requests to play
|
||||
// sounds will silently fail as if all sources were in use
|
||||
|
||||
} else {
|
||||
for (int ii = 0; ii < sources; ii++) {
|
||||
Source source = new Source();
|
||||
source.sourceId = _sourceIds.get(ii);
|
||||
_sources.add(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a {@link Sound} when it wants to obtain a source on which
|
||||
* to play its clip.
|
||||
*/
|
||||
protected int acquireSource (Sound acquirer)
|
||||
{
|
||||
// start at the beginning of the list looking for an available
|
||||
// source
|
||||
for (int ii = 0, ll = _sources.size(); ii < ll; ii++) {
|
||||
Source source = _sources.get(ii);
|
||||
if (source.holder == null || source.holder.reclaim()) {
|
||||
// note this source's new holder
|
||||
source.holder = acquirer;
|
||||
// move this source to the end of the list
|
||||
_sources.remove(ii);
|
||||
_sources.add(source);
|
||||
return source.sourceId;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to pass the base gain through to sound effects.
|
||||
*/
|
||||
protected float getBaseGain ()
|
||||
{
|
||||
return _manager.getBaseGain();
|
||||
}
|
||||
|
||||
/** Used to track which sources are in use. */
|
||||
protected static class Source
|
||||
{
|
||||
public int sourceId;
|
||||
public Sound holder;
|
||||
}
|
||||
|
||||
protected SoundManager _manager;
|
||||
protected ClipProvider _provider;
|
||||
|
||||
protected IntBuffer _sourceIds;
|
||||
protected ArrayList<Source> _sources = new ArrayList<Source>();
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
/**
|
||||
* An interface to the OpenAL library that provides a number of additional
|
||||
* services:
|
||||
*
|
||||
* <ul>
|
||||
* <li> an object oriented interface to the OpenAL system
|
||||
* <li> a mechanism for loading a group of sounds and freeing their
|
||||
* resources all at once
|
||||
* <li> a mechanism for loading sounds in a background thread and
|
||||
* preloading sounds that are likely to be needed soon
|
||||
* </ul>
|
||||
*
|
||||
* <p><em>Note:</em> the sound manager is not thread safe (other than
|
||||
* during its interactions with its internal background loading
|
||||
* thread). It assumes that all sound loading and play requests will be
|
||||
* made from a single thread.
|
||||
*/
|
||||
public class SoundManager
|
||||
{
|
||||
/**
|
||||
* Creates, initializes and returns the singleton sound manager
|
||||
* instance.
|
||||
*
|
||||
* @param rqueue a queue that the sound manager can use to post short
|
||||
* runnables that must be executed on the same thread from which all
|
||||
* other sound methods will be called.
|
||||
*/
|
||||
public static SoundManager createSoundManager (RunQueue rqueue)
|
||||
{
|
||||
if (_soundmgr != null) {
|
||||
throw new IllegalStateException(
|
||||
"A sound manager has already been created.");
|
||||
}
|
||||
_soundmgr = new SoundManager(rqueue);
|
||||
return _soundmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we were able to initialize the sound system.
|
||||
*/
|
||||
public boolean isInitialized ()
|
||||
{
|
||||
return (_toLoad != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the size of our sound cache. If this value is larger
|
||||
* than memory available to the underlying sound system, it will be
|
||||
* reduced when OpenAL first tells us we're out of memory.
|
||||
*/
|
||||
public void setCacheSize (int bytes)
|
||||
{
|
||||
_clips.setMaxSize(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the base gain (which must be a value between 0 and 1.0) which
|
||||
* is multiplied to the individual gain assigned to sound effects (but not
|
||||
* music).
|
||||
*/
|
||||
public void setBaseGain (float gain)
|
||||
{
|
||||
_baseGain = gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base gain used for sound effects (not music).
|
||||
*/
|
||||
public float getBaseGain ()
|
||||
{
|
||||
return _baseGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object that can be used to manage and play a group of
|
||||
* sounds. <em>Note:</em> the sound group <em>must</em> be disposed
|
||||
* when it is no longer needed via a call to {@link
|
||||
* SoundGroup#dispose}.
|
||||
*
|
||||
* @param provider indicates from where the sound group will load its
|
||||
* sounds.
|
||||
* @param sources indicates the maximum number of simultaneous sounds
|
||||
* that can play in this group.
|
||||
*/
|
||||
public SoundGroup createGroup (ClipProvider provider, int sources)
|
||||
{
|
||||
return new SoundGroup(this, provider, sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the list of active streams.
|
||||
*/
|
||||
public ArrayList<Stream> getStreams ()
|
||||
{
|
||||
return _streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all of the streams controlled by the manager. This should be
|
||||
* called once per frame by the application.
|
||||
*
|
||||
* @param time the number of seconds elapsed since the last update
|
||||
*/
|
||||
public void updateStreams (float time)
|
||||
{
|
||||
// iterate backwards through the list so that streams can dispose of
|
||||
// themselves during their update
|
||||
for (int ii = _streams.size() - 1; ii >= 0; ii--) {
|
||||
_streams.get(ii).update(time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sound manager and initializes the OpenAL sound subsystem.
|
||||
*/
|
||||
protected SoundManager (RunQueue rqueue)
|
||||
{
|
||||
_rqueue = rqueue;
|
||||
|
||||
// initialize the OpenAL sound system
|
||||
try {
|
||||
AL.create("", 44100, 15, false);
|
||||
} catch (Exception e) {
|
||||
Log.warning("Failed to initialize sound system.");
|
||||
Log.logStackTrace(e);
|
||||
// don't start the background loading thread
|
||||
return;
|
||||
}
|
||||
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
Log.warning("Failed to initialize sound system " +
|
||||
"[errno=" + errno + "].");
|
||||
// don't start the background loading thread
|
||||
return;
|
||||
}
|
||||
|
||||
// configure our LRU map with a removal observer
|
||||
_clips.setRemovalObserver(
|
||||
new LRUHashMap.RemovalObserver<Comparable,ClipBuffer>() {
|
||||
public void removedFromMap (LRUHashMap<Comparable,ClipBuffer> map,
|
||||
final ClipBuffer item) {
|
||||
_rqueue.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
Log.debug("Flushing " + item.getKey());
|
||||
item.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// create our loading queue
|
||||
_toLoad = new Queue();
|
||||
|
||||
// start up the background loader thread
|
||||
_loader.setDaemon(true);
|
||||
_loader.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clip buffer for the sound clip loaded via the specified
|
||||
* provider with the specified path. The clip buffer may come from teh
|
||||
* cache, and it will immediately be queued for loading if it is not
|
||||
* already loaded.
|
||||
*/
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path)
|
||||
{
|
||||
Comparable ckey = ClipBuffer.makeKey(provider, path);
|
||||
ClipBuffer buffer = _clips.get(ckey);
|
||||
try {
|
||||
if (buffer == null) {
|
||||
// check to see if this clip is currently loading
|
||||
buffer = _loading.get(ckey);
|
||||
if (buffer == null) {
|
||||
buffer = new ClipBuffer(this, provider, path);
|
||||
_loading.put(ckey, buffer);
|
||||
}
|
||||
}
|
||||
buffer.resolve(null);
|
||||
return buffer;
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Failure resolving buffer [key=" + ckey + "].");
|
||||
Log.logStackTrace(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the supplied clip buffer up for resolution. The {@link Clip}
|
||||
* will be loaded into memory and then bound into OpenAL on the
|
||||
* background thread.
|
||||
*/
|
||||
protected void queueClipLoad (ClipBuffer buffer)
|
||||
{
|
||||
if (_toLoad != null) {
|
||||
_toLoad.append(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the supplied clip buffer up using our {@link RunQueue} to
|
||||
* notify its observers that it failed to load.
|
||||
*/
|
||||
protected void queueClipFailure (final ClipBuffer buffer)
|
||||
{
|
||||
_rqueue.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
_loading.remove(buffer.getKey());
|
||||
buffer.failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied clip buffer back to the cache after it has been marked
|
||||
* for disposal and subsequently re-requested.
|
||||
*/
|
||||
protected void restoreClip (ClipBuffer buffer)
|
||||
{
|
||||
_clips.put(buffer.getKey(), buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a stream to the list maintained by the manager. Called by streams
|
||||
* when they are created.
|
||||
*/
|
||||
protected void addStream (Stream stream)
|
||||
{
|
||||
_streams.add(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a stream from the list maintained by the manager. Called by
|
||||
* streams when they are disposed.
|
||||
*/
|
||||
protected void removeStream (Stream stream)
|
||||
{
|
||||
_streams.remove(stream);
|
||||
}
|
||||
|
||||
/** The thread that loads up sound clips in the background. */
|
||||
protected Thread _loader = new Thread("SoundManager.Loader") {
|
||||
public void run () {
|
||||
while (true) {
|
||||
final ClipBuffer buffer = (ClipBuffer)_toLoad.get();
|
||||
try {
|
||||
Log.info("Loading " + buffer.getKey() + ".");
|
||||
final Clip clip = buffer.load();
|
||||
_rqueue.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
Comparable ckey = buffer.getKey();
|
||||
Log.debug("Loaded " + ckey + ".");
|
||||
_loading.remove(ckey);
|
||||
if (buffer.bind(clip)) {
|
||||
_clips.put(ckey, buffer);
|
||||
} else {
|
||||
// TODO: shrink the cache size if the bind
|
||||
// failed due to OUT_OF_MEMORY
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Failed to load clip " +
|
||||
"[key=" + buffer.getKey() + "].");
|
||||
Log.logStackTrace(t);
|
||||
|
||||
// let the clip and its observers know that we are a
|
||||
// miserable failure
|
||||
queueClipFailure(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Used to get back from the background thread to our "main" thread. */
|
||||
protected RunQueue _rqueue;
|
||||
|
||||
/** A base gain that is multiplied by the individual gain assigned to
|
||||
* sounds. */
|
||||
protected float _baseGain = 1;
|
||||
|
||||
/** Contains a mapping of all currently-loading clips. */
|
||||
protected HashMap<Comparable,ClipBuffer> _loading =
|
||||
new HashMap<Comparable,ClipBuffer>();
|
||||
|
||||
/** Contains a mapping of all loaded clips. */
|
||||
protected LRUHashMap<Comparable,ClipBuffer> _clips =
|
||||
new LRUHashMap<Comparable,ClipBuffer>(DEFAULT_CACHE_SIZE, _sizer);
|
||||
|
||||
/** Contains a queue of clip buffers waiting to be loaded. */
|
||||
protected Queue _toLoad;
|
||||
|
||||
/** The list of active streams. */
|
||||
protected ArrayList<Stream> _streams = new ArrayList<Stream>();
|
||||
|
||||
/** The one and only sound manager, here for an exclusive performance
|
||||
* by special request. Available for all your sound playing needs. */
|
||||
protected static SoundManager _soundmgr;
|
||||
|
||||
/** Used to compute the in-memory size of sound samples. */
|
||||
protected static LRUHashMap.ItemSizer<ClipBuffer> _sizer =
|
||||
new LRUHashMap.ItemSizer<ClipBuffer>() {
|
||||
public int computeSize (ClipBuffer item) {
|
||||
return item.getSize();
|
||||
}
|
||||
};
|
||||
|
||||
/** Default to a cache size of one megabyte. */
|
||||
protected static final int DEFAULT_CACHE_SIZE = 8 * 1024 * 1024;
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.IOException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Represents a streaming source of sound data.
|
||||
*/
|
||||
public abstract class Stream
|
||||
{
|
||||
/**
|
||||
* Creates a new stream. Call {@link #dispose} when finished with the
|
||||
* stream.
|
||||
*
|
||||
* @param soundmgr a reference to the sound manager that will update the
|
||||
* stream
|
||||
*/
|
||||
public Stream (SoundManager soundmgr)
|
||||
{
|
||||
_soundmgr = soundmgr;
|
||||
|
||||
// create the source and buffers
|
||||
_nbuf = BufferUtils.createIntBuffer(NUM_BUFFERS);
|
||||
_nbuf.limit(1);
|
||||
AL10.alGenSources(_nbuf);
|
||||
_sourceId = _nbuf.get();
|
||||
_nbuf.clear();
|
||||
AL10.alGenBuffers(_nbuf);
|
||||
_nbuf.get(_bufferIds);
|
||||
|
||||
// register with sound manager
|
||||
_soundmgr.addStream(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base gain of the stream.
|
||||
*/
|
||||
public void setGain (float gain)
|
||||
{
|
||||
_gain = gain;
|
||||
if (_fadeMode == FadeMode.NONE) {
|
||||
AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pitch of the stream.
|
||||
*/
|
||||
public void setPitch (float pitch)
|
||||
{
|
||||
_pitch = pitch;
|
||||
AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this stream is currently playing.
|
||||
*/
|
||||
public boolean isPlaying ()
|
||||
{
|
||||
return _state == AL10.AL_PLAYING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing this stream.
|
||||
*/
|
||||
public void play ()
|
||||
{
|
||||
if (_state == AL10.AL_PLAYING) {
|
||||
Log.warning("Tried to play stream already playing.");
|
||||
return;
|
||||
}
|
||||
if (_state == AL10.AL_INITIAL) {
|
||||
_qidx = _qlen = 0;
|
||||
queueBuffers(NUM_BUFFERS);
|
||||
}
|
||||
AL10.alSourcePlay(_sourceId);
|
||||
_state = AL10.AL_PLAYING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses this stream.
|
||||
*/
|
||||
public void pause ()
|
||||
{
|
||||
if (_state != AL10.AL_PLAYING) {
|
||||
Log.warning("Tried to pause stream that wasn't playing.");
|
||||
return;
|
||||
}
|
||||
AL10.alSourcePause(_sourceId);
|
||||
_state = AL10.AL_PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this stream.
|
||||
*/
|
||||
public void stop ()
|
||||
{
|
||||
if (_state == AL10.AL_STOPPED) {
|
||||
Log.warning("Tried to stop stream that was already stopped.");
|
||||
return;
|
||||
}
|
||||
AL10.alSourceStop(_sourceId);
|
||||
_state = AL10.AL_STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fades this stream in over the specified interval. If the stream isn't
|
||||
* playing, it will be started.
|
||||
*/
|
||||
public void fadeIn (float interval)
|
||||
{
|
||||
if (_state != AL10.AL_PLAYING) {
|
||||
play();
|
||||
}
|
||||
AL10.alSourcef(_sourceId, AL10.AL_GAIN, 0f);
|
||||
_fadeMode = FadeMode.IN;
|
||||
_fadeInterval = interval;
|
||||
_fadeElapsed = 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fades this stream out over the specified interval.
|
||||
*
|
||||
* @param dispose if true, dispose of the stream when done fading out
|
||||
*/
|
||||
public void fadeOut (float interval, boolean dispose)
|
||||
{
|
||||
_fadeMode = dispose ? FadeMode.OUT_DISPOSE : FadeMode.OUT;
|
||||
_fadeInterval = interval;
|
||||
_fadeElapsed = 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the resources held by this stream and removes it from
|
||||
* the manager.
|
||||
*/
|
||||
public void dispose ()
|
||||
{
|
||||
// make sure the stream is stopped
|
||||
if (_state != AL10.AL_STOPPED) {
|
||||
stop();
|
||||
}
|
||||
|
||||
// delete the source and buffers
|
||||
_nbuf.clear();
|
||||
_nbuf.put(_sourceId).flip();
|
||||
AL10.alDeleteSources(_nbuf);
|
||||
_nbuf.clear();
|
||||
_nbuf.put(_bufferIds).flip();
|
||||
AL10.alDeleteBuffers(_nbuf);
|
||||
|
||||
// remove from manager
|
||||
_soundmgr.removeStream(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of this stream, loading data into buffers and
|
||||
* adjusting gain as necessary. Called periodically by the
|
||||
* {@link SoundManager}.
|
||||
*
|
||||
* @param time the amount of time elapsed since the last update
|
||||
*/
|
||||
protected void update (float time)
|
||||
{
|
||||
// update fade, which may stop playing
|
||||
updateFade(time);
|
||||
if (_state != AL10.AL_PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find out how many buffers have been played and unqueue them
|
||||
int played = AL10.alGetSourcei(_sourceId, AL10.AL_BUFFERS_PROCESSED);
|
||||
if (played == 0) {
|
||||
return;
|
||||
}
|
||||
_nbuf.clear();
|
||||
for (int ii = 0; ii < played; ii++) {
|
||||
_nbuf.put(_bufferIds[_qidx]);
|
||||
_qidx = (_qidx + 1) % NUM_BUFFERS;
|
||||
_qlen--;
|
||||
}
|
||||
_nbuf.flip();
|
||||
AL10.alSourceUnqueueBuffers(_sourceId, _nbuf);
|
||||
|
||||
// enqueue up to the number of buffers played
|
||||
queueBuffers(played);
|
||||
|
||||
// find out if we're still playing; if not and we have buffers queued,
|
||||
// we must restart
|
||||
_state = AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE);
|
||||
if (_qlen > 0 && _state != AL10.AL_PLAYING) {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the gain of the stream according to the fade state.
|
||||
*/
|
||||
protected void updateFade (float time)
|
||||
{
|
||||
if (_fadeMode == FadeMode.NONE) {
|
||||
return;
|
||||
}
|
||||
float alpha = Math.min((_fadeElapsed += time) / _fadeInterval, 1f);
|
||||
AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain *
|
||||
(_fadeMode == FadeMode.IN ? alpha : (1f - alpha)));
|
||||
if (alpha == 1f) {
|
||||
if (_fadeMode == FadeMode.OUT) {
|
||||
stop();
|
||||
} else if (_fadeMode == FadeMode.OUT_DISPOSE) {
|
||||
dispose();
|
||||
}
|
||||
_fadeMode = FadeMode.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues (up to) the specified number of buffers.
|
||||
*/
|
||||
protected void queueBuffers (int buffers)
|
||||
{
|
||||
_nbuf.clear();
|
||||
for (int ii = 0; ii < buffers; ii++) {
|
||||
int bufferId = _bufferIds[(_qidx + _qlen) % NUM_BUFFERS];
|
||||
if (populateBuffer(bufferId)) {
|
||||
_nbuf.put(bufferId);
|
||||
_qlen++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_nbuf.flip();
|
||||
AL10.alSourceQueueBuffers(_sourceId, _nbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the identified buffer with as much data as it can hold.
|
||||
*
|
||||
* @return true if data was read into the buffer and it should be enqueued,
|
||||
* false if the end of the stream has been reached and no data was read
|
||||
* into the buffer
|
||||
*/
|
||||
protected boolean populateBuffer (int bufferId)
|
||||
{
|
||||
if (_abuf == null) {
|
||||
_abuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
}
|
||||
_abuf.clear();
|
||||
int read = 0;
|
||||
try {
|
||||
read = Math.max(populateBuffer(_abuf), 0);
|
||||
} catch (IOException e) {
|
||||
Log.warning("Error reading audio stream [error=" + e + "].");
|
||||
}
|
||||
if (read <= 0) {
|
||||
return false;
|
||||
}
|
||||
_abuf.rewind().limit(read);
|
||||
AL10.alBufferData(bufferId, getFormat(), _abuf, getFrequency());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OpenAL audio format of the stream.
|
||||
*/
|
||||
protected abstract int getFormat ();
|
||||
|
||||
/**
|
||||
* Returns the stream's playback frequency in samples per second.
|
||||
*/
|
||||
protected abstract int getFrequency ();
|
||||
|
||||
/**
|
||||
* Populates the given buffer with audio data.
|
||||
*
|
||||
* @return the total number of bytes read into the buffer, or -1 if the
|
||||
* end of the stream has been reached
|
||||
*/
|
||||
protected abstract int populateBuffer (ByteBuffer buf)
|
||||
throws IOException;
|
||||
|
||||
/** The manager to which the stream was added. */
|
||||
protected SoundManager _soundmgr;
|
||||
|
||||
/** The source through which the stream plays. */
|
||||
protected int _sourceId;
|
||||
|
||||
/** The buffers through which we cycle. */
|
||||
protected int[] _bufferIds = new int[NUM_BUFFERS];
|
||||
|
||||
/** The starting index and length of the current queue in
|
||||
* {@link #_bufferIds}. */
|
||||
protected int _qidx, _qlen;
|
||||
|
||||
/** The pitch of the stream. */
|
||||
protected float _pitch = 1f;
|
||||
|
||||
/** The gain of the stream. */
|
||||
protected float _gain = 1f;
|
||||
|
||||
/** The interval and elapsed time for fading. */
|
||||
protected float _fadeInterval, _fadeElapsed;
|
||||
|
||||
/** The type of fading being performed. */
|
||||
protected FadeMode _fadeMode = FadeMode.NONE;
|
||||
|
||||
/** The buffer used to store names. */
|
||||
protected IntBuffer _nbuf;
|
||||
|
||||
/** The buffer used to store audio data temporarily. */
|
||||
protected ByteBuffer _abuf;
|
||||
|
||||
/** The OpenAL state of the stream. */
|
||||
protected int _state = AL10.AL_INITIAL;
|
||||
|
||||
/** The size of the buffers in bytes. */
|
||||
protected static final int BUFFER_SIZE = 131072;
|
||||
|
||||
/** The number of buffers to use. */
|
||||
protected static final int NUM_BUFFERS = 4;
|
||||
|
||||
/** Fading modes. */
|
||||
protected enum FadeMode { NONE, IN, OUT, OUT_DISPOSE };
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// 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 java.io.IOException;
|
||||
|
||||
import org.lwjgl.util.WaveData;
|
||||
|
||||
/**
|
||||
* Loads clips via the class loader using LWJGL's {@link WaveData} utility
|
||||
* class.
|
||||
*/
|
||||
public class WaveDataClipProvider implements ClipProvider
|
||||
{
|
||||
public Clip loadClip (String path) throws IOException
|
||||
{
|
||||
Clip clip = new Clip();
|
||||
WaveData file = WaveData.create(path);
|
||||
if (file == null) {
|
||||
throw new IOException("Error loading " + path);
|
||||
}
|
||||
clip.format = file.format;
|
||||
clip.frequency = file.samplerate;
|
||||
clip.data = file.data;
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user