From fe0a5f545d94a101a7084b0c38e14a11e6fd7b8f Mon Sep 17 00:00:00 2001 From: Ray Greenwell Date: Wed, 13 Nov 2002 07:12:40 +0000 Subject: [PATCH] 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 --- .../threerings/media/sound/SoundManager.java | 220 ++++++++++++++---- 1 file changed, 170 insertions(+), 50 deletions(-) diff --git a/src/java/com/threerings/media/sound/SoundManager.java b/src/java/com/threerings/media/sound/SoundManager.java index a5ead966f..eb1b1545a 100644 --- a/src/java/com/threerings/media/sound/SoundManager.java +++ b/src/java/com/threerings/media/sound/SoundManager.java @@ -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();