A new sound system based on OpenAL. Only supports sound effects at the

moment, but OpenAL has the necessary business to later integrate "streams"
of audio (music).


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3669 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2005-08-05 03:12:16 +00:00
parent bdfc2a7005
commit 9d3eb07c06
8 changed files with 1059 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.nio.ByteBuffer;
/**
* Contains data for a single sampled sound.
*/
public class Clip
{
/** The OpenAL format of this clip: {@link AL10#AL_FORMAT_MONO8}, etc. */
public int format;
/** The frequency of this clip in samples per second. */
public int frequency;
/** The audio data. */
public ByteBuffer data;
}
@@ -0,0 +1,255 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.io.IOException;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.ObserverList;
/**
* Represents a sound that has been loaded into the OpenAL system, or one
* that was loaded but has been flushed and which can be reloaded as
* needed.
*/
public class ClipBuffer
implements LRUHashMap.LRUItem
{
/** Used to notify parties interested in when a clip is loaded. */
public static interface Observer
{
/** Called when a clip has completed loading and is ready to be
* played. */
public void clipLoaded (ClipBuffer buffer);
/** Called when a clip has failed to prepare itself for one reason
* or other. */
public void clipFailed (ClipBuffer buffer);
}
/**
* Create a key that uniquely identifies this combination of clip
* provider and path.
*/
public static Comparable makeKey (ClipProvider provider, String path)
{
// we'll just use a string, amazing!
return provider + path;
}
/**
* Creates a new clip buffer with the specified path that will obtain
* its clip data from the specified source. The clip will
* automatically queue itself up to be loaded into memory.
*/
public ClipBuffer (SoundManager manager, ClipProvider provider, String path)
{
_manager = manager;
_provider = provider;
_path = path;
}
/**
* Returns the unique key for this clip buffer.
*/
public Comparable getKey ()
{
return makeKey(_provider, _path);
}
/**
* Returns the provider used to load this clip.
*/
public ClipProvider getClipProvider ()
{
return _provider;
}
/**
* Returns the path that identifies this sound clip.
*/
public String getPath ()
{
return _path;
}
/**
* Returns true if this buffer is loaded and ready to go.
*/
public boolean isPlayable ()
{
return (_state == LOADED);
}
/**
* Returns the identifier for this clip's buffer or -1 if it is not
* loaded.
*/
public int getBufferId ()
{
return (_bufferId == null) ? -1 : _bufferId.get(0);
}
/**
* Returns the size (in bytes) of this clip as reported by OpenAL.
* This value will not be valid until the clip is bound.
*/
public int getSize ()
{
return _size;
}
/**
* Instructs this buffer to resolve its underlying clip and be ready
* to be played ASAP.
*/
public void resolve (Observer observer)
{
// if we're already loaded, this is easy
if (_state == LOADED) {
if (observer != null) {
observer.clipLoaded(this);
}
return;
}
// queue up the observer
if (observer != null) {
_observers.add(observer);
}
// if we're already loading, we can stop here
if (_state == LOADING) {
return;
}
// create our OpenAL buffer and then queue ourselves up to have
// our clip data loaded
_bufferId = BufferUtils.createIntBuffer(1);
AL10.alGenBuffers(_bufferId);
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to create buffer [key=" + getKey() +
", errno=" + errno + "].");
_bufferId = null;
// queue up a failure notification so that we properly return
// from this method and our sound has a chance to register
// itself as an observer before we jump up and declare failure
_manager.queueClipFailure(this);
} else {
_state = LOADING;
_manager.queueClipLoad(this);
}
}
// documentation inherited from interface LRUHashMap.LRUItem
public void removedFromMap (LRUHashMap map)
{
if (_bufferId != null) {
// we've been given the boot, free up our buffer
AL10.alDeleteBuffers(_bufferId);
_bufferId = null;
_state = UNLOADED;
}
}
/**
* This method is called by the background sound loading thread and
* actually loads the sound data from wherever it cometh.
*/
protected Clip load ()
throws IOException
{
return _provider.loadClip(_path);
}
/**
* This method is called back on the main thread and instructs this
* buffer to bind the clip data to this buffer's OpenAL buffer.
*
* @return true if the binding succeeded, false if we were unable to
* load the sound data into OpenAL.
*/
protected boolean bind (Clip clip)
{
AL10.alBufferData(
_bufferId.get(0), clip.format, clip.data, clip.frequency);
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to bind clip [key=" + getKey() +
", errno=" + errno + "].");
failed();
return false;
}
_state = LOADED;
_size = AL10.alGetBufferi(_bufferId.get(0), AL10.AL_SIZE);
_observers.apply(new ObserverList.ObserverOp() {
public boolean apply (Object observer) {
((Observer)observer).clipLoaded(ClipBuffer.this);
return true;
}
});
_observers.clear();
return true;
}
/**
* Called when we fail in some part of the process in resolving our
* clip data. Notifies our observers and resets the clip to the
* UNLOADED state.
*/
protected void failed ()
{
if (_bufferId != null) {
AL10.alDeleteBuffers(_bufferId);
_bufferId = null;
}
_state = UNLOADED;
_observers.apply(new ObserverList.ObserverOp() {
public boolean apply (Object observer) {
((Observer)observer).clipFailed(ClipBuffer.this);
return true;
}
});
_observers.clear();
}
protected SoundManager _manager;
protected ClipProvider _provider;
protected String _path;
protected int _state;
protected IntBuffer _bufferId;
protected int _size;
protected ObserverList _observers =
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
protected static final int UNLOADED = 0;
protected static final int LOADING = 1;
protected static final int LOADED = 2;
}
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.io.IOException;
/**
* Provides a generic mechanism for loading sound data.
*/
public interface ClipProvider
{
/** Loads the specified clip from the appropriate source. */
public Clip loadClip (String path) throws IOException;
}
+56
View File
@@ -0,0 +1,56 @@
//
// $Id: Log.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
/**
* A placeholder class that contains a reference to the log object used by
* this package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("narya.openal");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
+248
View File
@@ -0,0 +1,248 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
/**
* Represents an instance of a sound clip which can be positioned in 3D
* space, gain and pitch adjusted and played or looped.
*/
public class Sound
{
/**
* Returns the buffer of audio data associated with this sound.
*/
public ClipBuffer getBuffer ()
{
return _buffer;
}
/**
* Configures the location of this sound in 3D space. This will not
* affect an already playing sound but will take effect the next time
* it is played.
*/
public void setLocation (float x, float y, float z)
{
if (_position == null) {
_position = BufferUtils.createFloatBuffer(3);
}
_position.put(x).put(y).put(z);
_position.flip();
}
/**
* Configures the velocity of this sound in 3D space, in delta
* location per second (see {@link #setLocation}). This will not
* affect an already playing sound but will take effect the next time
* it is played.
*/
public void setVelocity (float dx, float dy, float dz)
{
if (_velocity == null) {
_velocity = BufferUtils.createFloatBuffer(3);
}
_velocity.put(dx).put(dy).put(dz);
_velocity.flip();
}
/**
* Configures the sounds's pitch. This value can range from 0.5 to
* 2.0. This will not affect an already playing sound but will take
* effect the next time it is played.
*/
public void setPitch (float pitch)
{
_pitch = pitch;
}
/**
* Configures the sound's gain (volume). This value can range from 0.0
* to 1.0 with 1.0 meaning no attenuation, each division by two
* corresponding to a -6db attentuation and each multiplication by two
* corresponding to +6db amplification. This will not affect an
* already playing sound but will take effect the next time it is
* played.
*/
public void setGain (float gain)
{
_gain = gain;
}
/**
* Plays this sound from the beginning. While the sound is playing, an
* audio channel will be locked and then freed when the sound
* completes.
*
* @param allowDefer if false, the sound will be played immediately or
* not at all. If true, the sound will be queued up for loading if it
* is currently flushed from the cache and played once loaded.
*
* @return true if the sound could be played and was started (or
* queued up to be loaded and played ASAP if it was specified as
* deferrable) or false if the sound could not be played either
* because it was not ready and deferral was not allowed or because
* too many other sounds were playing concurrently.
*/
public boolean play (boolean allowDefer)
{
return play(allowDefer, false);
}
/**
* 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);
}
/**
* Pauses this sound. A subsequent call to {@link #play} will resume
* the sound from the precise position that it left off. While the
* sound is paused, its audio channel will remain locked.
*/
public void pause ()
{
if (_sourceId != -1) {
AL10.alSourcePause(_sourceId);
}
}
/**
* Stops this sound and rewinds to its beginning. The audio channel
* being used to play the sound will be released.
*/
public void stop ()
{
if (_sourceId != -1) {
AL10.alSourceStop(_sourceId);
}
}
protected Sound (SoundGroup group, ClipBuffer buffer)
{
_group = group;
_buffer = buffer;
}
protected boolean play (boolean allowDefer, boolean loop)
{
// if we're not ready to go...
if (!_buffer.isPlayable()) {
if (allowDefer) {
// resolve the buffer and instruct it to play once it is
// resolved
_buffer.resolve(new ClipBuffer.Observer() {
public void clipLoaded (ClipBuffer buffer) {
play(false);
}
public void clipFailed (ClipBuffer buffer) {
// ugh. nothing to do here but be sad
}
});
} else {
// sorry charlie...
return false;
}
}
// if we do not already have a source, obtain one
if (_sourceId == -1) {
_sourceId = _group.acquireSource(this);
if (_sourceId == -1) {
return false;
}
// bind our clip buffer to the source
AL10.alSourcei(_sourceId, AL10.AL_BUFFER, _buffer.getBufferId());
}
// configure the source with our ephemera
AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch);
AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain);
if (_position != null) {
AL10.alSource (_sourceId, AL10.AL_POSITION, _position);
}
if (_velocity != null) {
AL10.alSource (_sourceId, AL10.AL_VELOCITY, _velocity);
}
// configure whether or not we should loop
AL10.alSourcei(_sourceId, AL10.AL_LOOPING,
loop ? AL10.AL_TRUE : AL10.AL_FALSE);
// and start that damned thing up!
AL10.alSourcePlay(_sourceId);
return true;
}
/**
* Called by the {@link SoundGroup} when it wants to reclaim our
* source.
*
* @return false if we have no source to reclaim or if we're still busy
* playing our sound, true if we gave up our source.
*/
protected boolean reclaim ()
{
if (_sourceId != -1 &&
AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE) ==
AL10.AL_STOPPED) {
_sourceId = -1;
return true;
}
return false;
}
/** The sound group with which we are associated. */
protected SoundGroup _group;
/** The OpenAL buffer from which we get our sound data. */
protected ClipBuffer _buffer;
/** The source via which we are playing our sound currently. */
protected int _sourceId = -1;
/** The pitch adjustment. */
protected float _pitch = 1;
/** The gain adjustment. */
protected float _gain = 1;
/** The starting position in 3D space. */
protected FloatBuffer _position;
/** The velocity vector. */
protected FloatBuffer _velocity;
}
@@ -0,0 +1,123 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.nio.IntBuffer;
import java.util.ArrayList;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
/**
* Manages a group of sounds, binding them to OpenAL sources as they are
* played and freeing up those sources for use by other sounds when the
* sounds are finished.
*/
public class SoundGroup
{
/**
* Queues up the specified sound clip for pre-loading into the cache.
*/
public void preloadClip (String path)
{
_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)
{
return new Sound(this, _manager.getClip(_provider, path));
}
/**
* 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 ()
{
AL10.alDeleteSources(_sourceIds);
}
protected SoundGroup (
SoundManager manager, ClipProvider provider, int sources)
{
_manager = manager;
_provider = provider;
// create our sources
_sourceIds = BufferUtils.createIntBuffer(sources);
AL10.alGenSources(_sourceIds);
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to create sources [cprov=" + provider +
", sources=" + sources + ", errno=" + errno + "].");
// we'll have no sources which means all requests to play
// sounds will silently fail as if all sources were in use
} else {
for (int ii = 0; ii < sources; ii++) {
Source source = new Source();
source.sourceId = _sourceIds.get(ii);
_sources.add(source);
}
}
}
/**
* Called by a {@link Sound} when it wants to obtain a source on which
* to play its clip.
*/
protected int acquireSource (Sound acquirer)
{
// start at the beginning of the list looking for an available
// source
for (int ii = 0, ll = _sources.size(); ii < ll; ii++) {
Source source = (Source)_sources.get(ii);
if (source.holder == null || source.holder.reclaim()) {
// note this source's new holder
source.holder = acquirer;
// move this source to the end of the list
_sources.remove(ii);
_sources.add(source);
return source.sourceId;
}
}
return -1;
}
/** Used to track which sources are in use. */
protected static class Source
{
public int sourceId;
public Sound holder;
}
protected SoundManager _manager;
protected ClipProvider _provider;
protected IntBuffer _sourceIds;
protected ArrayList _sources = new ArrayList();
}
@@ -0,0 +1,236 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.io.IOException;
import java.util.HashMap;
import java.util.WeakHashMap;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.Queue;
import com.samskivert.util.RunQueue;
/**
* An interface to the OpenAL library that provides a number of additional
* services:
*
* <ul>
* <li> an object oriented interface to the OpenAL system
* <li> a mechanism for loading a group of sounds and freeing their
* resources all at once
* <li> a mechanism for loading sounds in a background thread and
* preloading sounds that are likely to be needed soon
* </ul>
*
* <p><em>Note:</em> the sound manager is not thread safe (other than
* during its interactions with its internal background loading
* thread). It assumes that all sound loading and play requests will be
* made from a single thread.
*/
public class SoundManager
{
/**
* Creates, initializes and returns the singleton sound manager
* instance.
*
* @param rqueue a queue that the sound manager can use to post short
* runnables that must be executed on the same thread from which all
* other sound methods will be called.
*/
public static SoundManager createSoundManager (RunQueue rqueue)
{
if (_soundmgr != null) {
throw new IllegalStateException(
"A sound manager has already been created.");
}
_soundmgr = new SoundManager(rqueue);
return _soundmgr;
}
/**
* 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)
{
}
/**
* 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);
}
/**
* Creates a sound manager and initializes the OpenAL sound subsystem.
*/
protected SoundManager (RunQueue rqueue)
{
_rqueue = rqueue;
// initialize the OpenAL sound system
try {
AL.create("", 44100, 15, false);
} catch (Exception e) {
Log.warning("Failed to initialize sound system.");
Log.logStackTrace(e);
// don't start the background loading thread
return;
}
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to initialize sound system " +
"[errno=" + errno + "].");
// don't start the background loading thread
return;
}
// create our loading queue
_toLoad = new Queue();
// start up the background loader thread
_loader.setDaemon(true);
_loader.start();
}
/**
* Creates a clip buffer for the sound clip loaded via the specified
* provider with the specified path. The clip buffer may come from teh
* cache, and it will immediately be queued for loading if it is not
* already loaded.
*/
protected ClipBuffer getClip (ClipProvider provider, String path)
{
Comparable ckey = ClipBuffer.makeKey(provider, path);
ClipBuffer buffer = (ClipBuffer)_clips.get(ckey);
if (buffer == null) {
// check to see if this clip is currently loading
buffer = (ClipBuffer)_loading.get(ckey);
if (buffer == null) {
buffer = new ClipBuffer(this, provider, path);
_loading.put(ckey, buffer);
}
}
buffer.resolve(null);
return buffer;
}
/**
* 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();
}
});
}
/** The thread that loads up sound clips in the background. */
protected Thread _loader = new Thread("SoundManager.Loader") {
public void run () {
while (true) {
final ClipBuffer buffer = (ClipBuffer)_toLoad.get();
try {
final Clip clip = buffer.load();
_rqueue.postRunnable(new Runnable() {
public void run () {
Comparable ckey = buffer.getKey();
_loading.remove(ckey);
if (buffer.bind(clip)) {
_clips.put(ckey, buffer);
} else {
// TODO: shrink the cache size if the bind
// failed due to OUT_OF_MEMORY
}
}
});
} catch (Throwable t) {
Log.warning("Failed to load clip " +
"[key=" + buffer.getKey() + "].");
Log.logStackTrace(t);
// let the clip and its observers know that we are a
// miserable failure
queueClipFailure(buffer);
}
}
}
};
/** Used to get back from the background thread to our "main" thread. */
protected RunQueue _rqueue;
/** Contains a mapping of all currently-loading clips. */
protected HashMap _loading = new HashMap();
/** Contains a mapping of all loaded clips. */
protected LRUHashMap _clips = new LRUHashMap(DEFAULT_CACHE_SIZE, _sizer);
/** Contains a queue of clip buffers waiting to be loaded. */
protected Queue _toLoad;
/** 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 _sizer = new LRUHashMap.ItemSizer() {
public int computeSize (Object item) {
return ((ClipBuffer)item).getSize();
}
};
/** Default to a cache size of one megabyte. */
protected static final int DEFAULT_CACHE_SIZE = 1024 * 1024;
}