Updated the versioning system, README and added the audio submodule
This commit is contained in:
parent
8edb79c69b
commit
5d109865f6
55
README.md
55
README.md
|
@ -1,3 +1,56 @@
|
|||
# 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'
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
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
|
||||
*
|
||||
|
|
|
@ -15,8 +15,8 @@ public final class CommandRegistry
|
|||
{
|
||||
private static CommandRegistry instance;
|
||||
|
||||
private Set<CommandBase> commands;
|
||||
private Map<String, CommandBase> aliasTable;
|
||||
private final Set<CommandBase> commands;
|
||||
private final Map<String, CommandBase> aliasTable;
|
||||
|
||||
static
|
||||
{
|
||||
|
|
|
@ -4,4 +4,5 @@ description = "The foundation module for games and apps built on top of PlutoEng
|
|||
|
||||
dependencies {
|
||||
api project(":plutogui")
|
||||
api project(":plutoaudio")
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ include 'plutolib',
|
|||
'plutoframebuffer',
|
||||
'plutospritesheet',
|
||||
'plutogui',
|
||||
'plutoaudio',
|
||||
'plutocore'
|
||||
|
||||
rootProject.name = 'plutoengine'
|
||||
|
|
Loading…
Reference in New Issue