Pull the generic sound playing api from SoundManager up into
AbstractSoundManager and implement the actual sound playing with Java sound in SoundManager and with OpenAL in OpenALSoundManager. I left the SoundManager name alone so nothing using it will need to change, but if nothing besides yohoho is using it, I can refactor it to a more sane naming scheme. Rip out all the music and alternative format stuff since it didn't work anyway. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@622 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import com.threerings.media.sound.SoundManager.Frob;
|
||||
import com.threerings.media.sound.SoundManager.SoundType;
|
||||
|
||||
public abstract class AbstractSoundManager
|
||||
{
|
||||
/**
|
||||
* Shut the damn thing off.
|
||||
*/
|
||||
public abstract void shutdown ();
|
||||
|
||||
/**
|
||||
* Returns a string summarizing our volume settings and disabled sound types.
|
||||
*/
|
||||
public String summarizeState ()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("clipVol=").append(_clipVol);
|
||||
buf.append(", disabled=[");
|
||||
int ii = 0;
|
||||
for (SoundType soundType : _disabledTypes) {
|
||||
if (ii++ > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(soundType);
|
||||
}
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the specified soundtype enabled?
|
||||
*/
|
||||
public boolean isEnabled (SoundType type)
|
||||
{
|
||||
// by default, types are enabled..
|
||||
return (!_disabledTypes.contains(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on or off the specified sound type.
|
||||
*/
|
||||
public void setEnabled (SoundType type, boolean enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
_disabledTypes.remove(type);
|
||||
} else {
|
||||
_disabledTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume for all sound clips.
|
||||
*
|
||||
* @param vol a volume parameter between 0f and 1f, inclusive.
|
||||
*/
|
||||
public void setClipVolume (float vol)
|
||||
{
|
||||
_clipVol = Math.max(0f, Math.min(1f, vol));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume for all sound clips.
|
||||
*/
|
||||
public float getClipVolume ()
|
||||
{
|
||||
return _clipVol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally lock each of these keys prior to playing, to guarantee that it will be quickly
|
||||
* available for playing.
|
||||
*/
|
||||
public abstract void lock (String pkgPath, String... keys);
|
||||
|
||||
/**
|
||||
*Unlock the specified sounds so that its resources can be freed.
|
||||
*/
|
||||
public abstract void unlock (String pkgPath, String... keys);
|
||||
|
||||
/**
|
||||
* Play the specified sound as the specified type of sound, immediately. Note that a sound
|
||||
* need not be locked prior to playing.
|
||||
*
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key)
|
||||
{
|
||||
return play(type, pkgPath, key, 0, SoundManager.PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound as the specified type of sound, immediately, with the specified
|
||||
* pan value. Note that a sound need not be locked prior to playing.
|
||||
*
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, float pan)
|
||||
{
|
||||
return play(type, pkgPath, key, 0, pan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param delay the delay in milliseconds.
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, int delay)
|
||||
{
|
||||
return play(type, pkgPath, key, delay, SoundManager.PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param delay the delay in milliseconds.
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, final String pkgPath, final String key, int delay,
|
||||
final float pan)
|
||||
{
|
||||
if (!shouldPlay(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
new Interval(getSoundQueue()) {
|
||||
@Override
|
||||
public void expired () {
|
||||
play(pkgPath, key, pan);
|
||||
}
|
||||
}.schedule(delay);
|
||||
} else {
|
||||
play(pkgPath, key, pan);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
*/
|
||||
protected abstract void play (String pkgPath, String key, float pan);
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
public Frob loop (SoundType type, String pkgPath, String key)
|
||||
{
|
||||
return loop(type, pkgPath, key, SoundManager.PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
public Frob loop (SoundType type, String pkgPath, String key, float pan)
|
||||
{
|
||||
if (!shouldPlay(type)) {
|
||||
return null;
|
||||
}
|
||||
return loop(pkgPath, key, pan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
protected abstract Frob loop (String pkgPath, String key, float pan);
|
||||
|
||||
protected boolean shouldPlay (SoundType type)
|
||||
{
|
||||
if (type == null) {
|
||||
type = SoundManager.DEFAULT; // let the lazy kids play too
|
||||
}
|
||||
|
||||
return _clipVol != 0f && isEnabled(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the run queue on which sound should be played. It defaults to {@link RunQueue#AWT}.
|
||||
*/
|
||||
protected abstract RunQueue getSoundQueue ();
|
||||
|
||||
/** Volume level for sound clips. */
|
||||
protected float _clipVol = 1f;
|
||||
|
||||
/** A set of soundTypes for which sound is enabled. */
|
||||
protected Set<SoundType> _disabledTypes = Sets.newHashSet();
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.midi.MetaEventListener;
|
||||
import javax.sound.midi.MetaMessage;
|
||||
import javax.sound.midi.MidiChannel;
|
||||
import javax.sound.midi.MidiSystem;
|
||||
import javax.sound.midi.Sequencer;
|
||||
import javax.sound.midi.Synthesizer;
|
||||
|
||||
/**
|
||||
* Plays midi/rmf sounds using Java's sequencer, which is susceptible
|
||||
* to the accuracy of System.currentTimeMillis() and so currently sounds
|
||||
* like "ass" under Windows.
|
||||
*/
|
||||
public class MidiPlayer extends MusicPlayer
|
||||
implements MetaEventListener
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void init ()
|
||||
throws Exception
|
||||
{
|
||||
_sequencer = MidiSystem.getSequencer();
|
||||
_sequencer.open();
|
||||
if (_sequencer instanceof Synthesizer) {
|
||||
_channels = ((Synthesizer) _sequencer).getChannels();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void shutdown ()
|
||||
{
|
||||
_sequencer.close();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void start (InputStream stream)
|
||||
throws Exception
|
||||
{
|
||||
_sequencer.setSequence(new BufferedInputStream(stream));
|
||||
_sequencer.start();
|
||||
_sequencer.addMetaEventListener(this);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void stop ()
|
||||
{
|
||||
_sequencer.removeMetaEventListener(this);
|
||||
_sequencer.stop();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void setVolume (float volume)
|
||||
{
|
||||
if (_channels != null) {
|
||||
int setting = (int) (volume * 127.0);
|
||||
for (int ii=0; ii < _channels.length; ii++) {
|
||||
_channels[ii].controlChange(VOLUME_CONTROL, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface MetaEventListener
|
||||
public void meta (MetaMessage msg)
|
||||
{
|
||||
if (msg.getType() == END_OF_TRACK) {
|
||||
_musicListener.musicStopped();
|
||||
}
|
||||
}
|
||||
|
||||
// STUFF FROM ANOTHER TIME
|
||||
// /**
|
||||
// * Get a list of alternate midi devices.
|
||||
// */
|
||||
// public MidiDevice.Info[] getAlternateMidiDevices ()
|
||||
// {
|
||||
// ArrayList infos = new ArrayList();
|
||||
// CollectionUtil.addAll(infos, MidiSystem.getMidiDeviceInfo());
|
||||
//
|
||||
// // remove the synth/seqs, leaving only hardware midi thingies
|
||||
// for (Iterator iter=infos.iterator(); iter.hasNext(); ) {
|
||||
// try {
|
||||
// MidiDevice dev = MidiSystem.getMidiDevice(
|
||||
// (MidiDevice.Info) iter.next());
|
||||
// if ((dev instanceof Sequencer) ||
|
||||
// (dev instanceof Synthesizer)) {
|
||||
// iter.remove();
|
||||
// }
|
||||
// } catch (MidiUnavailableException mue) {
|
||||
// iter.remove();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return (MidiDevice.Info[]) infos.toArray(
|
||||
// new MidiDevice.Info[infos.size()]);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Attempt to use the alternate midi device for output.
|
||||
// * Return true if we're using it.
|
||||
// */
|
||||
// public boolean useAlternateDevice (MidiDevice.Info devinfo)
|
||||
// {
|
||||
// Log.info("Trying alternate device: " + devinfo);
|
||||
// try {
|
||||
// MidiDevice dev = MidiSystem.getMidiDevice(devinfo);
|
||||
// Receiver rec = dev.getReceiver();
|
||||
// if (rec == null) {
|
||||
// Log.info("Got no device!");
|
||||
// return false;
|
||||
// }
|
||||
// _stoppingSong = true;
|
||||
// _sequencer.stop();
|
||||
// _sequencer.close();
|
||||
//
|
||||
// Receiver old = _sequencer.getTransmitter().getReceiver();
|
||||
// Log.info("Old receiver: " + old);
|
||||
// if (old != null) {
|
||||
// old.close();
|
||||
// }
|
||||
// _sequencer.open();
|
||||
//
|
||||
// // THIS DOESN'T WORK.
|
||||
// // See bug #4347135, specifically notes on the bottom.
|
||||
// _sequencer.getTransmitter().setReceiver(rec);
|
||||
// playTopSong();
|
||||
//
|
||||
// // possibly shut down an old receiver
|
||||
// if (_receiver != null) {
|
||||
// _receiver.close();
|
||||
// }
|
||||
// // set the new receiver
|
||||
// _receiver = rec;
|
||||
//
|
||||
// return true;
|
||||
//
|
||||
// } catch (MidiUnavailableException mue) {
|
||||
// Log.warning("Use of alternate device failed [e=" + mue +
|
||||
// ", device=" + devinfo + "].");
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
/** This is apparently the midi code for end of track. Wack. */
|
||||
protected static final int END_OF_TRACK = 47;
|
||||
|
||||
/** The midi control for volume is 7. Ooooooo. */
|
||||
protected static final int VOLUME_CONTROL = 7;
|
||||
|
||||
/** The sequencer. */
|
||||
protected Sequencer _sequencer;
|
||||
|
||||
/** The channels in the sequencer, which we'll use to fuxor volumes. */
|
||||
protected MidiChannel[] _channels;
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import micromod.MicroMod;
|
||||
import micromod.Module;
|
||||
import micromod.ModuleLoader;
|
||||
import micromod.output.JavaSoundOutputDevice;
|
||||
import micromod.output.OutputDeviceException;
|
||||
import micromod.output.converters.SS16LEAudioFormatConverter;
|
||||
import micromod.resamplers.LinearResampler;
|
||||
|
||||
/**
|
||||
* A player that plays .mod format music.
|
||||
*/
|
||||
public class ModPlayer extends MusicPlayer
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void init ()
|
||||
throws Exception
|
||||
{
|
||||
_device = new NaryaSoundDevice();
|
||||
_device.start();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void shutdown ()
|
||||
{
|
||||
_device.stop();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void start (InputStream stream)
|
||||
throws Exception
|
||||
{
|
||||
Module module = ModuleLoader.read(new DataInputStream(stream));
|
||||
|
||||
final MicroMod mod = new MicroMod(module, _device, new LinearResampler());
|
||||
|
||||
_player = new Thread("narya mod player") {
|
||||
public void run () {
|
||||
|
||||
while (mod.getSequenceLoopCount() == 0) {
|
||||
|
||||
mod.doRealTimePlayback();
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException ie) {
|
||||
// WFCares
|
||||
}
|
||||
|
||||
if (_player != Thread.currentThread()) {
|
||||
// we were stopped!
|
||||
return;
|
||||
}
|
||||
}
|
||||
_device.drain();
|
||||
_musicListener.musicStopped();
|
||||
}
|
||||
};
|
||||
|
||||
_player.setDaemon(true);
|
||||
_player.start();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void stop ()
|
||||
{
|
||||
_player = null;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void setVolume (float vol)
|
||||
{
|
||||
_device.setVolume(vol);
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that allows us to access the dataline so we can adjust
|
||||
* the volume.
|
||||
*/
|
||||
protected static class NaryaSoundDevice extends JavaSoundOutputDevice
|
||||
{
|
||||
public NaryaSoundDevice ()
|
||||
throws OutputDeviceException
|
||||
{
|
||||
super(new SS16LEAudioFormatConverter(), 44100, 1000);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void setVolume (float vol)
|
||||
{
|
||||
SoundManager.adjustVolume(sourceDataLine, vol);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void drain ()
|
||||
{
|
||||
sourceDataLine.drain();
|
||||
}
|
||||
}
|
||||
|
||||
/** The thread that does the work. */
|
||||
protected Thread _player;
|
||||
|
||||
/** The sound output device. */
|
||||
protected NaryaSoundDevice _device;
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
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.DataLine;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Plays mp3 files. Depends on three external jar files that aren't even
|
||||
* imported here:
|
||||
* tritonus_share.jar
|
||||
* tritonus_mp3.jar
|
||||
* javalayer.jar
|
||||
*/
|
||||
public class Mp3Player extends MusicPlayer
|
||||
{
|
||||
@Override
|
||||
public void init ()
|
||||
{
|
||||
// TODO: some stuff needs to move here, like setting up the line
|
||||
// but we don't yet know the audio format, so I need to figure that
|
||||
// out (the format might always be known..).
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown ()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start (final InputStream stream)
|
||||
throws Exception
|
||||
{
|
||||
// TODO: some stuff needs to come out of here and into init/shutdown
|
||||
// but we'll deal with all that later, d00d.
|
||||
_player = new Thread("narya mp3 relay") {
|
||||
@Override
|
||||
public void run () {
|
||||
AudioInputStream inStream = null;
|
||||
try {
|
||||
inStream = AudioSystem.getAudioInputStream(
|
||||
new BufferedInputStream(stream, BUFFER_SIZE));
|
||||
} catch (Exception e) {
|
||||
log.warning("MP3 fuckola. [e=" + e + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
AudioFormat.Encoding targetEnc = AudioFormat.Encoding.PCM_SIGNED;
|
||||
|
||||
inStream = AudioSystem.getAudioInputStream(targetEnc, inStream);
|
||||
AudioFormat format = inStream.getFormat();
|
||||
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
|
||||
|
||||
try {
|
||||
_line = (SourceDataLine) AudioSystem.getLine(info);
|
||||
_line.open(format);
|
||||
} catch (LineUnavailableException lue) {
|
||||
log.warning("MP3 line unavailable: " + lue);
|
||||
return;
|
||||
}
|
||||
|
||||
_line.start();
|
||||
|
||||
byte[] data = new byte[BUFFER_SIZE];
|
||||
int count = 0;
|
||||
while (count >= 0) {
|
||||
try {
|
||||
count = inStream.read(data, 0, data.length);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error reading MP3: " + ioe);
|
||||
break;
|
||||
}
|
||||
if (count >= 0) {
|
||||
_line.write(data, 0, count);
|
||||
}
|
||||
if (_player != Thread.currentThread()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_line.drain();
|
||||
_line.close();
|
||||
_musicListener.musicStopped();
|
||||
}
|
||||
};
|
||||
|
||||
_player.setDaemon(true);
|
||||
//_player.setPriority(_player.getPriority() + 1);
|
||||
_player.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop ()
|
||||
{
|
||||
_player = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume (float volume)
|
||||
{
|
||||
// TODO : line won't be null when we initialize it in the right place
|
||||
if (_line != null) {
|
||||
SoundManager.adjustVolume(_line, volume);
|
||||
}
|
||||
}
|
||||
|
||||
/** The thread that transfers data to the line. */
|
||||
protected Thread _player;
|
||||
|
||||
/** The line that we play through. */
|
||||
protected SourceDataLine _line;
|
||||
|
||||
/** The size of our buffer. */
|
||||
protected static final int BUFFER_SIZE = 8192;
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import com.samskivert.util.Config;
|
||||
import com.samskivert.util.RandomUtil;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Manages the playing of audio files.
|
||||
*/
|
||||
// TODO:
|
||||
// - fade music out when stopped?
|
||||
// - be able to pause music
|
||||
public class MusicManager
|
||||
{
|
||||
/**
|
||||
* Constructs a music manager.
|
||||
*
|
||||
* @param smgr The soundManager we work with.
|
||||
* @param runQueue the client event run queue.
|
||||
*
|
||||
*/
|
||||
public MusicManager (SoundManager smgr, RunQueue runQueue)
|
||||
{
|
||||
_smgr = smgr;
|
||||
_runQueue = runQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut the damn thing off.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
_musicStack.clear();
|
||||
stopMusicPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string summarizing our volume settings and disabled sound types.
|
||||
*/
|
||||
public String summarizeState ()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("musicVol=").append(_musicVol);
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume for music.
|
||||
*
|
||||
* @param vol a volume parameter between 0f and 1f, inclusive.
|
||||
*/
|
||||
public void setMusicVolume (float vol)
|
||||
{
|
||||
float oldvol = _musicVol;
|
||||
_musicVol = Math.max(0f, Math.min(1f, vol));
|
||||
if (_musicPlayer != null) {
|
||||
_musicPlayer.setVolume(_musicVol);
|
||||
}
|
||||
|
||||
if ((oldvol == 0f) && (_musicVol != 0f)) {
|
||||
playTopMusic();
|
||||
} else if ((oldvol != 0f) && (_musicVol == 0f)) {
|
||||
stopMusicPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the music volume.
|
||||
*/
|
||||
public float getMusicVolume ()
|
||||
{
|
||||
return _musicVol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start playing the specified music repeatedly.
|
||||
*/
|
||||
public void pushMusic (String pkgPath, String key)
|
||||
{
|
||||
pushMusic(pkgPath, key, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start playing music for the specified number of loops. If no other
|
||||
* music is pushed, the specified music will play for the number of loops
|
||||
* specified, and will then be popped off the stack.
|
||||
*/
|
||||
public void pushMusic (String pkgPath, String key, int numloops)
|
||||
{
|
||||
MusicKey mkey = new MusicKey(pkgPath, key, numloops);
|
||||
|
||||
// stop any existing playing music
|
||||
if (_musicPlayer != null) {
|
||||
_musicPlayer.stop();
|
||||
handleMusicStopped();
|
||||
}
|
||||
|
||||
// add the new song
|
||||
_musicStack.addFirst(mkey);
|
||||
|
||||
// and play it
|
||||
playTopMusic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified music from the playlist. If it is currently
|
||||
* playing, it will be stopped and the previous song will be started.
|
||||
*/
|
||||
public void removeMusic (String pkgPath, String key)
|
||||
{
|
||||
MusicKey mkey = new MusicKey(pkgPath, key, -1);
|
||||
|
||||
if (!_musicStack.isEmpty()) {
|
||||
MusicKey current = _musicStack.getFirst();
|
||||
|
||||
// if we're currently playing this song..
|
||||
if (mkey.equals(current)) {
|
||||
// stop it
|
||||
if (_musicPlayer != null) {
|
||||
_musicPlayer.stop();
|
||||
}
|
||||
|
||||
// remove it from the stack
|
||||
_musicStack.removeFirst();
|
||||
// start playing the next..
|
||||
playTopMusic();
|
||||
return;
|
||||
|
||||
} else {
|
||||
// we aren't currently playing this song. Simply remove.
|
||||
for (Iterator<MusicKey> iter=_musicStack.iterator(); iter.hasNext(); ) {
|
||||
if (key.equals(iter.next())) {
|
||||
iter.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Sequence stopped that wasn't in the stack anymore [key=" + mkey + "].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the specified sequence.
|
||||
*/
|
||||
protected void playTopMusic ()
|
||||
{
|
||||
if (_musicStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the volume is off, we don't actually want to play anything but we want to at least
|
||||
// decrement any loopers by one and keep them on the top of the queue
|
||||
if (_musicVol == 0f) {
|
||||
handleMusicStopped();
|
||||
return;
|
||||
}
|
||||
|
||||
MusicKey info = _musicStack.getFirst();
|
||||
|
||||
Config c = _smgr.getConfig(info);
|
||||
String[] names = c.getValue(info.key, (String[])null);
|
||||
if ((names == null) || (names.length == 0)) {
|
||||
log.warning("No such music [key=" + info + "].");
|
||||
_musicStack.removeFirst();
|
||||
playTopMusic();
|
||||
return;
|
||||
}
|
||||
String music = names[RandomUtil.getInt(names.length)];
|
||||
|
||||
Class<?> playerClass = getMusicPlayerClass(music);
|
||||
|
||||
// if we don't have a player for this song, play the next song
|
||||
if (playerClass == null) {
|
||||
_musicStack.removeFirst();
|
||||
playTopMusic();
|
||||
return;
|
||||
}
|
||||
|
||||
// shutdown the old player if we're switching music types
|
||||
if (!playerClass.isInstance(_musicPlayer)) {
|
||||
if (_musicPlayer != null) {
|
||||
_musicPlayer.shutdown();
|
||||
}
|
||||
|
||||
// set up the new player
|
||||
try {
|
||||
_musicPlayer = (MusicPlayer) playerClass.newInstance();
|
||||
_musicPlayer.init(_playerListener);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to instantiate music player [class=" + playerClass +
|
||||
", e=" + e + "].");
|
||||
|
||||
// scrap it, try again with the next song
|
||||
_musicPlayer = null;
|
||||
_musicStack.removeFirst();
|
||||
playTopMusic();
|
||||
return;
|
||||
}
|
||||
|
||||
_musicPlayer.setVolume(_musicVol);
|
||||
}
|
||||
|
||||
// play!
|
||||
String bundle = c.getValue("bundle", (String)null);
|
||||
try {
|
||||
// TODO: buffer for the music player?
|
||||
_musicPlayer.start(_smgr._rmgr.getResource(bundle, music));
|
||||
} catch (Exception e) {
|
||||
log.warning("Error playing music, skipping [e=" + e +
|
||||
", bundle=" + bundle + ", music=" + music + "].");
|
||||
_musicStack.removeFirst();
|
||||
playTopMusic();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate music player for the specified music file.
|
||||
*/
|
||||
protected static Class<?> getMusicPlayerClass (String path)
|
||||
{
|
||||
path = path.toLowerCase();
|
||||
|
||||
// if (path.endsWith(".mid") || path.endsWith(".rmf")) {
|
||||
// return MidiPlayer.class;
|
||||
|
||||
// } else if (path.endsWith(".mod")) {
|
||||
// return ModPlayer.class;
|
||||
|
||||
// } else if (path.endsWith(".mp3")) {
|
||||
// return Mp3Player.class;
|
||||
|
||||
// } else if (path.endsWith(".ogg")) {
|
||||
// return OggPlayer.class;
|
||||
|
||||
// } else {
|
||||
return null;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop whatever song is currently playing and deal with the MusicKey associated with it.
|
||||
*/
|
||||
protected void handleMusicStopped ()
|
||||
{
|
||||
if (_musicStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// see what was playing
|
||||
MusicKey current = _musicStack.getFirst();
|
||||
|
||||
// see how many times the song was to loop and act accordingly
|
||||
switch (current.loops) {
|
||||
default:
|
||||
current.loops--;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// sorry charlie
|
||||
_musicStack.removeFirst();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the current music player.
|
||||
*/
|
||||
protected void stopMusicPlayer ()
|
||||
{
|
||||
if (_musicPlayer != null) {
|
||||
_musicPlayer.stop();
|
||||
_musicPlayer.shutdown();
|
||||
_musicPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that tracks the information about our playing music files.
|
||||
*/
|
||||
protected static class MusicKey extends SoundManager.SoundKey
|
||||
{
|
||||
/** How many times to loop, or -1 for forever. */
|
||||
public int loops;
|
||||
|
||||
public MusicKey (String set, String path, int loops)
|
||||
{
|
||||
super((byte) -1, set, path, null);
|
||||
this.loops = loops;
|
||||
}
|
||||
}
|
||||
|
||||
/** The sound manager we work with. */
|
||||
protected SoundManager _smgr;
|
||||
|
||||
/** The client event run queue. */
|
||||
protected RunQueue _runQueue;
|
||||
|
||||
/** Volume level for music. */
|
||||
protected float _musicVol = 1f;
|
||||
|
||||
/** The stack of songs that we're playing. */
|
||||
protected LinkedList<MusicKey> _musicStack = new LinkedList<MusicKey>();
|
||||
|
||||
/** The current music player, if any. */
|
||||
protected MusicPlayer _musicPlayer;
|
||||
|
||||
/** Event listener for receiving information about a song ending. */
|
||||
protected MusicPlayer.MusicEventListener _playerListener =
|
||||
new MusicPlayer.MusicEventListener() {
|
||||
public void musicStopped () {
|
||||
_runQueue.postRunnable(new Runnable() {
|
||||
public void run() {
|
||||
handleMusicStopped();
|
||||
playTopMusic();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Abstract music player.
|
||||
*/
|
||||
public abstract class MusicPlayer
|
||||
{
|
||||
/**
|
||||
* A watcher interested in music events.
|
||||
*/
|
||||
public interface MusicEventListener
|
||||
{
|
||||
/**
|
||||
* A callback that all players should use when the song they are
|
||||
* playing has finished playing (completely).
|
||||
*/
|
||||
public void musicStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the music player.
|
||||
*/
|
||||
public final void init (MusicEventListener musicListener)
|
||||
throws Exception
|
||||
{
|
||||
_musicListener = musicListener;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do your init here.
|
||||
*/
|
||||
public void init ()
|
||||
throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown and free all resources.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Start playing song data from the specified stream.
|
||||
*/
|
||||
public abstract void start (InputStream stream)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Stop playing the specified song.
|
||||
*/
|
||||
public abstract void stop ();
|
||||
|
||||
/**
|
||||
* Set the volume.
|
||||
*
|
||||
* @param volume 0f - 1f, inclusive.
|
||||
*/
|
||||
public abstract void setVolume (float volume);
|
||||
|
||||
/** Tell this guy about it when a song stops. */
|
||||
protected MusicEventListener _musicListener;
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
import com.jcraft.jorbis.*;
|
||||
import com.jcraft.jogg.*;
|
||||
|
||||
/**
|
||||
* Plays Ogg Vorbis streams.
|
||||
*
|
||||
* Hacked together from NASTY code from JOrbis.
|
||||
*/
|
||||
// TODO- this would need to be greatly cleaned up if we were serious about
|
||||
// using it.
|
||||
public class OggPlayer extends MusicPlayer
|
||||
{
|
||||
// documentation inherited
|
||||
public void start (final InputStream stream)
|
||||
{
|
||||
_player = new Thread("narya ogg player") {
|
||||
public void run () {
|
||||
playStream(stream);
|
||||
}
|
||||
};
|
||||
_player.setDaemon(true);
|
||||
_player.start();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void stop ()
|
||||
{
|
||||
_player = null;
|
||||
}
|
||||
|
||||
static final int BUFSIZE=4096*2;
|
||||
static int convsize=BUFSIZE*2;
|
||||
static byte[] convbuffer=new byte[convsize];
|
||||
|
||||
SyncState oy;
|
||||
StreamState os;
|
||||
Page og;
|
||||
Packet op;
|
||||
Info vi;
|
||||
Comment vc;
|
||||
DspState vd;
|
||||
Block vb;
|
||||
|
||||
byte[] buffer=null;
|
||||
int bytes=0;
|
||||
|
||||
int format;
|
||||
int rate=0;
|
||||
int channels=0;
|
||||
SourceDataLine outputLine=null;
|
||||
|
||||
int frameSizeInBytes;
|
||||
int bufferLengthInBytes;
|
||||
|
||||
void init_jorbis(){
|
||||
oy=new SyncState();
|
||||
os=new StreamState();
|
||||
og=new Page();
|
||||
op=new Packet();
|
||||
|
||||
vi=new Info();
|
||||
vc=new Comment();
|
||||
vd=new DspState();
|
||||
vb=new Block(vd);
|
||||
|
||||
buffer=null;
|
||||
bytes=0;
|
||||
|
||||
oy.init();
|
||||
}
|
||||
|
||||
SourceDataLine getOutputLine(int channels, int rate){
|
||||
if(outputLine!=null || this.rate!=rate || this.channels!=channels){
|
||||
if(outputLine!=null){
|
||||
outputLine.drain();
|
||||
outputLine.stop();
|
||||
outputLine.close();
|
||||
}
|
||||
init_audio(channels, rate);
|
||||
outputLine.start();
|
||||
}
|
||||
return outputLine;
|
||||
}
|
||||
|
||||
void init_audio(int channels, int rate){
|
||||
try {
|
||||
//ClassLoader originalClassLoader=null;
|
||||
//try{
|
||||
// originalClassLoader=Thread.currentThread().getContextClassLoader();
|
||||
// Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
|
||||
//}
|
||||
//catch(Exception ee){
|
||||
// System.out.println(ee);
|
||||
//}
|
||||
AudioFormat audioFormat =
|
||||
new AudioFormat((float)rate,
|
||||
16,
|
||||
channels,
|
||||
true, // PCM_Signed
|
||||
false // littleEndian
|
||||
);
|
||||
DataLine.Info info =
|
||||
new DataLine.Info(SourceDataLine.class,
|
||||
audioFormat,
|
||||
AudioSystem.NOT_SPECIFIED);
|
||||
if (!AudioSystem.isLineSupported(info)) {
|
||||
//System.out.println("Line " + info + " not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
outputLine = (SourceDataLine) AudioSystem.getLine(info);
|
||||
//outputLine.addLineListener(this);
|
||||
outputLine.open(audioFormat);
|
||||
}
|
||||
catch (LineUnavailableException ex) {
|
||||
System.out.println("Unable to open the sourceDataLine: " + ex);
|
||||
return;
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
System.out.println("Illegal Argument: " + ex);
|
||||
return;
|
||||
}
|
||||
|
||||
frameSizeInBytes = audioFormat.getFrameSize();
|
||||
int bufferLengthInFrames = outputLine.getBufferSize()/frameSizeInBytes/2;
|
||||
bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
|
||||
|
||||
//buffer = new byte[bufferLengthInBytes];
|
||||
//if(originalClassLoader!=null)
|
||||
// Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
|
||||
this.rate=rate;
|
||||
this.channels=channels;
|
||||
}
|
||||
catch(Exception ee){
|
||||
System.out.println(ee);
|
||||
}
|
||||
}
|
||||
|
||||
protected void playStream (InputStream stream)
|
||||
{
|
||||
init_jorbis();
|
||||
|
||||
loop:
|
||||
while (true) {
|
||||
int eos = 0;
|
||||
|
||||
int index = oy.buffer(BUFSIZE);
|
||||
buffer = oy.data;
|
||||
try {
|
||||
bytes = stream.read(buffer, index, BUFSIZE);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
return;
|
||||
}
|
||||
oy.wrote(bytes);
|
||||
|
||||
if (oy.pageout(og) != 1) {
|
||||
if (bytes < BUFSIZE) {
|
||||
break;
|
||||
}
|
||||
System.err.println("Input does not appear to be an Ogg bitstream.");
|
||||
return;
|
||||
}
|
||||
|
||||
os.init(og.serialno());
|
||||
os.reset();
|
||||
|
||||
vi.init();
|
||||
vc.init();
|
||||
|
||||
if (os.pagein(og) < 0) {
|
||||
// error; stream version mismatch perhaps
|
||||
System.err.println("Error reading first page of Ogg bitstream data.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (os.packetout(op) != 1) {
|
||||
// no page? must not be vorbis
|
||||
System.err.println("Error reading initial header packet.");
|
||||
break;
|
||||
// return;
|
||||
}
|
||||
|
||||
if (vi.synthesis_headerin(vc, op) < 0) {
|
||||
// error case; not a vorbis header
|
||||
System.err.println("This Ogg bitstream does not contain Vorbis audio data.");
|
||||
return;
|
||||
}
|
||||
|
||||
int i=0;
|
||||
|
||||
while (i < 2) {
|
||||
while (i < 2) {
|
||||
int result = oy.pageout(og);
|
||||
if (result==0) {
|
||||
break; // Need more data
|
||||
}
|
||||
if (result==1) {
|
||||
os.pagein(og);
|
||||
while (i < 2) {
|
||||
result = os.packetout(op);
|
||||
if (result == 0) {
|
||||
break;
|
||||
}
|
||||
if (result == -1) {
|
||||
System.err.println("Corrupt secondary header. Exiting.");
|
||||
//return;
|
||||
break loop;
|
||||
}
|
||||
vi.synthesis_headerin(vc, op);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index = oy.buffer(BUFSIZE);
|
||||
buffer = oy.data;
|
||||
try {
|
||||
bytes = stream.read(buffer, index, BUFSIZE);
|
||||
}
|
||||
catch(Exception e){
|
||||
System.err.println(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes == 0 && i < 2) {
|
||||
System.err.println("End of file before finding all Vorbis headers!");
|
||||
return;
|
||||
}
|
||||
oy.wrote(bytes);
|
||||
}
|
||||
|
||||
convsize=BUFSIZE/vi.channels;
|
||||
|
||||
vd.synthesis_init(vi);
|
||||
vb.init(vd);
|
||||
|
||||
double[][][] _pcm=new double[1][][];
|
||||
float[][][] _pcmf=new float[1][][];
|
||||
int[] _index=new int[vi.channels];
|
||||
|
||||
getOutputLine(vi.channels, vi.rate);
|
||||
|
||||
while (eos == 0) {
|
||||
while (eos == 0) {
|
||||
|
||||
if (_player != Thread.currentThread()) {
|
||||
//System.err.println("bye.");
|
||||
try {
|
||||
//outputLine.drain();
|
||||
//outputLine.stop();
|
||||
//outputLine.close();
|
||||
stream.close();
|
||||
} catch(Exception ee) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int result = oy.pageout(og);
|
||||
if (result == 0) {
|
||||
break; // need more data
|
||||
}
|
||||
if (result == -1) { // missing or corrupt data at this page position
|
||||
// System.err.println("Corrupt or missing data in bitstream; continuing...");
|
||||
|
||||
} else {
|
||||
os.pagein(og);
|
||||
while (true) {
|
||||
result = os.packetout(op);
|
||||
if (result == 0) break; // need more data
|
||||
if (result == -1) { // missing or corrupt data at this page position
|
||||
// no reason to complain; already complained above
|
||||
} else {
|
||||
// we have a packet. Decode it
|
||||
int samples;
|
||||
if (vb.synthesis(op) == 0) { // test for success!
|
||||
vd.synthesis_blockin(vb);
|
||||
}
|
||||
while ((samples =
|
||||
vd.synthesis_pcmout(_pcmf, _index)) > 0) {
|
||||
|
||||
double[][] pcm = _pcm[0];
|
||||
float[][] pcmf = _pcmf[0];
|
||||
boolean clipflag = false;
|
||||
int bout = Math.min(samples, convsize);
|
||||
|
||||
// convert doubles to 16 bit signed ints (host order) and
|
||||
// interleave
|
||||
for (i = 0; i < vi.channels; i++) {
|
||||
int ptr = i*2;
|
||||
//int ptr=i;
|
||||
int mono = _index[i];
|
||||
for (int j = 0; j < bout; j++) {
|
||||
int val = (int)
|
||||
(pcmf[i][mono+j] * 32767.);
|
||||
if (val > 32767){
|
||||
val = 32767;
|
||||
clipflag = true;
|
||||
|
||||
} else if (val < -32768) {
|
||||
val = -32768;
|
||||
clipflag = true;
|
||||
}
|
||||
if (val < 0) {
|
||||
val = val | 0x8000;
|
||||
}
|
||||
convbuffer[ptr] = (byte)(val);
|
||||
convbuffer[ptr+1] = (byte)(val>>>8);
|
||||
ptr += 2 * (vi.channels);
|
||||
}
|
||||
}
|
||||
outputLine.write(convbuffer, 0,
|
||||
2 * vi.channels * bout);
|
||||
vd.synthesis_read(bout);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (og.eos() != 0) {
|
||||
eos=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (eos==0) {
|
||||
index = oy.buffer(BUFSIZE);
|
||||
buffer = oy.data;
|
||||
try {
|
||||
bytes = stream.read(buffer,index,BUFSIZE);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
return;
|
||||
}
|
||||
if (bytes == -1) {
|
||||
break;
|
||||
}
|
||||
oy.wrote(bytes);
|
||||
if (bytes==0) {
|
||||
eos=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.clear();
|
||||
vb.clear();
|
||||
vd.clear();
|
||||
vi.clear();
|
||||
}
|
||||
|
||||
oy.clear();
|
||||
|
||||
//System.err.println("Done.");
|
||||
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void setVolume (float volume)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
protected Thread _player;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.threerings.media.sound;
|
||||
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import com.threerings.media.sound.SoundManager.Frob;
|
||||
import com.threerings.openal.Clip;
|
||||
import com.threerings.openal.ClipBuffer;
|
||||
import com.threerings.openal.ClipProvider;
|
||||
import com.threerings.openal.Sound;
|
||||
import com.threerings.openal.SoundGroup;
|
||||
import com.threerings.openal.Stream;
|
||||
|
||||
/**
|
||||
* Plays sounds via OpenAL.
|
||||
*/
|
||||
public class OpenALSoundManager extends AbstractSoundManager
|
||||
implements ClipProvider
|
||||
{
|
||||
|
||||
public OpenALSoundManager (SoundLoader loader)
|
||||
{
|
||||
_loader = loader;
|
||||
_alSoundManager = new MediaALSoundManager();
|
||||
_group = _alSoundManager.createGroup(this, SOURCE_COUNT);
|
||||
}
|
||||
|
||||
public Clip loadClip (String path)
|
||||
throws IOException
|
||||
{
|
||||
byte[] data;
|
||||
AudioInputStream in;
|
||||
int pkgEnd = path.lastIndexOf("/") + 1;
|
||||
try {
|
||||
data = _loader.load(path.substring(0, pkgEnd), path.substring(pkgEnd))[0];
|
||||
in = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
|
||||
} catch (UnsupportedAudioFileException e) {
|
||||
throw new RuntimeException("Unsupported format: " + path, e);
|
||||
}
|
||||
AudioFormat format = in.getFormat();
|
||||
int alformat = 0;
|
||||
if (format.getChannels() == 1) {
|
||||
if (format.getSampleSizeInBits() == 8) {
|
||||
alformat = AL10.AL_FORMAT_MONO8;
|
||||
} else if (format.getSampleSizeInBits() == 16) {
|
||||
alformat = AL10.AL_FORMAT_MONO16;
|
||||
} else {
|
||||
throw new IOException("Illegal audio sample size: " + path);
|
||||
}
|
||||
} else if (format.getChannels() == 2) {
|
||||
if (format.getSampleSizeInBits() == 8) {
|
||||
alformat = AL10.AL_FORMAT_STEREO8;
|
||||
} else if (format.getSampleSizeInBits() == 16) {
|
||||
alformat = AL10.AL_FORMAT_STEREO16;
|
||||
} else {
|
||||
throw new IOException("Illegal audio sample size: " + path);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Only mono and stereo are supported: " + path);
|
||||
}
|
||||
Clip clip = new Clip();
|
||||
clip.format = alformat;
|
||||
clip.data = ByteBuffer.wrap(data);
|
||||
clip.frequency = (int)format.getFrameRate();
|
||||
return clip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunQueue getSoundQueue ()
|
||||
{
|
||||
return RunQueue.AWT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock (final String pkgPath, String... keys)
|
||||
{
|
||||
for (final String key : keys) {
|
||||
final String path = pkgPath + key;
|
||||
if (_locked.containsKey(path)) {
|
||||
continue;
|
||||
}
|
||||
_alSoundManager.loadClip(this, path, new ClipBuffer.Observer() {
|
||||
public void clipFailed (ClipBuffer buffer) {
|
||||
log.warning("Unable to load sound", "path", path);
|
||||
}
|
||||
|
||||
public void clipLoaded (ClipBuffer buffer) {
|
||||
_locked.put(path, buffer);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock (String pkgPath, String... keys)
|
||||
{
|
||||
for (String key : keys) {
|
||||
_locked.remove(pkgPath + key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Frob loop (String pkgPath, String key, float pan)
|
||||
{
|
||||
final Sound sound = _group.getSound(pkgPath + key);
|
||||
sound.loop(true);
|
||||
return new Frob(){
|
||||
public float getPan () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public float getVolume () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setPan (float pan) {}
|
||||
|
||||
public void setVolume (float vol) { }
|
||||
|
||||
public void stop () {
|
||||
sound.stop();
|
||||
}};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play (String pkgPath, String key, float pan)
|
||||
{
|
||||
Sound sound = _group.getSound(pkgPath + key);
|
||||
sound.play(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown ()
|
||||
{
|
||||
_group.dispose();
|
||||
_locked.clear();
|
||||
for (Stream stream : _alSoundManager.getStreams()) {
|
||||
stream.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected class MediaALSoundManager extends com.threerings.openal.SoundManager {
|
||||
protected MediaALSoundManager () {
|
||||
super(getSoundQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path) {
|
||||
if (_locked.containsKey(path)) {
|
||||
return _locked.get(path);
|
||||
}
|
||||
return super.getClip(provider, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, ClipBuffer> _locked = Maps.newHashMap();
|
||||
|
||||
protected final SoundLoader _loader;
|
||||
|
||||
protected final SoundGroup _group;
|
||||
|
||||
protected final com.threerings.openal.SoundManager _alSoundManager;
|
||||
|
||||
/** Number of sounds that can be played simultaneously. */
|
||||
protected final int SOURCE_COUNT = 10;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.threerings.media.sound;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.samskivert.util.Config;
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
/**
|
||||
* Loads sound clips specified by package-based properties files.
|
||||
*/
|
||||
public class SoundLoader
|
||||
{
|
||||
public SoundLoader (ResourceManager rmgr, String defaultBundle, String defaultPath)
|
||||
{
|
||||
_rmgr = rmgr;
|
||||
_defaultClipBundle = defaultBundle;
|
||||
_defaultClipPath = defaultPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the sounds for key from the config in
|
||||
* <code><packagePath>/sound.properties</code>
|
||||
*/
|
||||
public byte[][] load (String packagePath, String key)
|
||||
throws IOException
|
||||
{
|
||||
// otherwise, randomize between all available sounds
|
||||
Config c = getConfig(packagePath);
|
||||
String[] names = c.getValue(key, (String[])null);
|
||||
if (names == null) {
|
||||
log.warning("No such sound", "key", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[][] data = new byte[names.length][];
|
||||
String bundle = c.getValue("bundle", (String)null);
|
||||
for (int ii = 0; ii < names.length; ii++) {
|
||||
data[ii] = loadClipData(bundle, names[ii]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void shutdown ()
|
||||
{
|
||||
_configs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached Config.
|
||||
*/
|
||||
protected Config getConfig (String packagePath)
|
||||
{
|
||||
Config c = _configs.get(packagePath);
|
||||
if (c == null) {
|
||||
String propPath = packagePath + Sounds.PROP_NAME;
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props = ConfigUtil.loadInheritedProperties(propPath + ".properties",
|
||||
_rmgr.getClassLoader());
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to load sound properties", "path", propPath, ioe);
|
||||
}
|
||||
c = new Config(propPath, props);
|
||||
_configs.put(packagePath, c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data from the resource manager.
|
||||
*/
|
||||
protected byte[] loadClipData (String bundle, String path)
|
||||
throws IOException
|
||||
{
|
||||
InputStream clipin = null;
|
||||
try {
|
||||
clipin = _rmgr.getResource(bundle, path);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// try from the classpath
|
||||
try {
|
||||
clipin = _rmgr.getResource(path);
|
||||
} catch (FileNotFoundException fnfe2) {
|
||||
// only play the default sound if we have verbose sound debugging turned on.
|
||||
if (SoundManager._verbose.getValue()) {
|
||||
log.warning("Could not locate sound data", "bundle", bundle, "path", path);
|
||||
if (_defaultClipPath != null) {
|
||||
try {
|
||||
clipin = _rmgr.getResource(_defaultClipBundle, _defaultClipPath);
|
||||
} catch (FileNotFoundException fnfe3) {
|
||||
try {
|
||||
clipin = _rmgr.getResource(_defaultClipPath);
|
||||
} catch (FileNotFoundException fnfe4) {
|
||||
log.warning(
|
||||
"Additionally, the default fallback sound could not be located",
|
||||
"bundle", _defaultClipBundle, "path", _defaultClipPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warning("No fallback default sound specified!");
|
||||
}
|
||||
}
|
||||
// if we couldn't load the default, rethrow
|
||||
if (clipin == null) {
|
||||
throw fnfe2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return IOUtils.toByteArray(clipin);
|
||||
}
|
||||
|
||||
protected ResourceManager _rmgr;
|
||||
|
||||
/** The path of the default sound to use for missing sounds. */
|
||||
protected String _defaultClipBundle, _defaultClipPath;
|
||||
|
||||
/** A cache of config objects we've created. */
|
||||
protected LRUHashMap<String, Config> _configs = new LRUHashMap<String, Config>(5);
|
||||
}
|
||||
@@ -26,13 +26,10 @@ import static com.threerings.media.Log.log;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
@@ -46,9 +43,6 @@ import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.samskivert.util.Config;
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.RandomUtil;
|
||||
@@ -61,9 +55,9 @@ import com.threerings.media.MediaPrefs;
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
/**
|
||||
* Manages the playing of audio files.
|
||||
* Manages the playing of audio files via the Java Sound APIs.
|
||||
*/
|
||||
public class SoundManager
|
||||
public class SoundManager extends AbstractSoundManager
|
||||
{
|
||||
/** A pan value indicating that a sound should play from the left only. */
|
||||
public static final float PAN_LEFT = -1f;
|
||||
@@ -169,11 +163,14 @@ public class SoundManager
|
||||
*/
|
||||
public SoundManager (ResourceManager rmgr, String defaultClipBundle, String defaultClipPath,
|
||||
int cacheSize)
|
||||
{
|
||||
this(new SoundLoader(rmgr, defaultClipBundle, defaultClipPath), cacheSize);
|
||||
}
|
||||
|
||||
public SoundManager (SoundLoader loader, int cacheSize)
|
||||
{
|
||||
// save things off
|
||||
_rmgr = rmgr;
|
||||
_defaultClipBundle = defaultClipBundle;
|
||||
_defaultClipPath = defaultClipPath;
|
||||
_loader = loader;
|
||||
_clipCache = new LRUHashMap<SoundKey, byte[][]>(cacheSize,
|
||||
new LRUHashMap.ItemSizer<byte[][]>() {
|
||||
public int computeSize (byte[][] value) {
|
||||
@@ -186,9 +183,7 @@ public class SoundManager
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut the damn thing off.
|
||||
*/
|
||||
@Override
|
||||
public void shutdown ()
|
||||
{
|
||||
// TODO: we need to stop any looping sounds
|
||||
@@ -200,250 +195,65 @@ public class SoundManager
|
||||
}
|
||||
synchronized (_clipCache) {
|
||||
_lockedClips.clear();
|
||||
_configs.clear();
|
||||
_loader.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string summarizing our volume settings and disabled sound types.
|
||||
* Sets the run queue on which sound should be played.
|
||||
*/
|
||||
public String summarizeState ()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("clipVol=").append(_clipVol);
|
||||
buf.append(", disabled=[");
|
||||
int ii = 0;
|
||||
for (SoundType soundType : _disabledTypes) {
|
||||
if (ii++ > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(soundType);
|
||||
}
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the specified soundtype enabled?
|
||||
*/
|
||||
public boolean isEnabled (SoundType type)
|
||||
{
|
||||
// by default, types are enabled..
|
||||
return (!_disabledTypes.contains(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on or off the specified sound type.
|
||||
*/
|
||||
public void setEnabled (SoundType type, boolean enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
_disabledTypes.remove(type);
|
||||
} else {
|
||||
_disabledTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the run queue on which sound ending runnables are dispatched.
|
||||
*/
|
||||
public void setCallbackQueue (RunQueue queue)
|
||||
public void setSoundQueue (RunQueue queue)
|
||||
{
|
||||
_callbackQueue = queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the run queue on which sound ending runnables are dispatched. It defaults to
|
||||
* {@link RunQueue#AWT}.
|
||||
*/
|
||||
public RunQueue getCallbackQueue ()
|
||||
@Override
|
||||
public RunQueue getSoundQueue ()
|
||||
{
|
||||
return _callbackQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume for all sound clips.
|
||||
*
|
||||
* @param vol a volume parameter between 0f and 1f, inclusive.
|
||||
*/
|
||||
public void setClipVolume (float vol)
|
||||
{
|
||||
_clipVol = Math.max(0f, Math.min(1f, vol));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume for all sound clips.
|
||||
*/
|
||||
public float getClipVolume ()
|
||||
{
|
||||
return _clipVol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally lock each of these keys prior to playing, to guarantee that it will be quickly
|
||||
* available for playing.
|
||||
*/
|
||||
@Override
|
||||
public void lock (String pkgPath, String... keys)
|
||||
{
|
||||
lock(pkgPath, null, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally lock each of these keys prior to playing, to guarantee that it will be quickly
|
||||
* available for playing. <code>onLock</code> will be called on the run queue from
|
||||
* {@link #getCallbackQueue()} when locking is complete.
|
||||
*/
|
||||
public void lock (String pkgPath, Runnable onLock, String... keys)
|
||||
{
|
||||
for (int ii=0; ii < keys.length; ii++) {
|
||||
enqueue(new SoundKey(LOCK, pkgPath, keys[ii], onLock), (ii == 0));
|
||||
enqueue(new SoundKey(LOCK, pkgPath, keys[ii]), (ii == 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*Unlock the specified sounds so that its resources can be freed.
|
||||
*/
|
||||
@Override
|
||||
public void unlock (String pkgPath, String... keys)
|
||||
{
|
||||
unlock(pkgPath, null, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
*Unlock the specified sounds so that its resources can be freed. <code>onUnock</code> will
|
||||
* be called on the run queue from {@link #getCallbackQueue()} when unlocking is complete.
|
||||
*/
|
||||
public void unlock (String pkgPath, Runnable onUnlock, String... keys)
|
||||
{
|
||||
for (int ii = 0; ii < keys.length; ii++) {
|
||||
enqueue(new SoundKey(UNLOCK, pkgPath, keys[ii], onUnlock), (ii == 0));
|
||||
enqueue(new SoundKey(UNLOCK, pkgPath, keys[ii]), (ii == 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound as the specified type of sound, immediately. Note that a sound
|
||||
* need not be locked prior to playing.
|
||||
*
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key)
|
||||
{
|
||||
return play(type, pkgPath, key, 0, PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound as the specified type of sound, immediately,
|
||||
* with the specified pan value.
|
||||
* Note that a sound need not be locked prior to playing.
|
||||
*
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, float pan)
|
||||
{
|
||||
return play(type, pkgPath, key, 0, pan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param delay the delay in milliseconds.
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, int delay)
|
||||
{
|
||||
return play(type, pkgPath, key, delay, PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param delay the delay in milliseconds.
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
* @return true if the sound actually played, or false if its sound type was disabled or if
|
||||
* sound is off altogether.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, int delay, float pan)
|
||||
{
|
||||
return play(type, pkgPath, key, delay, pan, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the specified sound after the specified delay.
|
||||
* @param delay the delay in milliseconds.
|
||||
* @param pan a value from -1f (all left) to +1f (all right).
|
||||
* @param onStop a runnable that will be run on the queue from {@link #getCallbackQueue()}
|
||||
* when the sound stops playing.
|
||||
*/
|
||||
public boolean play (SoundType type, String pkgPath, String key, int delay, float pan,
|
||||
Runnable onStop)
|
||||
{
|
||||
if (type == null) {
|
||||
type = DEFAULT; // let the lazy kids play too
|
||||
}
|
||||
|
||||
if ((_clipVol != 0f) && isEnabled(type)) {
|
||||
final SoundKey skey = new SoundKey(PLAY, pkgPath, key, delay, _clipVol, pan, onStop);
|
||||
if (delay > 0) {
|
||||
new Interval() {
|
||||
@Override
|
||||
public void expired () {
|
||||
addToPlayQueue(skey);
|
||||
}
|
||||
}.schedule(delay);
|
||||
} else {
|
||||
addToPlayQueue(skey);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
public Frob loop (SoundType type, String pkgPath, String key)
|
||||
{
|
||||
return loop(type, pkgPath, key, PAN_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
public Frob loop (SoundType type, String pkgPath, String key, float pan)
|
||||
{
|
||||
return loop(type, pkgPath, key, pan, LOOP, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping as quickly as possible when stop is called.
|
||||
*/
|
||||
public Frob loop (SoundType type, String pkgPath, String key, float pan, Runnable onStop)
|
||||
{
|
||||
return loop(type, pkgPath, key, pan, LOOP, onStop);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping after the current iteration completes when stop is
|
||||
* called.
|
||||
*/
|
||||
public Frob loopToCompletion (SoundType type, String pkgPath, String key)
|
||||
{
|
||||
return loop(type, pkgPath, key, PAN_CENTER, LOOP_TO_COMPLETION, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound, stopping after the current iteration completes when stop is
|
||||
* called.
|
||||
*/
|
||||
public Frob loopToCompletion (SoundType type, String pkgPath, String key, Runnable onStop)
|
||||
{
|
||||
return loop(type, pkgPath, key, PAN_CENTER, LOOP_TO_COMPLETION, onStop);
|
||||
}
|
||||
|
||||
// ==== End of public methods ====
|
||||
|
||||
@Override
|
||||
protected void play (String pkgPath, String key, float pan)
|
||||
{
|
||||
addToPlayQueue(new SoundKey(PLAY, pkgPath, key, 0, _clipVol, pan));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Frob loop (String pkgPath, String key, float pan)
|
||||
{
|
||||
return loop(pkgPath, key, pan, LOOP);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound.
|
||||
*/
|
||||
protected Frob loop (String pkgPath, String key, float pan, byte cmd)
|
||||
{
|
||||
SoundKey skey = new SoundKey(cmd, pkgPath, key, 0, _clipVol, pan);
|
||||
addToPlayQueue(skey);
|
||||
return skey; // it is a frob
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the sound clip key to the queue to be played.
|
||||
*/
|
||||
@@ -537,7 +347,6 @@ public class SoundManager
|
||||
switch (key.cmd) {
|
||||
case PLAY:
|
||||
case LOOP:
|
||||
case LOOP_TO_COMPLETION:
|
||||
playSound(key);
|
||||
break;
|
||||
|
||||
@@ -564,19 +373,26 @@ public class SoundManager
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (key.onProcessed != null) {
|
||||
_callbackQueue.postRunnable(key.onProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up an audio stream from the given byte array, and gets it to convert itself to PCM
|
||||
* data for writing to our output line (if it isn't already that)
|
||||
*/
|
||||
protected AudioInputStream setupAudioStream (byte[] data)
|
||||
public static AudioInputStream setupAudioStream (byte[] data)
|
||||
throws UnsupportedAudioFileException, IOException
|
||||
{
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
|
||||
return setupAudioStream(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up an audio stream from the given byte array, and gets it to convert itself to PCM
|
||||
* data for writing to our output line (if it isn't already that)
|
||||
*/
|
||||
public static AudioInputStream setupAudioStream (InputStream in)
|
||||
throws UnsupportedAudioFileException, IOException
|
||||
{
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(in);
|
||||
AudioFormat format = stream.getFormat();
|
||||
if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
|
||||
stream = AudioSystem.getAudioInputStream(
|
||||
@@ -624,7 +440,7 @@ public class SoundManager
|
||||
|
||||
// open the sound line
|
||||
AudioFormat format = stream.getFormat();
|
||||
line = (SourceDataLine) AudioSystem.getLine(
|
||||
line = (SourceDataLine)AudioSystem.getLine(
|
||||
new DataLine.Info(SourceDataLine.class, format));
|
||||
line.open(format, LINEBUF_SIZE);
|
||||
float setVolume = 1;
|
||||
@@ -639,7 +455,7 @@ public class SoundManager
|
||||
do {
|
||||
// play the sound
|
||||
int count = 0;
|
||||
while ((key.running || key.cmd == LOOP_TO_COMPLETION) && count != -1) {
|
||||
while (key.running && count != -1) {
|
||||
float vol = key.volume;
|
||||
if (vol != setVolume) {
|
||||
adjustVolume(line, vol);
|
||||
@@ -766,19 +582,7 @@ public class SoundManager
|
||||
data[0] = IOUtils.toByteArray(stream);
|
||||
|
||||
} else {
|
||||
// otherwise, randomize between all available sounds
|
||||
Config c = getConfig(key);
|
||||
String[] names = c.getValue(key.key, (String[])null);
|
||||
if (names == null) {
|
||||
log.warning("No such sound [key=" + key + "].");
|
||||
return null;
|
||||
}
|
||||
|
||||
data = new byte[names.length][];
|
||||
String bundle = c.getValue("bundle", (String)null);
|
||||
for (int ii=0; ii < names.length; ii++) {
|
||||
data[ii] = loadClipData(bundle, names[ii]);
|
||||
}
|
||||
data = _loader.load(key.pkgPath, key.key);
|
||||
}
|
||||
|
||||
_clipCache.put(key, data);
|
||||
@@ -827,9 +631,11 @@ public class SoundManager
|
||||
return false;
|
||||
}
|
||||
});
|
||||
int size = (list == null) ? 0 : list.length;
|
||||
if (size > 0) {
|
||||
File pick = list[RandomUtil.getInt(size)];
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
if (list.length > 0) {
|
||||
File pick = list[RandomUtil.getInt(list.length)];
|
||||
try {
|
||||
return new FileInputStream(pick);
|
||||
} catch (Exception e) {
|
||||
@@ -839,114 +645,6 @@ public class SoundManager
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data from the resource manager.
|
||||
*/
|
||||
protected byte[] loadClipData (String bundle, String path)
|
||||
throws IOException
|
||||
{
|
||||
InputStream clipin = null;
|
||||
try {
|
||||
clipin = _rmgr.getResource(bundle, path);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// try from the classpath
|
||||
try {
|
||||
clipin = _rmgr.getResource(path);
|
||||
} catch (FileNotFoundException fnfe2) {
|
||||
// only play the default sound if we have verbose sound debugging turned on.
|
||||
if (_verbose.getValue()) {
|
||||
log.warning("Could not locate sound data [bundle=" + bundle +
|
||||
", path=" + path + "].");
|
||||
if (_defaultClipPath != null) {
|
||||
try {
|
||||
clipin = _rmgr.getResource(_defaultClipBundle, _defaultClipPath);
|
||||
} catch (FileNotFoundException fnfe3) {
|
||||
try {
|
||||
clipin = _rmgr.getResource(_defaultClipPath);
|
||||
} catch (FileNotFoundException fnfe4) {
|
||||
log.warning("Additionally, the default " +
|
||||
"fallback sound could not be located " +
|
||||
"[bundle=" + _defaultClipBundle +
|
||||
", path=" + _defaultClipPath + "].");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warning("No fallback default sound specified!");
|
||||
}
|
||||
}
|
||||
// if we couldn't load the default, rethrow
|
||||
if (clipin == null) {
|
||||
throw fnfe2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return IOUtils.toByteArray(clipin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached Config.
|
||||
*/
|
||||
protected Config getConfig (SoundKey key)
|
||||
{
|
||||
Config c = _configs.get(key.pkgPath);
|
||||
if (c == null) {
|
||||
String propPath = key.pkgPath + Sounds.PROP_NAME;
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props = ConfigUtil.loadInheritedProperties(
|
||||
propPath + ".properties", _rmgr.getClassLoader());
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to load sound properties " +
|
||||
"[path=" + propPath + ", error=" + ioe + "].");
|
||||
}
|
||||
c = new Config(propPath, props);
|
||||
_configs.put(key.pkgPath, c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop the specified sound.
|
||||
*/
|
||||
protected Frob loop (SoundType type, String pkgPath, String key, float pan, byte cmd,
|
||||
Runnable onStop)
|
||||
{
|
||||
if (type == null) {
|
||||
type = DEFAULT;
|
||||
}
|
||||
|
||||
if (!isEnabled(type)) {
|
||||
return null;
|
||||
}
|
||||
SoundKey skey = new SoundKey(cmd, pkgPath, key, 0, _clipVol, pan, onStop);
|
||||
addToPlayQueue(skey);
|
||||
return skey; // it is a frob
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Adjust the volume of this clip.
|
||||
// */
|
||||
// protected static void adjustVolumeIdeally (Line line, float volume)
|
||||
// {
|
||||
// if (line.isControlSupported(FloatControl.Type.VOLUME)) {
|
||||
// FloatControl vol = (FloatControl)
|
||||
// line.getControl(FloatControl.Type.VOLUME);
|
||||
//
|
||||
// float min = vol.getMinimum();
|
||||
// float max = vol.getMaximum();
|
||||
//
|
||||
// float ourval = (volume * (max - min)) + min;
|
||||
// Log.debug("adjust vol: [min=" + min + ", ourval=" + ourval +
|
||||
// ", max=" + max + "].");
|
||||
// vol.setValue(ourval);
|
||||
//
|
||||
// } else {
|
||||
// // fall back
|
||||
// adjustVolume(line, volume);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Use the gain control to implement volume.
|
||||
*/
|
||||
@@ -1006,9 +704,6 @@ public class SoundManager
|
||||
/** The player thread, if it's playing us. */
|
||||
public Thread thread;
|
||||
|
||||
/** Run when the processed method is called. */
|
||||
public Runnable onProcessed;
|
||||
|
||||
/**
|
||||
* Create a SoundKey that just contains the specified command.
|
||||
*/
|
||||
@@ -1020,21 +715,20 @@ public class SoundManager
|
||||
/**
|
||||
* Quicky constructor for music keys and lock operations.
|
||||
*/
|
||||
public SoundKey (byte cmd, String pkgPath, String key, Runnable onProcessed)
|
||||
public SoundKey (byte cmd, String pkgPath, String key)
|
||||
{
|
||||
this(cmd);
|
||||
this.pkgPath = pkgPath;
|
||||
this.key = key;
|
||||
this.onProcessed = onProcessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a sound effect soundkey.
|
||||
*/
|
||||
public SoundKey (byte cmd, String pkgPath, String key, int delay, float volume,
|
||||
float pan, Runnable onProcessed)
|
||||
float pan)
|
||||
{
|
||||
this(cmd, pkgPath, key, onProcessed);
|
||||
this(cmd, pkgPath, key);
|
||||
|
||||
stamp = System.currentTimeMillis() + delay;
|
||||
setVolume(volume);
|
||||
@@ -1088,7 +782,7 @@ public class SoundManager
|
||||
* If this key is one of the two loop types.
|
||||
*/
|
||||
protected boolean isLoop() {
|
||||
return cmd == LOOP || cmd == LOOP_TO_COMPLETION;
|
||||
return cmd == LOOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1114,15 +808,12 @@ public class SoundManager
|
||||
}
|
||||
}
|
||||
|
||||
/** Does our package based sound loading. */
|
||||
protected SoundLoader _loader;
|
||||
|
||||
/** The queue where callbacks for keys being processed are dispatched. */
|
||||
protected RunQueue _callbackQueue = RunQueue.AWT;
|
||||
|
||||
/** The path of the default sound to use for missing sounds. */
|
||||
protected String _defaultClipBundle, _defaultClipPath;
|
||||
|
||||
/** The resource manager from which we obtain audio files. */
|
||||
protected ResourceManager _rmgr;
|
||||
|
||||
/** The queue of sound clips to be played. */
|
||||
protected Queue<SoundKey> _queue = new Queue<SoundKey>();
|
||||
|
||||
@@ -1132,30 +823,21 @@ public class SoundManager
|
||||
/** If we every play a sound successfully, this is set to true. */
|
||||
protected boolean _soundSeemsToWork = false;
|
||||
|
||||
/** Volume level for sound clips. */
|
||||
protected float _clipVol = 1f;
|
||||
|
||||
/** The cache of recent audio clips . */
|
||||
protected LRUHashMap<SoundKey, byte[][]> _clipCache;
|
||||
|
||||
/** The set of locked audio clips; this is separate from the LRU so
|
||||
* that locking clips doesn't booch up an otherwise normal caching
|
||||
* agenda. */
|
||||
/**
|
||||
* The set of locked audio clips; this is separate from the LRU so that locking clips doesn't
|
||||
* booch up an otherwise normal caching agenda.
|
||||
*/
|
||||
protected HashMap<SoundKey,byte[][]> _lockedClips = new HashMap<SoundKey,byte[][]>();
|
||||
|
||||
/** A set of soundTypes for which sound is enabled. */
|
||||
protected HashSet<SoundType> _disabledTypes = new HashSet<SoundType>();
|
||||
|
||||
/** A cache of config objects we've created. */
|
||||
protected LRUHashMap<String,Config> _configs = new LRUHashMap<String,Config>(5);
|
||||
|
||||
/** Soundkey command constants. */
|
||||
protected static final byte PLAY = 0;
|
||||
protected static final byte LOCK = 1;
|
||||
protected static final byte UNLOCK = 2;
|
||||
protected static final byte DIE = 3;
|
||||
protected static final byte LOOP = 4;
|
||||
protected static final byte LOOP_TO_COMPLETION = 5;
|
||||
|
||||
/** A pref that specifies a directory for us to get test sounds from. */
|
||||
protected static RuntimeAdjust.FileAdjust _testDir =
|
||||
|
||||
@@ -21,8 +21,11 @@
|
||||
|
||||
package com.threerings.openal;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
@@ -32,7 +35,7 @@ import com.samskivert.util.LRUHashMap;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import static com.threerings.openal.Log.log;
|
||||
import com.threerings.openal.ClipBuffer.Observer;
|
||||
|
||||
/**
|
||||
* An interface to the OpenAL library that provides a number of additional services:
|
||||
@@ -142,7 +145,16 @@ public class SoundManager
|
||||
*/
|
||||
public void loadClip (ClipProvider provider, String path)
|
||||
{
|
||||
getClip(provider, path);
|
||||
loadClip(provider, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a clip buffer for the sound clip loaded via the specified provider with the
|
||||
* specified path. The loaded clip is placed in the cache.
|
||||
*/
|
||||
public void loadClip (ClipProvider provider, String path, Observer observer)
|
||||
{
|
||||
getClip(provider, path, observer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,6 +207,16 @@ public class SoundManager
|
||||
* for loading if it is not already loaded.
|
||||
*/
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path)
|
||||
{
|
||||
return getClip(provider, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clip buffer for the sound clip loaded via the specified provider with the
|
||||
* specified path. The clip buffer may come from the cache, and it will immediately be queued
|
||||
* for loading if it is not already loaded.
|
||||
*/
|
||||
protected ClipBuffer getClip (ClipProvider provider, String path, Observer observer)
|
||||
{
|
||||
Comparable ckey = ClipBuffer.makeKey(provider, path);
|
||||
ClipBuffer buffer = _clips.get(ckey);
|
||||
@@ -207,7 +229,7 @@ public class SoundManager
|
||||
_loading.put(ckey, buffer);
|
||||
}
|
||||
}
|
||||
buffer.resolve(null);
|
||||
buffer.resolve(observer);
|
||||
return buffer;
|
||||
|
||||
} catch (Throwable t) {
|
||||
|
||||
Reference in New Issue
Block a user