Allow setting the volume independantly for any SoundType.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1944 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: SoundManager.java,v 1.9 2002/11/05 21:02:03 ray Exp $
|
||||
// $Id: SoundManager.java,v 1.10 2002/11/13 07:12:40 ray Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
@@ -20,11 +20,14 @@ import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.DataLine.Info;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.FloatControl;
|
||||
import javax.sound.sampled.FloatControl.Type;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineEvent;
|
||||
import javax.sound.sampled.LineListener;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import javax.swing.Timer;
|
||||
@@ -42,11 +45,13 @@ import com.threerings.media.Log;
|
||||
* Manages the playing of audio files.
|
||||
*/
|
||||
// TODO:
|
||||
// - volume for different sound types
|
||||
// Clip.getControl(FloatControl.Type.VOLUME);
|
||||
// - maybe a way to put items in the queue with a timestamp,
|
||||
// and if they're processed after that time, we cut them out.
|
||||
// - looping a sound
|
||||
// - looping a sound (is this really needed? Music should loop, but sfx?)
|
||||
// - Music is special type that has it's own methods
|
||||
// - push()
|
||||
// - pop()
|
||||
// - fade music out when it's popped and was playing
|
||||
public class SoundManager
|
||||
{
|
||||
/**
|
||||
@@ -55,21 +60,28 @@ public class SoundManager
|
||||
*/
|
||||
public static class SoundType
|
||||
{
|
||||
public SoundType (String description)
|
||||
/**
|
||||
* Construct a new SoundType.
|
||||
* Which should be a static variable stashed somewhere for the
|
||||
* entire application to share.
|
||||
*
|
||||
* @param strname a short string identifier, preferably without spaces.
|
||||
*/
|
||||
public SoundType (String strname)
|
||||
{
|
||||
_desc = description;
|
||||
_strname = strname;
|
||||
}
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
return _desc;
|
||||
return _strname;
|
||||
}
|
||||
|
||||
protected String _desc;
|
||||
protected String _strname;
|
||||
}
|
||||
|
||||
/** The default sound type. */
|
||||
public static final SoundType DEFAULT = null;
|
||||
public static final SoundType DEFAULT = new SoundType("default");
|
||||
|
||||
/**
|
||||
* Constructs a sound manager.
|
||||
@@ -79,9 +91,6 @@ public class SoundManager
|
||||
// save things off
|
||||
_rmgr = rmgr;
|
||||
|
||||
// enable default sounds
|
||||
setEnabled(DEFAULT, true);
|
||||
|
||||
// // obtain the mixer
|
||||
// _mixer = AudioSystem.getMixer(null);
|
||||
// if (_mixer == null) {
|
||||
@@ -95,26 +104,31 @@ public class SoundManager
|
||||
_player = new Thread() {
|
||||
public void run () {
|
||||
while (amRunning()) {
|
||||
// Log.info("Waiting for clip");
|
||||
try {
|
||||
// Log.info("Waiting for clip");
|
||||
|
||||
// wait until there is an item to get from the queue
|
||||
Object o = _queue.get();
|
||||
// wait until there is an item to get from the queue
|
||||
Object o = _queue.get();
|
||||
|
||||
// play sounds
|
||||
if (o instanceof String) {
|
||||
playSound((String) o);
|
||||
// play sounds
|
||||
if (o instanceof SoundKey) {
|
||||
playSound((SoundKey) o);
|
||||
|
||||
// see if we got the flush rsrcs signal
|
||||
} else if (o == FLUSH) {
|
||||
flushResources(false);
|
||||
// and re-start the resource freer timer to do
|
||||
// it again in 3 seconds
|
||||
_resourceFreer.restart();
|
||||
// see if we got the flush rsrcs signal
|
||||
} else if (o == FLUSH) {
|
||||
flushResources(false);
|
||||
// and re-start the resource freer timer to do
|
||||
// it again in 3 seconds
|
||||
_resourceFreer.restart();
|
||||
|
||||
// see if we got the die signal
|
||||
} else if (o == DIE) {
|
||||
_resourceFreer.stop();
|
||||
flushResources(true);
|
||||
// see if we got the die signal
|
||||
} else if (o == DIE) {
|
||||
_resourceFreer.stop();
|
||||
flushResources(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.warning("Captured exception playing sound.");
|
||||
Log.logStackTrace(e);
|
||||
}
|
||||
}
|
||||
Log.debug("SoundManager exit.");
|
||||
@@ -150,23 +164,26 @@ public class SoundManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether sound is enabled.
|
||||
* Sets the volume of a particular type of sound.
|
||||
*
|
||||
* @param val a volume parameter between 0f and 1f, inclusive.
|
||||
*/
|
||||
public void setEnabled (SoundType type, boolean enabled)
|
||||
public void setVolume (SoundType type, float val)
|
||||
{
|
||||
if (enabled) {
|
||||
_enabled.add(type);
|
||||
} else {
|
||||
_enabled.remove(type);
|
||||
}
|
||||
// bound it in
|
||||
val = Math.max(0f, Math.min(1f, val));
|
||||
_volumes.put(type, new Float(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the specified soundtype enabled?
|
||||
*/
|
||||
public boolean isEnabled (SoundType type)
|
||||
public float getVolume (SoundType type)
|
||||
{
|
||||
return _enabled.contains(type);
|
||||
Float vol = (Float) _volumes.get(type);
|
||||
|
||||
// now we're nice to unregistered sounds..
|
||||
return (vol != null) ? vol.floatValue() : 1f;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,11 +192,17 @@ public class SoundManager
|
||||
*/
|
||||
public void play (SoundType type, String path)
|
||||
{
|
||||
if (_player != null && isEnabled(type)) {
|
||||
if (type == null) {
|
||||
// let the lazy kids play too
|
||||
type = DEFAULT;
|
||||
}
|
||||
|
||||
if (_player != null && (0f != getVolume(type))) {
|
||||
// Log.info("Queueing sound [path=" + path + "].");
|
||||
synchronized (_queue) {
|
||||
if (_queue.size() < MAX_SOUNDS_ENQUEUED) {
|
||||
_queue.append(path);
|
||||
SoundKey key = new SoundKey(path, type);
|
||||
_queue.append(key);
|
||||
} else {
|
||||
Log.warning("SoundManager not playing sound because " +
|
||||
"too many sounds in queue [path=" + path +
|
||||
@@ -192,19 +215,19 @@ public class SoundManager
|
||||
/**
|
||||
* Plays the sound file with the given pathname.
|
||||
*/
|
||||
protected void playSound (String path)
|
||||
protected void playSound (SoundKey key)
|
||||
{
|
||||
// Log.info("Playing sound [path=" + path + "].");
|
||||
|
||||
// see if we're playing the same sound that was just played.
|
||||
SoundRecord rec = (SoundRecord) _active.get(path);
|
||||
SoundRecord rec = (SoundRecord) _active.get(key);
|
||||
if (rec != null) {
|
||||
rec.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
// get the sound data from our LRU cache
|
||||
byte[] data = getAudioData(path);
|
||||
byte[] data = getAudioData(key.path);
|
||||
if (data == null) {
|
||||
return; // borked!
|
||||
}
|
||||
@@ -219,21 +242,21 @@ public class SoundManager
|
||||
Clip clip = (Clip) AudioSystem.getLine(info);
|
||||
clip.open(stream);
|
||||
|
||||
rec = new SoundRecord(clip);
|
||||
rec = new SoundRecord(key, clip);
|
||||
rec.start();
|
||||
_active.put(path, rec);
|
||||
_active.put(key, rec);
|
||||
// Log.info("clip played [path=" + path + "]");
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error loading sound file [path=" + path +
|
||||
Log.warning("Error loading sound file [path=" + key.path +
|
||||
", e=" + ioe + "].");
|
||||
|
||||
} catch (UnsupportedAudioFileException uafe) {
|
||||
Log.warning("Unsupported sound format [path=" + path + ", e=" +
|
||||
Log.warning("Unsupported sound format [path=" + key.path + ", e=" +
|
||||
uafe + "].");
|
||||
|
||||
} catch (LineUnavailableException lue) {
|
||||
Log.warning("Line not available to play sound [path=" + path +
|
||||
Log.warning("Line not available to play sound [path=" + key.path +
|
||||
", e=" + lue + "].");
|
||||
}
|
||||
}
|
||||
@@ -281,20 +304,51 @@ public class SoundManager
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A key for looking up a SoundRecord.
|
||||
*/
|
||||
protected static class SoundKey
|
||||
{
|
||||
public String path;
|
||||
public SoundType type;
|
||||
|
||||
public SoundKey (String path, SoundType type)
|
||||
{
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int hashCode ()
|
||||
{
|
||||
return path.hashCode() ^ type.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
if (other instanceof SoundKey) {
|
||||
SoundKey that = (SoundKey) other;
|
||||
return this.path.equals(that.path) &&
|
||||
this.type.equals(that.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A record to help us manage the use of sound resources.
|
||||
* We don't free the resources associated with a clip immediately, because
|
||||
* it may be played again shortly.
|
||||
*/
|
||||
protected static class SoundRecord
|
||||
protected class SoundRecord
|
||||
implements LineListener
|
||||
{
|
||||
|
||||
/**
|
||||
* Construct a SoundRecord.
|
||||
*/
|
||||
public SoundRecord (Clip clip)
|
||||
public SoundRecord (SoundKey key, Clip clip)
|
||||
{
|
||||
_key = key;
|
||||
_clip = clip;
|
||||
|
||||
// The mess:
|
||||
@@ -334,6 +388,7 @@ public class SoundManager
|
||||
*/
|
||||
public void start ()
|
||||
{
|
||||
adjustVolume(_clip, _key.type);
|
||||
_clip.start();
|
||||
didStart();
|
||||
}
|
||||
@@ -438,6 +493,71 @@ public class SoundManager
|
||||
|
||||
/** The clip we're wrapping. */
|
||||
protected Clip _clip;
|
||||
|
||||
/** Other information associated with this clip. */
|
||||
protected SoundKey _key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the volume of this clip.
|
||||
*/
|
||||
protected void adjustVolume (Line c, SoundType type)
|
||||
{
|
||||
if (c.isControlSupported(FloatControl.Type.VOLUME)) {
|
||||
try {
|
||||
FloatControl vol = (FloatControl)
|
||||
c.getControl(FloatControl.Type.VOLUME);
|
||||
|
||||
float min = vol.getMinimum();
|
||||
float max = vol.getMaximum();
|
||||
|
||||
float ourval = (getVolume(type) * (max - min)) + min;
|
||||
Log.debug("adjust vol: [min=" + min + ", ourval=" + ourval +
|
||||
", max=" + max + "].");
|
||||
vol.setValue(ourval);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Log.logStackTrace(e);
|
||||
}
|
||||
}
|
||||
// fall back
|
||||
adjustVolumeFallback(c, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the gain control to implement volume.
|
||||
*/
|
||||
protected void adjustVolumeFallback (Line c, SoundType type)
|
||||
{
|
||||
FloatControl vol = (FloatControl)
|
||||
c.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
|
||||
// the only problem is that gain is specified in decibals,
|
||||
// which is a logarithmic scale.
|
||||
// Since we want max volume to leave the sample unchanged, our
|
||||
// maximum volume translates into a 0db gain.
|
||||
float ourSetting = getVolume(type);
|
||||
float gain;
|
||||
if (ourSetting == 1f) {
|
||||
// because our max is a 0db gain, and we're short-circuiting
|
||||
// divide-by-zero problems down below
|
||||
gain = 0f;
|
||||
|
||||
} else {
|
||||
|
||||
// if our setting isn't 1, we need to scale it by the
|
||||
// linear value of the minimum gain.
|
||||
// And we have to be very careful with the sign, since
|
||||
// it fucks up these log/pow methods.
|
||||
double absmin = Math.abs(vol.getMinimum());
|
||||
double minlin = Math.pow(10.0, absmin / 20.0);
|
||||
double ourval = ((1f - ourSetting) * absmin);
|
||||
|
||||
gain = (float) ((Math.log(ourval) / Math.log(10)) * -20d);
|
||||
}
|
||||
|
||||
vol.setValue(gain);
|
||||
//Log.info("Set gain: " + gain);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,7 +592,7 @@ public class SoundManager
|
||||
protected HashMap _active = new HashMap();
|
||||
|
||||
/** A set of soundTypes for which sound is enabled. */
|
||||
protected HashSet _enabled = new HashSet();
|
||||
protected HashMap _volumes = new HashMap();
|
||||
|
||||
/** Signals to the queue to do different things. */
|
||||
protected Object FLUSH = new Object(), DIE = new Object();
|
||||
|
||||
Reference in New Issue
Block a user