First pass at midi/rmf support. Mostly works, but songs aren't looping more
than twice. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1949 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: SoundManager.java,v 1.12 2002/11/15 02:06:41 ray Exp $
|
||||
// $Id: SoundManager.java,v 1.13 2002/11/15 05:34:43 ray Exp $
|
||||
|
||||
package com.threerings.media;
|
||||
|
||||
@@ -14,6 +14,16 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.sound.midi.InvalidMidiDataException;
|
||||
import javax.sound.midi.MetaEventListener;
|
||||
import javax.sound.midi.MetaMessage;
|
||||
import javax.sound.midi.MidiChannel;
|
||||
import javax.sound.midi.MidiSystem;
|
||||
import javax.sound.midi.MidiUnavailableException;
|
||||
import javax.sound.midi.Sequencer;
|
||||
import javax.sound.midi.Synthesizer;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
@@ -46,14 +56,12 @@ import com.threerings.media.Log;
|
||||
* Manages the playing of audio files.
|
||||
*/
|
||||
// TODO:
|
||||
// - 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 (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
|
||||
// - 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
|
||||
{
|
||||
/**
|
||||
* Create instances of this for your application to differentiate
|
||||
@@ -111,10 +119,10 @@ public class SoundManager
|
||||
type = (SoundType) _queue.get();
|
||||
path = (String) _queue.get();
|
||||
|
||||
} else if (LOCK == command) {
|
||||
path = (String) _queue.get();
|
||||
|
||||
} else if (UNLOCK == command) {
|
||||
} else if ((PLAYMUSIC == command) ||
|
||||
(STOPMUSIC == command) ||
|
||||
(LOCK == command) ||
|
||||
(UNLOCK == command)) {
|
||||
path = (String) _queue.get();
|
||||
}
|
||||
}
|
||||
@@ -123,6 +131,12 @@ public class SoundManager
|
||||
if (PLAY == command) {
|
||||
playSound(type, path);
|
||||
|
||||
} else if (PLAYMUSIC == command) {
|
||||
playSequence(path);
|
||||
|
||||
} else if (STOPMUSIC == command) {
|
||||
stopSequence(path);
|
||||
|
||||
} else if (LOCK == command) {
|
||||
_dataCache.lock(path);
|
||||
getAudioData(path); // preload
|
||||
@@ -200,6 +214,16 @@ public class SoundManager
|
||||
return (vol != null) ? vol.floatValue() : 1f;
|
||||
}
|
||||
|
||||
public void setMusicVolume (float vol)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public float getMusicVolume ()
|
||||
{
|
||||
return 1f; // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally lock the sound data prior to playing, to guarantee
|
||||
* that it will be quickly available for playing.
|
||||
@@ -250,6 +274,22 @@ public class SoundManager
|
||||
}
|
||||
}
|
||||
|
||||
public void pushMusic (String path)
|
||||
{
|
||||
synchronized (_queue) {
|
||||
_queue.append(PLAYMUSIC);
|
||||
_queue.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMusic (String path)
|
||||
{
|
||||
synchronized (_queue) {
|
||||
_queue.append(STOPMUSIC);
|
||||
_queue.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On the SoundManager thread,
|
||||
* plays the sound file with the given pathname.
|
||||
@@ -277,7 +317,7 @@ public class SoundManager
|
||||
|
||||
SoundRecord rec = new SoundRecord(path, clip);
|
||||
rec.start(type);
|
||||
_active.add(rec);
|
||||
_activeClips.add(rec);
|
||||
|
||||
// and rewind the stream to the beginning for next time
|
||||
stream.reset();
|
||||
@@ -300,8 +340,8 @@ public class SoundManager
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// we just go through all the sounds. There'll be 32 max, so fuckit.
|
||||
for (int ii=0, nn=_active.size(); ii < nn; ii++) {
|
||||
SoundRecord rec = (SoundRecord) _active.get(ii);
|
||||
for (int ii=0, nn=_activeClips.size(); ii < nn; ii++) {
|
||||
SoundRecord rec = (SoundRecord) _activeClips.get(ii);
|
||||
if (rec.path.equals(path) && rec.isStoppedSince(now)) {
|
||||
rec.restart(type);
|
||||
return true;
|
||||
@@ -311,6 +351,87 @@ public class SoundManager
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a sequence from the specified path.
|
||||
*/
|
||||
protected void playSequence (String path)
|
||||
{
|
||||
if (_sequencer == null) {
|
||||
try {
|
||||
Sequencer seq = MidiSystem.getSequencer();
|
||||
seq.open();
|
||||
if (seq instanceof Synthesizer) {
|
||||
_midiChannels = ((Synthesizer) seq).getChannels();
|
||||
}
|
||||
_sequencer = seq;
|
||||
_sequencer.addMetaEventListener(this);
|
||||
|
||||
} catch (MidiUnavailableException mue) {
|
||||
Log.warning("Midi unavailable. Can't play music.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// stop the existing song
|
||||
_sequencer.stop();
|
||||
|
||||
// start the new one
|
||||
try {
|
||||
_sequencer.setSequence(getResource(path));
|
||||
_sequencer.start();
|
||||
_midiStack.addFirst(path);
|
||||
|
||||
} catch (InvalidMidiDataException imda) {
|
||||
Log.warning("Invalid midi data, not playing [path=" + path + "].");
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("ioe=" + ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the sequence at the specified path.
|
||||
*/
|
||||
protected void stopSequence (String path)
|
||||
{
|
||||
if (_midiStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.stop();
|
||||
_sequencer.close();
|
||||
_sequencer.removeMetaEventListener(this);
|
||||
_sequencer = null;
|
||||
_midiChannels = null;
|
||||
|
||||
} else {
|
||||
// play the next one on the stack (will also stop this one)
|
||||
playSequence((String) _midiStack.removeFirst());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface MetaEventListener
|
||||
public void meta (MetaMessage msg)
|
||||
{
|
||||
if (msg.getType() == MIDI_END_OF_TRACK) {
|
||||
// loop that puppy
|
||||
// _sequencer.stop();
|
||||
// _sequencer.setTickPosition(0);
|
||||
_sequencer.start();
|
||||
}
|
||||
//
|
||||
// Log.info("meta message: " + msg.getType() + ", msg=" +
|
||||
// new String(msg.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called occaisionally to flush sound resources.
|
||||
*
|
||||
@@ -322,7 +443,7 @@ public class SoundManager
|
||||
{
|
||||
long then = System.currentTimeMillis() - EXPIRE_TIME;
|
||||
|
||||
for (Iterator iter=_active.iterator(); iter.hasNext(); ) {
|
||||
for (Iterator iter=_activeClips.iterator(); iter.hasNext(); ) {
|
||||
SoundRecord rec = (SoundRecord) iter.next();
|
||||
if (force || rec.isStoppedSince(then)) {
|
||||
iter.remove();
|
||||
@@ -342,10 +463,8 @@ public class SoundManager
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] data = StreamUtils.streamAsBytes(
|
||||
_rmgr.getResource(path), BUFFER_SIZE);
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(
|
||||
new ByteArrayInputStream(data));
|
||||
getResource(path));
|
||||
DataLine.Info info = new DataLine.Info(
|
||||
Clip.class, stream.getFormat());
|
||||
stuff = new Object[] { info, stream };
|
||||
@@ -363,6 +482,17 @@ public class SoundManager
|
||||
return stuff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data specified by the path from the resource bundle.
|
||||
* No caching is done.
|
||||
*/
|
||||
protected InputStream getResource (String path)
|
||||
throws IOException
|
||||
{
|
||||
return new ByteArrayInputStream(StreamUtils.streamAsBytes(
|
||||
_rmgr.getResource(path), BUFFER_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* A record to help us manage the use of sound resources.
|
||||
* We don't free the resources associated with a clip immediately, because
|
||||
@@ -613,18 +743,32 @@ public class SoundManager
|
||||
protected LockableLRUHashMap _dataCache = new LockableLRUHashMap(10);
|
||||
|
||||
/** The clips that are currently active. */
|
||||
protected ArrayList _active = new ArrayList();
|
||||
protected ArrayList _activeClips = new ArrayList();
|
||||
|
||||
/** The sequencer that plays midi music. */
|
||||
protected Sequencer _sequencer;
|
||||
|
||||
/** The channels in the sequencer, which we'll use to fuxor volumes. */
|
||||
protected MidiChannel[] _midiChannels;
|
||||
|
||||
/** The stack of songs that we're playing. */
|
||||
protected LinkedList _midiStack = new LinkedList();
|
||||
|
||||
/** A set of soundTypes for which sound is enabled. */
|
||||
protected HashMap _volumes = new HashMap();
|
||||
|
||||
/** Signals to the queue to do different things. */
|
||||
protected Object PLAY = new Object();
|
||||
protected Object PLAYMUSIC = new Object();
|
||||
protected Object STOPMUSIC = new Object();
|
||||
protected Object LOCK = new Object();
|
||||
protected Object UNLOCK = new Object();
|
||||
protected Object FLUSH = new Object();
|
||||
protected Object DIE = new Object();
|
||||
|
||||
/** This is apparently the midi code for end of track. Wack. */
|
||||
protected static final int MIDI_END_OF_TRACK = 47;
|
||||
|
||||
/** Default volume float object if no others found for a sound type. */
|
||||
protected static final Float DEFAULT_VOLUME = new Float(1f);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user