diff --git a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java index 0ab1bd0..25e19e9 100644 --- a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java +++ b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java @@ -17,17 +17,13 @@ *******************************************************************************/ package ru.windcorp.progressia.client; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import ru.windcorp.progressia.ProgressiaLauncher; +import ru.windcorp.progressia.test.ALTest; public class ProgressiaClientMain { - private static final Logger logger = LogManager.getLogger(ProgressiaClientMain.class.getName()); - public static void main(String[] args) { - logger.info("App started!"); - + ALTest.execute(); ProgressiaLauncher.launch(args, new ClientProxy()); } diff --git a/src/main/java/ru/windcorp/progressia/client/audio/AudioFormat.java b/src/main/java/ru/windcorp/progressia/client/audio/AudioFormat.java new file mode 100644 index 0000000..c17f385 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/AudioFormat.java @@ -0,0 +1,5 @@ +package ru.windcorp.progressia.client.audio; + +public enum AudioFormat { + MONO, STEREO +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java b/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java new file mode 100644 index 0000000..3935e68 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java @@ -0,0 +1,143 @@ +package ru.windcorp.progressia.client.audio; + +import org.lwjgl.openal.AL; +import org.lwjgl.openal.ALC; +import org.lwjgl.openal.ALCCapabilities; +import org.lwjgl.openal.ALCapabilities; +import ru.windcorp.progressia.client.audio.backend.AudioReader; +import ru.windcorp.progressia.client.audio.backend.Listener; +import ru.windcorp.progressia.client.audio.backend.SoundType; +import ru.windcorp.progressia.client.audio.backend.Speaker; + +import static org.lwjgl.openal.AL11.*; +import static org.lwjgl.openal.ALC10.*; + +import java.util.ArrayList; +import java.util.List; + +public class AudioManager { + private static long device; + private static ALCCapabilities deviceCapabilities; + private static ALCapabilities alCapabilities; + + private static final int SOUNDS_NUM = 64; + private static int lastSoundIndex = 0; + + private static List soundSpeakers = new ArrayList<>(SOUNDS_NUM); + private static Speaker musicSpeaker; + private static ArrayList soundsBuffer = new ArrayList<>(); + + public static void initAL() { + String defaultDeviceName = alcGetString( + 0, + ALC_DEFAULT_DEVICE_SPECIFIER + ); + + device = alcOpenDevice(defaultDeviceName); + + int[] attributes = new int[1]; + long context = alcCreateContext(device, attributes); + alcMakeContextCurrent(context); + + deviceCapabilities = ALC.createCapabilities(device); + alCapabilities = AL.createCapabilities(deviceCapabilities); + + checkALError(); + createBuffers(); + } + + public static void update() { + // Position of the listener + Listener.getInstance().update(); + + } + + private static Speaker getLastSpeaker() { + Speaker speaker; + do { + lastSoundIndex++; + if (lastSoundIndex >= SOUNDS_NUM) { + lastSoundIndex = 0; + } + speaker = soundSpeakers.get(lastSoundIndex); + } while (speaker.getState() + .equals(Speaker.State.PLAYING_LOOP)); + return speaker; + } + + private static SoundType findSoundType(String soundID) throws Exception { + for (SoundType s : soundsBuffer) { + if (s.getId().equals(soundID)) { + return s; + } + } + throw new Exception("ERROR: The selected sound is not loaded or" + + " not exists"); + } + + public static Speaker initSpeaker(String soundID) { + Speaker speaker = getLastSpeaker(); + try { + findSoundType(soundID).initSpeaker(speaker); + } catch (Exception ex) + { + throw new RuntimeException(); + } + return speaker; + } + + public static Speaker initMusicSpeaker(String soundID) { + try { + findSoundType(soundID).initSpeaker(musicSpeaker); + } catch (Exception ex) + { + throw new RuntimeException(); + } + return musicSpeaker; + } + + public static void checkALError() { + int errorCode = alGetError(); + if (alGetError() != AL_NO_ERROR) { + throw new RuntimeException(String.valueOf(errorCode)); + } + } + + public static void loadSound(String path, String id, AudioFormat format) { + if (format == AudioFormat.MONO) { + soundsBuffer.add(AudioReader.readAsMono(path, id)); + } else + { + soundsBuffer.add(AudioReader.readAsStereo(path, id)); + } + } + + public static void closeAL() { + for (Speaker s : soundSpeakers) { + alDeleteBuffers(s.getAudioData()); + alDeleteBuffers(s.getSourceData()); + } + alDeleteBuffers(musicSpeaker.getAudioData()); + alDeleteBuffers(musicSpeaker.getSourceData()); + + alcCloseDevice(device); + } + + public static ALCapabilities getALCapabilities() { + return alCapabilities; + } + + public static ALCCapabilities getDeviceCapabilities() { + return deviceCapabilities; + } + + public static void createBuffers() + { + for (int i = 0; i < SOUNDS_NUM; ++i) { + soundSpeakers.add(new Speaker()); + } + + musicSpeaker = new Speaker(); + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/audio/Music.java b/src/main/java/ru/windcorp/progressia/client/audio/Music.java new file mode 100644 index 0000000..9c1d12d --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/Music.java @@ -0,0 +1,70 @@ +package ru.windcorp.progressia.client.audio; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.audio.backend.Speaker; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; + +public class Music extends Namespaced { + private Vec3 position = new Vec3(); + private Vec3 velocity = new Vec3(); + private float pitch = 1.0f; + private float gain = 1.0f; + + + public Music(String id) + { + super(id); + } + + public Music(String id, + Vec3 position, + Vec3 velocity, + float pitch, + float gain) + { + this(id); + this.position = position; + this.velocity = velocity; + this.pitch = pitch; + this.gain = gain; + } + + public void play(boolean loop) + { + Speaker speaker = AudioManager.initMusicSpeaker(this.getId()); + speaker.setGain(gain); + speaker.setPitch(pitch); + speaker.setPosition(position); + speaker.setVelocity(velocity); + + if (loop) { + speaker.playLoop(); + } else { + speaker.play(); + } + } + + public void setGain(float gain) { + this.gain = gain; + } + + public void setPitch(float pitch) { this.pitch = pitch; } + + public void setVelocity(Vec3 velocity) { + this.velocity = velocity; + } + + public Vec3 getPosition() { return position; } + + public float getGain() { + return gain; + } + + public Vec3 getVelocity() { + return velocity; + } + + public float getPitch() { + return pitch; + } +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java b/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java new file mode 100644 index 0000000..1f77e7a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java @@ -0,0 +1,78 @@ +package ru.windcorp.progressia.client.audio; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.audio.backend.Speaker; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; + +public class SoundEffect + extends Namespaced { + + private Vec3 position = new Vec3(); + private Vec3 velocity = new Vec3(); + private float pitch = 1.0f; + private float gain = 1.0f; + + + public SoundEffect(String id) + { + super(id); + } + + public SoundEffect(String id, + Vec3 position, + Vec3 velocity, + float pitch, + float gain) + { + this(id); + this.position = position; + this.velocity = velocity; + this.pitch = pitch; + this.gain = gain; + } + + public void play(boolean loop) + { + Speaker speaker = AudioManager.initSpeaker(this.getId()); + speaker.setGain(gain); + speaker.setPitch(pitch); + speaker.setPosition(position); + speaker.setVelocity(velocity); + + if (loop) { + speaker.playLoop(); + } else { + speaker.play(); + } + } + + public void setGain(float gain) { + this.gain = gain; + } + + public void setPitch(float pitch) { this.pitch = pitch; } + + public void setPosition(Vec3 position) { + this.position = position; + } + + public void setVelocity(Vec3 velocity) { + this.velocity = velocity; + } + + public Vec3 getPosition() { + return position; + } + + public float getGain() { + return gain; + } + + public Vec3 getVelocity() { + return velocity; + } + + public float getPitch() { + return pitch; + } +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java new file mode 100644 index 0000000..8cc2eff --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java @@ -0,0 +1,49 @@ +package ru.windcorp.progressia.client.audio.backend; + +import org.lwjgl.BufferUtils; +import ru.windcorp.progressia.client.audio.backend.SoundType; +import ru.windcorp.progressia.common.resource.*; + +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import static org.lwjgl.stb.STBVorbis.*; +import static org.lwjgl.openal.AL10.*; + +public class AudioReader { + + private AudioReader() {} + + // TODO fix converting from mono-stereo + private static SoundType readAsSpecified(String path, String id, int format) { + IntBuffer channelBuffer = BufferUtils.createIntBuffer(1); + IntBuffer rateBuffer = BufferUtils.createIntBuffer(1); + + Resource res = ResourceManager.getResource(path); + + ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer); + + return new SoundType(id, rawAudio, format, + rateBuffer.get(0)); + } + + public static SoundType readAsMono(String path, String id) { + return readAsSpecified(path, id, AL_FORMAT_MONO16); + } + + public static SoundType readAsStereo(String path,String id) { + return readAsSpecified(path, id, AL_FORMAT_STEREO16); + } + + private static ShortBuffer decodeVorbis( + Resource dataToDecode, + IntBuffer channelsBuffer, + IntBuffer rateBuffer + ) { + return stb_vorbis_decode_memory( + dataToDecode.readAsBytes(), + channelsBuffer, + rateBuffer + ); + } +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/Listener.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/Listener.java new file mode 100644 index 0000000..ee6cc30 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/Listener.java @@ -0,0 +1,80 @@ +package ru.windcorp.progressia.client.audio.backend; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.Client; +import ru.windcorp.progressia.client.ClientState; +import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; +import ru.windcorp.progressia.client.graphics.world.Camera; + +import static org.lwjgl.openal.AL10.*; + +public class Listener { + + private static final Listener INSTANCE = new Listener(); + + private Listener() {} + + public static Listener getInstance() { + return INSTANCE; + } + + // Params + private final Vec3 position = new Vec3(); + private final Vec3 velocity = new Vec3(); + private final Vec3 oriAt = new Vec3(); + private final Vec3 oriUp = new Vec3(); + + private boolean isInWorld = false; + + public void update() { + Client client = ClientState.getInstance(); + Camera camera = client == null ? null : client.getCamera(); + + boolean wasInWorld = isInWorld; + isInWorld = client != null && camera.getAnchor() != null; + + if (isInWorld) { + + if (wasInWorld) { + velocity.set(camera.getLastAnchorPosition()).sub(position).div( + (float) GraphicsInterface.getFrameLength() + ); + } else { + // If !wasInWorld, previous position is nonsence. Assume 0. + velocity.set(0); + } + + position.set(camera.getLastAnchorPosition()); + + oriAt.set(camera.getLastAnchorLookingAt()); + oriUp.set(camera.getLastAnchorUp()); + } else if (wasInWorld) { // Do not reset if we weren't in world + resetParams(); + } + + /* + * Only apply if there is a chance that params changed. + * This can only happen if we are in world now (isInWorld) or we just + * left world (wasInWorld, then we need to reset). + */ + if (isInWorld || wasInWorld) { + applyParams(); + } + } + + private void resetParams() { + position.set(0); + velocity.set(0); + oriAt.set(0); + oriUp.set(0); + } + + private void applyParams() { + alListener3f(AL_POSITION, position.x, position.y, position.z); + alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); + alListenerfv(AL_ORIENTATION, new float[] { + oriAt.x, oriAt.y, oriAt.z, oriUp.x, oriUp.y, oriUp.z + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java new file mode 100644 index 0000000..18e32e0 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java @@ -0,0 +1,41 @@ +package ru.windcorp.progressia.client.audio.backend; + +import ru.windcorp.progressia.common.util.namespaces.Namespaced; + +import java.nio.ShortBuffer; +import static org.lwjgl.openal.AL11.*; + +public class SoundType extends Namespaced { + + private ShortBuffer rawAudio; + private int sampleRate; + private int format; + private int audioBuffer; + + public SoundType(String id, ShortBuffer rawAudio, + int format, int sampleRate) { + super(id); + this.rawAudio = rawAudio; + this.sampleRate = sampleRate; + this.format = format; + createAudioBuffer(); + } + + private void createAudioBuffer() { + this.audioBuffer = alGenBuffers(); + alBufferData(audioBuffer, format, rawAudio, sampleRate); + } + + //TODO What is this (Eugene Smirnov) + private Speaker createSound(int source, int audio) { + if (!alIsBuffer(audio) || !alIsSource(source)) + throw new RuntimeException(); + + alBufferData(audio, format, rawAudio, sampleRate); + return new Speaker(audio); + } + + public void initSpeaker(Speaker speaker) { + speaker.setAudioData(audioBuffer); + } +} diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/Speaker.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/Speaker.java new file mode 100644 index 0000000..8685632 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/Speaker.java @@ -0,0 +1,151 @@ +package ru.windcorp.progressia.client.audio.backend; + +import glm.vec._3.Vec3; +import static org.lwjgl.openal.AL11.*; + +public class Speaker { + + public enum State { + NOT_PLAYING, + PLAYING, + PLAYING_LOOP + } + + // Buffers + private int audioData; + private int sourceData; + + // Characteristics + private Vec3 position = new Vec3(); + private Vec3 velocity = new Vec3(); + private float pitch = 1.0f; + private float gain = 1.0f; + private State state = State.NOT_PLAYING; + + public Speaker() { + sourceData = alGenSources(); + } + + public Speaker(int audioData) { + this(); + setAudioData(audioData); + } + + public Speaker( + int audioData, + Vec3 position, + Vec3 velocity, + float pitch, + float gain + ) { + setAudioData(audioData); + setPosition(position); + setVelocity(velocity); + setPitch(pitch); + setGain(gain); + } + + public Speaker( + Vec3 position, + Vec3 velocity, + float pitch, + float gain + ) { + setPosition(position); + setVelocity(velocity); + setPitch(pitch); + setGain(gain); + } + + public void play() { + alSourcePlay(sourceData); + state = State.PLAYING; + } + + public void playLoop() { + alSourcei(sourceData, AL_LOOPING, AL_TRUE); + alSourcePlay(sourceData); + state = State.PLAYING_LOOP; + } + + public void stop() { + alSourceStop(sourceData); + if (state == State.PLAYING_LOOP) { + alSourcei(sourceData, AL_LOOPING, AL_FALSE); + } + state = State.NOT_PLAYING; + } + + public void pause() { + alSourcePause(sourceData); + state = State.NOT_PLAYING; + } + + public boolean isPlaying() { + final int speakerState = alGetSourcei(sourceData, AL_SOURCE_STATE); + if (speakerState == AL_PLAYING) { + return true; + } + else { + state = State.NOT_PLAYING; + return false; + } + } + + // GETTERS & SETTERS + + public int getAudioData() { + return audioData; + } + + public int getSourceData() { + return sourceData; + } + + public void setAudioData(int audioData) { + this.audioData = audioData; + alSourcei(this.sourceData, AL_BUFFER, audioData); + } + + public void setPosition(Vec3 position) { + this.position = position; + alSource3f(sourceData, AL_POSITION, position.x, position.y, position.z); + } + + public Vec3 getPosition() { + return position; + } + + public void setVelocity(Vec3 velocity) { + alSource3f(sourceData, AL_VELOCITY, velocity.x, velocity.y, velocity.z); + this.velocity = velocity; + } + + public Vec3 getVelocity() { + return velocity; + } + + public void setPitch(float pitch) { + alSourcef(sourceData, AL_PITCH, pitch); + this.pitch = pitch; + } + + public float getPitch() { + return pitch; + } + + public void setGain(float gain) { + alSourcef(sourceData, AL_GAIN, gain); + this.gain = gain; + } + + public float getGain() { + return gain; + } + + public State getState() + { + return state; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderThread.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderThread.java index fbb6330..93c1c8d 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderThread.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderThread.java @@ -20,6 +20,7 @@ package ru.windcorp.progressia.client.graphics.backend; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; +import ru.windcorp.progressia.client.audio.AudioManager; import ru.windcorp.progressia.client.graphics.GUI; class RenderThread extends Thread { @@ -60,6 +61,7 @@ class RenderThread extends Thread { private void doRender() { GUI.render(); + AudioManager.update(); } private void waitForFrame() { diff --git a/src/main/java/ru/windcorp/progressia/test/ALTest.java b/src/main/java/ru/windcorp/progressia/test/ALTest.java new file mode 100644 index 0000000..45777c5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/ALTest.java @@ -0,0 +1,30 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.client.audio.AudioFormat; +import ru.windcorp.progressia.client.audio.AudioManager; +import ru.windcorp.progressia.client.audio.Music; + +public class ALTest { + static private void initializeAL() { + AudioManager.initAL(); + } + + static void loadALData() { + AudioManager.loadSound("assets/sounds/sample_stereo.ogg", + "Progressia:SampleStereo", + AudioFormat.STEREO); + AudioManager.loadSound("assets/sounds/block_destroy_clap.ogg", + "Progressia:BlockDestroy", + AudioFormat.MONO); + Music music = new Music("Progressia:SampleStereo"); + music.setGain(0.5f); + //music.play(false); + } + + public static void execute() { + initializeAL(); + Thread shutdownHook = new Thread(AudioManager::closeAL, "AL Shutdown Hook"); + Runtime.getRuntime().addShutdownHook(shutdownHook); + loadALData(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index a7a5027..6100cbd 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -9,6 +9,7 @@ import org.lwjgl.glfw.GLFW; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.ClientState; +import ru.windcorp.progressia.client.audio.SoundEffect; import ru.windcorp.progressia.client.comms.controls.*; import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyMatcher; @@ -213,6 +214,10 @@ public class TestContent { private static void onBlockBreakTrigger(ControlData control) { ((ControlBreakBlockData) control).setBlockInWorld(getSelection().getBlock()); + SoundEffect sfx = new SoundEffect("Progressia:BlockDestroy"); + sfx.setPosition(getSelection().getPoint()); + sfx.setPitch((float) (Math.random() + 1 * 0.5)); + sfx.play(false); } private static void onBlockBreakReceived(Server server, PacketControl packet, ru.windcorp.progressia.server.comms.Client client) { diff --git a/src/main/resources/assets/sounds/block_destroy_clap.ogg b/src/main/resources/assets/sounds/block_destroy_clap.ogg new file mode 100644 index 0000000..22fe4e6 Binary files /dev/null and b/src/main/resources/assets/sounds/block_destroy_clap.ogg differ diff --git a/src/main/resources/assets/sounds/sample_mono.ogg b/src/main/resources/assets/sounds/sample_mono.ogg new file mode 100644 index 0000000..640167f Binary files /dev/null and b/src/main/resources/assets/sounds/sample_mono.ogg differ diff --git a/src/main/resources/assets/sounds/sample_stereo.ogg b/src/main/resources/assets/sounds/sample_stereo.ogg new file mode 100644 index 0000000..f51caa2 Binary files /dev/null and b/src/main/resources/assets/sounds/sample_stereo.ogg differ