From 5d109865f6902d0eff506048d5a6edc89596eea4 Mon Sep 17 00:00:00 2001 From: Tefek <493msi@gmail.com> Date: Fri, 4 Sep 2020 14:59:14 +0200 Subject: [PATCH] Updated the versioning system, README and added the audio submodule --- README.md | 55 +++- build.gradle | 31 +- plutoaudio/build.gradle | 10 + .../tefek/pluto/engine/audio/AudioLoader.java | 275 ++++++++++++++++++ .../pluto/engine/audio/IAudioStream.java | 14 + .../engine/audio/ISeekableAudioTrack.java | 25 ++ .../pluto/engine/audio/al/AudioEngine.java | 128 ++++++++ .../pluto/engine/audio/al/AudioSource.java | 45 +++ .../pluto/engine/audio/al/AudioTrack.java | 124 ++++++++ .../pluto/engine/audio/util/AudioUtil.java | 25 ++ .../pluto/command/parser/CommandParser.java | 4 +- .../command/registry/CommandRegistry.java | 4 +- plutocore/build.gradle | 1 + .../java/cz/tefek/pluto/PlutoApplication.java | 52 +++- .../tefek/pluto/io/logger/SmartSeverity.java | 6 +- plutostatic/build.gradle | 2 - settings.gradle | 1 + 17 files changed, 775 insertions(+), 27 deletions(-) create mode 100644 plutoaudio/build.gradle create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/AudioLoader.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/IAudioStream.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/ISeekableAudioTrack.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioEngine.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioSource.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioTrack.java create mode 100644 plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/util/AudioUtil.java diff --git a/README.md b/README.md index 080854a..2e3bb9b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,56 @@ # plutoengine -My hobby game engine. This repository unifies all my previous Pluto repositories. \ No newline at end of file +My hobby game engine. This repository unifies all my previous Pluto repositories. + +### Versioning + +All submodules share a version number for simplicity reasons. + +Since version `20.2.0.0-alpha.0`, PlutoEngine uses +a combined version of [semantic versioning](https://semver.org/) +and [calendar versioning](https://calver.org/), the first number +denotes the year. + +Therefore, the version format is always `YY.MAJOR.MINOR.PATCH-prerelease`. + +*Only `major` and `year` version changes will bring breaking API changes, +**except for pre-release versions**, which may introduce breaking changes +at any time. Pre-release versions will never increment the `minor` or `patch` +version numbers.* + + +## Usability status of submodules + +### Safe submodules + * **PlutoCore** - Stable + * **PlutoFrameBuffer** - Stable + * **PlutoGUI** - Stable, awaiting a rewrite + * **PlutoMesher** - Stable + * **PlutoShader** - Stable + * **PlutoSpritesheet** - Stable, some features are unfinished + * **PlutoStatic** - Stable, collision API nowhere near completition + +### Unstable submodules + * **PlutoAudio** - Somewhat usable, unfinished + * **PlutoLib** - Somewhat usable, requires further polish + +### Broken submodules, do NOT use + * **PlutoCommandParser** - Unfinished, broken, unusable + * **PlutoDB** - Broken, unusable + +## Current priorities + +### Very high priority + * Finishing PlutoAudio + * Further engine restructure + +### High priority + * Finishing PlutoCommandParser + * The stage system and automated asset loading + +### Normal priority + * The collision system for PlutoStatic + +### Low priority + * Polishing PlutoLib + * A networking API \ No newline at end of file diff --git a/build.gradle b/build.gradle index 82ec1b3..0080205 100644 --- a/build.gradle +++ b/build.gradle @@ -4,16 +4,35 @@ allprojects { apply plugin: 'java' group = "cz.tefek" - version = "20.1.0" + version = "20.2.0.0-alpha.0" + + tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + } + + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } } subprojects { + apply plugin: 'maven-publish' + project.ext.lwjglVersion = "3.2.3" project.ext.jomlVersion = "1.9.25" project.ext.steamworks4jVersion = "1.8.0" project.ext.steamworks4jServerVersion = "1.8.0" + publishing { + publications { + maven(MavenPublication) { + from components.java + } + } + } + switch (OperatingSystem.current()) { case OperatingSystem.LINUX: project.ext.lwjglNatives = "natives-linux" @@ -26,17 +45,7 @@ subprojects { break } - tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - } - - java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - repositories { mavenCentral() } } - diff --git a/plutoaudio/build.gradle b/plutoaudio/build.gradle new file mode 100644 index 0000000..ba9d446 --- /dev/null +++ b/plutoaudio/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java-library' + +description = "PlutoEngine's sound subsystem." + +dependencies { + api project(":plutostatic") + + api "org.lwjgl:lwjgl-openal" + runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives" +} \ No newline at end of file diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/AudioLoader.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/AudioLoader.java new file mode 100644 index 0000000..5edbda1 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/AudioLoader.java @@ -0,0 +1,275 @@ +package cz.tefek.pluto.engine.audio; + +import org.lwjgl.stb.STBVorbis; +import org.lwjgl.stb.STBVorbisInfo; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.nio.file.Files; + +import cz.tefek.pluto.engine.buffer.BufferHelper; +import cz.tefek.pluto.io.asl.resource.ResourceAddress; +import cz.tefek.pluto.io.logger.Logger; +import cz.tefek.pluto.io.logger.SmartSeverity; + +public class AudioLoader +{ + /** + * Loads an audio track denoted by this {@link ResourceAddress} into memory + * for from-memory Vorbis decoding and streaming. Good for frequently used + * medium sized audio files, however it is discouraged to use such a track + * in multiple audio sources at once due to the cost of seeking. + */ + public static ISeekableAudioTrack loadMemoryDecoded(ResourceAddress address) + { + + Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString()); + + try + { + return new MemoryDecodedVorbisTrack(address); + } + catch (IOException e) + { + Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString()); + e.printStackTrace(); + return null; + } + } + + /** + * Loads an audio track denoted by this {@link ResourceAddress} into memory + * for from-memory PCM streaming. Good for frequently used small audio + * files. + */ + public static ISeekableAudioTrack loadMemoryPCM(ResourceAddress address) + { + Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString()); + + try + { + return new MemoryPCMTrack(address); + } + catch (IOException e) + { + Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString()); + e.printStackTrace(); + return null; + } + } + + private static ByteBuffer loadIntoMemory(ResourceAddress addr) throws IOException + { + var path = addr.toNIOPath(); + var size = Files.size(path); + + if (size > Integer.MAX_VALUE) + { + throw new IOException("File '" + addr.toString() + "' is too big to be loaded!"); + } + + var readData = MemoryUtil.memAlloc((int) size); + + return BufferHelper.readToByteBuffer(path, readData); + } + + private abstract static class StreamableTrack implements IAudioStream + { + protected int channels; + protected int sampleRate; + + @Override + public int getChannels() + { + return this.channels; + } + + @Override + public int getSampleRate() + { + return this.sampleRate; + } + } + + private abstract static class SeekableTrack extends StreamableTrack implements ISeekableAudioTrack + { + protected int samplesLength; + + @Override + public int getLengthInSamples() + { + return this.samplesLength; + } + } + + public static class MemoryPCMTrack extends SeekableTrack + { + private ShortBuffer pcmAudio = null; + + private int sampleOffset = 0; + + private MemoryPCMTrack(ResourceAddress address) throws IOException + { + long handle = MemoryUtil.NULL; + ByteBuffer audioBytes = null; + + try (MemoryStack stack = MemoryStack.stackPush()) + { + audioBytes = loadIntoMemory(address); + + IntBuffer error = stack.mallocInt(1); + handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null); + + if (handle == MemoryUtil.NULL) + { + this.close(); + throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0))); + } + + STBVorbisInfo info = STBVorbisInfo.mallocStack(stack); + STBVorbis.stb_vorbis_get_info(handle, info); + + this.channels = info.channels(); + this.sampleRate = info.sample_rate(); + + // Downmix to mono, SOUNDS HORRIBLE + // + // this.channels = 1; + // this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels; + // this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength); + // var ptr = stack.pointers(this.pcmAudio); + // STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength); + + this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels; + this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength); + STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio); + } + catch (IOException e) + { + this.close(); + throw e; + } + finally + { + MemoryUtil.memFree(audioBytes); + + if (handle != MemoryUtil.NULL) + { + STBVorbis.stb_vorbis_close(handle); + } + } + } + + @Override + public void seek(int sampleIndex) + { + this.sampleOffset = sampleIndex * this.getChannels(); + } + + @Override + public synchronized int getSamples(ShortBuffer pcm) + { + this.pcmAudio.limit(Math.min(this.sampleOffset + pcm.remaining(), this.pcmAudio.capacity())); + int read = this.pcmAudio.remaining(); + pcm.put(this.pcmAudio); + this.sampleOffset += read; + pcm.clear(); + + return read / this.getChannels(); + } + + @Override + public int getSampleOffset() + { + return this.sampleOffset / this.getChannels(); + } + + @Override + public void close() + { + MemoryUtil.memFree(this.pcmAudio); + } + } + + public static class MemoryDecodedVorbisTrack extends SeekableTrack + { + protected long handle; + + private final ByteBuffer encodedAudio; + + private MemoryDecodedVorbisTrack(ResourceAddress address) throws IOException + { + try + { + this.encodedAudio = loadIntoMemory(address); + } + catch (IOException e) + { + this.close(); + throw e; + } + + try (MemoryStack stack = MemoryStack.stackPush()) + { + IntBuffer error = stack.mallocInt(1); + this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null); + + if (this.handle == MemoryUtil.NULL) + { + this.close(); + throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0))); + } + + STBVorbisInfo info = STBVorbisInfo.mallocStack(stack); + STBVorbis.stb_vorbis_get_info(this.handle, info); + + this.channels = info.channels(); + this.sampleRate = info.sample_rate(); + + } + + this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle); + + Logger.logf(SmartSeverity.AUDIO, "\tSample rate:\t%d\n\t\tChannels:\t%d\n\t\tSamples:\t%d\n", this.sampleRate, this.channels, this.samplesLength); + } + + @Override + public synchronized int getSamples(ShortBuffer pcm) + { + if (this.handle == MemoryUtil.NULL) + { + return -1; + } + + return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm); + } + + @Override + public void close() + { + MemoryUtil.memFree(this.encodedAudio); + + if (this.handle != MemoryUtil.NULL) + { + STBVorbis.stb_vorbis_close(this.handle); + this.handle = MemoryUtil.NULL; + } + } + + @Override + public synchronized void seek(int sampleIndex) + { + STBVorbis.stb_vorbis_seek(this.handle, sampleIndex); + } + + @Override + public int getSampleOffset() + { + return STBVorbis.stb_vorbis_get_sample_offset(this.handle); + } + } +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/IAudioStream.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/IAudioStream.java new file mode 100644 index 0000000..ae5903b --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/IAudioStream.java @@ -0,0 +1,14 @@ +package cz.tefek.pluto.engine.audio; + +import java.nio.ShortBuffer; + +public interface IAudioStream +{ + int getSamples(ShortBuffer pcm); + + int getSampleRate(); + + int getChannels(); + + void close(); +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/ISeekableAudioTrack.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/ISeekableAudioTrack.java new file mode 100644 index 0000000..26278b4 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/ISeekableAudioTrack.java @@ -0,0 +1,25 @@ +package cz.tefek.pluto.engine.audio; + +public interface ISeekableAudioTrack extends IAudioStream +{ + int getSampleOffset(); + + int getLengthInSamples(); + + default void skip(int sampleCount) + { + this.seek(Math.min(Math.max(0, this.getSampleOffset() + sampleCount), this.getLengthInSamples())); + } + + default void skipTo(float offset0to1) + { + this.seek(Math.round(this.getLengthInSamples() * offset0to1)); + } + + default void rewind() + { + this.seek(0); + } + + void seek(int sampleIndex); +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioEngine.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioEngine.java new file mode 100644 index 0000000..6fd0220 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioEngine.java @@ -0,0 +1,128 @@ +package cz.tefek.pluto.engine.audio.al; + +import org.joml.Vector3f; +import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL10; +import org.lwjgl.openal.ALC; +import org.lwjgl.openal.ALC10; +import org.lwjgl.openal.ALCCapabilities; +import org.lwjgl.openal.ALCapabilities; +import org.lwjgl.openal.EXTThreadLocalContext; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import javax.annotation.concurrent.ThreadSafe; + +import cz.tefek.pluto.Pluto; +import cz.tefek.pluto.io.logger.Logger; +import cz.tefek.pluto.io.logger.SmartSeverity; + +/** + * @author 493msi + * + */ +@ThreadSafe +public class AudioEngine +{ + private static ThreadLocal device = new ThreadLocal<>() { + @Override + protected Long initialValue() + { + return MemoryUtil.NULL; + } + }; + + private static ThreadLocal context = new ThreadLocal<>() { + @Override + protected Long initialValue() + { + return MemoryUtil.NULL; + } + }; + + private static ThreadLocal capabilities = new ThreadLocal<>(); + + public static void initialize() + { + var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null); + if (devicePtr == MemoryUtil.NULL) + { + Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device."); + + // No audio device found, but the game should not crash just because we have no audio + return; + } + + device.set(devicePtr); + + var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null); + if (contextPtr == MemoryUtil.NULL) + { + ALC10.alcCloseDevice(devicePtr); + + Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context."); + + // The game should not crash just because we have no audio + return; + } + + context.set(contextPtr); + + EXTThreadLocalContext.alcSetThreadContext(contextPtr); + + ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr); + var alCapabilities = AL.createCapabilities(deviceCaps); + + if (Pluto.DEBUG_MODE) + { + Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10); + Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11); + } + + capabilities.set(alCapabilities); + } + + public static void setSpeed(Vector3f speed) + { + AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z); + } + + public static void setPosition(Vector3f position) + { + AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z); + } + + public static void setVolume(float volume) + { + AL10.alListenerf(AL10.AL_GAIN, volume); + } + + public static 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; + AL10.alListenerfv(AL10.AL_ORIENTATION, data); + } + + public static boolean isReady() + { + return capabilities.get() != null; + } + + public static void exit() + { + EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL); + ALC10.alcDestroyContext(context.get()); + ALC10.alcCloseDevice(device.get()); + + context.remove(); + device.remove(); + } +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioSource.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioSource.java new file mode 100644 index 0000000..ad36184 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioSource.java @@ -0,0 +1,45 @@ +package cz.tefek.pluto.engine.audio.al; + +import org.joml.Vector3fc; +import org.lwjgl.openal.AL10; + +public abstract class AudioSource +{ + protected final int source; + + protected AudioSource() + { + this.source = AL10.alGenSources(); + } + + public void stop() + { + AL10.alSourceStop(this.source); + } + + public void close() + { + this.stop(); + AL10.alDeleteSources(this.source); + } + + public void position(Vector3fc pos) + { + AL10.alSource3f(this.source, AL10.AL_POSITION, pos.x(), pos.y(), pos.z()); + } + + public void velocity(Vector3fc velocity) + { + AL10.alSource3f(this.source, AL10.AL_VELOCITY, velocity.x(), velocity.y(), velocity.z()); + } + + public void pitch(float f) + { + AL10.alSourcef(this.source, AL10.AL_PITCH, f); + } + + public void volume(float f) + { + AL10.alSourcef(this.source, AL10.AL_GAIN, f); + } +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioTrack.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioTrack.java new file mode 100644 index 0000000..68898a9 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/al/AudioTrack.java @@ -0,0 +1,124 @@ +package cz.tefek.pluto.engine.audio.al; + +import org.lwjgl.openal.AL10; +import org.lwjgl.openal.SOFTDirectChannels; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ShortBuffer; + +import cz.tefek.pluto.engine.audio.IAudioStream; +import cz.tefek.pluto.engine.audio.ISeekableAudioTrack; + +public class AudioTrack extends AudioSource +{ + private static final int BUFFER_SIZE_PER_CHANNEL = 16384; + + private final IAudioStream track; + + private final int format; + + private static final int DOUBLE_BUFFER = 2; + + private final int[] buffers; + + private boolean closeOnFinish; + + private final ShortBuffer pcm; + + public AudioTrack(IAudioStream track) + { + this.track = track; + + switch (track.getChannels()) + { + case 1: + this.format = AL10.AL_FORMAT_MONO16; + break; + case 2: + this.format = AL10.AL_FORMAT_STEREO16; + break; + default: + throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels()); + } + + int bufferSize = track.getChannels() * BUFFER_SIZE_PER_CHANNEL; + + this.pcm = MemoryUtil.memAllocShort(bufferSize); + + this.buffers = new int[DOUBLE_BUFFER]; + AL10.alGenBuffers(this.buffers); + + AL10.alSourcei(this.source, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE); + } + + @Override + public void close() + { + AL10.alDeleteBuffers(this.buffers); + AL10.alDeleteSources(this.source); + + MemoryUtil.memFree(this.pcm); + } + + public boolean play() + { + if (this.track instanceof ISeekableAudioTrack) + { + ((ISeekableAudioTrack) this.track).rewind(); + } + + for (int buf : this.buffers) + { + this.stream(buf); + } + + AL10.alSourcePlay(this.source); + + return true; + } + + public void setCloseOnFinish() + { + this.closeOnFinish = true; + } + + private void stream(int buffer) + { + this.pcm.clear(); + int samplesPerChannel = this.track.getSamples(this.pcm); + + if (samplesPerChannel == 0) + { + return; + } + + var samples = samplesPerChannel * this.track.getChannels(); + this.pcm.limit(samples); + AL10.alBufferData(buffer, this.format, this.pcm, this.track.getSampleRate()); + AL10.alSourceQueueBuffers(this.source, buffer); + } + + public boolean update() + { + int processed = AL10.alGetSourcei(this.source, AL10.AL_BUFFERS_PROCESSED); + + for (int i = 0; i < processed; i++) + { + int buffer = AL10.alSourceUnqueueBuffers(this.source); + this.stream(buffer); + } + + if (AL10.alGetSourcei(this.source, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED) + { + if (this.closeOnFinish) + { + this.close(); + } + + return false; + } + + return true; + } + +} diff --git a/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/util/AudioUtil.java b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/util/AudioUtil.java new file mode 100644 index 0000000..c70d6c9 --- /dev/null +++ b/plutoaudio/src/main/java/cz/tefek/pluto/engine/audio/util/AudioUtil.java @@ -0,0 +1,25 @@ +package cz.tefek.pluto.engine.audio.util; + +import org.lwjgl.stb.STBVorbis; +import org.lwjgl.stb.STBVorbisInfo; + +import cz.tefek.pluto.io.logger.Logger; +import cz.tefek.pluto.io.logger.SmartSeverity; + +public class AudioUtil +{ + public static void printInfo(long handle, STBVorbisInfo info) + { + Logger.log(SmartSeverity.AUDIO, "stream length, samples: " + STBVorbis.stb_vorbis_stream_length_in_samples(handle)); + Logger.log(SmartSeverity.AUDIO, "stream length, seconds: " + STBVorbis.stb_vorbis_stream_length_in_seconds(handle)); + + Logger.log(SmartSeverity.AUDIO); + + Logger.log(SmartSeverity.AUDIO, "channels = " + info.channels()); + Logger.log(SmartSeverity.AUDIO, "sampleRate = " + info.sample_rate()); + Logger.log(SmartSeverity.AUDIO, "maxFrameSize = " + info.max_frame_size()); + Logger.log(SmartSeverity.AUDIO, "setupMemoryRequired = " + info.setup_memory_required()); + Logger.log(SmartSeverity.AUDIO, "setupTempMemoryRequired() = " + info.setup_temp_memory_required()); + Logger.log(SmartSeverity.AUDIO, "tempMemoryRequired = " + info.temp_memory_required()); + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java index b19beb8..8f2dfbd 100644 --- a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java @@ -166,7 +166,7 @@ public class CommandParser private boolean hasEmptyPrefix() { - return this.prefixes.stream().filter(Predicate.not(OfInt::hasNext)).findAny().isPresent(); + return this.prefixes.stream().anyMatch(Predicate.not(OfInt::hasNext)); } /** @@ -241,7 +241,7 @@ public class CommandParser // At this point we are 100% sure the command was resolved and can validate the parameters - /** + /* * * TODO: Validate parameters here * diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java index 74de0b4..a69c96d 100644 --- a/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java @@ -15,8 +15,8 @@ public final class CommandRegistry { private static CommandRegistry instance; - private Set commands; - private Map aliasTable; + private final Set commands; + private final Map aliasTable; static { diff --git a/plutocore/build.gradle b/plutocore/build.gradle index 48de58a..c27e2a8 100644 --- a/plutocore/build.gradle +++ b/plutocore/build.gradle @@ -4,4 +4,5 @@ description = "The foundation module for games and apps built on top of PlutoEng dependencies { api project(":plutogui") + api project(":plutoaudio") } \ No newline at end of file diff --git a/plutocore/src/main/java/cz/tefek/pluto/PlutoApplication.java b/plutocore/src/main/java/cz/tefek/pluto/PlutoApplication.java index c212bfa..744b8fb 100644 --- a/plutocore/src/main/java/cz/tefek/pluto/PlutoApplication.java +++ b/plutocore/src/main/java/cz/tefek/pluto/PlutoApplication.java @@ -6,6 +6,7 @@ import org.lwjgl.glfw.GLFW; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL33; +import cz.tefek.pluto.engine.audio.al.AudioEngine; import cz.tefek.pluto.engine.buffer.GLFWImageUtil; import cz.tefek.pluto.engine.display.Display; import cz.tefek.pluto.engine.display.DisplayBuilder; @@ -17,34 +18,65 @@ import cz.tefek.pluto.modloader.ModLoaderCore; public abstract class PlutoApplication { - public static final boolean DEBUG_MODE = Boolean.valueOf(System.getProperty("cz.tefek.pluto.debug")); - protected Display display; protected abstract Class getMainModule(); protected abstract void loop(); - public final void run(String[] args) + protected static class StartupConfig { + public boolean coreProfile = true; + public int majorOpenGLVersion = 3; + public int minorOpenGLVersion = 3; + public String windowName = "Pluto Engine"; + public int windowMSAA = 4; + public int windowInitialWidth = 1280; + public int windowInitialHeight = 720; + public int windowMinWidth = 1000; + public int windowMinHeight = 600; + public int vsync = 0; + public boolean windowResizable = true; + + public StartupConfig() + { + + } + } + + public final void run(String[] args, StartupConfig config) + { + if (config == null) + { + config = new StartupConfig(); + } + Logger.setup(); - Logger.log(SmartSeverity.INFO, "Debug mode: " + (DEBUG_MODE ? "enabled" : "disabled")); + Logger.log(SmartSeverity.INFO, "Debug mode: " + (Pluto.DEBUG_MODE ? "enabled" : "disabled")); PlutoL10n.init(Locale.UK); DisplayBuilder.initGLFW(); - this.display = new DisplayBuilder().hintOpenGLVersion(3, 3).hintDebugContext(DEBUG_MODE).hintMSAA(4).hintVisible(true).hintResizeable(true).setInitialSize(1280, 720).export(); + if (config.coreProfile) + { + this.display = new DisplayBuilder().hintOpenGLVersion(config.majorOpenGLVersion, config.minorOpenGLVersion).hintDebugContext(Pluto.DEBUG_MODE).hintMSAA(config.windowMSAA).hintVisible(true).hintResizeable(config.windowResizable).setInitialSize(config.windowInitialWidth, config.windowInitialHeight).export(); + } + else + { + this.display = new DisplayBuilder().hintOpenGLVersionLegacy(config.majorOpenGLVersion, config.minorOpenGLVersion).hintDebugContext(Pluto.DEBUG_MODE).hintMSAA(config.windowMSAA).hintVisible(true).hintResizeable(config.windowResizable).setInitialSize(config.windowInitialWidth, config.windowInitialHeight).export(); + } - this.display.create("Stardust Miner"); + this.display.create(config.windowName); - this.display.setWindowSizeLimits(1000, 600, GLFW.GLFW_DONT_CARE, GLFW.GLFW_DONT_CARE); + this.display.setWindowSizeLimits(config.windowMinWidth, config.windowMinHeight, GLFW.GLFW_DONT_CARE, GLFW.GLFW_DONT_CARE); - this.display.lockSwapInterval(0); + this.display.lockSwapInterval(config.vsync); this.display.show(); + // TODO Un-hardcode these var icons = GLFWImageUtil.loadIconSet("data/icon16.png", "data/icon32.png", "data/icon64.png", "data/icon128.png"); this.display.setIcons(icons); @@ -53,6 +85,8 @@ public abstract class PlutoApplication InputBus.init(this.display); + AudioEngine.initialize(); + ModLoaderCore.registerMod(this.getMainModule()); ModLoaderCore.loadProcedure(); @@ -72,6 +106,8 @@ public abstract class PlutoApplication this.display.pollEvents(); } + AudioEngine.exit(); + InputBus.destroy(); ModLoaderCore.unloadProcedure(); diff --git a/plutolib/src/main/java/cz/tefek/pluto/io/logger/SmartSeverity.java b/plutolib/src/main/java/cz/tefek/pluto/io/logger/SmartSeverity.java index 7ee618d..1d9352c 100644 --- a/plutolib/src/main/java/cz/tefek/pluto/io/logger/SmartSeverity.java +++ b/plutolib/src/main/java/cz/tefek/pluto/io/logger/SmartSeverity.java @@ -16,7 +16,11 @@ public enum SmartSeverity implements ISeverity WARNING("[!] ", true), ERROR("[X] ", true), - AUDIO("[♪] ", false), + AUDIO("[i] [♪] ", false), + AUDIO_PLUS("[+] [♪] ", false), + AUDIO_MINUS("[-] [♪] ", false), + AUDIO_WARNING("[!] [♪] ", true), + AUDIO_ERROR("[X] [♪] ", true), MODULE("[i] [M] ", false), MODULE_PLUS("[+] [M] ", false), diff --git a/plutostatic/build.gradle b/plutostatic/build.gradle index 7a377a0..0b681cb 100644 --- a/plutostatic/build.gradle +++ b/plutostatic/build.gradle @@ -7,12 +7,10 @@ dependencies { api "org.lwjgl:lwjgl" api "org.lwjgl:lwjgl-glfw" - api "org.lwjgl:lwjgl-openal" api "org.lwjgl:lwjgl-opengl" api "org.lwjgl:lwjgl-stb" runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" - runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" diff --git a/settings.gradle b/settings.gradle index 6cdbe5f..6a07ee3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,7 @@ include 'plutolib', 'plutoframebuffer', 'plutospritesheet', 'plutogui', + 'plutoaudio', 'plutocore' rootProject.name = 'plutoengine'