/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.audio.openAL;
import com.bulletphysics.linearmath.QuaternionUtil;
import com.google.common.collect.Maps;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALC11;
import org.lwjgl.openal.ALCcontext;
import org.lwjgl.openal.ALCdevice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.asset.AssetFactory;
import org.terasology.asset.AssetUri;
import org.terasology.audio.AudioEndListener;
import org.terasology.audio.AudioManager;
import org.terasology.audio.Sound;
import org.terasology.audio.StaticSound;
import org.terasology.audio.StaticSoundData;
import org.terasology.audio.StreamingSound;
import org.terasology.audio.StreamingSoundData;
import org.terasology.audio.openAL.staticSound.OpenALSound;
import org.terasology.audio.openAL.staticSound.OpenALSoundPool;
import org.terasology.audio.openAL.streamingSound.OpenALStreamingSound;
import org.terasology.audio.openAL.streamingSound.OpenALStreamingSoundPool;
import org.terasology.config.AudioConfig;
import org.terasology.math.Direction;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3f;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.Map;
public class OpenALManager implements AudioManager {
private static final Logger logger = LoggerFactory.getLogger(OpenALManager.class);
/**
* For faster distance check *
*/
private static final float MAX_DISTANCE_SQUARED = MAX_DISTANCE * MAX_DISTANCE;
protected Map<String, SoundPool<? extends Sound<?>, ?>> pools = Maps.newHashMap();
private Vector3f listenerPosition = new Vector3f();
private Map<SoundSource<?>, AudioEndListener> endListeners = Maps.newHashMap();
private PropertyChangeListener configListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(AudioConfig.MUSIC_VOLUME)) {
setMusicVolume((Float) evt.getNewValue());
} else if (evt.getPropertyName().equals(AudioConfig.SOUND_VOLUME)) {
setSoundVolume((Float) evt.getNewValue());
}
}
};
public OpenALManager(AudioConfig config) throws OpenALException, LWJGLException {
logger.info("Initializing OpenAL audio manager");
config.subscribe(configListener);
AL.create();
AL10.alGetError();
logger.info("OpenAL {} initialized!", AL10.alGetString(AL10.AL_VERSION));
ALCcontext context = ALC10.alcGetCurrentContext();
ALCdevice device = ALC10.alcGetContextsDevice(context);
logger.info("Using OpenAL: {} by {}", AL10.alGetString(AL10.AL_RENDERER), AL10.alGetString(AL10.AL_VENDOR));
logger.info("Using device: {}", ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER));
logger.info("Available AL extensions: {}", AL10.alGetString(AL10.AL_EXTENSIONS));
logger.info("Available ALC extensions: {}", ALC10.alcGetString(device, ALC10.ALC_EXTENSIONS));
IntBuffer buffer = BufferUtils.createIntBuffer(1);
ALC10.alcGetInteger(device, ALC11.ALC_MONO_SOURCES, buffer);
logger.info("Max mono sources: {}", buffer.get(0));
buffer.rewind();
ALC10.alcGetInteger(device, ALC11.ALC_STEREO_SOURCES, buffer);
logger.info("Max stereo sources: {}", buffer.get(0));
buffer.rewind();
ALC10.alcGetInteger(device, ALC10.ALC_FREQUENCY, buffer);
logger.info("Mixer frequency: {}", buffer.get(0));
buffer.rewind();
AL10.alDistanceModel(AL10.AL_INVERSE_DISTANCE_CLAMPED);
// Initialize sound pools
pools.put("sfx", new OpenALSoundPool(30)); // effects pool
pools.put("music", new OpenALStreamingSoundPool(2)); // music pool
pools.get("sfx").setVolume(config.getSoundVolume());
pools.get("music").setVolume(config.getMusicVolume());
}
@Override
public void dispose() {
AL.destroy();
}
@Override
public void stopAllSounds() {
for (SoundPool<?, ?> pool : pools.values()) {
pool.stopAll();
}
}
@Override
public boolean isMute() {
return AL10.alGetListenerf(AL10.AL_GAIN) < 0.01f;
}
@Override
public void setMute(boolean mute) {
if (mute) {
AL10.alListenerf(AL10.AL_GAIN, 0);
} else {
AL10.alListenerf(AL10.AL_GAIN, 1.0f);
}
}
private void setSoundVolume(float volume) {
pools.get("sfx").setVolume(volume);
}
private void setMusicVolume(float volume) {
pools.get("music").setVolume(volume);
}
@Override
public void playSound(StaticSound sound) {
playSound(sound, null, 1.0f, PRIORITY_NORMAL);
}
@Override
public void playSound(StaticSound sound, float volume) {
playSound(sound, null, volume, PRIORITY_NORMAL);
}
@Override
public void playSound(StaticSound sound, float volume, int priority) {
playSound(sound, null, volume, priority);
}
@Override
public void playSound(StaticSound sound, Vector3f position) {
playSound(sound, position, 1.0f, PRIORITY_NORMAL);
}
@Override
public void playSound(StaticSound sound, Vector3f position, float volume) {
playSound(sound, position, volume, PRIORITY_NORMAL);
}
@Override
public void playSound(StaticSound sound, Vector3f position, float volume, int priority) {
playSound(sound, position, volume, priority, null);
}
@Override
public void playSound(StaticSound sound, Vector3f position, float volume, int priority, AudioEndListener endListener) {
if (position != null && !checkDistance(position)) {
return;
}
SoundPool<StaticSound, ?> pool = (SoundPool<StaticSound, ?>) pools.get("sfx");
SoundSource<?> source = pool.getSource(sound, priority);
if (source != null) {
source.setAbsolute(position != null);
if (position != null) {
source.setPosition(position);
}
source.setGain(volume);
source.play();
if (endListener != null) {
endListeners.put(source, endListener);
}
}
}
@Override
public void playMusic(StreamingSound music) {
playMusic(music, 1.0f, null);
}
@Override
public void playMusic(StreamingSound music, AudioEndListener endListener) {
playMusic(music, 1.0f, endListener);
}
@Override
public void playMusic(StreamingSound music, float volume) {
playMusic(music, volume, null);
}
@Override
public void playMusic(StreamingSound music, float volume, AudioEndListener endListener) {
SoundPool<StreamingSound, ?> pool = (SoundPool<StreamingSound, ?>) pools.get("music");
pool.stopAll();
if (music == null) {
return;
}
SoundSource<?> source = pool.getSource(music);
if (source != null) {
source.setGain(volume).play();
if (endListener != null) {
endListeners.put(source, endListener);
}
}
}
@Override
public void updateListener(Vector3f position, Quat4f orientation, Vector3f velocity) {
AL10.alListener3f(AL10.AL_VELOCITY, velocity.x, velocity.y, velocity.z);
OpenALException.checkState("Setting listener velocity");
Vector3f dir = QuaternionUtil.quatRotate(orientation, Direction.FORWARD.getVector3f(), new Vector3f());
Vector3f up = QuaternionUtil.quatRotate(orientation, Direction.UP.getVector3f(), new Vector3f());
FloatBuffer listenerOri = BufferUtils.createFloatBuffer(6).put(new float[]{dir.x, dir.y, dir.z, up.x, up.y, up.z});
listenerOri.flip();
AL10.alListener(AL10.AL_ORIENTATION, listenerOri);
OpenALException.checkState("Setting listener orientation");
this.listenerPosition.set(position);
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
OpenALException.checkState("Setting listener position");
}
@Override
public void update(float delta) {
for (SoundPool<?, ?> pool : pools.values()) {
pool.update(delta);
}
Iterator<Map.Entry<SoundSource<?>, AudioEndListener>> iterator = endListeners.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<SoundSource<?>, AudioEndListener> entry = iterator.next();
if (!entry.getKey().isPlaying()) {
iterator.remove();
entry.getValue().onAudioEnd();
}
}
}
protected boolean checkDistance(Vector3f soundPosition) {
Vector3f distance = new Vector3f(soundPosition);
distance.sub(listenerPosition);
return distance.lengthSquared() < MAX_DISTANCE_SQUARED;
}
@Override
public AssetFactory<StaticSoundData, StaticSound> getStaticSoundFactory() {
return new AssetFactory<StaticSoundData, StaticSound>() {
@Override
public StaticSound buildAsset(AssetUri uri, StaticSoundData data) {
return new OpenALSound(uri, data, OpenALManager.this);
}
};
}
@Override
public AssetFactory<StreamingSoundData, StreamingSound> getStreamingSoundFactory() {
return new AssetFactory<StreamingSoundData, StreamingSound>() {
@Override
public StreamingSound buildAsset(AssetUri uri, StreamingSoundData data) {
return new OpenALStreamingSound(uri, data, OpenALManager.this);
}
};
}
public void purgeSound(Sound<?> sound) {
for (SoundPool<?, ?> pool : pools.values()) {
pool.purge(sound);
}
}
}