Updated the versioning system, README and added the audio submodule
This commit is contained in:
parent
8edb79c69b
commit
5d109865f6
53
README.md
53
README.md
|
@ -1,3 +1,56 @@
|
||||||
# plutoengine
|
# plutoengine
|
||||||
|
|
||||||
My hobby game engine. This repository unifies all my previous Pluto repositories.
|
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
|
31
build.gradle
31
build.gradle
|
@ -4,16 +4,35 @@ allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
|
||||||
group = "cz.tefek"
|
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 {
|
subprojects {
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
project.ext.lwjglVersion = "3.2.3"
|
project.ext.lwjglVersion = "3.2.3"
|
||||||
|
|
||||||
project.ext.jomlVersion = "1.9.25"
|
project.ext.jomlVersion = "1.9.25"
|
||||||
project.ext.steamworks4jVersion = "1.8.0"
|
project.ext.steamworks4jVersion = "1.8.0"
|
||||||
project.ext.steamworks4jServerVersion = "1.8.0"
|
project.ext.steamworks4jServerVersion = "1.8.0"
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
maven(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (OperatingSystem.current()) {
|
switch (OperatingSystem.current()) {
|
||||||
case OperatingSystem.LINUX:
|
case OperatingSystem.LINUX:
|
||||||
project.ext.lwjglNatives = "natives-linux"
|
project.ext.lwjglNatives = "natives-linux"
|
||||||
|
@ -26,17 +45,7 @@ subprojects {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
|
||||||
options.encoding = "UTF-8"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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<Long> device = new ThreadLocal<>() {
|
||||||
|
@Override
|
||||||
|
protected Long initialValue()
|
||||||
|
{
|
||||||
|
return MemoryUtil.NULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ThreadLocal<Long> context = new ThreadLocal<>() {
|
||||||
|
@Override
|
||||||
|
protected Long initialValue()
|
||||||
|
{
|
||||||
|
return MemoryUtil.NULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ThreadLocal<ALCapabilities> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -166,7 +166,7 @@ public class CommandParser
|
||||||
|
|
||||||
private boolean hasEmptyPrefix()
|
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
|
// At this point we are 100% sure the command was resolved and can validate the parameters
|
||||||
|
|
||||||
/**
|
/*
|
||||||
*
|
*
|
||||||
* TODO: Validate parameters here
|
* TODO: Validate parameters here
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,8 +15,8 @@ public final class CommandRegistry
|
||||||
{
|
{
|
||||||
private static CommandRegistry instance;
|
private static CommandRegistry instance;
|
||||||
|
|
||||||
private Set<CommandBase> commands;
|
private final Set<CommandBase> commands;
|
||||||
private Map<String, CommandBase> aliasTable;
|
private final Map<String, CommandBase> aliasTable;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,4 +4,5 @@ description = "The foundation module for games and apps built on top of PlutoEng
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":plutogui")
|
api project(":plutogui")
|
||||||
|
api project(":plutoaudio")
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ import org.lwjgl.glfw.GLFW;
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
import org.lwjgl.opengl.GL33;
|
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.buffer.GLFWImageUtil;
|
||||||
import cz.tefek.pluto.engine.display.Display;
|
import cz.tefek.pluto.engine.display.Display;
|
||||||
import cz.tefek.pluto.engine.display.DisplayBuilder;
|
import cz.tefek.pluto.engine.display.DisplayBuilder;
|
||||||
|
@ -17,34 +18,65 @@ import cz.tefek.pluto.modloader.ModLoaderCore;
|
||||||
|
|
||||||
public abstract class PlutoApplication
|
public abstract class PlutoApplication
|
||||||
{
|
{
|
||||||
public static final boolean DEBUG_MODE = Boolean.valueOf(System.getProperty("cz.tefek.pluto.debug"));
|
|
||||||
|
|
||||||
protected Display display;
|
protected Display display;
|
||||||
|
|
||||||
protected abstract Class<?> getMainModule();
|
protected abstract Class<?> getMainModule();
|
||||||
|
|
||||||
protected abstract void loop();
|
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.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);
|
PlutoL10n.init(Locale.UK);
|
||||||
|
|
||||||
DisplayBuilder.initGLFW();
|
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();
|
this.display.show();
|
||||||
|
|
||||||
|
// TODO Un-hardcode these
|
||||||
var icons = GLFWImageUtil.loadIconSet("data/icon16.png", "data/icon32.png", "data/icon64.png", "data/icon128.png");
|
var icons = GLFWImageUtil.loadIconSet("data/icon16.png", "data/icon32.png", "data/icon64.png", "data/icon128.png");
|
||||||
|
|
||||||
this.display.setIcons(icons);
|
this.display.setIcons(icons);
|
||||||
|
@ -53,6 +85,8 @@ public abstract class PlutoApplication
|
||||||
|
|
||||||
InputBus.init(this.display);
|
InputBus.init(this.display);
|
||||||
|
|
||||||
|
AudioEngine.initialize();
|
||||||
|
|
||||||
ModLoaderCore.registerMod(this.getMainModule());
|
ModLoaderCore.registerMod(this.getMainModule());
|
||||||
|
|
||||||
ModLoaderCore.loadProcedure();
|
ModLoaderCore.loadProcedure();
|
||||||
|
@ -72,6 +106,8 @@ public abstract class PlutoApplication
|
||||||
this.display.pollEvents();
|
this.display.pollEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioEngine.exit();
|
||||||
|
|
||||||
InputBus.destroy();
|
InputBus.destroy();
|
||||||
|
|
||||||
ModLoaderCore.unloadProcedure();
|
ModLoaderCore.unloadProcedure();
|
||||||
|
|
|
@ -16,7 +16,11 @@ public enum SmartSeverity implements ISeverity
|
||||||
WARNING("[!] ", true),
|
WARNING("[!] ", true),
|
||||||
ERROR("[X] ", 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("[i] [M] ", false),
|
||||||
MODULE_PLUS("[+] [M] ", false),
|
MODULE_PLUS("[+] [M] ", false),
|
||||||
|
|
|
@ -7,12 +7,10 @@ dependencies {
|
||||||
|
|
||||||
api "org.lwjgl:lwjgl"
|
api "org.lwjgl:lwjgl"
|
||||||
api "org.lwjgl:lwjgl-glfw"
|
api "org.lwjgl:lwjgl-glfw"
|
||||||
api "org.lwjgl:lwjgl-openal"
|
|
||||||
api "org.lwjgl:lwjgl-opengl"
|
api "org.lwjgl:lwjgl-opengl"
|
||||||
api "org.lwjgl:lwjgl-stb"
|
api "org.lwjgl:lwjgl-stb"
|
||||||
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ include 'plutolib',
|
||||||
'plutoframebuffer',
|
'plutoframebuffer',
|
||||||
'plutospritesheet',
|
'plutospritesheet',
|
||||||
'plutogui',
|
'plutogui',
|
||||||
|
'plutoaudio',
|
||||||
'plutocore'
|
'plutocore'
|
||||||
|
|
||||||
rootProject.name = 'plutoengine'
|
rootProject.name = 'plutoengine'
|
||||||
|
|
Loading…
Reference in New Issue