package org.terasology.audio;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.terasology.game.CoreRegistry;
import org.terasology.logic.manager.SoundManager;
import org.terasology.rendering.world.WorldRenderer;
import javax.vecmath.Vector3d;
import static org.lwjgl.openal.AL10.*;
public class BasicSoundSource implements SoundSource {
protected Sound audio;
protected int sourceId;
protected float srcGain = 1.0f;
protected float targetGain = 1.0f;
protected boolean fade = false;
protected Vector3d position = new Vector3d();
protected Vector3d velocity = new Vector3d();
protected Vector3d direction = new Vector3d();
protected boolean absolutePosition = false;
protected boolean _playing = false;
public BasicSoundSource() {
sourceId = alGenSources();
OpenALException.checkState("Creating sound source");
reset();
}
public BasicSoundSource(Sound audio) {
this();
setAudio(audio);
}
@Override
public SoundSource play() {
if (!isPlaying()) {
AL10.alSourcePlay(getSourceId());
_playing = true;
}
return this;
}
@Override
public SoundSource stop() {
if (audio != null) {
audio.reset();
}
alSourceStop(getSourceId());
OpenALException.checkState("Rewind");
alSourceRewind(getSourceId());
OpenALException.checkState("Stop playback");
_playing = false;
return this;
}
@Override
public SoundSource pause() {
if (isPlaying()) {
AL10.alSourcePause(getSourceId());
OpenALException.checkState("Pause playback");
_playing = false;
}
return this;
}
@Override
public boolean isPlaying() {
return alGetSourcei(sourceId, AL_SOURCE_STATE) == AL_PLAYING || _playing;
}
@Override
public void update() {
updateFade();
if (absolutePosition) {
updatePosition(position);
}
updateState();
}
protected void updateState() {
if (_playing && alGetSourcei(sourceId, AL_SOURCE_STATE) != AL_PLAYING) {
_playing = false; // sound stop playing
}
}
@Override
public SoundSource reset() {
setPitch(1.0f);
setLooping(false);
setGain(1.0f);
setAbsolute(false);
Vector3d zeroVector = new Vector3d();
setPosition(zeroVector);
setVelocity(zeroVector);
setDirection(zeroVector);
// some additional settings
alSourcef(getSourceId(), AL_MAX_DISTANCE, SoundManager.MAX_DISTANCE);
alSourcef(getSourceId(), AL_REFERENCE_DISTANCE, 0.1f);
fade = false;
srcGain = 1.0f;
targetGain = 1.0f;
return this;
}
@Override
public int getLength() {
return audio.getLength();
}
@Override
public SoundSource setPlaybackPosition(int position) {
boolean playing = isPlaying();
if (playing) {
AL10.alSourceStop(getSourceId());
}
AL10.alSourceRewind(getSourceId());
AL10.alSourcei(getSourceId(), AL11.AL_SAMPLE_OFFSET, audio.getSamplingRate() * position);
OpenALException.checkState("Setting sound playback absolute position");
if (playing) {
play();
}
return this;
}
@Override
public int getPlaybackPosition() {
return AL10.alGetSourcei(getSourceId(), AL11.AL_SAMPLE_OFFSET) / audio.getSamplingRate();
}
@Override
public SoundSource setPlaybackPosition(float position) {
boolean playing = isPlaying();
if (playing) {
AL10.alSourceStop(getSourceId());
}
AL10.alSourceRewind(getSourceId());
AL10.alSourcei(getSourceId(), AL11.AL_BYTE_OFFSET, (int) (audio.getBufferSize() * position));
OpenALException.checkState("Setting sound playback relaive position");
if (playing) {
play();
}
return this;
}
@Override
public float getPlaybackPositionf() {
return AL10.alGetSourcei(getSourceId(), AL11.AL_BYTE_OFFSET) / audio.getBufferSize();
}
@Override
public SoundSource setAbsolute(boolean absolute) {
absolutePosition = absolute;
return this;
}
@Override
public boolean isAbsolute() {
return absolutePosition;
}
@Override
public Vector3d getVelocity() {
return velocity;
}
@Override
public SoundSource setVelocity(Vector3d velocity) {
if (velocity == null || this.velocity.equals(velocity)) {
return this;
}
this.velocity.set(velocity);
AL10.alSource3f(getSourceId(), AL10.AL_VELOCITY, (float) velocity.x, (float) velocity.y, (float) velocity.z);
OpenALException.checkState("Setting sound source velocity");
return this;
}
@Override
public Vector3d getPosition() {
return position;
}
@Override
public SoundSource setPosition(Vector3d position) {
if (position == null || this.position.equals(position)) {
return this;
}
this.position.set(position);
updatePosition(position);
return this;
}
@Override
public SoundSource setDirection(Vector3d direction) {
if (direction == null || this.direction.equals(direction)) {
return this;
}
AL10.alSource3f(getSourceId(), AL10.AL_DIRECTION, (float) direction.x, (float) direction.y, (float) direction.z);
OpenALException.checkState("Setting sound source direction");
this.direction.set(direction);
return this;
}
@Override
public Vector3d getDirection() {
return direction;
}
@Override
public float getPitch() {
return AL10.alGetSourcef(getSourceId(), AL10.AL_PITCH);
}
@Override
public SoundSource setPitch(float pitch) {
AL10.alSourcef(getSourceId(), AL10.AL_PITCH, pitch);
OpenALException.checkState("Setting sound pitch");
return this;
}
@Override
public float getGain() {
return alGetSourcef(getSourceId(), AL_GAIN);
}
@Override
public SoundSource setGain(float gain) {
alSourcef(getSourceId(), AL_GAIN, gain);
OpenALException.checkState("Setting sound gain");
return this;
}
@Override
public boolean isLooping() {
return alGetSourcei(getSourceId(), AL_LOOPING) == AL_TRUE;
}
@Override
public SoundSource setLooping(boolean looping) {
alSourcei(getSourceId(), AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
OpenALException.checkState("Setting sound looping");
return this;
}
@Override
public SoundSource setAudio(Sound sound) {
boolean playing = isPlaying();
if (playing) {
stop();
}
reset();
if (sound instanceof AbstractSound) {
audio = sound;
AL10.alSourcei(getSourceId(), AL10.AL_BUFFER, audio.getBufferId());
OpenALException.checkState("Assigning buffer to source");
} else {
throw new IllegalArgumentException("Unsupported sound object!");
}
if (playing) {
play();
}
return this;
}
@Override
public Sound getAudio() {
return audio;
}
@Override
public SoundSource fade(float targetGain) {
srcGain = getGain();
this.targetGain = targetGain;
fade = true;
return this;
}
public int getSourceId() {
return sourceId;
}
protected void updatePosition(Vector3d position) {
float[] pos = new float[]{(float) position.x, (float) position.y, (float) position.z};
if (isAbsolute()) {
Vector3d cameraPos = getCameraPosition();
pos[0] -= cameraPos.x;
pos[1] -= cameraPos.y;
pos[2] -= cameraPos.z;
}
alSource3f(getSourceId(), AL10.AL_POSITION, pos[0], pos[1], pos[2]);
OpenALException.checkState("Changing sound position");
}
private void updateFade() {
if (!fade) {
return;
}
float delta = (srcGain - targetGain) / 100;
setGain(getGain() - delta);
if (getGain() >= targetGain) {
if (targetGain == 0.0f) {
stop();
}
fade = false;
}
}
private Vector3d getCameraPosition() {
return CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition();
}
@Override
// TODO: This is no guaranteed to be executed at all – move to a safer place
protected void finalize() throws Throwable {
if (sourceId != 0) {
AL10.alDeleteSources(sourceId);
}
super.finalize();
}
@Override
public int hashCode() {
return getSourceId();
}
}