added support for specifying # of loops for a song,

fixed up song stack handling with loop logic.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1956 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2002-11-16 03:17:41 +00:00
parent d4126e3514
commit 01b203fd13
@@ -1,5 +1,5 @@
// //
// $Id: SoundManager.java,v 1.16 2002/11/16 00:13:47 ray Exp $ // $Id: SoundManager.java,v 1.17 2002/11/16 03:17:41 ray Exp $
package com.threerings.media; package com.threerings.media;
@@ -46,6 +46,7 @@ import javax.swing.Timer;
import org.apache.commons.io.StreamUtils; import org.apache.commons.io.StreamUtils;
import com.samskivert.util.LockableLRUHashMap; import com.samskivert.util.LockableLRUHashMap;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.Queue; import com.samskivert.util.Queue;
import com.threerings.resource.ResourceManager; import com.threerings.resource.ResourceManager;
@@ -57,9 +58,6 @@ import com.threerings.media.Log;
*/ */
// TODO: // TODO:
// - fade music out when stopped? // - fade music out when stopped?
// - redo volume stuff so that there is SFX and music volume, and then
// sounds can be turned on/off by SoundType
// - sounds only seem to loop once. WTF?
public class SoundManager public class SoundManager
implements MetaEventListener implements MetaEventListener
{ {
@@ -106,6 +104,7 @@ public class SoundManager
public void run () { public void run () {
Object command = null; Object command = null;
String path = null; String path = null;
MidiInfo midiInfo = null;
while (amRunning()) { while (amRunning()) {
try { try {
@@ -116,12 +115,14 @@ public class SoundManager
// some commands have an additional argument. // some commands have an additional argument.
if ((PLAY == command) || if ((PLAY == command) ||
(PLAYMUSIC == command) ||
(STOPMUSIC == command) || (STOPMUSIC == command) ||
(LOCK == command) || (LOCK == command) ||
(UNLOCK == command)) { (UNLOCK == command)) {
path = (String) _queue.get(); path = (String) _queue.get();
} else if (PLAYMUSIC == command) {
midiInfo = (MidiInfo) _queue.get();
} }
} }
@@ -130,7 +131,7 @@ public class SoundManager
playSound(path); playSound(path);
} else if (PLAYMUSIC == command) { } else if (PLAYMUSIC == command) {
playSequence(path); playSequence(midiInfo);
} else if (STOPMUSIC == command) { } else if (STOPMUSIC == command) {
stopSequence(path); stopSequence(path);
@@ -301,14 +302,21 @@ public class SoundManager
} }
/** /**
* Start playing the specified music, stopping any currently played * Start playing the specified music repeatedly.
* music.
*/ */
public void pushMusic (String path) public void pushMusic (String path)
{
pushMusic(path, -1);
}
/**
* Start playing music for the specified number of loops.
*/
public void pushMusic (String path, int numloops)
{ {
synchronized (_queue) { synchronized (_queue) {
_queue.append(PLAYMUSIC); _queue.append(PLAYMUSIC);
_queue.append(path); _queue.append(new MidiInfo(path, numloops));
} }
} }
@@ -336,13 +344,13 @@ public class SoundManager
return; return;
} }
// get the sound data from our LRU cache
ClipInfo info = getClipData(path);
if (info == null) {
return; // borked!
}
try { try {
// get the sound data from our LRU cache
ClipInfo info = getClipData(path);
if (info == null) {
return; // borked!
}
Clip clip = (Clip) AudioSystem.getLine(info.info); Clip clip = (Clip) AudioSystem.getLine(info.info);
clip.open(info.stream); clip.open(info.stream);
@@ -354,6 +362,10 @@ public class SoundManager
Log.warning("Error loading sound file [path=" + path + Log.warning("Error loading sound file [path=" + path +
", e=" + ioe + "]."); ", e=" + ioe + "].");
} catch (UnsupportedAudioFileException uafe) {
Log.warning("Unsupported sound format [path=" + path + ", e=" +
uafe + "].");
} catch (LineUnavailableException lue) { } catch (LineUnavailableException lue) {
Log.warning("Line not available to play sound [path=" + path + Log.warning("Line not available to play sound [path=" + path +
", e=" + lue + "]."); ", e=" + lue + "].");
@@ -382,7 +394,7 @@ public class SoundManager
/** /**
* Play a sequence from the specified path. * Play a sequence from the specified path.
*/ */
protected void playSequence (String path) protected void playSequence (MidiInfo info)
{ {
if (_sequencer == null) { if (_sequencer == null) {
try { try {
@@ -401,51 +413,116 @@ public class SoundManager
} }
} }
// stop the existing song stopCurrentSong(false);
_sequencer.stop(); _midiStack.addFirst(info);
playTopSong();
}
/**
* Start the specified sequence.
*/
protected void playTopSong ()
{
if (_midiStack.isEmpty()) {
return;
}
MidiInfo info = (MidiInfo) _midiStack.getFirst();
// start the new one // start the new one
try { try {
_sequencer.setSequence(getResource(path)); _sequencer.setSequence(getMidiData(info.path));
if (info.msPosition != -1) {
// TODO: this doesn't work correctly
_sequencer.setTickPosition(info.tickPosition);
_sequencer.setMicrosecondPosition(info.msPosition);
info.msPosition = -1;
}
_sequencer.start(); _sequencer.start();
_midiStack.addFirst(path); //updateMusicVolume();
updateMusicVolume(); //Log.info("Now playing : " + info.path);
} catch (InvalidMidiDataException imda) { } catch (InvalidMidiDataException imda) {
Log.warning("Invalid midi data, not playing [path=" + path + "]."); Log.warning("Invalid midi data, not playing [path=" +
info.path + "].");
} catch (IOException ioe) { } catch (IOException ioe) {
Log.warning("ioe=" + ioe); Log.warning("ioe=" + ioe);
} }
} }
/**
* Stop whatever song is currently playing and deal with the
* MidiInfo associated with it.
*/
protected void stopCurrentSong (boolean wasStopped)
{
if (_midiStack.isEmpty()) {
return;
}
// stop the existing song
if (!wasStopped) {
// TODO: fade?
_stoppingSong = true;
_sequencer.stop();
}
// see what was playing
MidiInfo current = (MidiInfo) _midiStack.getFirst();
switch (current.loops) {
default:
current.loops--;
break;
case 1:
// sorry charlie
_midiStack.removeFirst();
break;
case -1:
// save info...
// TODO: this doesn't seem to work, and is out-n-out broken
// if we use the tickPos
current.msPosition = _sequencer.getMicrosecondPosition();
current.tickPosition = _sequencer.getTickPosition();
break;
}
}
/** /**
* Stop the sequence at the specified path. * Stop the sequence at the specified path.
*/ */
protected void stopSequence (String path) protected void stopSequence (String path)
{ {
if (_midiStack.isEmpty()) { if (! _midiStack.isEmpty()) {
return; MidiInfo current = (MidiInfo) _midiStack.getFirst();
}
// if we're currently playing this song.. // if we're currently playing this song..
if (path.equals(_midiStack.getFirst())) { if (path.equals(current.path)) {
// remove it from the stack // stop it
_midiStack.removeFirst(); _stoppingSong = true;
if (_midiStack.isEmpty()) {
// no more to play? Stop and shutdown.
_sequencer.removeMetaEventListener(this);
_sequencer.stop(); _sequencer.stop();
_sequencer.close(); // remove it from the stack
_sequencer = null; _midiStack.removeFirst();
_midiChannels = null; // start playing the next..
playTopSong();
} else { } else {
// play the next one on the stack (will also stop this one) // we aren't currently playing this song. Simply remove.
playSequence((String) _midiStack.removeFirst()); for (Iterator iter=_midiStack.iterator(); iter.hasNext(); ) {
if (path.equals(((MidiInfo) iter.next()).path)) {
iter.remove();
return;
}
}
} }
} }
Log.debug("Sequence stopped that wasn't in the stack anymore " +
"[path=" + path + "].");
} }
/** /**
@@ -453,15 +530,12 @@ public class SoundManager
*/ */
protected void shutdownMidi () protected void shutdownMidi ()
{ {
// remove all songs but the currently playing _sequencer.removeMetaEventListener(this);
while (_midiStack.size() > 1) { stopCurrentSong(false);
_midiStack.removeLast(); _sequencer.close();
} _sequencer = null;
_midiChannels = null;
// then stop the currently playing _midiStack.clear();
if (! _midiStack.isEmpty()) {
stopSequence((String) _midiStack.getFirst());
}
} }
/** /**
@@ -487,8 +561,13 @@ public class SoundManager
// new String(msg.getData())); // new String(msg.getData()));
if (msg.getType() == MIDI_END_OF_TRACK) { if (msg.getType() == MIDI_END_OF_TRACK) {
// loop that puppy
playSequence((String) _midiStack.removeFirst()); if (_stoppingSong) {
_stoppingSong = false;
} else {
stopCurrentSong(true);
playTopSong();
}
} }
} }
@@ -516,39 +595,46 @@ public class SoundManager
* Get the audio data for the specified path. * Get the audio data for the specified path.
*/ */
protected ClipInfo getClipData (String path) protected ClipInfo getClipData (String path)
throws IOException, UnsupportedAudioFileException
{ {
ClipInfo info = (ClipInfo) _clipCache.get(path); ClipInfo info = (ClipInfo) _clipCache.get(path);
if (info != null) { if (info != null) {
// we are re-using an old stream, make sure to rewind it. // we are re-using an old stream, make sure to rewind it.
try { info.stream.reset();
info.stream.reset();
} catch (IOException ioe) {
Log.warning("Couldn't reset audio stream! " +
"(this shouldn't happen)");
}
return info;
}
try { } else {
// set it up and put it in the cache
AudioInputStream stream = AudioSystem.getAudioInputStream( AudioInputStream stream = AudioSystem.getAudioInputStream(
getResource(path)); getResource(path));
DataLine.Info dinfo = new DataLine.Info( DataLine.Info dinfo = new DataLine.Info(
Clip.class, stream.getFormat()); Clip.class, stream.getFormat());
info = new ClipInfo(dinfo, stream); info = new ClipInfo(dinfo, stream);
_clipCache.put(path, info); _clipCache.put(path, info);
} catch (UnsupportedAudioFileException uafe) {
Log.warning("Unsupported sound format [path=" + path + ", e=" +
uafe + "].");
} catch (IOException ioe) {
Log.warning("Error loading sound file [path=" + path + ", e=" +
ioe + "].");
} }
return info; return info;
} }
/**
* Get the midi data for the specified path.
*/
protected InputStream getMidiData (String path)
throws IOException
{
InputStream stream = (InputStream) _midiCache.get(path);
if (stream != null) {
// reset the stream for the new user
stream.reset();
} else {
stream = getResource(path);
_midiCache.put(path, stream);
}
return stream;
}
/** /**
* Get the data specified by the path from the resource bundle. * Get the data specified by the path from the resource bundle.
* No caching is done. * No caching is done.
@@ -783,6 +869,28 @@ public class SoundManager
} }
} }
/**
* A class that tracks the information about our playing midi files.
*/
protected static class MidiInfo
{
/** The path, duh. */
public String path;
/** How many times to loop, or -1 for forever. */
public int loops;
/** The position of big loopers, or -1 if none. */
public long tickPosition = -1;
public long msPosition = -1;
public MidiInfo (String path, int loops)
{
this.path = path;
this.loops = loops;
}
}
/** /**
* Every 3 seconds we look for sounds that haven't been used for 4 and * Every 3 seconds we look for sounds that haven't been used for 4 and
* free them up. * free them up.
@@ -805,12 +913,19 @@ public class SoundManager
/** The queue of sound clips to be played. */ /** The queue of sound clips to be played. */
protected Queue _queue = new Queue(); protected Queue _queue = new Queue();
/** So that we ignore the stopped meta event when we stop a song
* ourselves. */
protected boolean _stoppingSong = false;
/** Volume levels for both sound clips and music. */ /** Volume levels for both sound clips and music. */
protected float _clipVol = 1f, _musicVol = 1f; protected float _clipVol = 1f, _musicVol = 1f;
/** The cache of recent audio clips . */ /** The cache of recent audio clips . */
protected LockableLRUHashMap _clipCache = new LockableLRUHashMap(10); protected LockableLRUHashMap _clipCache = new LockableLRUHashMap(10);
/** The cache of recent midi sequences. */
protected LRUHashMap _midiCache = new LRUHashMap(4);
/** The clips that are currently active. */ /** The clips that are currently active. */
protected ArrayList _activeClips = new ArrayList(); protected ArrayList _activeClips = new ArrayList();