From 2fbb274c3109c89645a73d7ff5a30b1b55935920 Mon Sep 17 00:00:00 2001 From: Eugenuss Date: Sun, 22 Nov 2020 14:44:19 +0300 Subject: [PATCH] Fixed AL close action Added a test for the Audio Engine Refactored Audio Engine --- .../client/ProgressiaClientMain.java | 2 +- .../progressia/client/audio/ALTest.java | 24 ---------- .../progressia/client/audio/AudioManager.java | 33 ++++++------- .../progressia/client/audio/Music.java | 26 ++++------ .../progressia/client/audio/SoundEffect.java | 15 ++---- .../client/audio/backend/AudioReader.java | 45 ++++++++---------- .../client/audio/backend/Listener.java | 36 +++++++------- .../client/audio/backend/SoundType.java | 14 +++--- .../client/audio/backend/Speaker.java | 4 ++ .../ru/windcorp/progressia/test/ALTest.java | 30 ++++++++++++ .../windcorp/progressia/test/TestContent.java | 5 ++ .../assets/sounds/block_destroy_clap.ogg | Bin 0 -> 7121 bytes 12 files changed, 116 insertions(+), 118 deletions(-) delete mode 100644 src/main/java/ru/windcorp/progressia/client/audio/ALTest.java create mode 100644 src/main/java/ru/windcorp/progressia/test/ALTest.java create mode 100644 src/main/resources/assets/sounds/block_destroy_clap.ogg diff --git a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java index bf38de9..25e19e9 100644 --- a/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java +++ b/src/main/java/ru/windcorp/progressia/client/ProgressiaClientMain.java @@ -18,7 +18,7 @@ package ru.windcorp.progressia.client; import ru.windcorp.progressia.ProgressiaLauncher; -import ru.windcorp.progressia.client.audio.ALTest; +import ru.windcorp.progressia.test.ALTest; public class ProgressiaClientMain { diff --git a/src/main/java/ru/windcorp/progressia/client/audio/ALTest.java b/src/main/java/ru/windcorp/progressia/client/audio/ALTest.java deleted file mode 100644 index 5e1c5ce..0000000 --- a/src/main/java/ru/windcorp/progressia/client/audio/ALTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.windcorp.progressia.client.audio; - -public class ALTest { - static private void initializeAL() { - AudioManager.initAL(); - } - - static void loadALData() { - AudioManager.loadSound("assets/sounds/sample_stereo.ogg", - "Progressia", "SampleStereo", - AudioFormat.STEREO); - Music music = new Music("Progressia", "SampleStereo"); - music.play(false); - } - - static void killALData() { - //TODO implement the method or its analogue - } - - public static void execute() { - initializeAL(); - loadALData(); - } -} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java b/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java index e002f4a..3935e68 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/AudioManager.java @@ -26,7 +26,7 @@ public class AudioManager { 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, @@ -45,13 +45,13 @@ public class AudioManager { checkALError(); createBuffers(); } - + public static void update() { // Position of the listener Listener.getInstance().update(); } - + private static Speaker getLastSpeaker() { Speaker speaker; do { @@ -72,9 +72,9 @@ public class AudioManager { } } throw new Exception("ERROR: The selected sound is not loaded or" + - " not exists"); + " not exists"); } - + public static Speaker initSpeaker(String soundID) { Speaker speaker = getLastSpeaker(); try { @@ -103,29 +103,30 @@ public class AudioManager { } } - public static void loadSound(String path, String namespace, String name, - AudioFormat format) { + public static void loadSound(String path, String id, AudioFormat format) { if (format == AudioFormat.MONO) { - soundsBuffer.add(AudioReader.readAsMono(path, namespace, name)); + soundsBuffer.add(AudioReader.readAsMono(path, id)); } else { - soundsBuffer.add(AudioReader.readAsStereo(path, namespace, name)); + soundsBuffer.add(AudioReader.readAsStereo(path, id)); } } - + public static void closeAL() { - //clearSounds(); - //TODO replace alDeleteSources(SOURCES); 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; } @@ -138,5 +139,5 @@ public class AudioManager { 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 index 27f5e25..9c1d12d 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/Music.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/Music.java @@ -2,30 +2,27 @@ 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.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; -public class Music - extends 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 namespace, - String name) + public Music(String id) { - super(namespace, name); + super(id); } - public Music(String namespace, - String name, - Vec3 position, - Vec3 velocity, - float pitch, - float gain) + public Music(String id, + Vec3 position, + Vec3 velocity, + float pitch, + float gain) { - this(namespace, name); + this(id); this.position = position; this.velocity = velocity; this.pitch = pitch; @@ -47,9 +44,6 @@ public class Music } } - //TODO implement - public void stop() {} - public void setGain(float gain) { this.gain = gain; } diff --git a/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java b/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java index a97bc52..1f77e7a 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/SoundEffect.java @@ -2,7 +2,7 @@ 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.Namespaced; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; public class SoundEffect extends Namespaced { @@ -13,20 +13,18 @@ public class SoundEffect private float gain = 1.0f; - public SoundEffect(String namespace, - String name) + public SoundEffect(String id) { - super(namespace, name); + super(id); } - public SoundEffect(String namespace, - String name, + public SoundEffect(String id, Vec3 position, Vec3 velocity, float pitch, float gain) { - this(namespace, name); + this(id); this.position = position; this.velocity = velocity; this.pitch = pitch; @@ -48,9 +46,6 @@ public class SoundEffect } } - //TODO implement - public void stop() {} - public void setGain(float gain) { this.gain = gain; } 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 index f23e4aa..8cc2eff 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java @@ -11,44 +11,39 @@ import static org.lwjgl.stb.STBVorbis.*; import static org.lwjgl.openal.AL10.*; public class AudioReader { - + private AudioReader() {} - + // TODO fix converting from mono-stereo - // TODO change audio naming from full path to just name - private static SoundType readAsSpecified(String path, String audioNamespace, - String audioName, int format) { + 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(audioNamespace ,audioName, rawAudio, format, + + return new SoundType(id, rawAudio, format, rateBuffer.get(0)); } - - public static SoundType readAsMono(String path, String audioNamespace, - String audioName) { - return readAsSpecified(path, audioNamespace, - audioName, AL_FORMAT_MONO16); + + public static SoundType readAsMono(String path, String id) { + return readAsSpecified(path, id, AL_FORMAT_MONO16); } - - public static SoundType readAsStereo(String path,String audioNamespace, - String audioName) { - return readAsSpecified(path, audioNamespace, audioName, AL_FORMAT_STEREO16); + + 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 + Resource dataToDecode, + IntBuffer channelsBuffer, + IntBuffer rateBuffer ) { return stb_vorbis_decode_memory( - dataToDecode.readAsBytes(), - channelsBuffer, - rateBuffer + 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 index 8e97f9e..ee6cc30 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/Listener.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/Listener.java @@ -8,52 +8,50 @@ import ru.windcorp.progressia.client.graphics.world.Camera; import static org.lwjgl.openal.AL10.*; -//TODO add getters and setters - 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() + (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 @@ -63,20 +61,20 @@ public class Listener { 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 + 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 index 670ab18..18e32e0 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java @@ -1,26 +1,26 @@ package ru.windcorp.progressia.client.audio.backend; -import ru.windcorp.progressia.common.util.Namespaced; +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 namespace, String name, ShortBuffer rawAudio, + + public SoundType(String id, ShortBuffer rawAudio, int format, int sampleRate) { - super(namespace, name); + super(id); this.rawAudio = rawAudio; this.sampleRate = sampleRate; this.format = format; createAudioBuffer(); } - + private void createAudioBuffer() { this.audioBuffer = alGenBuffers(); alBufferData(audioBuffer, format, rawAudio, sampleRate); @@ -30,7 +30,7 @@ public class SoundType extends Namespaced { private Speaker createSound(int source, int audio) { if (!alIsBuffer(audio) || !alIsSource(source)) throw new RuntimeException(); - + alBufferData(audio, format, rawAudio, sampleRate); return new Speaker(audio); } 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 index 196d474..8685632 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/Speaker.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/Speaker.java @@ -98,6 +98,10 @@ public class Speaker { return audioData; } + public int getSourceData() { + return sourceData; + } + public void setAudioData(int audioData) { this.audioData = audioData; alSourcei(this.sourceData, AL_BUFFER, audioData); 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 0000000000000000000000000000000000000000..22fe4e6ece7f91abae29075cae990529d13fd248 GIT binary patch literal 7121 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 awO-YHch9~T6|E*-TXeiS?BBXT(EkBAx$edQ literal 0 HcmV?d00001