diff --git a/src/java/com/threerings/media/sound/SoundManager.java b/src/java/com/threerings/media/sound/SoundManager.java
new file mode 100644
index 000000000..eb3a30255
--- /dev/null
+++ b/src/java/com/threerings/media/sound/SoundManager.java
@@ -0,0 +1,191 @@
+//
+// $Id: SoundManager.java,v 1.1 2002/07/24 21:28:09 shaper Exp $
+
+package com.threerings.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+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.Line;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+
+import com.samskivert.util.Queue;
+
+import com.threerings.resource.ResourceManager;
+
+import com.threerings.media.Log;
+
+/**
+ * Manages the playing of audio files.
+ */
+public class SoundManager
+ implements LineListener
+{
+ /**
+ * Constructs a sound manager.
+ */
+ public SoundManager (ResourceManager rmgr)
+ {
+ // save things off
+ _rmgr = rmgr;
+
+ // obtain the mixer
+ _mixer = AudioSystem.getMixer(null);
+ if (_mixer == null) {
+ Log.warning("Can't get default mixer, aborting audio " +
+ "initialization.");
+ return;
+ }
+
+ // create a thread to manage the sound queue
+ _player = new Thread() {
+ public void run () {
+ while (true) {
+ // wait for a sound
+ _clips.waitForItem();
+
+ // play the sound
+ playSound((String)_clips.get());
+ }
+ }
+ };
+ _player.setDaemon(true);
+ _player.start();
+ }
+
+ /**
+ * Queues up the sound file with the given pathname to be played when
+ * the sound manager deems the time appropriate.
+ */
+ public void play (String path)
+ {
+ Log.debug("Queueing sound [path=" + path + "].");
+ _clips.append(path);
+ }
+
+ /**
+ * Plays the sound file with the given pathname.
+ */
+ protected void playSound (String path)
+ {
+ Log.debug("Playing sound [path=" + path + "].");
+
+ // get the resource input stream
+ InputStream in = null;
+ try {
+ in = _rmgr.getResource(path);
+ } catch (IOException ioe) {
+ Log.warning("Failed to obtain resource input stream " +
+ "[path=" + path + ", ioe=" + ioe + "].");
+ return;
+ }
+
+ try {
+ // get the audio input stream and format
+ AudioInputStream ais = AudioSystem.getAudioInputStream(in);
+ // ais = getPCMAudioStream(ais);
+ AudioFormat format = ais.getFormat();
+ Log.debug("Obtained stream [format=" + format + "].");
+
+ // get the audio output line
+ Clip clip = (Clip)getLine(Clip.class, ais);
+ if (clip != null) {
+ // listen to our audio antics
+ clip.addLineListener(this);
+
+ // and start the clip playing
+ clip.open(ais);
+ clip.start();
+ }
+
+ } catch (Exception e) {
+ Log.warning("Failed to play sound [path=" + path +
+ ", e=" + e + "].");
+ Log.logStackTrace(e);
+ }
+ }
+
+ // documentation inherited
+ public void update (LineEvent event)
+ {
+ // Log.info("LineListener update [event=" + event + "].");
+ Clip clip = (Clip)event.getLine();
+ if (event.getType() == LineEvent.Type.STOP) {
+ // shut the clip down
+// clip.stop();
+// clip.close();
+
+ Log.debug("Finished playing sound [clip=" + clip + "].");
+ }
+ }
+
+ /**
+ * Returns an audio stream from the given audio stream with the sound
+ * data converted to a PCM_SIGNED format playable in JDK
+ * 1.4-beta3.
+ */
+ protected AudioInputStream getPCMAudioStream (AudioInputStream ais)
+ {
+ AudioFormat format = ais.getFormat();
+ AudioFormat pcmFormat = new AudioFormat(
+ AudioFormat.Encoding.PCM_SIGNED,
+ format.getSampleRate(),
+ format.getSampleSizeInBits() * 2,
+ format.getChannels(),
+ format.getFrameSize() * 2,
+ format.getFrameRate(),
+ true);
+ return AudioSystem.getAudioInputStream(pcmFormat, ais);
+ }
+
+ /**
+ * Returns a line suitable for playing the audio in the given stream,
+ * or null if an error occurred.
+ */
+ protected Line getLine (Class clazz, AudioInputStream ais)
+ {
+ AudioFormat format = ais.getFormat();
+ int bufferSize = (int)(ais.getFrameLength() * format.getFrameSize());
+ DataLine.Info info = new DataLine.Info(clazz, format, bufferSize);
+ if (!AudioSystem.isLineSupported(info)) {
+ Log.warning("Audio line for clip playback not supported " +
+ "[format=" + format + "].");
+ return null;
+ }
+
+ // Log.info("Checking available lines " +
+ // "[num=" + _mixer.getMaxLines(info) + "].");
+
+ // obtain a reference to the line
+ Line line;
+ try {
+ line = AudioSystem.getLine(info);
+ // line.open(format);
+ } catch (LineUnavailableException lue) {
+ Log.warning("Failed to open audio line [lue=" + lue + "].");
+ return null;
+ }
+ return line;
+ }
+
+ /** The resource manager from which we obtain audio files. */
+ protected ResourceManager _rmgr;
+
+ /** The thread that plays sounds. */
+ protected Thread _player;
+
+ /** The queue of sound clips to be played. */
+ protected Queue _clips = new Queue();
+
+ /** The default mixer. */
+ protected Mixer _mixer;
+}
diff --git a/tests/src/java/com/threerings/media/sound/SoundTestApp.java b/tests/src/java/com/threerings/media/sound/SoundTestApp.java
new file mode 100644
index 000000000..2f7419178
--- /dev/null
+++ b/tests/src/java/com/threerings/media/sound/SoundTestApp.java
@@ -0,0 +1,40 @@
+//
+// $Id: SoundTestApp.java,v 1.1 2002/07/24 21:28:10 shaper Exp $
+
+package com.threerings.media;
+
+import com.samskivert.util.StringUtil;
+import com.threerings.resource.ResourceManager;
+import com.threerings.media.Log;
+
+public class SoundTestApp
+{
+ public SoundTestApp (String[] args)
+ {
+ if (args.length == 0) {
+ Log.info("Usage: runjava com.threerings.media.SoundTestApp " +
+ " [ ...]");
+ System.exit(0);
+ }
+
+ ResourceManager rmgr = new ResourceManager("rsrc");
+ _soundmgr = new SoundManager(rmgr);
+ _files = args;
+ }
+
+ public void run ()
+ {
+ for (int ii = 0; ii < _files.length; ii++) {
+ _soundmgr.play(_files[ii]);
+ }
+ }
+
+ public static void main (String[] args)
+ {
+ SoundTestApp app = new SoundTestApp(args);
+ app.run();
+ }
+
+ protected String[] _files;
+ protected SoundManager _soundmgr;
+}