diff --git a/pom.xml b/pom.xml index 5c42214f..f9a283f4 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,17 @@ lwjgl-opengles ${lwjgl.version} + + org.lwjgl + lwjgl-openal + ${lwjgl.version} + + + org.lwjgl + lwjgl-stb + ${lwjgl.version} + + org.lwjgl lwjgl @@ -46,6 +57,7 @@ ${lwjgl.natives} runtime + org.lwjgl lwjgl-assimp @@ -53,6 +65,7 @@ ${lwjgl.natives} runtime + org.lwjgl lwjgl-glfw @@ -60,6 +73,7 @@ ${lwjgl.natives} runtime + org.lwjgl lwjgl-opengl @@ -67,6 +81,7 @@ ${lwjgl.natives} runtime + org.lwjgl lwjgl-opengles @@ -74,6 +89,21 @@ ${lwjgl.natives} runtime + + + org.lwjgl + lwjgl-openal + ${lwjgl.version} + ${lwjgl.natives} + runtime + + + org.lwjgl + lwjgl-stb + ${lwjgl.version} + ${lwjgl.natives} + runtime + org.joml joml diff --git a/src/main/java/electrosphere/audio/AudioBuffer.java b/src/main/java/electrosphere/audio/AudioBuffer.java index 13ac9a41..6272ef15 100644 --- a/src/main/java/electrosphere/audio/AudioBuffer.java +++ b/src/main/java/electrosphere/audio/AudioBuffer.java @@ -1,5 +1,110 @@ package electrosphere.audio; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.lwjgl.BufferUtils; +import static org.lwjgl.BufferUtils.createByteBuffer; +import static org.lwjgl.openal.AL10.*; +import static org.lwjgl.stb.STBVorbis.*; +import org.lwjgl.stb.STBVorbisInfo; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.system.MemoryUtil.NULL; + public class AudioBuffer { + private int bufferId; + + private ByteBuffer vorbis = null; + + private ShortBuffer pcm = null; + + public AudioBuffer(String fileName) throws Exception { + bufferId = alGenBuffers(); + try (STBVorbisInfo info = STBVorbisInfo.malloc()) { + ShortBuffer pcm = readVorbis(fileName, 32 * 1024, info); + // Copy to buffer + alBufferData(bufferId, info.channels() == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, pcm, info.sample_rate()); + } + } + + + private ShortBuffer readVorbis(String resource, int bufferSize, STBVorbisInfo info) throws Exception { + try (MemoryStack stack = MemoryStack.stackPush()) { + vorbis = AudioBuffer.ioResourceToByteBuffer(resource, bufferSize); + IntBuffer error = stack.mallocInt(1); + long decoder = stb_vorbis_open_memory(vorbis, error, null); + if (decoder == NULL) { + throw new RuntimeException("Failed to open Ogg Vorbis file. Error: " + error.get(0)); + } + + stb_vorbis_get_info(decoder, info); + + int channels = info.channels(); + + int lengthSamples = stb_vorbis_stream_length_in_samples(decoder); + + pcm = MemoryUtil.memAllocShort(lengthSamples); + + pcm.limit(stb_vorbis_get_samples_short_interleaved(decoder, channels, pcm) * channels); + stb_vorbis_close(decoder); + + return pcm; + } + } + + public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException { + ByteBuffer buffer; + + Path path = Paths.get(resource); + if (Files.isReadable(path)) { + try (SeekableByteChannel fc = Files.newByteChannel(path)) { + buffer = BufferUtils.createByteBuffer((int) fc.size() + 1); + while (fc.read(buffer) != -1) ; + } + } else { + try ( + InputStream source = AudioBuffer.class.getResourceAsStream(resource); + ReadableByteChannel rbc = Channels.newChannel(source)) { + buffer = createByteBuffer(bufferSize); + + while (true) { + int bytes = rbc.read(buffer); + if (bytes == -1) { + break; + } + if (buffer.remaining() == 0) { + int capacity = buffer.capacity(); + ByteBuffer newBuffer = createByteBuffer(capacity * 2); + for(int i = 0; i < capacity; i++){ + newBuffer.put(buffer.get()); + } + buffer = newBuffer; + } + } + } + } + + buffer.flip(); + return buffer; + } + + + public int getBufferId() { + return this.bufferId; + } + + public void cleanup() { + alDeleteBuffers(this.bufferId); + } + } diff --git a/src/main/java/electrosphere/audio/AudioEngine.java b/src/main/java/electrosphere/audio/AudioEngine.java index a78428f2..2d872fea 100644 --- a/src/main/java/electrosphere/audio/AudioEngine.java +++ b/src/main/java/electrosphere/audio/AudioEngine.java @@ -1,5 +1,46 @@ package electrosphere.audio; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.joml.Matrix4f; +import org.lwjgl.openal.*; +import static org.lwjgl.openal.ALC10.*; +import static org.lwjgl.system.MemoryUtil.NULL; + public class AudioEngine { - + private long device; + + private long context; + + private AudioListener listener; + + private final List soundBufferList; + + private final Map soundSourceMap; + + private final Matrix4f cameraMatrix; + + public AudioEngine() { + soundBufferList = new ArrayList<>(); + soundSourceMap = new HashMap<>(); + cameraMatrix = new Matrix4f(); + } + + public void init() throws Exception { + this.device = alcOpenDevice((ByteBuffer) null); + if (device == NULL) { + throw new IllegalStateException("Failed to open the default OpenAL device."); + } + ALCCapabilities deviceCaps = ALC.createCapabilities(device); + this.context = alcCreateContext(device, (IntBuffer) null); + if (context == NULL) { + throw new IllegalStateException("Failed to create OpenAL context."); + } + alcMakeContextCurrent(context); + AL.createCapabilities(deviceCaps); + } } diff --git a/src/main/java/electrosphere/audio/AudioListener.java b/src/main/java/electrosphere/audio/AudioListener.java new file mode 100644 index 00000000..2de5b51b --- /dev/null +++ b/src/main/java/electrosphere/audio/AudioListener.java @@ -0,0 +1,42 @@ +package electrosphere.audio; + +import org.joml.Vector3f; +import static org.lwjgl.openal.AL10.*; + +/** + * + * @author amaterasu + */ +public class AudioListener { + + public AudioListener() { + this(new Vector3f(0, 0, 0)); + } + + public AudioListener(Vector3f position) { + alListener3f(AL_POSITION, position.x, position.y, position.z); + alListener3f(AL_VELOCITY, 0, 0, 0); + + } + + public void setSpeed(Vector3f speed) { + alListener3f(AL_VELOCITY, speed.x, speed.y, speed.z); + } + + public void setPosition(Vector3f position) { + alListener3f(AL_POSITION, position.x, position.y, position.z); + } + + public void setOrientation(Vector3f at, Vector3f up) { + float[] data = new float[6]; + data[0] = at.x; + data[1] = at.y; + data[2] = at.z; + data[3] = up.x; + data[4] = up.y; + data[5] = up.z; + alListenerfv(AL_ORIENTATION, data); + } + + +} diff --git a/src/main/java/electrosphere/audio/AudioSource.java b/src/main/java/electrosphere/audio/AudioSource.java new file mode 100644 index 00000000..65ed6f9d --- /dev/null +++ b/src/main/java/electrosphere/audio/AudioSource.java @@ -0,0 +1,67 @@ +package electrosphere.audio; + +import org.joml.Vector3f; +import static org.lwjgl.openal.AL10.*; + +/** + * + * @author amaterasu + */ +public class AudioSource { + + int sourceId; + + + public AudioSource(boolean loop, boolean relative){ + this.sourceId = alGenSources(); + if (loop) { + alSourcei(sourceId, AL_LOOPING, AL_TRUE); + } + if (relative) { + alSourcei(sourceId, AL_SOURCE_RELATIVE, AL_TRUE); + } + } + + + public void setBuffer(int bufferId) { + stop(); + alSourcei(sourceId, AL_BUFFER, bufferId); + } + + public void setPosition(Vector3f position) { + alSource3f(sourceId, AL_POSITION, position.x, position.y, position.z); + } + + public void setSpeed(Vector3f speed) { + alSource3f(sourceId, AL_VELOCITY, speed.x, speed.y, speed.z); + } + + public void setGain(float gain) { + alSourcef(sourceId, AL_GAIN, gain); + } + + public void setProperty(int param, float value) { + alSourcef(sourceId, param, value); + } + + public void play() { + alSourcePlay(sourceId); + } + + public boolean isPlaying() { + return alGetSourcei(sourceId, AL_SOURCE_STATE) == AL_PLAYING; + } + + public void pause() { + alSourcePause(sourceId); + } + + public void stop() { + alSourceStop(sourceId); + } + + public void cleanup() { + stop(); + alDeleteSources(sourceId); + } +}