Get our tools from narya.jar and its dependencies. Moved src/java into
src/main/java. Prepared to move tests out into main project. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1040 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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;
|
||||
|
||||
/**
|
||||
* 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,113 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Represents an OpenAL buffer object.
|
||||
*/
|
||||
public class Buffer
|
||||
{
|
||||
/**
|
||||
* Creates a new buffer for the specified sound manager.
|
||||
*/
|
||||
public Buffer (SoundManager soundmgr)
|
||||
{
|
||||
_soundmgr = soundmgr;
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(1);
|
||||
AL10.alGenBuffers(idbuf);
|
||||
_id = idbuf.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this buffer's OpenAL identifier.
|
||||
*/
|
||||
public final int getId ()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data in this buffer.
|
||||
*/
|
||||
public void setData (int format, ByteBuffer data, int frequency)
|
||||
{
|
||||
AL10.alBufferData(_id, format, data, frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data in this buffer.
|
||||
*/
|
||||
public void setData (int format, IntBuffer data, int frequency)
|
||||
{
|
||||
AL10.alBufferData(_id, format, data, frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data in this buffer.
|
||||
*/
|
||||
public void setData (int format, ShortBuffer data, int frequency)
|
||||
{
|
||||
AL10.alBufferData(_id, format, data, frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this buffer.
|
||||
*/
|
||||
public int getSize ()
|
||||
{
|
||||
return AL10.alGetBufferi(_id, AL10.AL_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this buffer, rendering it unusable.
|
||||
*/
|
||||
public void delete ()
|
||||
{
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(1);
|
||||
idbuf.put(_id).rewind();
|
||||
AL10.alDeleteBuffers(idbuf);
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize ()
|
||||
throws Throwable
|
||||
{
|
||||
super.finalize();
|
||||
if (_id > 0) {
|
||||
_soundmgr.bufferFinalized(_id);
|
||||
}
|
||||
}
|
||||
|
||||
/** The sound manager responsible for this buffer. */
|
||||
protected SoundManager _soundmgr;
|
||||
|
||||
/** The OpenAL identifier for this buffer. */
|
||||
protected int _id;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.nio.ByteBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.util.WaveData;
|
||||
|
||||
/**
|
||||
* Contains data for a single sampled sound.
|
||||
*/
|
||||
public class Clip
|
||||
{
|
||||
public Clip ()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Fills in a clip from the given wave data.
|
||||
*/
|
||||
public Clip (WaveData data)
|
||||
{
|
||||
format = data.format;
|
||||
frequency = data.samplerate;
|
||||
this.data = data.data;
|
||||
}
|
||||
|
||||
/** 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,281 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.io.IOException;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
/**
|
||||
* 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 String 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 String 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 a reference to this clip's buffer or <code>null</code> if it is not loaded.
|
||||
*/
|
||||
public Buffer getBuffer ()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
_buffer = new Buffer(_manager);
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
log.warning("Failed to create buffer [key=" + getKey() +
|
||||
", errno=" + errno + "].");
|
||||
_buffer = 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 (_buffer != 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
|
||||
_buffer.delete();
|
||||
_buffer = 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)
|
||||
{
|
||||
_buffer.setData(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 = _buffer.getSize();
|
||||
_observers.apply(new ObserverList.ObserverOp<Observer>() {
|
||||
public boolean apply (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 (_buffer != null) {
|
||||
_buffer.delete();
|
||||
_buffer = null;
|
||||
}
|
||||
_state = UNLOADED;
|
||||
|
||||
_observers.apply(new ObserverList.ObserverOp<Observer>() {
|
||||
public boolean apply (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 Buffer _buffer;
|
||||
protected int _size;
|
||||
protected ObserverList<Observer> _observers =
|
||||
new ObserverList<Observer>(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$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 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,116 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.ArrayList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* An audio stream read from one or more files.
|
||||
*/
|
||||
public class FileStream extends Stream
|
||||
{
|
||||
/**
|
||||
* Creates a new 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 FileStream (SoundManager soundmgr, File file, boolean loop)
|
||||
throws IOException
|
||||
{
|
||||
super(soundmgr);
|
||||
_file = new QueuedFile(file, loop);
|
||||
_decoder = StreamDecoder.createInstance(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 QueuedFile(file, loop));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFormat ()
|
||||
{
|
||||
return _decoder.getFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrequency ()
|
||||
{
|
||||
return _decoder.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int populateBuffer (ByteBuffer buf)
|
||||
throws IOException
|
||||
{
|
||||
int read = _decoder.read(buf);
|
||||
while (buf.hasRemaining() && (!_queue.isEmpty() || _file.loop)) {
|
||||
if (!_queue.isEmpty()) {
|
||||
_file = _queue.remove(0);
|
||||
}
|
||||
_decoder = StreamDecoder.createInstance(_file.file);
|
||||
read = Math.max(0, read);
|
||||
read += _decoder.read(buf);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
/** The file currently being played. */
|
||||
protected QueuedFile _file;
|
||||
|
||||
/** The stream decoder for the current file. */
|
||||
protected StreamDecoder _decoder;
|
||||
|
||||
/** The queue of files to play after the current one. */
|
||||
protected ArrayList<QueuedFile> _queue = Lists.newArrayList();
|
||||
|
||||
/** A file queued for play. */
|
||||
protected class QueuedFile
|
||||
{
|
||||
/** 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 QueuedFile (File file, boolean loop)
|
||||
{
|
||||
this.file = file;
|
||||
this.loop = loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.nio.FloatBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Represents the OpenAL listener object.
|
||||
*/
|
||||
public class Listener
|
||||
{
|
||||
/**
|
||||
* Sets the position of the listener.
|
||||
*/
|
||||
public void setPosition (float x, float y, float z)
|
||||
{
|
||||
if (_px != x || _py != y || _pz != z) {
|
||||
AL10.alListener3f(AL10.AL_POSITION, _px = x, _py = y, _pz = z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the velocity of the listener.
|
||||
*/
|
||||
public void setVelocity (float x, float y, float z)
|
||||
{
|
||||
if (_vx != x || _vy != y || _vz != z) {
|
||||
AL10.alListener3f(AL10.AL_VELOCITY, _vx = x, _vy = y, _vz = z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain of the listener.
|
||||
*/
|
||||
public void setGain (float gain)
|
||||
{
|
||||
if (_gain != gain) {
|
||||
AL10.alListenerf(AL10.AL_GAIN, _gain = gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation of the listener in terms of an "at" (direction) and "up" vector.
|
||||
*/
|
||||
public void setOrientation (float ax, float ay, float az, float ux, float uy, float uz)
|
||||
{
|
||||
if (_ax != ax || _ay != ay || _az != az || _ux != ux || _uy != uy || _uz != uz) {
|
||||
_vbuf.put(_ax = ax).put(_ay = ay).put(_az = az);
|
||||
_vbuf.put(_ux = ux).put(_uy = uy).put(_uz = uz).rewind();
|
||||
AL10.alListener(AL10.AL_ORIENTATION, _vbuf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener is only to be created by the {@link SoundManager}.
|
||||
*/
|
||||
protected Listener ()
|
||||
{
|
||||
}
|
||||
|
||||
/** The position of the listener. */
|
||||
protected float _px, _py, _pz;
|
||||
|
||||
/** The velocity of the listener. */
|
||||
protected float _vx, _vy, _vz;
|
||||
|
||||
/** The gain of the listener. */
|
||||
protected float _gain = 1f;
|
||||
|
||||
/** The orientation of the listener (initialized to the OpenAL defaults). */
|
||||
protected float _ax, _ay, _az = -1f, _ux, _uy = 1f, _uz;
|
||||
|
||||
/** A buffer for floating point values. */
|
||||
protected FloatBuffer _vbuf = BufferUtils.createFloatBuffer(6);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by this package.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
public static Logger log = Logger.getLogger("com.threerings.openal");
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import javazoom.jl.decoder.Bitstream;
|
||||
import javazoom.jl.decoder.Decoder;
|
||||
import javazoom.jl.decoder.Header;
|
||||
import javazoom.jl.decoder.JavaLayerException;
|
||||
import javazoom.jl.decoder.SampleBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
/**
|
||||
* Decodes MP3 streams.
|
||||
*/
|
||||
public class Mp3StreamDecoder extends StreamDecoder
|
||||
{
|
||||
@Override
|
||||
public void init (InputStream in)
|
||||
throws IOException
|
||||
{
|
||||
_istream = new Bitstream(in);
|
||||
try {
|
||||
_header = _istream.readFrame();
|
||||
} catch (JavaLayerException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
_decoder = new Decoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFormat ()
|
||||
{
|
||||
return AL10.AL_FORMAT_STEREO16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrequency ()
|
||||
{
|
||||
return _header.frequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read (ByteBuffer buf)
|
||||
throws IOException
|
||||
{
|
||||
ShortBuffer sbuf = buf.asShortBuffer();
|
||||
int total = 0;
|
||||
while (sbuf.hasRemaining() && _header != null) {
|
||||
if (_buffer == null) {
|
||||
try {
|
||||
_buffer = (SampleBuffer)_decoder.decodeFrame(_header, _istream);
|
||||
_istream.closeFrame();
|
||||
_header = _istream.readFrame();
|
||||
} catch (JavaLayerException e) {
|
||||
throw new IOException(e.toString());
|
||||
}
|
||||
}
|
||||
int blen = _buffer.getBufferLength(),
|
||||
length = Math.min(sbuf.remaining(), blen - _offset);
|
||||
sbuf.put(_buffer.getBuffer(), _offset, length);
|
||||
if ((_offset += length) >= blen) {
|
||||
_offset = 0;
|
||||
_buffer = null;
|
||||
}
|
||||
total += (length * 2);
|
||||
}
|
||||
buf.position(buf.position() + total);
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Handles reading the mp3 data. */
|
||||
protected Bitstream _istream;
|
||||
|
||||
/** The mp3 header. */
|
||||
protected Header _header;
|
||||
|
||||
/** Handles decoding the mp3 data. */
|
||||
protected Decoder _decoder;
|
||||
|
||||
/** The sample buffer used for output. */
|
||||
protected SampleBuffer _buffer;
|
||||
|
||||
/** Our offset into the currently decoded frame. */
|
||||
protected int _offset;
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.jcraft.jogg.Packet;
|
||||
import com.jcraft.jogg.Page;
|
||||
import com.jcraft.jogg.StreamState;
|
||||
import com.jcraft.jogg.SyncState;
|
||||
import com.jcraft.jorbis.Block;
|
||||
import com.jcraft.jorbis.Comment;
|
||||
import com.jcraft.jorbis.DspState;
|
||||
import com.jcraft.jorbis.Info;
|
||||
|
||||
/**
|
||||
* Decodes Ogg Vorbis streams.
|
||||
*/
|
||||
public class OggStreamDecoder extends StreamDecoder
|
||||
{
|
||||
@Override
|
||||
public void init (InputStream in)
|
||||
throws IOException
|
||||
{
|
||||
_in = in;
|
||||
_sync.init();
|
||||
_info.init();
|
||||
|
||||
// read the first packet "manually" to make sure everything's kosher
|
||||
readChunk();
|
||||
if (_sync.pageout(_page) != 1) {
|
||||
throw new IOException("Input is not an Ogg bitstream.");
|
||||
}
|
||||
_stream.init(_page.serialno());
|
||||
_stream.reset();
|
||||
|
||||
if (_stream.pagein(_page) < 0) {
|
||||
throw new IOException("Error reading first page of Ogg bitstream data.");
|
||||
}
|
||||
if (_stream.packetout(_packet) != 1) {
|
||||
throw new IOException("Error reading initial header packet.");
|
||||
}
|
||||
|
||||
Comment comment = new Comment();
|
||||
comment.init();
|
||||
if (_info.synthesis_headerin(comment, _packet) < 0) {
|
||||
throw new IOException("Ogg bitstream does not contain Vorbis audio data.");
|
||||
}
|
||||
|
||||
// two more packets in header
|
||||
for (int ii = 0; ii < 2; ii++) {
|
||||
if (!readPacket()) {
|
||||
throw new IOException("End of file before reading all Vorbis headers.");
|
||||
}
|
||||
_info.synthesis_headerin(comment, _packet);
|
||||
}
|
||||
_dsp.synthesis_init(_info);
|
||||
_block.init(_dsp);
|
||||
_offsets = new int[_info.channels];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFormat ()
|
||||
{
|
||||
return (_info.channels == 1) ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrequency ()
|
||||
{
|
||||
return _info.rate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read (ByteBuffer buf)
|
||||
throws IOException
|
||||
{
|
||||
ShortBuffer sbuf = buf.asShortBuffer();
|
||||
int channels = _info.channels;
|
||||
int total = 0;
|
||||
while (sbuf.hasRemaining()) {
|
||||
int samples = Math.min(readSamples(), sbuf.remaining() / channels);
|
||||
if (samples == 0) {
|
||||
break;
|
||||
}
|
||||
int length = samples * channels;
|
||||
if (_data.length < length) {
|
||||
_data = new short[length];
|
||||
}
|
||||
short[] data = _data;
|
||||
for (int ii = 0; ii < channels; ii++) {
|
||||
float[] pcm = _pcm[0][ii];
|
||||
int sidx = _offsets[ii], didx = ii;
|
||||
for (int jj = 0; jj < samples; jj++) {
|
||||
float value = Math.min(Math.max(pcm[sidx], -1f), +1f);
|
||||
data[didx] = (short)(value * 32767f);
|
||||
sidx++;
|
||||
didx += channels;
|
||||
}
|
||||
}
|
||||
sbuf.put(_data, 0, length);
|
||||
_dsp.synthesis_read(samples);
|
||||
total += (length * 2);
|
||||
}
|
||||
buf.position(buf.position() + total);
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a buffer's worth of samples.
|
||||
*
|
||||
* @return the number of samples read, or zero if we've reached the end of the stream.
|
||||
*/
|
||||
protected int readSamples ()
|
||||
throws IOException
|
||||
{
|
||||
int samples;
|
||||
while ((samples = _dsp.synthesis_pcmout(_pcm, _offsets)) <= 0) {
|
||||
if (samples == 0 && !readPacket()) {
|
||||
return 0;
|
||||
}
|
||||
if (_block.synthesis(_packet) == 0) {
|
||||
_dsp.synthesis_blockin(_block);
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a packet.
|
||||
*
|
||||
* @return true if a packet was read, false if we've reached the end of the stream.
|
||||
*/
|
||||
protected boolean readPacket ()
|
||||
throws IOException
|
||||
{
|
||||
int result;
|
||||
while ((result = _stream.packetout(_packet)) != 1) {
|
||||
if (result == 0 && !readPage()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a page.
|
||||
*
|
||||
* @return true if a page was read, false if we've reached the end of the stream.
|
||||
*/
|
||||
protected boolean readPage ()
|
||||
throws IOException
|
||||
{
|
||||
int result;
|
||||
while ((result = _sync.pageout(_page)) != 1) {
|
||||
if (result == 0 && !readChunk()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_stream.pagein(_page);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a chunk of data from the underlying input stream.
|
||||
*
|
||||
* @return true if a chunk was read, false if we've reached the end of the stream.
|
||||
*/
|
||||
protected boolean readChunk ()
|
||||
throws IOException
|
||||
{
|
||||
int offset = _sync.buffer(BUFFER_SIZE);
|
||||
int bytes = _in.read(_sync.data, offset, BUFFER_SIZE);
|
||||
if (bytes > 0) {
|
||||
_sync.wrote(bytes);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The underlying input stream. */
|
||||
protected InputStream _in;
|
||||
|
||||
/** The sync state. */
|
||||
protected SyncState _sync = new SyncState();
|
||||
|
||||
/** The stream state. */
|
||||
protected StreamState _stream = new StreamState();
|
||||
|
||||
/** The output page. */
|
||||
protected Page _page = new Page();
|
||||
|
||||
/** The output packet. */
|
||||
protected Packet _packet = new Packet();
|
||||
|
||||
/** The stream info. */
|
||||
protected Info _info = new Info();
|
||||
|
||||
/** The DSP state. */
|
||||
protected DspState _dsp = new DspState();
|
||||
|
||||
/** The output block. */
|
||||
protected Block _block = new Block(_dsp);
|
||||
|
||||
/** Holds the decoded PCM data buffers. */
|
||||
protected float[][][] _pcm = new float[1][][];
|
||||
|
||||
/** The DSP offsets for each channel. */
|
||||
protected int[] _offsets;
|
||||
|
||||
/** Intermediate storage for converted data. */
|
||||
protected short[] _data = new short[0];
|
||||
|
||||
/** The decode buffer size. */
|
||||
protected static final int BUFFER_SIZE = 4096 * 2;
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.Map;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.sound.sampled.AudioFileFormat;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.util.WaveData;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.BasicRunQueue;
|
||||
import com.samskivert.util.RandomUtil;
|
||||
import com.samskivert.util.ResultListener;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import com.threerings.media.FrameManager;
|
||||
import com.threerings.media.sound.JavaSoundPlayer;
|
||||
import com.threerings.media.sound.SoundLoader;
|
||||
import com.threerings.media.sound.SoundPlayer;
|
||||
import com.threerings.media.timer.MediaTimer;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Implements the abstract pieces of {@link SoundPlayer} via OpenAL.
|
||||
*/
|
||||
public class OpenALSoundPlayer extends SoundPlayer
|
||||
implements ClipProvider
|
||||
{
|
||||
|
||||
public OpenALSoundPlayer (SoundLoader loader)
|
||||
{
|
||||
_loader = loader;
|
||||
try {
|
||||
_alSoundManager = createSoundManager();
|
||||
_group = _alSoundManager.createGroup(this, SOURCE_COUNT);
|
||||
} catch (Throwable t) {
|
||||
log.warning("Unable to initialize OpenAL", "cause", t);
|
||||
}
|
||||
_ticker.start();
|
||||
}
|
||||
|
||||
public Clip loadClip (String path)
|
||||
throws IOException
|
||||
{
|
||||
int bundleEnd = path.lastIndexOf(":");
|
||||
InputStream sound = _loader.getSound(path.substring(0, bundleEnd),
|
||||
path.substring(bundleEnd + 1));
|
||||
if (path.endsWith(".ogg")) {
|
||||
try {
|
||||
AudioInputStream instream = JavaSoundPlayer.setupAudioStream(sound);
|
||||
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
|
||||
|
||||
byte[] buf = new byte[16 * 1024];
|
||||
int read;
|
||||
do {
|
||||
read = instream.read(buf, 0, buf.length);
|
||||
if (read >= 0) {
|
||||
outstream.write(buf, 0, read);
|
||||
}
|
||||
} while (read >= 0);
|
||||
|
||||
byte[] audio = outstream.toByteArray();
|
||||
AudioFormat format = instream.getFormat();
|
||||
long length = audio.length / format.getFrameSize();
|
||||
|
||||
instream = new AudioInputStream(new ByteArrayInputStream(audio), format, length);
|
||||
|
||||
outstream = new ByteArrayOutputStream();
|
||||
|
||||
AudioSystem.write(instream, AudioFileFormat.Type.WAVE, outstream);
|
||||
|
||||
sound = new ByteArrayInputStream(outstream.toByteArray());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Error decompressing audio clip", "path", path, e);
|
||||
return new Clip();
|
||||
}
|
||||
}
|
||||
return new Clip(WaveData.create(sound));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the loader used by this player.
|
||||
*/
|
||||
public SoundLoader getSoundLoader ()
|
||||
{
|
||||
return _loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunQueue getSoundQueue ()
|
||||
{
|
||||
return _ticker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClipVolume (final float vol)
|
||||
{
|
||||
super.setClipVolume(vol);
|
||||
getSoundQueue().postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
_alSoundManager.setBaseGain(vol);
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock (String pkgPath, String... keys)
|
||||
{
|
||||
for (String key : keys) {
|
||||
for (final String path : getPaths(pkgPath, key)) {
|
||||
getSoundQueue().postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
if (_locked.containsKey(path)) {
|
||||
return;
|
||||
}
|
||||
_alSoundManager.loadClip(OpenALSoundPlayer.this, path,
|
||||
new ClipBuffer.Observer() {
|
||||
public void clipFailed (ClipBuffer buffer) {
|
||||
log.warning("Unable to load sound", "path", path);
|
||||
}
|
||||
|
||||
public void clipLoaded (ClipBuffer buffer) {
|
||||
_locked.put(path, buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock (final String pkgPath, String... keys)
|
||||
{
|
||||
for (final String key : keys) {
|
||||
getSoundQueue().postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
for (String path : getPaths(pkgPath, key)) {
|
||||
_locked.remove(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams ogg files from the given bundle and path.
|
||||
*/
|
||||
public void stream (final String bundle, final String path, final boolean loop,
|
||||
final ResultListener<Stream> listener)
|
||||
throws IOException
|
||||
{
|
||||
if (!path.endsWith(".ogg")) {
|
||||
log.warning("Unknown file type for streaming", "bundle", bundle, "path", path);
|
||||
return;
|
||||
}
|
||||
InputStream rsrc = _loader.getSound(bundle, path);
|
||||
final StreamDecoder dec = new OggStreamDecoder();
|
||||
dec.init(rsrc);
|
||||
getSoundQueue().postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
Stream s = new Stream(_alSoundManager) {
|
||||
@Override
|
||||
protected void update (float time) {
|
||||
super.update(time);
|
||||
if (_state != AL10.AL_PLAYING) {
|
||||
return;
|
||||
}
|
||||
super.setGain(_clipVol * _streamGain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGain (float gain) {
|
||||
_streamGain = gain;
|
||||
super.setGain(_clipVol * _streamGain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFormat () {
|
||||
return dec.getFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrequency () {
|
||||
return dec.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int populateBuffer (ByteBuffer buf) throws IOException {
|
||||
int read = dec.read(buf);
|
||||
if (buf.hasRemaining() && loop) {
|
||||
dec.init(_loader.getSound(bundle, path));
|
||||
read = Math.max(0, read);
|
||||
read += dec.read(buf);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
protected float _streamGain = 1F;
|
||||
};
|
||||
s.setGain(_clipVol);
|
||||
listener.requestCompleted(s);
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frob loop (String pkgPath, String key, float pan)
|
||||
{
|
||||
return loop(pkgPath, key, pan, 1f);
|
||||
}
|
||||
|
||||
public Frob loop (String pkgPath, String key, float pan, float gain)
|
||||
{
|
||||
return loop(null, pkgPath, key, gain, null);
|
||||
}
|
||||
|
||||
public Frob loop (SoundType type, String pkgPath, String key, final float gain,
|
||||
final float[] pos)
|
||||
{
|
||||
if (!shouldPlay(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final SoundGrabber loader = new SoundGrabber(pkgPath, key) {
|
||||
@Override
|
||||
protected void soundLoaded () {
|
||||
sound.setGain(gain);
|
||||
if (pos != null) {
|
||||
sound.setPosition(pos[0], pos[1], pos[2]);
|
||||
}
|
||||
sound.loop(true);
|
||||
}};
|
||||
getSoundQueue().postRunnable(loader);
|
||||
return new Frob() {
|
||||
public float getPan () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public float getVolume () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setPan (float pan) {}
|
||||
|
||||
public void setVolume (float vol) {}
|
||||
|
||||
public void stop () {
|
||||
getSoundQueue().postRunnable(new Runnable(){
|
||||
public void run () {
|
||||
if (loader.sound != null) {
|
||||
loader.sound.stop();
|
||||
}
|
||||
}});
|
||||
}};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play (String pkgPath, String key, float pan)
|
||||
{
|
||||
play(pkgPath, key, pan, 1f);
|
||||
}
|
||||
|
||||
public void play (String pkgPath, String key, float pan, final float gain)
|
||||
{
|
||||
play(null, pkgPath, key, gain, null);
|
||||
}
|
||||
|
||||
public boolean play (SoundType type, String pkgPath, String key, final float gain,
|
||||
final float[] pos)
|
||||
{
|
||||
if (!shouldPlay(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getSoundQueue().postRunnable(new SoundGrabber(pkgPath, key) {
|
||||
@Override
|
||||
protected void soundLoaded () {
|
||||
sound.setGain(gain);
|
||||
if (pos != null) {
|
||||
sound.setPosition(pos[0], pos[1], pos[2]);
|
||||
}
|
||||
sound.play(true);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown ()
|
||||
{
|
||||
getSoundQueue().postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
_group.dispose();
|
||||
_locked.clear();
|
||||
for (Stream stream : _alSoundManager.getStreams()) {
|
||||
stream.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bundle:path for all sounds under key in pkgPath.
|
||||
*/
|
||||
protected String[] getPaths (String pkgPath, String key)
|
||||
{
|
||||
|
||||
String bundle = _loader.getBundle(pkgPath);
|
||||
Preconditions.checkNotNull(bundle,
|
||||
"Unable to find the bundle name for a package [package=%s, key=%s]", pkgPath, key);
|
||||
String[] names = _loader.getPaths(pkgPath, key);
|
||||
Preconditions.checkNotNull(names, "No such sound [package=%s, key=%s]", pkgPath, key);
|
||||
String[] paths = new String[names.length];
|
||||
for (int ii = 0; ii < paths.length; ii++) {
|
||||
paths[ii] = bundle + ":" + names[ii];
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates our SoundManager.
|
||||
*/
|
||||
protected SoundManager createSoundManager ()
|
||||
{
|
||||
return new MediaALSoundManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends sound manager to allow sounds to be pulled out of the locked map.
|
||||
*/
|
||||
protected class MediaALSoundManager extends SoundManager {
|
||||
protected MediaALSoundManager () {
|
||||
super(getSoundQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path) {
|
||||
if (_locked.containsKey(path)) {
|
||||
return _locked.get(path);
|
||||
}
|
||||
return super.getClip(provider, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sound manager's streams every STREAM_UPDATE_INTERVAL and processes sound
|
||||
* runnables added to its queue.
|
||||
*/
|
||||
protected class TickingQueue extends BasicRunQueue
|
||||
{
|
||||
public TickingQueue ()
|
||||
{
|
||||
super("SoundPlayerQueue");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void iterate ()
|
||||
{
|
||||
long elapsed = _timer.getElapsedMillis() - _lastTick;
|
||||
Runnable r;
|
||||
if (elapsed >= STREAM_UPDATE_INTERVAL) {
|
||||
r = _queue.getNonBlocking();
|
||||
} else {
|
||||
r = _queue.get(STREAM_UPDATE_INTERVAL - elapsed);
|
||||
}
|
||||
long newTime;
|
||||
if (_alSoundManager == null) {
|
||||
// We weren't able to initialize the sound system, and we logged it earlier, so
|
||||
// just empty the queue and update the tick time without running the code that
|
||||
// needs the sound manager.
|
||||
newTime = _timer.getElapsedMillis();
|
||||
} else {
|
||||
if (r != null) {
|
||||
try {
|
||||
r.run();
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.warning("Runnable posted to SoundPlayerQueue barfed.", t);
|
||||
}
|
||||
}
|
||||
newTime = _timer.getElapsedMillis();
|
||||
try {
|
||||
_alSoundManager.updateStreams((newTime - _lastTick) / 1000F);
|
||||
} catch (Throwable t) {
|
||||
log.warning("Updating OpenAL streams barfed.", t);
|
||||
}
|
||||
}
|
||||
_lastTick = newTime;
|
||||
}
|
||||
|
||||
protected MediaTimer _timer = FrameManager.createTimer();
|
||||
|
||||
protected long _lastTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a sound in its run method and calls subclasses with soundLoaded to let them know it's
|
||||
* ready.
|
||||
*/
|
||||
protected abstract class SoundGrabber
|
||||
implements Runnable
|
||||
{
|
||||
public String path;
|
||||
|
||||
public Sound sound;
|
||||
|
||||
public SoundGrabber (String pkgPath, String key)
|
||||
{
|
||||
String[] paths = getPaths(pkgPath, key);
|
||||
path = paths[RandomUtil.getInt(paths.length)];
|
||||
}
|
||||
|
||||
public void run ()
|
||||
{
|
||||
sound = _group.getSound(path);
|
||||
soundLoaded();
|
||||
}
|
||||
|
||||
protected abstract void soundLoaded ();
|
||||
}
|
||||
|
||||
/** Number of milliseconds to wait between stream updates. */
|
||||
protected static final int STREAM_UPDATE_INTERVAL = 100;
|
||||
|
||||
protected TickingQueue _ticker = new TickingQueue();
|
||||
|
||||
protected Map<String, ClipBuffer> _locked = Maps.newHashMap();
|
||||
|
||||
protected SoundLoader _loader;
|
||||
protected SoundGroup _group;
|
||||
protected SoundManager _alSoundManager;
|
||||
|
||||
/** Number of sounds that can be played simultaneously. */
|
||||
protected final int SOURCE_COUNT = 10;
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 ()
|
||||
{
|
||||
if (_source != null) {
|
||||
_source.pause();
|
||||
} else {
|
||||
_stateDesired = AL10.AL_PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this sound and rewinds to its beginning. The audio channel being used to play the
|
||||
* sound will be released.
|
||||
*/
|
||||
public void stop ()
|
||||
{
|
||||
if (_source != null) {
|
||||
_source.stop();
|
||||
} else {
|
||||
_stateDesired = AL10.AL_STOPPED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to check if this sound is currently playing.
|
||||
*/
|
||||
public boolean isPlaying ()
|
||||
{
|
||||
return _source != null && _source.isPlaying();
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
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 (_source == null) {
|
||||
_source = _group.acquireSource(this);
|
||||
if (_source == null) {
|
||||
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;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.ArrayList;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
/**
|
||||
* Sets the base gain for this group, or -1 to inherit from the manager (the default).
|
||||
*/
|
||||
public void setBaseGain (float gain)
|
||||
{
|
||||
if (_baseGain != gain) {
|
||||
_baseGain = gain;
|
||||
baseGainChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base gain for this group, or -1 if inherited from the manager.
|
||||
*/
|
||||
public float getBaseGain ()
|
||||
{
|
||||
return _baseGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ()
|
||||
{
|
||||
reclaimAll();
|
||||
for (PooledSource pooled : _sources) {
|
||||
pooled.source.delete();
|
||||
}
|
||||
_sources.clear();
|
||||
|
||||
// remove from the manager
|
||||
_manager.removeGroup(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and reclaims all sounds from this sound group but does not free the sources.
|
||||
*/
|
||||
public void reclaimAll ()
|
||||
{
|
||||
// make sure any bound sources are released
|
||||
for (PooledSource pooled : _sources) {
|
||||
if (pooled.holder != null) {
|
||||
pooled.holder.stop();
|
||||
pooled.holder.reclaim();
|
||||
pooled.holder = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SoundGroup (SoundManager manager, ClipProvider provider, int sources)
|
||||
{
|
||||
_manager = manager;
|
||||
_provider = provider;
|
||||
|
||||
// register with the manager
|
||||
_manager.addGroup(this);
|
||||
|
||||
// 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 (or as many of them as we can)
|
||||
for (int ii = 0; ii < sources; ii++) {
|
||||
PooledSource pooled = new PooledSource();
|
||||
pooled.source = new Source(manager);
|
||||
int errno = AL10.alGetError();
|
||||
if (errno != AL10.AL_NO_ERROR) {
|
||||
log.warning("Failed to create sources [cprov=" + provider +
|
||||
", sources=" + sources + ", errno=" + errno + "].");
|
||||
return;
|
||||
}
|
||||
_sources.add(pooled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a {@link Sound} when it wants to obtain a source on which to play its clip.
|
||||
*/
|
||||
protected Source 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++) {
|
||||
PooledSource pooled = _sources.get(ii);
|
||||
if (pooled.holder == null || pooled.holder.reclaim()) {
|
||||
// note this source's new holder
|
||||
pooled.holder = acquirer;
|
||||
// move this source to the end of the list
|
||||
_sources.remove(ii);
|
||||
_sources.add(pooled);
|
||||
return pooled.source;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to pass the base gain through to sound effects.
|
||||
*/
|
||||
protected float getInheritedBaseGain ()
|
||||
{
|
||||
return (_baseGain < 0f) ? _manager.getBaseGain() : _baseGain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the manager when the base gain has changed.
|
||||
*/
|
||||
protected void baseGainChanged ()
|
||||
{
|
||||
// notify any sound currently holding a source
|
||||
for (int ii = 0, nn = _sources.size(); ii < nn; ii++) {
|
||||
Sound holder = _sources.get(ii).holder;
|
||||
if (holder != null) {
|
||||
holder.updateSourceGain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to track which sources are in use. */
|
||||
protected static class PooledSource
|
||||
{
|
||||
public Source source;
|
||||
public Sound holder;
|
||||
}
|
||||
|
||||
protected SoundManager _manager;
|
||||
protected ClipProvider _provider;
|
||||
|
||||
protected ArrayList<PooledSource> _sources = Lists.newArrayList();
|
||||
|
||||
/** The base gain, or -1 to inherit from the manager. */
|
||||
protected float _baseGain = -1f;
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.IntListUtil;
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import com.threerings.openal.ClipBuffer.Observer;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the sound manager.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
if (isInitialized()) {
|
||||
AL.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the listener object.
|
||||
*/
|
||||
public Listener getListener ()
|
||||
{
|
||||
return _listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (_baseGain == gain) {
|
||||
return;
|
||||
}
|
||||
_baseGain = gain;
|
||||
|
||||
// alert the groups that inherite the gain
|
||||
for (int ii = 0, nn = _groups.size(); ii < nn; ii++) {
|
||||
SoundGroup group = _groups.get(ii);
|
||||
if (group.getBaseGain() < 0f) {
|
||||
group.baseGainChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// delete any finalized objects
|
||||
deleteFinalizedObjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a clip buffer for the sound clip loaded via the specified provider with the
|
||||
* specified path. The loaded clip is placed in the cache.
|
||||
*/
|
||||
public void loadClip (ClipProvider provider, String path)
|
||||
{
|
||||
loadClip(provider, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a clip buffer for the sound clip loaded via the specified provider with the
|
||||
* specified path. The loaded clip is placed in the cache.
|
||||
*/
|
||||
public void loadClip (ClipProvider provider, String path, Observer observer)
|
||||
{
|
||||
getClip(provider, path, observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.", 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<String, ClipBuffer>() {
|
||||
public void removedFromMap (LRUHashMap<String, 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<ClipBuffer>();
|
||||
|
||||
// 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 the cache, and it will immediately be queued
|
||||
* for loading if it is not already loaded.
|
||||
*/
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path)
|
||||
{
|
||||
return getClip(provider, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clip buffer for the sound clip loaded via the specified provider with the
|
||||
* specified path. The clip buffer may come from the cache, and it will immediately be queued
|
||||
* for loading if it is not already loaded.
|
||||
*/
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path, Observer observer)
|
||||
{
|
||||
String 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(observer);
|
||||
return buffer;
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.warning("Failure resolving buffer [key=" + ckey + "].", 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a group to the list maintained by the manager. Called by groups when they are created.
|
||||
*/
|
||||
protected void addGroup (SoundGroup group)
|
||||
{
|
||||
_groups.add(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a group from the list maintained by the manager. Called by groups when they are
|
||||
* disposed.
|
||||
*/
|
||||
protected void removeGroup (SoundGroup group)
|
||||
{
|
||||
_groups.remove(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a source has been finalized.
|
||||
*/
|
||||
protected synchronized void sourceFinalized (int id)
|
||||
{
|
||||
_finalizedSources = IntListUtil.add(_finalizedSources, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a buffer has been finalized.
|
||||
*/
|
||||
protected synchronized void bufferFinalized (int id)
|
||||
{
|
||||
_finalizedBuffers = IntListUtil.add(_finalizedBuffers, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all finalized objects.
|
||||
*/
|
||||
protected synchronized void deleteFinalizedObjects ()
|
||||
{
|
||||
if (_finalizedSources != null) {
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(_finalizedSources.length);
|
||||
idbuf.put(_finalizedSources).rewind();
|
||||
AL10.alDeleteSources(idbuf);
|
||||
_finalizedSources = null;
|
||||
}
|
||||
if (_finalizedBuffers != null) {
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(_finalizedBuffers.length);
|
||||
idbuf.put(_finalizedBuffers).rewind();
|
||||
AL10.alDeleteBuffers(idbuf);
|
||||
_finalizedBuffers = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The thread that loads up sound clips in the background. */
|
||||
protected Thread _loader = new Thread("SoundManager.Loader") {
|
||||
@Override
|
||||
public void run () {
|
||||
while (true) {
|
||||
final ClipBuffer buffer = _toLoad.get();
|
||||
try {
|
||||
log.debug("Loading " + buffer.getKey() + ".");
|
||||
final Clip clip = buffer.load();
|
||||
_rqueue.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
String 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() + "].", 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;
|
||||
|
||||
/** The listener object. */
|
||||
protected Listener _listener = new Listener();
|
||||
|
||||
/** 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<String, ClipBuffer> _loading = Maps.newHashMap();
|
||||
|
||||
/** Contains a mapping of all loaded clips. */
|
||||
protected LRUHashMap<String, ClipBuffer> _clips =
|
||||
new LRUHashMap<String, ClipBuffer>(DEFAULT_CACHE_SIZE, _sizer);
|
||||
|
||||
/** Contains a queue of clip buffers waiting to be loaded. */
|
||||
protected Queue<ClipBuffer> _toLoad;
|
||||
|
||||
/** The list of active streams. */
|
||||
protected ArrayList<Stream> _streams = Lists.newArrayList();
|
||||
|
||||
/** The list of active groups. */
|
||||
protected List<SoundGroup> _groups = Lists.newArrayList();
|
||||
|
||||
/** The list of sources to be deleted. */
|
||||
protected int[] _finalizedSources;
|
||||
|
||||
/** The list of buffers to be deleted. */
|
||||
protected int[] _finalizedBuffers;
|
||||
|
||||
/** 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,426 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.ArrayList;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.AL11;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Represents an OpenAL source object.
|
||||
*/
|
||||
public class Source
|
||||
{
|
||||
/**
|
||||
* Creates a new source for the specified sound manager.
|
||||
*/
|
||||
public Source (SoundManager soundmgr)
|
||||
{
|
||||
_soundmgr = soundmgr;
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(1);
|
||||
AL10.alGenSources(idbuf);
|
||||
_id = idbuf.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this source's OpenAL identifier.
|
||||
*/
|
||||
public final int getId ()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the source.
|
||||
*/
|
||||
public void setPosition (float x, float y, float z)
|
||||
{
|
||||
if (_px != x || _py != y || _pz != z) {
|
||||
AL10.alSource3f(_id, AL10.AL_POSITION, _px = x, _py = y, _pz = z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the velocity of the source.
|
||||
*/
|
||||
public void setVelocity (float x, float y, float z)
|
||||
{
|
||||
if (_vx != x || _vy != y || _vz != z) {
|
||||
AL10.alSource3f(_id, AL10.AL_VELOCITY, _vx = x, _vy = y, _vz = z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain of the source.
|
||||
*/
|
||||
public void setGain (float gain)
|
||||
{
|
||||
if (_gain != gain) {
|
||||
AL10.alSourcef(_id, AL10.AL_GAIN, _gain = gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the position, velocity, etc., of the source are relative to the
|
||||
* listener.
|
||||
*/
|
||||
public void setSourceRelative (boolean relative)
|
||||
{
|
||||
if (_sourceRelative != relative) {
|
||||
_sourceRelative = relative;
|
||||
AL10.alSourcei(_id, AL10.AL_SOURCE_RELATIVE, relative ? AL10.AL_TRUE : AL10.AL_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the source is looping.
|
||||
*/
|
||||
public void setLooping (boolean looping)
|
||||
{
|
||||
if (_looping != looping) {
|
||||
_looping = looping;
|
||||
AL10.alSourcei(_id, AL10.AL_LOOPING, looping ? AL10.AL_TRUE : AL10.AL_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum gain.
|
||||
*/
|
||||
public void setMinGain (float gain)
|
||||
{
|
||||
if (_minGain != gain) {
|
||||
AL10.alSourcef(_id, AL10.AL_MIN_GAIN, _minGain = gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum gain.
|
||||
*/
|
||||
public void setMaxGain (float gain)
|
||||
{
|
||||
if (_maxGain != gain) {
|
||||
AL10.alSourcef(_id, AL10.AL_MAX_GAIN, _maxGain = gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference distance for attenuation.
|
||||
*/
|
||||
public void setReferenceDistance (float distance)
|
||||
{
|
||||
if (_referenceDistance != distance) {
|
||||
AL10.alSourcef(_id, AL10.AL_REFERENCE_DISTANCE, _referenceDistance = distance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rolloff factor for attenuation.
|
||||
*/
|
||||
public void setRolloffFactor (float rolloff)
|
||||
{
|
||||
if (_rolloffFactor != rolloff) {
|
||||
AL10.alSourcef(_id, AL10.AL_ROLLOFF_FACTOR, _rolloffFactor = rolloff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum distance for attenuation.
|
||||
*/
|
||||
public void setMaxDistance (float distance)
|
||||
{
|
||||
if (_maxDistance != distance) {
|
||||
AL10.alSourcef(_id, AL10.AL_MAX_DISTANCE, _maxDistance = distance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pitch multiplier.
|
||||
*/
|
||||
public void setPitch (float pitch)
|
||||
{
|
||||
if (_pitch != pitch) {
|
||||
AL10.alSourcef(_id, AL10.AL_PITCH, _pitch = pitch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the direction of the source.
|
||||
*/
|
||||
public void setDirection (float x, float y, float z)
|
||||
{
|
||||
if (_dx != x || _dy != y || _dz != z) {
|
||||
AL10.alSource3f(_id, AL10.AL_DIRECTION, _dx = x, _dy = y, _dz = z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the inside angle of the sound cone.
|
||||
*/
|
||||
public void setConeInnerAngle (float angle)
|
||||
{
|
||||
if (_coneInnerAngle != angle) {
|
||||
AL10.alSourcef(_id, AL10.AL_CONE_INNER_ANGLE, _coneInnerAngle = angle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the outside angle of the sound cone.
|
||||
*/
|
||||
public void setConeOuterAngle (float angle)
|
||||
{
|
||||
if (_coneOuterAngle != angle) {
|
||||
AL10.alSourcef(_id, AL10.AL_CONE_OUTER_ANGLE, _coneOuterAngle = angle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain outside of the sound cone.
|
||||
*/
|
||||
public void setConeOuterGain (float gain)
|
||||
{
|
||||
if (_coneOuterGain != gain) {
|
||||
AL10.alSourcef(_id, AL10.AL_CONE_OUTER_GAIN, _coneOuterGain = gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source buffer. Equivalent to unqueueing all buffers, then queuing the provided
|
||||
* buffer. Cannot be called when the source is playing or paused.
|
||||
*
|
||||
* @param buffer the buffer to set, or <code>null</code> to clear.
|
||||
*/
|
||||
public void setBuffer (Buffer buffer)
|
||||
{
|
||||
_queue.clear();
|
||||
if (buffer != null) {
|
||||
_queue.add(buffer);
|
||||
}
|
||||
AL10.alSourcei(_id, AL10.AL_BUFFER, buffer == null ? AL10.AL_NONE : buffer.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the specified buffers.
|
||||
*/
|
||||
public void queueBuffers (Buffer... buffers)
|
||||
{
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(buffers.length);
|
||||
for (int ii = 0; ii < buffers.length; ii++) {
|
||||
Buffer buffer = buffers[ii];
|
||||
_queue.add(buffer);
|
||||
idbuf.put(ii, buffer.getId());
|
||||
}
|
||||
AL10.alSourceQueueBuffers(_id, idbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified buffers from the queue.
|
||||
*/
|
||||
public void unqueueBuffers (Buffer... buffers)
|
||||
{
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(buffers.length);
|
||||
for (int ii = 0; ii < buffers.length; ii++) {
|
||||
Buffer buffer = buffers[ii];
|
||||
_queue.remove(buffer);
|
||||
idbuf.put(ii, buffer.getId());
|
||||
}
|
||||
AL10.alSourceUnqueueBuffers(_id, idbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the source is playing.
|
||||
*/
|
||||
public boolean isPlaying ()
|
||||
{
|
||||
return getSourceState() == AL10.AL_PLAYING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the source is paused.
|
||||
*/
|
||||
public boolean isPaused ()
|
||||
{
|
||||
return getSourceState() == AL10.AL_PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the source is stopped.
|
||||
*/
|
||||
public boolean isStopped ()
|
||||
{
|
||||
return getSourceState() == AL10.AL_STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the source: {@link AL10#AL_INITIAL}, {@link AL10#AL_PLAYING},
|
||||
* {@link AL10#AL_PAUSED}, or {@link AL10#AL_STOPPED}.
|
||||
*/
|
||||
public int getSourceState ()
|
||||
{
|
||||
return AL10.alGetSourcei(_id, AL10.AL_SOURCE_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of buffers that have been processed.
|
||||
*/
|
||||
public int getBuffersProcessed ()
|
||||
{
|
||||
return AL10.alGetSourcei(_id, AL10.AL_BUFFERS_PROCESSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position offset of the source within the queued buffers, in seconds.
|
||||
*/
|
||||
public float getSecOffset ()
|
||||
{
|
||||
return AL10.alGetSourcef(_id, AL11.AL_SEC_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position offset of the source within the queued buffers, in samples.
|
||||
*/
|
||||
public int getSampleOffset ()
|
||||
{
|
||||
return AL10.alGetSourcei(_id, AL11.AL_SAMPLE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position offset of the source within the queued buffers, in bytes.
|
||||
*/
|
||||
public int getByteOffset ()
|
||||
{
|
||||
return AL10.alGetSourcei(_id, AL11.AL_BYTE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing the source.
|
||||
*/
|
||||
public void play ()
|
||||
{
|
||||
AL10.alSourcePlay(_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the source.
|
||||
*/
|
||||
public void pause ()
|
||||
{
|
||||
AL10.alSourcePause(_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the source.
|
||||
*/
|
||||
public void stop ()
|
||||
{
|
||||
AL10.alSourceStop(_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the source.
|
||||
*/
|
||||
public void rewind ()
|
||||
{
|
||||
AL10.alSourceRewind(_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this source, rendering it unusable.
|
||||
*/
|
||||
public void delete ()
|
||||
{
|
||||
IntBuffer idbuf = BufferUtils.createIntBuffer(1);
|
||||
idbuf.put(_id).rewind();
|
||||
AL10.alDeleteSources(idbuf);
|
||||
_id = 0;
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize ()
|
||||
throws Throwable
|
||||
{
|
||||
super.finalize();
|
||||
if (_id > 0) {
|
||||
_soundmgr.sourceFinalized(_id);
|
||||
}
|
||||
}
|
||||
|
||||
/** The sound manager responsible for this source. */
|
||||
protected SoundManager _soundmgr;
|
||||
|
||||
/** The OpenAL identifier for this source. */
|
||||
protected int _id;
|
||||
|
||||
/** The position of the source. */
|
||||
protected float _px, _py, _pz;
|
||||
|
||||
/** The velocity of the source. */
|
||||
protected float _vx, _vy, _vz;
|
||||
|
||||
/** The gain of the source. */
|
||||
protected float _gain = 1f;
|
||||
|
||||
/** Whether or not the source's position, velocity, etc. are relative to the listener. */
|
||||
protected boolean _sourceRelative;
|
||||
|
||||
/** Whether or not the source is looping. */
|
||||
protected boolean _looping;
|
||||
|
||||
/** 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 source. */
|
||||
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;
|
||||
|
||||
/** The source's queue of buffers (storing them keeps them from being garbage-collected). */
|
||||
protected ArrayList<Buffer> _queue = Lists.newArrayList();
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.io.IOException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
/**
|
||||
* 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
|
||||
_source = new Source(soundmgr);
|
||||
for (int ii = 0; ii < _buffers.length; ii++) {
|
||||
_buffers[ii] = new Buffer(soundmgr);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
_source.setGain(_gain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the stream source.
|
||||
*/
|
||||
public Source getSource ()
|
||||
{
|
||||
return _source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(_buffers.length);
|
||||
}
|
||||
_source.play();
|
||||
_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;
|
||||
}
|
||||
_source.pause();
|
||||
_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;
|
||||
}
|
||||
_source.stop();
|
||||
_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();
|
||||
}
|
||||
_source.setGain(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
|
||||
_source.delete();
|
||||
for (Buffer buffer : _buffers) {
|
||||
buffer.delete();
|
||||
}
|
||||
|
||||
// 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 = _source.getBuffersProcessed();
|
||||
if (played == 0) {
|
||||
return;
|
||||
}
|
||||
for (int ii = 0; ii < played; ii++) {
|
||||
_source.unqueueBuffers(_buffers[_qidx]);
|
||||
_qidx = (_qidx + 1) % _buffers.length;
|
||||
_qlen--;
|
||||
}
|
||||
|
||||
// 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 = _source.getSourceState();
|
||||
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);
|
||||
_source.setGain(_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)
|
||||
{
|
||||
for (int ii = 0; ii < buffers; ii++) {
|
||||
Buffer buffer = _buffers[(_qidx + _qlen) % _buffers.length];
|
||||
if (populateBuffer(buffer)) {
|
||||
_source.queueBuffers(buffer);
|
||||
_qlen++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (Buffer buffer)
|
||||
{
|
||||
if (_abuf == null) {
|
||||
_abuf = ByteBuffer.allocateDirect(getBufferSize()).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
_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);
|
||||
buffer.setData(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;
|
||||
|
||||
/**
|
||||
* Returns the size in bytes of the buffers to use.
|
||||
*/
|
||||
protected int getBufferSize ()
|
||||
{
|
||||
return 131072;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of buffers to use.
|
||||
*/
|
||||
protected int getNumBuffers ()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
/** The manager to which the stream was added. */
|
||||
protected SoundManager _soundmgr;
|
||||
|
||||
/** The source through which the stream plays. */
|
||||
protected Source _source;
|
||||
|
||||
/** The buffers through which we cycle. */
|
||||
protected Buffer[] _buffers = new Buffer[getNumBuffers()];
|
||||
|
||||
/** The starting index and length of the current queue in {@link #_buffers}. */
|
||||
protected int _qidx, _qlen;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Fading modes. */
|
||||
protected enum FadeMode { NONE, IN, OUT, OUT_DISPOSE }
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 java.util.HashMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
/**
|
||||
* Decodes audio streams from data read from an {@link InputStream}.
|
||||
*/
|
||||
public abstract class StreamDecoder
|
||||
{
|
||||
/**
|
||||
* Registers a class of {@link StreamDecoder} for the specified file extension.
|
||||
*/
|
||||
public static void registerExtension (String extension, Class<? extends StreamDecoder> clazz)
|
||||
{
|
||||
_extensions.put(extension, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a stream decoder for the specified file.
|
||||
*/
|
||||
public static StreamDecoder createInstance (File file)
|
||||
throws IOException
|
||||
{
|
||||
String path = file.getPath();
|
||||
int idx = path.lastIndexOf('.');
|
||||
if (idx == -1) {
|
||||
log.warning("Missing extension for file [file=" + path + "].");
|
||||
return null;
|
||||
}
|
||||
String extension = path.substring(idx+1);
|
||||
Class<? extends StreamDecoder> clazz = _extensions.get(extension);
|
||||
if (clazz == null) {
|
||||
log.warning("No decoder registered for extension [extension=" + extension +
|
||||
", file=" + path + "].");
|
||||
return null;
|
||||
}
|
||||
StreamDecoder decoder;
|
||||
try {
|
||||
decoder = clazz.newInstance();
|
||||
} catch (Exception e) {
|
||||
log.warning("Error instantiating decoder [file=" + path + ", error=" + e + "].");
|
||||
return null;
|
||||
}
|
||||
decoder.init(new FileInputStream(file));
|
||||
return decoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the decoder with its input stream.
|
||||
*/
|
||||
public abstract void init (InputStream in)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the sound format (see {@link Stream#getFormat}).
|
||||
*/
|
||||
public abstract int getFormat ();
|
||||
|
||||
/**
|
||||
* Returns the sound frequency (see {@link Stream#getFrequency}).
|
||||
*/
|
||||
public abstract int getFrequency ();
|
||||
|
||||
/**
|
||||
* Reads as much data as will fit into the specified buffer.
|
||||
*
|
||||
* @return the number of bytes read. If less than or equal to zero, the decoder has reached
|
||||
* the end of the stream.
|
||||
*/
|
||||
public abstract int read (ByteBuffer buf)
|
||||
throws IOException;
|
||||
|
||||
/** Maps file extensions to decoder classes. */
|
||||
protected static HashMap<String, Class<? extends StreamDecoder>> _extensions =
|
||||
Maps.newHashMap();
|
||||
static {
|
||||
registerExtension("ogg", OggStreamDecoder.class);
|
||||
registerExtension("mp3", Mp3StreamDecoder.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 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
|
||||
{
|
||||
WaveData file = WaveData.create(path);
|
||||
if (file == null) {
|
||||
throw new IOException("Error loading " + path);
|
||||
}
|
||||
return new Clip(file);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user