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;
@@ -46,6 +46,7 @@ import javax.swing.Timer;
import org.apache.commons.io.StreamUtils;
import com.samskivert.util.LockableLRUHashMap;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.Queue;
import com.threerings.resource.ResourceManager;
@@ -57,9 +58,6 @@ import com.threerings.media.Log;
*/
// TODO:
// - 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
implements MetaEventListener
{
@@ -106,6 +104,7 @@ public class SoundManager
public void run () {
Object command = null;
String path = null;
MidiInfo midiInfo = null;
while (amRunning()) {
try {
@@ -116,12 +115,14 @@ public class SoundManager
// some commands have an additional argument.
if ((PLAY == command) ||
(PLAYMUSIC == command) ||
(STOPMUSIC == command) ||
(LOCK == command) ||
(UNLOCK == command)) {
path = (String) _queue.get();
} else if (PLAYMUSIC == command) {
midiInfo = (MidiInfo) _queue.get();
}
}
@@ -130,7 +131,7 @@ public class SoundManager
playSound(path);
} else if (PLAYMUSIC == command) {
playSequence(path);
playSequence(midiInfo);
} else if (STOPMUSIC == command) {
stopSequence(path);
@@ -301,14 +302,21 @@ public class SoundManager
}
/**
* Start playing the specified music, stopping any currently played
* music.
* Start playing the specified music repeatedly.
*/
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) {
_queue.append(PLAYMUSIC);
_queue.append(path);
_queue.append(new MidiInfo(path, numloops));
}
}
@@ -336,13 +344,13 @@ public class SoundManager
return;
}
// get the sound data from our LRU cache
ClipInfo info = getClipData(path);
if (info == null) {
return; // borked!
}
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.open(info.stream);
@@ -354,6 +362,10 @@ public class SoundManager
Log.warning("Error loading sound file [path=" + path +
", e=" + ioe + "].");
} catch (UnsupportedAudioFileException uafe) {
Log.warning("Unsupported sound format [path=" + path + ", e=" +
uafe + "].");
} catch (LineUnavailableException lue) {
Log.warning("Line not available to play sound [path=" + path +
", e=" + lue + "].");
@@ -382,7 +394,7 @@ public class SoundManager
/**
* Play a sequence from the specified path.
*/
protected void playSequence (String path)
protected void playSequence (MidiInfo info)
{
if (_sequencer == null) {
try {
@@ -401,51 +413,116 @@ public class SoundManager
}
}
// stop the existing song
_sequencer.stop();
stopCurrentSong(false);
_midiStack.addFirst(info);
playTopSong();
}
/**
* Start the specified sequence.
*/
protected void playTopSong ()
{
if (_midiStack.isEmpty()) {
return;
}
MidiInfo info = (MidiInfo) _midiStack.getFirst();
// start the new one
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();
_midiStack.addFirst(path);
updateMusicVolume();
//updateMusicVolume();
//Log.info("Now playing : " + info.path);
} 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) {
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.
*/
protected void stopSequence (String path)
{
if (_midiStack.isEmpty()) {
return;
}
if (! _midiStack.isEmpty()) {
MidiInfo current = (MidiInfo) _midiStack.getFirst();
// if we're currently playing this song..
if (path.equals(_midiStack.getFirst())) {
// remove it from the stack
_midiStack.removeFirst();
if (_midiStack.isEmpty()) {
// no more to play? Stop and shutdown.
_sequencer.removeMetaEventListener(this);
// if we're currently playing this song..
if (path.equals(current.path)) {
// stop it
_stoppingSong = true;
_sequencer.stop();
_sequencer.close();
_sequencer = null;
_midiChannels = null;
// remove it from the stack
_midiStack.removeFirst();
// start playing the next..
playTopSong();
} else {
// play the next one on the stack (will also stop this one)
playSequence((String) _midiStack.removeFirst());
// we aren't currently playing this song. Simply remove.
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 ()
{
// remove all songs but the currently playing
while (_midiStack.size() > 1) {
_midiStack.removeLast();
}
// then stop the currently playing
if (! _midiStack.isEmpty()) {
stopSequence((String) _midiStack.getFirst());
}
_sequencer.removeMetaEventListener(this);
stopCurrentSong(false);
_sequencer.close();
_sequencer = null;
_midiChannels = null;
_midiStack.clear();
}
/**
@@ -487,8 +561,13 @@ public class SoundManager
// new String(msg.getData()));
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.
*/
protected ClipInfo getClipData (String path)
throws IOException, UnsupportedAudioFileException
{
ClipInfo info = (ClipInfo) _clipCache.get(path);
if (info != null) {
// we are re-using an old stream, make sure to rewind it.
try {
info.stream.reset();
} catch (IOException ioe) {
Log.warning("Couldn't reset audio stream! " +
"(this shouldn't happen)");
}
return info;
}
info.stream.reset();
try {
} else {
// set it up and put it in the cache
AudioInputStream stream = AudioSystem.getAudioInputStream(
getResource(path));
DataLine.Info dinfo = new DataLine.Info(
Clip.class, stream.getFormat());
info = new ClipInfo(dinfo, stream);
_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;
}
/**
* 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.
* 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
* free them up.
@@ -804,6 +912,10 @@ public class SoundManager
/** The queue of sound clips to be played. */
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. */
protected float _clipVol = 1f, _musicVol = 1f;
@@ -811,6 +923,9 @@ public class SoundManager
/** The cache of recent audio clips . */
protected LockableLRUHashMap _clipCache = new LockableLRUHashMap(10);
/** The cache of recent midi sequences. */
protected LRUHashMap _midiCache = new LRUHashMap(4);
/** The clips that are currently active. */
protected ArrayList _activeClips = new ArrayList();