Files
narya/src/java/com/threerings/openal/ClipBuffer.java
T
Andrzej Kapolka e04f7eb1f8 Unbind buffer from source before we attempt to delete it, allow
re-resolving clips waiting to be unloaded.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4155 542714f4-19e9-0310-aa3c-eee0fc999fb1
2006-05-29 02:53:50 +00:00

288 lines
8.1 KiB
Java

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.openal;
import java.io.IOException;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import com.samskivert.util.ObserverList;
/**
* Represents a sound that has been loaded into the OpenAL system.
*/
public class ClipBuffer
{
/** Used to notify parties interested in when a clip is loaded. */
public static interface Observer
{
/** Called when a clip has completed loading and is ready to be
* played. */
public void clipLoaded (ClipBuffer buffer);
/** Called when a clip has failed to prepare itself for one reason
* or other. */
public void clipFailed (ClipBuffer buffer);
}
/**
* Create a key that uniquely identifies this combination of clip
* provider and path.
*/
public static Comparable makeKey (ClipProvider provider, String path)
{
// we'll just use a string, amazing!
return provider + path;
}
/**
* Creates a new clip buffer with the specified path that will obtain
* its clip data from the specified source. The clip will
* automatically queue itself up to be loaded into memory.
*/
public ClipBuffer (SoundManager manager, ClipProvider provider, String path)
{
_manager = manager;
_provider = provider;
_path = path;
}
/**
* Returns the unique key for this clip buffer.
*/
public Comparable getKey ()
{
return makeKey(_provider, _path);
}
/**
* Returns the provider used to load this clip.
*/
public ClipProvider getClipProvider ()
{
return _provider;
}
/**
* Returns the path that identifies this sound clip.
*/
public String getPath ()
{
return _path;
}
/**
* Returns true if this buffer is loaded and ready to go.
*/
public boolean isPlayable ()
{
return (_state == LOADED);
}
/**
* Returns the identifier for this clip's buffer or -1 if it is not
* loaded.
*/
public int getBufferId ()
{
return (_bufferId == null) ? -1 : _bufferId.get(0);
}
/**
* Returns the size (in bytes) of this clip as reported by OpenAL.
* This value will not be valid until the clip is bound.
*/
public int getSize ()
{
return _size;
}
/**
* Instructs this buffer to resolve its underlying clip and be ready
* to be played ASAP.
*/
public void resolve (Observer observer)
{
// if we were waiting to unload, cancel that
if (_state == UNLOADING) {
_state = LOADED;
_manager.restoreClip(this);
}
// if we're already loaded, this is easy
if (_state == LOADED) {
if (observer != null) {
observer.clipLoaded(this);
}
return;
}
// queue up the observer
if (observer != null) {
_observers.add(observer);
}
// if we're already loading, we can stop here
if (_state == LOADING) {
return;
}
// create our OpenAL buffer and then queue ourselves up to have
// our clip data loaded
_bufferId = BufferUtils.createIntBuffer(1);
AL10.alGenBuffers(_bufferId);
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to create buffer [key=" + getKey() +
", errno=" + errno + "].");
_bufferId = null;
// queue up a failure notification so that we properly return
// from this method and our sound has a chance to register
// itself as an observer before we jump up and declare failure
_manager.queueClipFailure(this);
} else {
_state = LOADING;
_manager.queueClipLoad(this);
}
}
/**
* Frees up the internal audio buffers associated with this clip.
*/
public void dispose ()
{
if (_bufferId != null) {
// if there are sources bound to this buffer, we must wait
// for them to be unbound
if (_bound > 0) {
_state = UNLOADING;
return;
}
// free up our buffer
AL10.alDeleteBuffers(_bufferId);
_bufferId = null;
_state = UNLOADED;
}
}
/**
* This method is called by the background sound loading thread and
* actually loads the sound data from wherever it cometh.
*/
protected Clip load ()
throws IOException
{
return _provider.loadClip(_path);
}
/**
* This method is called back on the main thread and instructs this
* buffer to bind the clip data to this buffer's OpenAL buffer.
*
* @return true if the binding succeeded, false if we were unable to
* load the sound data into OpenAL.
*/
protected boolean bind (Clip clip)
{
AL10.alBufferData(
_bufferId.get(0), clip.format, clip.data, clip.frequency);
int errno = AL10.alGetError();
if (errno != AL10.AL_NO_ERROR) {
Log.warning("Failed to bind clip [key=" + getKey() +
", errno=" + errno + "].");
failed();
return false;
}
_state = LOADED;
_size = AL10.alGetBufferi(_bufferId.get(0), AL10.AL_SIZE);
_observers.apply(new ObserverList.ObserverOp() {
public boolean apply (Object observer) {
((Observer)observer).clipLoaded(ClipBuffer.this);
return true;
}
});
_observers.clear();
return true;
}
/**
* Called when we fail in some part of the process in resolving our
* clip data. Notifies our observers and resets the clip to the
* UNLOADED state.
*/
protected void failed ()
{
if (_bufferId != null) {
AL10.alDeleteBuffers(_bufferId);
_bufferId = null;
}
_state = UNLOADED;
_observers.apply(new ObserverList.ObserverOp() {
public boolean apply (Object observer) {
((Observer)observer).clipFailed(ClipBuffer.this);
return true;
}
});
_observers.clear();
}
/**
* Notifies the buffer that a source has been bound to it.
*/
protected void sourceBound ()
{
_bound++;
}
/**
* Notifies the buffer that a source has been unbound from it.
*/
protected void sourceUnbound ()
{
// dispose of the buffer when the last source is unbound
if (--_bound == 0 && _state == UNLOADING) {
dispose();
}
}
protected SoundManager _manager;
protected ClipProvider _provider;
protected String _path;
protected int _state;
protected IntBuffer _bufferId;
protected int _size;
protected ObserverList _observers =
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
protected int _bound;
protected static final int UNLOADED = 0;
protected static final int LOADING = 1;
protected static final int LOADED = 2;
protected static final int UNLOADING = 3;
}