Reworked audio

This commit is contained in:
Евгений Смирнов 2020-11-14 17:51:12 +03:00
parent ececa9506d
commit f3ec9911ae
16 changed files with 576 additions and 379 deletions

View File

@ -18,7 +18,7 @@
package ru.windcorp.progressia.client;
import ru.windcorp.progressia.ProgressiaLauncher;
import ru.windcorp.progressia.client.audio.backend.ALTest;
import ru.windcorp.progressia.client.audio.ALTest;
public class ProgressiaClientMain {

View File

@ -0,0 +1,24 @@
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();
}
}

View File

@ -0,0 +1,5 @@
package ru.windcorp.progressia.client.audio;
public enum AudioFormat {
MONO, STEREO
}

View File

@ -0,0 +1,142 @@
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<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
private static Speaker musicSpeaker;
private static ArrayList<SoundType> 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 namespace, String name,
AudioFormat format) {
if (format == AudioFormat.MONO) {
soundsBuffer.add(AudioReader.readAsMono(path, namespace, name));
} else
{
soundsBuffer.add(AudioReader.readAsStereo(path, namespace, name));
}
}
public static void closeAL() {
//clearSounds();
//TODO replace alDeleteSources(SOURCES);
for (Speaker s : soundSpeakers) {
alDeleteBuffers(s.getAudioData());
}
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();
}
}

View File

@ -1,48 +0,0 @@
package ru.windcorp.progressia.client.audio;
import org.lwjgl.BufferUtils;
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 audioName, int format) {
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
Resource res = ResourceManager.getResource(audioName);
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
return new SoundType(rawAudio, format, rateBuffer.get(0));
}
public static SoundType readAsMono(String audioName) {
return readAsSpecified(audioName, AL_FORMAT_MONO16);
}
public static SoundType readAsStereo(String audioName) {
return readAsSpecified(audioName, AL_FORMAT_STEREO16);
}
private static ShortBuffer decodeVorbis(
Resource dataToDecode,
IntBuffer channelsBuffer,
IntBuffer rateBuffer
) {
return stb_vorbis_decode_memory(
dataToDecode.readAsBytes(),
channelsBuffer,
rateBuffer
);
}
}

View File

@ -0,0 +1,76 @@
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;
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)
{
super(namespace, name);
}
public Music(String namespace,
String name,
Vec3 position,
Vec3 velocity,
float pitch,
float gain)
{
this(namespace, name);
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();
}
}
//TODO implement
public void stop() {}
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;
}
}

View File

@ -1,142 +0,0 @@
package ru.windcorp.progressia.client.audio;
import org.lwjgl.BufferUtils;
import java.nio.FloatBuffer;
import static org.lwjgl.openal.AL11.*;
public class Sound {
// Buffers
private int audio;
private int source;
// Characteristics
private FloatBuffer position = (FloatBuffer) BufferUtils.createFloatBuffer(
3
).put(new float[] {
0.0f, 0.0f, 0.0f
}).rewind();
private FloatBuffer velocity = (FloatBuffer) BufferUtils.createFloatBuffer(
3
).put(new float[] {
0.0f, 0.0f, 0.0f
}).rewind();
private float pitch = 1.0f;
private float gain = 1.0f;
public Sound() {}
public Sound(int audio, int source) {
setAudio(audio);
setSource(source);
}
public Sound(
int audio,
FloatBuffer position,
FloatBuffer velocity,
float pitch,
float gain
) {
setAudio(audio);
setPosition(position);
setVelocity(velocity);
setPitch(pitch);
setGain(gain);
}
public Sound(
FloatBuffer position,
FloatBuffer velocity,
float pitch,
float gain
) {
setPosition(position);
setVelocity(velocity);
setPitch(pitch);
setGain(gain);
}
public void playOnce() {
alSourcePlay(source);
}
public void playLoop() {
alSourcei(source, AL_LOOPING, AL_TRUE);
playOnce();
alSourcei(source, AL_LOOPING, AL_FALSE);
}
public void stop() {
alSourceStop(source);
}
public void pause() {
alSourcePause(source);
}
public boolean isPlaying() {
final int state = alGetSourcei(source, AL_SOURCE_STATE);
return state == AL_PLAYING;
}
public int getAudio() {
return audio;
}
public void setAudio(int audio) {
this.audio = audio;
}
public void setSource(int source) {
this.source = source;
alSourcei(this.source, AL_BUFFER, audio);
}
public int getSource() {
return source;
}
// OTHER
public void setPosition(FloatBuffer position) {
this.position = position;
alSourcefv(source, AL_POSITION, position);
}
public FloatBuffer getPosition() {
return position;
}
public void setVelocity(FloatBuffer velocity) {
alSourcefv(source, AL_VELOCITY, velocity);
this.velocity = velocity;
}
public FloatBuffer getVelocity() {
return velocity;
}
public void setPitch(float pitch) {
alSourcef(source, AL_PITCH, pitch);
this.pitch = pitch;
}
public float getPitch() {
return pitch;
}
public void setGain(float gain) {
alSourcef(source, AL_GAIN, gain);
this.gain = gain;
}
public float getGain() {
return gain;
}
}

View File

@ -0,0 +1,83 @@
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;
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 namespace,
String name)
{
super(namespace, name);
}
public SoundEffect(String namespace,
String name,
Vec3 position,
Vec3 velocity,
float pitch,
float gain)
{
this(namespace, name);
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();
}
}
//TODO implement
public void stop() {}
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;
}
}

View File

@ -1,107 +0,0 @@
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 static org.lwjgl.openal.AL11.*;
import static org.lwjgl.openal.ALC10.*;
import java.util.concurrent.ArrayBlockingQueue;
public class SoundManager {
private static final int SOURCES_NUM = 64;
private static int lastSourceIndex = -1;
private static final int[] SOURCES = new int[SOURCES_NUM];
private static final ArrayBlockingQueue<Sound> SOUNDS =
new ArrayBlockingQueue<>(SOURCES_NUM);
private static long device;
private static ALCCapabilities deviceCapabilities;
private static ALCapabilities alCapabilities;
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();
alGenSources(SOURCES);
}
public static void update() {
// Position of the listener
Listener.getInstance().update();
}
private static void addSound(Sound sound) {
if (!SOUNDS.offer(sound)) {
Sound polled = SOUNDS.poll();
assert polled != null;
polled.stop();
if (!SOUNDS.offer(sound)) {
throw new RuntimeException();
}
}
}
private static int getNextSource() {
if (++lastSourceIndex > SOURCES_NUM)
lastSourceIndex = 0;
return SOURCES[lastSourceIndex];
}
public static Sound createSound(SoundType soundType) {
Sound sound = soundType.genSoundSource(getNextSource());
addSound(sound);
return sound;
}
public static void clearSounds() {
Sound polled = SOUNDS.poll();
while (polled != null) {
polled.stop();
polled = SOUNDS.poll();
}
}
public static void checkALError() {
int errorCode = alGetError();
if (alGetError() != AL_NO_ERROR) {
throw new RuntimeException(String.valueOf(errorCode));
}
}
public static void closeAL() {
clearSounds();
alDeleteSources(SOURCES);
for (Sound s : SOUNDS) {
alDeleteBuffers(s.getAudio());
}
alcCloseDevice(device);
}
public static ALCapabilities getALCapabilities() {
return alCapabilities;
}
public static ALCCapabilities getDeviceCapabilities() {
return deviceCapabilities;
}
}

View File

@ -1,51 +0,0 @@
package ru.windcorp.progressia.client.audio;
import java.nio.ShortBuffer;
import static org.lwjgl.openal.AL11.*;
public class SoundType {
private ShortBuffer rawAudio;
private int sampleRate;
private int format;
public SoundType(ShortBuffer rawAudio, int format, int sampleRate) {
this.rawAudio = rawAudio;
this.sampleRate = sampleRate;
this.format = format;
}
public static int genEmptyAudio() {
return alGenBuffers();
}
public static int genEmptySource() {
return alGenSources();
}
public int genAudio() {
int audio = alGenBuffers();
alBufferData(audio, format, rawAudio, sampleRate);
return audio;
}
public Sound genSoundSource() {
return new Sound(genAudio(), alGenSources());
}
public Sound genSoundSource(int source) {
if (!alIsSource(source))
throw new RuntimeException();
return new Sound(genAudio(), source);
}
public Sound genSoundSource(int source, int audio) {
if (!alIsBuffer(audio) || !alIsSource(source))
throw new RuntimeException();
alBufferData(audio, format, rawAudio, sampleRate);
return new Sound(audio, alGenSources());
}
}

View File

@ -1,27 +0,0 @@
package ru.windcorp.progressia.client.audio.backend;
import ru.windcorp.progressia.client.audio.AudioReader;
import ru.windcorp.progressia.client.audio.Sound;
import ru.windcorp.progressia.client.audio.SoundManager;
public class ALTest {
static private void initializeAL() {
SoundManager.initAL();
}
static void loadALData() {
Sound music = SoundManager.createSound(AudioReader.readAsMono("assets/sounds/sample_mono.ogg"));
music.playOnce();
/*music = SoundManager.createSound(AudioReader.readAsStereo("assets/sounds/sample_mono.ogg"));
music.playOnce();*/
}
static void killALData() {
//TODO implement the method or its analogue
}
public static void execute() {
initializeAL();
loadALData();
}
}

View File

@ -0,0 +1,54 @@
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
// TODO change audio naming from full path to just name
private static SoundType readAsSpecified(String path, String audioNamespace,
String audioName, 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,
rateBuffer.get(0));
}
public static SoundType readAsMono(String path, String audioNamespace,
String audioName) {
return readAsSpecified(path, audioNamespace,
audioName, AL_FORMAT_MONO16);
}
public static SoundType readAsStereo(String path,String audioNamespace,
String audioName) {
return readAsSpecified(path, audioNamespace, audioName, AL_FORMAT_STEREO16);
}
private static ShortBuffer decodeVorbis(
Resource dataToDecode,
IntBuffer channelsBuffer,
IntBuffer rateBuffer
) {
return stb_vorbis_decode_memory(
dataToDecode.readAsBytes(),
channelsBuffer,
rateBuffer
);
}
}

View File

@ -1,4 +1,4 @@
package ru.windcorp.progressia.client.audio;
package ru.windcorp.progressia.client.audio.backend;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.Client;

View File

@ -0,0 +1,41 @@
package ru.windcorp.progressia.client.audio.backend;
import ru.windcorp.progressia.common.util.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,
int format, int sampleRate) {
super(namespace, name);
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);
}
}

View File

@ -0,0 +1,147 @@
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 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;
}
}

View File

@ -20,7 +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.SoundManager;
import ru.windcorp.progressia.client.audio.AudioManager;
import ru.windcorp.progressia.client.graphics.GUI;
class RenderThread extends Thread {
@ -61,7 +61,7 @@ class RenderThread extends Thread {
private void doRender() {
GUI.render();
SoundManager.update();
AudioManager.update();
}
private void waitForFrame() {