diff --git a/src/java/com/threerings/openal/Buffer.java b/src/java/com/threerings/openal/Buffer.java new file mode 100644 index 00000000..75786aa9 --- /dev/null +++ b/src/java/com/threerings/openal/Buffer.java @@ -0,0 +1,113 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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 // documentation inherited + 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; +} diff --git a/src/java/com/threerings/openal/ClipBuffer.java b/src/java/com/threerings/openal/ClipBuffer.java index db5aadde..6677f14e 100644 --- a/src/java/com/threerings/openal/ClipBuffer.java +++ b/src/java/com/threerings/openal/ClipBuffer.java @@ -101,12 +101,11 @@ public class ClipBuffer } /** - * Returns the identifier for this clip's buffer or -1 if it is not - * loaded. + * Returns a reference to this clip's buffer or null if it is not loaded. */ - public int getBufferId () + public Buffer getBuffer () { - return (_bufferId == null) ? -1 : _bufferId.get(0); + return _buffer; } /** @@ -150,13 +149,12 @@ public class ClipBuffer // create our OpenAL buffer and then queue ourselves up to have // our clip data loaded - _bufferId = BufferUtils.createIntBuffer(1); - AL10.alGenBuffers(_bufferId); + _buffer = new Buffer(_manager); int errno = AL10.alGetError(); if (errno != AL10.AL_NO_ERROR) { log.warning("Failed to create buffer [key=" + getKey() + ", errno=" + errno + "]."); - _bufferId = null; + _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 @@ -173,7 +171,7 @@ public class ClipBuffer */ public void dispose () { - if (_bufferId != null) { + if (_buffer != null) { // if there are sources bound to this buffer, we must wait // for them to be unbound if (_bound > 0) { @@ -182,8 +180,8 @@ public class ClipBuffer } // free up our buffer - AL10.alDeleteBuffers(_bufferId); - _bufferId = null; + _buffer.delete(); + _buffer = null; _state = UNLOADED; } } @@ -207,7 +205,7 @@ public class ClipBuffer */ protected boolean bind (Clip clip) { - AL10.alBufferData(_bufferId.get(0), clip.format, clip.data, clip.frequency); + _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); @@ -216,7 +214,7 @@ public class ClipBuffer } _state = LOADED; - _size = AL10.alGetBufferi(_bufferId.get(0), AL10.AL_SIZE); + _size = _buffer.getSize(); _observers.apply(new ObserverList.ObserverOp() { public boolean apply (Observer observer) { observer.clipLoaded(ClipBuffer.this); @@ -234,9 +232,9 @@ public class ClipBuffer */ protected void failed () { - if (_bufferId != null) { - AL10.alDeleteBuffers(_bufferId); - _bufferId = null; + if (_buffer != null) { + _buffer.delete(); + _buffer = null; } _state = UNLOADED; @@ -272,7 +270,7 @@ public class ClipBuffer protected ClipProvider _provider; protected String _path; protected int _state; - protected IntBuffer _bufferId; + protected Buffer _buffer; protected int _size; protected ObserverList _observers = new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY); diff --git a/src/java/com/threerings/openal/Listener.java b/src/java/com/threerings/openal/Listener.java new file mode 100644 index 00000000..4213960a --- /dev/null +++ b/src/java/com/threerings/openal/Listener.java @@ -0,0 +1,97 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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); +} diff --git a/src/java/com/threerings/openal/Sound.java b/src/java/com/threerings/openal/Sound.java index f1e38c9d..eea540cb 100644 --- a/src/java/com/threerings/openal/Sound.java +++ b/src/java/com/threerings/openal/Sound.java @@ -54,53 +54,164 @@ public class Sound } /** - * 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. + * Sets the position of the sound. */ - public void setLocation (float x, float y, float z) + public void setPosition (float x, float y, float z) { - if (_position == null) { - _position = BufferUtils.createFloatBuffer(3); + if (_source != null) { + _source.setPosition(x, y, z); } - _position.put(x).put(y).put(z); - _position.flip(); + _px = x; + _py = y; + _pz = z; } /** - * 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. + * Sets the velocity of the sound. */ - public void setVelocity (float dx, float dy, float dz) + public void setVelocity (float x, float y, float z) { - if (_velocity == null) { - _velocity = BufferUtils.createFloatBuffer(3); + if (_source != null) { + _source.setVelocity(x, y, z); } - _velocity.put(dx).put(dy).put(dz); - _velocity.flip(); + _vx = x; + _vy = y; + _vz = z; } /** - * 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. + * Sets the gain of the sound. + */ + public void setGain (float gain) + { + if (_source != null) { + _source.setGain(gain); + } + _gain = gain; + } + + /** + * 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; } /** - * 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. - * - *

- * Note: this value is multiplied by the base gain configured in the sound manager. + * Sets the direction of the sound. */ - public void setGain (float gain) + public void setDirection (float x, float y, float z) { - _gain = gain; + 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; } /** @@ -151,8 +262,8 @@ public class Sound */ public void pause () { - if (_sourceId != -1) { - AL10.alSourcePause(_sourceId); + if (_source != null) { + _source.pause(); } } @@ -162,8 +273,8 @@ public class Sound */ public void stop () { - if (_sourceId != -1) { - AL10.alSourceStop(_sourceId); + if (_source != null) { + _source.stop(); } } @@ -172,8 +283,7 @@ public class Sound */ public boolean isPlaying () { - return (_sourceId != -1 && AL10.AL_PLAYING == - AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE)); + return _source != null && _source.isPlaying(); } protected Sound (SoundGroup group, ClipBuffer buffer) @@ -224,32 +334,38 @@ public class Sound } // if we do not already have a source, obtain one - if (_sourceId == -1) { - _sourceId = _group.acquireSource(this); - if (_sourceId == -1) { + if (_source == null) { + _source = _group.acquireSource(this); + if (_source == null) { return false; } // bind our clip buffer to the source and notify it - AL10.alSourcei(_sourceId, AL10.AL_BUFFER, _buffer.getBufferId()); + _source.setBuffer(_buffer.getBuffer()); _buffer.sourceBound(); - } - // configure the source with our ephemera - AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch); - AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain * _group.getBaseGain()); - if (_position != null) { - AL10.alSource(_sourceId, AL10.AL_POSITION, _position); - } - if (_velocity != null) { - AL10.alSource(_sourceId, AL10.AL_VELOCITY, _velocity); + // configure the source with our ephemera + _source.setPosition(_px, _py, _pz); + _source.setVelocity(_vx, _vy, _vz); + _source.setGain(_gain * _group.getBaseGain()); + _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 - AL10.alSourcei(_sourceId, AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE); + _source.setLooping(loop); // and start that damned thing up! - AL10.alSourcePlay(_sourceId); + _source.play(); return true; } @@ -262,11 +378,10 @@ public class Sound */ protected boolean reclaim () { - if (_sourceId != -1 && - AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED) { - AL10.alSourcei(_sourceId, AL10.AL_BUFFER, 0); + if (_source != null && _source.isStopped()) { + _source.setBuffer(null); _buffer.sourceUnbound(); - _sourceId = -1; + _source = null; return true; } return false; @@ -279,17 +394,47 @@ public class Sound protected ClipBuffer _buffer; /** The source via which we are playing our sound currently. */ - protected int _sourceId = -1; + protected Source _source; - /** The pitch adjustment. */ - protected float _pitch = 1; + /** The position of the sound. */ + protected float _px, _py, _pz; - /** The gain adjustment. */ - protected float _gain = 1; + /** The velocity of the sound. */ + protected float _vx, _vy, _vz; - /** The starting position in 3D space. */ - protected FloatBuffer _position; + /** The gain of the sound. */ + protected float _gain = 1f; - /** The velocity vector. */ - protected FloatBuffer _velocity; + /** 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; } diff --git a/src/java/com/threerings/openal/SoundGroup.java b/src/java/com/threerings/openal/SoundGroup.java index dc87a7af..b70978db 100644 --- a/src/java/com/threerings/openal/SoundGroup.java +++ b/src/java/com/threerings/openal/SoundGroup.java @@ -65,11 +65,10 @@ public class SoundGroup public void dispose () { reclaimAll(); - if (_sourceIds != null) { - _sources.clear(); - AL10.alDeleteSources(_sourceIds); - _sourceIds = null; + for (PooledSource pooled : _sources) { + pooled.source.delete(); } + _sources.clear(); } /** @@ -77,14 +76,12 @@ public class SoundGroup */ public void reclaimAll () { - if (_sourceIds != null) { - // make sure any bound sources are released - for (Source source : _sources) { - if (source.holder != null) { - source.holder.stop(); - source.holder.reclaim(); - source.holder = null; - } + // make sure any bound sources are released + for (PooledSource pooled : _sources) { + if (pooled.holder != null) { + pooled.holder.stop(); + pooled.holder.reclaim(); + pooled.holder = null; } } } @@ -100,45 +97,38 @@ public class SoundGroup return; } - // create our sources - _sourceIds = BufferUtils.createIntBuffer(sources); - AL10.alGenSources(_sourceIds); - int errno = AL10.alGetError(); - if (errno != AL10.AL_NO_ERROR) { - log.warning("Failed to create sources [cprov=" + provider + - ", sources=" + sources + ", errno=" + errno + "]."); - _sourceIds = null; - // we'll have no sources which means all requests to play - // sounds will silently fail as if all sources were in use - - } else { - for (int ii = 0; ii < sources; ii++) { - Source source = new Source(); - source.sourceId = _sourceIds.get(ii); - _sources.add(source); + // 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 int acquireSource (Sound acquirer) + protected Source acquireSource (Sound acquirer) { - // start at the beginning of the list looking for an available - // source + // start at the beginning of the list looking for an available source for (int ii = 0, ll = _sources.size(); ii < ll; ii++) { - Source source = _sources.get(ii); - if (source.holder == null || source.holder.reclaim()) { + PooledSource pooled = _sources.get(ii); + if (pooled.holder == null || pooled.holder.reclaim()) { // note this source's new holder - source.holder = acquirer; + pooled.holder = acquirer; // move this source to the end of the list _sources.remove(ii); - _sources.add(source); - return source.sourceId; + _sources.add(pooled); + return pooled.source; } } - return -1; + return null; } /** @@ -150,15 +140,14 @@ public class SoundGroup } /** Used to track which sources are in use. */ - protected static class Source + protected static class PooledSource { - public int sourceId; + public Source source; public Sound holder; } protected SoundManager _manager; protected ClipProvider _provider; - protected IntBuffer _sourceIds; - protected ArrayList _sources = new ArrayList(); + protected ArrayList _sources = new ArrayList(); } diff --git a/src/java/com/threerings/openal/SoundManager.java b/src/java/com/threerings/openal/SoundManager.java index 5f2cbcda..9d3a25ab 100644 --- a/src/java/com/threerings/openal/SoundManager.java +++ b/src/java/com/threerings/openal/SoundManager.java @@ -23,14 +23,18 @@ package com.threerings.openal; import static com.threerings.openal.Log.log; +import java.nio.IntBuffer; + import java.util.ArrayList; import java.util.HashMap; +import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL; import org.lwjgl.openal.AL10; 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; @@ -85,6 +89,14 @@ public class SoundManager _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). @@ -137,6 +149,9 @@ public class SoundManager for (int ii = _streams.size() - 1; ii >= 0; ii--) { _streams.get(ii).update(time); } + + // delete any finalized objects + deleteFinalizedObjects(); } /** @@ -290,6 +305,41 @@ public class SoundManager _streams.remove(stream); } + /** + * 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 @@ -326,6 +376,9 @@ public class SoundManager /** 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; @@ -342,6 +395,12 @@ public class SoundManager /** The list of active streams. */ protected ArrayList _streams = new ArrayList(); + /** 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; diff --git a/src/java/com/threerings/openal/Source.java b/src/java/com/threerings/openal/Source.java new file mode 100644 index 00000000..4a171379 --- /dev/null +++ b/src/java/com/threerings/openal/Source.java @@ -0,0 +1,399 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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.IntBuffer; + +import java.util.ArrayList; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL10; + +/** + * 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) { + AL10.alSourcei(_id, AL10.AL_SOURCE_RELATIVE, + (_sourceRelative = relative) ? AL10.AL_TRUE : AL10.AL_FALSE); + } + } + + /** + * Sets whether or not the source is looping. + */ + public void setLooping (boolean looping) + { + if (_looping != looping) { + AL10.alSourcei(_id, AL10.AL_LOOPING, + (_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 null 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); + } + + /** + * 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 // documentation inherited + 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 _queue = new ArrayList(); +} diff --git a/src/java/com/threerings/openal/Stream.java b/src/java/com/threerings/openal/Stream.java index d5c58d2c..d68984e6 100644 --- a/src/java/com/threerings/openal/Stream.java +++ b/src/java/com/threerings/openal/Stream.java @@ -47,13 +47,10 @@ public abstract class Stream _soundmgr = soundmgr; // create the source and buffers - _nbuf = BufferUtils.createIntBuffer(NUM_BUFFERS); - _nbuf.limit(1); - AL10.alGenSources(_nbuf); - _sourceId = _nbuf.get(); - _nbuf.clear(); - AL10.alGenBuffers(_nbuf); - _nbuf.get(_bufferIds); + _source = new Source(soundmgr); + for (int ii = 0; ii < _buffers.length; ii++) { + _buffers[ii] = new Buffer(soundmgr); + } // register with sound manager _soundmgr.addStream(this); @@ -66,17 +63,16 @@ public abstract class Stream { _gain = gain; if (_fadeMode == FadeMode.NONE) { - AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain); + _source.setGain(_gain); } } /** - * Sets the pitch of the stream. + * Returns a reference to the stream source. */ - public void setPitch (float pitch) + public Source getSource () { - _pitch = pitch; - AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch); + return _source; } /** @@ -100,7 +96,7 @@ public abstract class Stream _qidx = _qlen = 0; queueBuffers(NUM_BUFFERS); } - AL10.alSourcePlay(_sourceId); + _source.play(); _state = AL10.AL_PLAYING; } @@ -113,7 +109,7 @@ public abstract class Stream log.warning("Tried to pause stream that wasn't playing."); return; } - AL10.alSourcePause(_sourceId); + _source.pause(); _state = AL10.AL_PAUSED; } @@ -126,7 +122,7 @@ public abstract class Stream log.warning("Tried to stop stream that was already stopped."); return; } - AL10.alSourceStop(_sourceId); + _source.stop(); _state = AL10.AL_STOPPED; } @@ -139,7 +135,7 @@ public abstract class Stream if (_state != AL10.AL_PLAYING) { play(); } - AL10.alSourcef(_sourceId, AL10.AL_GAIN, 0f); + _source.setGain(0f); _fadeMode = FadeMode.IN; _fadeInterval = interval; _fadeElapsed = 0f; @@ -168,12 +164,10 @@ public abstract class Stream } // delete the source and buffers - _nbuf.clear(); - _nbuf.put(_sourceId).flip(); - AL10.alDeleteSources(_nbuf); - _nbuf.clear(); - _nbuf.put(_bufferIds).flip(); - AL10.alDeleteBuffers(_nbuf); + _source.delete(); + for (Buffer buffer : _buffers) { + buffer.delete(); + } // remove from manager _soundmgr.removeStream(this); @@ -194,24 +188,21 @@ public abstract class Stream } // find out how many buffers have been played and unqueue them - int played = AL10.alGetSourcei(_sourceId, AL10.AL_BUFFERS_PROCESSED); + int played = _source.getBuffersProcessed(); if (played == 0) { return; } - _nbuf.clear(); for (int ii = 0; ii < played; ii++) { - _nbuf.put(_bufferIds[_qidx]); + _source.unqueueBuffers(_buffers[_qidx]); _qidx = (_qidx + 1) % NUM_BUFFERS; _qlen--; } - _nbuf.flip(); - AL10.alSourceUnqueueBuffers(_sourceId, _nbuf); // enqueue up to the number of buffers played queueBuffers(played); // find out if we're still playing; if not and we have buffers queued, we must restart - _state = AL10.alGetSourcei(_sourceId, AL10.AL_SOURCE_STATE); + _state = _source.getSourceState(); if (_qlen > 0 && _state != AL10.AL_PLAYING) { play(); } @@ -226,8 +217,7 @@ public abstract class Stream return; } float alpha = Math.min((_fadeElapsed += time) / _fadeInterval, 1f); - AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain * - (_fadeMode == FadeMode.IN ? alpha : (1f - alpha))); + _source.setGain(_gain * (_fadeMode == FadeMode.IN ? alpha : (1f - alpha))); if (alpha == 1f) { if (_fadeMode == FadeMode.OUT) { stop(); @@ -243,18 +233,15 @@ public abstract class Stream */ protected void queueBuffers (int buffers) { - _nbuf.clear(); for (int ii = 0; ii < buffers; ii++) { - int bufferId = _bufferIds[(_qidx + _qlen) % NUM_BUFFERS]; - if (populateBuffer(bufferId)) { - _nbuf.put(bufferId); + Buffer buffer = _buffers[(_qidx + _qlen) % NUM_BUFFERS]; + if (populateBuffer(buffer)) { + _source.queueBuffers(buffer); _qlen++; } else { break; } } - _nbuf.flip(); - AL10.alSourceQueueBuffers(_sourceId, _nbuf); } /** @@ -263,7 +250,7 @@ public abstract class Stream * @return true if data was read into the buffer and it should be enqueued, false if the end of * the stream has been reached and no data was read into the buffer */ - protected boolean populateBuffer (int bufferId) + protected boolean populateBuffer (Buffer buffer) { if (_abuf == null) { _abuf = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder()); @@ -279,7 +266,7 @@ public abstract class Stream return false; } _abuf.rewind().limit(read); - AL10.alBufferData(bufferId, getFormat(), _abuf, getFrequency()); + buffer.setData(getFormat(), _abuf, getFrequency()); return true; } @@ -306,17 +293,14 @@ public abstract class Stream protected SoundManager _soundmgr; /** The source through which the stream plays. */ - protected int _sourceId; + protected Source _source; /** The buffers through which we cycle. */ - protected int[] _bufferIds = new int[NUM_BUFFERS]; + protected Buffer[] _buffers = new Buffer[NUM_BUFFERS]; /** The starting index and length of the current queue in {@link #_bufferIds}. */ protected int _qidx, _qlen; - /** The pitch of the stream. */ - protected float _pitch = 1f; - /** The gain of the stream. */ protected float _gain = 1f;