Ok, now I have a working implementation which is quite robust (we can switch environments on mouse-moves - resulting in many switches before cross fading has completed). It took a few fixes in the AudioSystem as well as adding a new class similar to EnvironmentalPool.
Here is a patch for the audio-folder based on JME from CVS (10/22/07 07:20PM).
Index: AudioSystem.java
===================================================================
--- AudioSystem.java (revision 272)
+++ AudioSystem.java (revision 448)
@@ -50,6 +50,10 @@
private MusicTrackQueue musicQueue = new MusicTrackQueue();
private EnvironmentalPool envPool = new EnvironmentalPool();
private float unitsPerMeter = 10;
+ /** Indicates if threads are to be created for every streaming player. */
+ private boolean createStreamThreads = true;
+ /** Indicates the interval in milliseconds that streams should be updated with. */
+ private long streamUpdateInterval = 200;
/**
* Singleton access to the audio system. FIXME: Currently hardcoded to
@@ -105,7 +109,24 @@
public void setUnitsPerMeter(float toMeterValue) {
this.unitsPerMeter = toMeterValue;
}
+
+ public boolean getCreateStreamThreads() {
+ return createStreamThreads;
+ }
+
+ public void setCreateStreamThreads(boolean createStreamThreads) {
+ this.createStreamThreads = createStreamThreads;
+ }
+
+ public long getStreamUpdateInterval() {
+ return streamUpdateInterval;
+ }
+ public void setStreamUpdateInterval(long streamUpdateInterval) {
+ this.streamUpdateInterval = streamUpdateInterval;
+ }
+
+
public void cleanup() {
system = null;
}
Index: AudioTrack.java
===================================================================
--- AudioTrack.java (revision 272)
+++ AudioTrack.java (revision 448)
@@ -152,7 +152,9 @@
}
public void addTrackStateListener(TrackStateListener listener) {
- trackListeners.add(listener);
+ if(!trackListeners.contains(listener)) {
+ trackListeners.add(listener);
+ }
}
public void removeTrackStateListener(TrackStateListener listener) {
@@ -181,23 +183,29 @@
}
public void setVolume(float volume) {
- if (volume > 1.0f)
- volume = 1.0f;
- this.volume = volume;
- player.setVolume(volume);
+ this.volume = volume < 0.0f ? 0.0f : volume > 1.0f ? 1.0f : volume;
+ player.setVolume(this.volume);
}
public void fadeOut(float time) {
- targetVolume = 0;
- volumeChangeRate = (volume - targetVolume) / time;
+ fadeTo(time, 0);
}
- public void fadeIn(float time, float maxVolume) {
+ public void fadeIn(float time, float targetVolume) {
setVolume(0);
- targetVolume = maxVolume;
- volumeChangeRate = maxVolume / time;
+ fadeTo(time, targetVolume);
}
+ public void fadeTo(float time, float newvolume) {
+ targetVolume = newvolume < 0.0f ? 0.0f : newvolume > 1.0f ? 1.0f : newvolume;
+ if(volume != targetVolume) {
+ volumeChangeRate = (targetVolume - volume) / time;
+ } else {
+ volumeChangeRate = 0.0f;
+ fireFinishedFade();
+ }
+ }
+
public AudioPlayer getPlayer() {
return player;
}
@@ -262,17 +270,15 @@
dt = FastMath.FLT_EPSILON;
// Do volume changes:
- if (volume != targetVolume) {
- if (volume < targetVolume) {
- volume += volumeChangeRate * dt;
- if (volume > targetVolume) volume = targetVolume;
+ if (volume != targetVolume && volumeChangeRate != 0.0f) {
+ float newvolume = volume + volumeChangeRate * dt;
+ if (volumeChangeRate > 0.0f) {
+ if (newvolume > targetVolume) newvolume = targetVolume;
} else {
- volume -= volumeChangeRate * dt;
- if (volume < targetVolume) volume = targetVolume;
+ if (newvolume < targetVolume) newvolume = targetVolume;
}
- if (volume < 0 || volume > 1) volume = targetVolume;
- setVolume(volume);
- if (volume == targetVolume) {
+ setVolume(newvolume);
+ if (newvolume == targetVolume) {
fireFinishedFade();
}
}
Index: MusicTrackQueue.java
===================================================================
--- MusicTrackQueue.java (revision 272)
+++ MusicTrackQueue.java (revision 448)
@@ -65,6 +65,7 @@
private float crossfadeoutTime = 3.5f;
private float crossfadeinTime = 3.5f;
+ private float volume = 1.0f;
public MusicTrackQueue() {
}
@@ -137,9 +138,9 @@
AudioTrack track = tracks.get(currentTrack);
if (crossfadeinTime > 0)
- track.fadeIn(crossfadeinTime, track.getTargetVolume() > 0 ? track.getTargetVolume() : 1.0f);
+ track.fadeIn(crossfadeinTime, track.getTargetVolume() > 0 ? track.getTargetVolume() : getVolume());
else
- track.setVolume(track.getTargetVolume() > 0 ? track.getTargetVolume() : 1.0f);
+ track.setVolume(track.getTargetVolume() > 0 ? track.getTargetVolume() : getVolume());
if (!track.isPlaying())
track.play();
}
@@ -373,6 +374,14 @@
return isPlaying;
}
+ public void setVolume(float volume) {
+ this.volume = volume;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
public void fadeOutAndClear(float fadeTime) {
// fade out the current track.
AudioTrack t = getCurrentTrack();
Index: EnvironmentalPool.java
===================================================================
--- EnvironmentalPool.java (revision 272)
+++ EnvironmentalPool.java (revision 448)
@@ -53,6 +53,7 @@
private float crossfadeoutTime = 3.5f;
private float crossfadeinTime = 3.5f;
+ private float volume = 1.0f;
public EnvironmentalPool() {
}
@@ -97,7 +98,7 @@
if (t.isEnabled() && !t.isActive()) {
t.stop();
if (crossfadeinTime > 0)
- t.fadeIn(crossfadeinTime, 1.0f);
+ t.fadeIn(crossfadeinTime, getVolume());
t.play();
} else if (!t.isEnabled() && t.isActive()) {
if (crossfadeoutTime > 0) {
@@ -108,14 +109,14 @@
public void trackFinishedFade(AudioTrack track) {
track.removeTrackStateListener(this);
track.stop();
- track.setVolume(1.0f);
- track.setTargetVolume(1.0f);
+ track.setVolume(getVolume());
+ track.setTargetVolume(getVolume());
}
@Override
public void trackStopped(AudioTrack track) {
track.removeTrackStateListener(this);
- track.setVolume(1.0f);
- track.setTargetVolume(1.0f);
+ track.setVolume(getVolume());
+ track.setTargetVolume(getVolume());
}
});
} else
@@ -165,6 +166,14 @@
this.crossfadeoutTime = crossfadeoutTime;
}
+ public void setVolume(float volume) {
+ this.volume = volume;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
public void fadeOutAndClear(float fadeTime) {
// remove any listeners
listListeners.clear();
Index: SoundEnvironment.java
===================================================================
--- SoundEnvironment.java (revision 0)
+++ SoundEnvironment.java (revision 448)
@@ -0,0 +1,101 @@
+package com.jmex.audio;
+
+import java.net.URL;
+import java.util.Hashtable;
+import java.util.logging.Logger;
+
+import com.jme.math.FastMath;
+import com.jmex.audio.AudioTrack.TrackType;
+import com.jmex.audio.event.TrackStateListener;
+import com.jmex.audio.util.AudioDebugging;
+
+
+/**
+ * Much like the EnvironmentalPool but with only one sound playing at a time, using crossfade for transitions
+ * and keeping track of all players current location to avoid playing the start of the environment-tracks again
+ * and again.
+ *
+ * @author Emanuel Greisen <jme@emanuelgreisen.dk>
+ */
+public class SoundEnvironment
+{
+ final static Logger logger = Logger.getLogger(SoundEnvironment.class.getName());
+ private float crossFadeTime;
+ private float volume;
+ private AudioTrack current_track;
+ private TrackStateListener pause_when_fade_complete_listener = new TrackStateListener()
+ {
+ public void trackFinishedFade(AudioTrack track)
+ {
+ logger.fine("Track.trackFinishedFade("+track.getVolume()+")");
+ if(track.getVolume() <= 0.001f)
+ {
+ track.pause();
+ track.removeTrackStateListener(this);
+ }
+ }
+
+ public void trackPaused(AudioTrack track)
+ {
+ logger.fine("Track.paused():"+AudioDebugging.printSoundFileName(track));
+ }
+
+ public void trackPlayed(AudioTrack track)
+ {
+ logger.fine("Track.played():"+AudioDebugging.printSoundFileName(track));
+ }
+
+ public void trackStopped(AudioTrack track)
+ {
+ logger.fine("Track.stopped():"+AudioDebugging.printSoundFileName(track));
+ }
+ };
+
+
+
+ public SoundEnvironment(float crossFadeTime, float volume)
+ {
+ this.crossFadeTime = crossFadeTime;
+ this.volume = volume;
+ }
+
+ public void setVolume(float volume)
+ {
+ this.volume = FastMath.clamp(volume, 0.0f, 1.0f);
+ if(current_track != null)
+ {
+ current_track.fadeTo(0.2f, this.volume);
+ }
+ }
+
+ /**
+ * Sets the current track for the environment.
+ *
+ * @param track
+ */
+ public void setCurrentTrack(AudioTrack track)
+ {
+ if(track == current_track)
+ return; // We are already playing this track
+
+ // Fade out old track
+ if(current_track != null)
+ {
+ current_track.fadeTo(crossFadeTime, 0.0f);
+ }
+
+ current_track = track;
+
+ // Fade in new track (and set some important stuff)
+ if(current_track != null)
+ {
+ current_track.setType(TrackType.ENVIRONMENT);
+ current_track.setLooping(true);
+ current_track.addTrackStateListener(pause_when_fade_complete_listener);
+ current_track.setEnabled(true);
+ current_track.setVolume(0);
+ current_track.fadeTo(crossFadeTime, volume);
+ current_track.play();
+ }
+ }
+}
Index: util/AudioDebugging.java
===================================================================
--- util/AudioDebugging.java (revision 0)
+++ util/AudioDebugging.java (revision 448)
@@ -0,0 +1,15 @@
+package com.jmex.audio.util;
+
+import java.io.File;
+
+import com.jmex.audio.AudioTrack;
+
+public class AudioDebugging
+{
+ public static String printSoundFileName(AudioTrack track)
+ {
+ if(track == null || track.getResource() == null)
+ return "null";
+ return new File(track.getResource().getFile()).getName();
+ }
+}
Index: openal/OpenALStreamedAudioPlayer.java
===================================================================
--- openal/OpenALStreamedAudioPlayer.java (revision 272)
+++ openal/OpenALStreamedAudioPlayer.java (revision 448)
@@ -163,7 +163,15 @@
AL10.alSourcei(source.getId(), AL10.AL_SOURCE_RELATIVE, getTrack()
.isRelative() ? AL10.AL_TRUE : AL10.AL_FALSE);
- playInNewThread(200);
+ if(AudioSystem.getSystem().getCreateStreamThreads()) {
+ playInNewThread();
+ } else {
+ if (playStream()) {
+ ((OpenALSystem)AudioSystem.getSystem()).addStreamedPlayer(this);
+ } else {
+ logger.warning("Could not play stream: "+getTrack().getResource());
+ }
+ }
}
}
@@ -209,10 +217,10 @@
* at which interval should the thread call update, in
* milliseconds.
*/
- public boolean playInNewThread(long updateIntervalMillis) {
+ public boolean playInNewThread() {
try {
if (playStream()) {
- playerThread = new PlayerThread(updateIntervalMillis);
+ playerThread = new PlayerThread();
playerThread.start();
return true;
}
@@ -314,12 +322,16 @@
} else if (getStream().getDepth() == 16) {
format = (mono ? AL10.AL_FORMAT_MONO16
: AL10.AL_FORMAT_STEREO16);
- } else return false;
-
+ } else {
+ return false;
+ }
+
AL10.alBufferData(buffer, format, dataBuffer, getStream()
.getBitRate());
+
return true;
}
+
if (isLoop() && getTrack().isEnabled()) {
setStream(getStream().makeNew());
return stream(buffer);
@@ -346,37 +358,53 @@
* XXX: I am considering abolishing these one-per-sound threads.
*/
class PlayerThread extends Thread {
- // at what interval update is called.
- long interval;
/** Creates the PlayerThread */
- PlayerThread(long interval) {
- this.interval = interval;
+ PlayerThread() {
setDaemon(true);
}
/** Calls update at an interval */
public void run() {
try {
- while (!isStopped && update()) {
- sleep(interval);
+ if(threadUpdate()) {
+ sleep(AudioSystem.getSystem().getStreamUpdateInterval());
}
- while (isActive()) {
- sleep(interval);
- }
- OpenALStreamedAudioPlayer.this.stop();
- } catch (Exception e) {
-// e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
}
+
+ /**
+ * This is what the driver-thread should call every now and then to keep data
+ * coming to the audio-track.
+ *
+ * @return <code>true</code> if the thread should continue running.
+ * @throws IOException
+ */
+ public boolean threadUpdate() {
+ try {
+ if(!isStopped && update()) {
+ return true;
+ }
+
+ if(isActive()) {
+ return true;
+ }
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ OpenALStreamedAudioPlayer.this.stop();
+ return false;
+ }
@Override
public void applyTrackProperties() {
OpenALPropertyTool.applyProperties(this, source);
}
- @Override
+ @Override
public void updateTrackPlacement() {
Vector3f pos = getTrack().getWorldPosition();
Vector3f vel = getTrack().getCurrVelocity();
Index: openal/OpenALSystem.java
===================================================================
--- openal/OpenALSystem.java (revision 272)
+++ openal/OpenALSystem.java (revision 448)
@@ -32,10 +32,12 @@
package com.jmex.audio.openal;
+import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.IntBuffer;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
@@ -68,6 +70,8 @@
.75f, true));
private long held = 0L;
private long lastTime = System.currentTimeMillis();
+ private LinkedList<OpenALStreamedAudioPlayer> streamedPlayers = new LinkedList<OpenALStreamedAudioPlayer>();
+ private long lastStreamPlayerTime = System.currentTimeMillis();
public OpenALSystem() {
ear = new OpenALEar();
@@ -109,6 +113,25 @@
long thisTime = System.currentTimeMillis();
float dt = (thisTime - lastTime) / 1000f;
lastTime = thisTime;
+
+ // Update any stream-players?
+ if(!getCreateStreamThreads()) {
+ if(lastStreamPlayerTime + getStreamUpdateInterval() < thisTime) {
+ lastStreamPlayerTime = thisTime;
+ Iterator<OpenALStreamedAudioPlayer> player_iterator = streamedPlayers.iterator();
+ OpenALStreamedAudioPlayer player = null;
+ while(player_iterator.hasNext()) {
+ player = player_iterator.next();
+ //System.out.println("Updating["+player.isPlaying()+"]:"+new File(player.getTrack().getResource().getFile()).getName()+":"+player.getVolume()+"/"+player.getTrack().getVolume());
+ //if(player.getTrack().getVolume() != player.getTrack().getTargetVolume()) {
+ // System.out.println("Fading from "+player.getTrack().getVolume()+" to "+player.getTrack().getTargetVolume());
+ //}
+ if(!player.threadUpdate()) {
+ player_iterator.remove();
+ }
+ }
+ }
+ }
try {
for (int x = 0; x < MAX_SOURCES; x++) {
@@ -278,4 +301,12 @@
public void setSpeedOfSound(float unitsPerSecond) {
AL10.alDopplerVelocity(unitsPerSecond);
}
+
+ public void addStreamedPlayer(OpenALStreamedAudioPlayer player) {
+ synchronized (this) {
+ if(!streamedPlayers.contains(player)) {
+ streamedPlayers.add(player);
+ }
+ }
+ }
}
No newline at end of file
Index: openal/OpenALUtil.java
===================================================================
--- openal/OpenALUtil.java (revision 0)
+++ openal/OpenALUtil.java (revision 448)
@@ -0,0 +1,41 @@
+package com.jmex.audio.openal;
+
+import java.nio.IntBuffer;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.openal.AL;
+import org.lwjgl.openal.AL10;
+import org.lwjgl.openal.ALC10;
+import org.lwjgl.openal.ALCcontext;
+import org.lwjgl.openal.ALCdevice;
+
+public class OpenALUtil
+{
+ private OpenALUtil() {
+ // no c'tor
+ }
+
+ public static void printOpenALInfo()
+ {
+ ALCdevice device = AL.getDevice();
+ ALCcontext context = AL.getContext();
+ System.out.println("Device: "+device);
+ System.out.println("Context: "+context);
+
+ // Device?
+ System.out.println("ALC_DEVICE_SPECIFIER:"+ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER));
+ System.out.println("ALC_DEFAULT_DEVICE_SPECIFIER:"+ALC10.alcGetString(device, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER));
+
+ // Settings
+ IntBuffer integerdata = BufferUtils.createIntBuffer(1);
+ ALC10.alcGetInteger(device, ALC10.ALC_ATTRIBUTES_SIZE, integerdata);
+ integerdata = BufferUtils.createIntBuffer(integerdata.get(0));
+ ALC10.alcGetInteger(device, ALC10.ALC_ALL_ATTRIBUTES, integerdata);
+ integerdata.rewind();
+ while(integerdata.position() != integerdata.capacity())
+ {
+ System.out.println("I:"+integerdata.get());
+ }
+ //System.out.println(AL10.alGetString());
+ }
+}