diff --git a/Platform/Apple/tools/jace/pom.xml b/Platform/Apple/tools/jace/pom.xml
index d79876a9..99603a85 100644
--- a/Platform/Apple/tools/jace/pom.xml
+++ b/Platform/Apple/tools/jace/pom.xml
@@ -201,6 +201,10 @@
org.lwjgl
lwjgl-openal
+
+ org.lwjgl
+ lwjgl-stb
+
org.lwjgl
lwjgl
@@ -216,7 +220,7 @@
jlayer
1.0.2-gdx
-
+
lwjgl-natives-linux-amd64
diff --git a/Platform/Apple/tools/jace/src/main/java/jace/lawless/LawlessHacks.java b/Platform/Apple/tools/jace/src/main/java/jace/lawless/LawlessHacks.java
index 40f7cc8b..0fb2ba34 100644
--- a/Platform/Apple/tools/jace/src/main/java/jace/lawless/LawlessHacks.java
+++ b/Platform/Apple/tools/jace/src/main/java/jace/lawless/LawlessHacks.java
@@ -1,6 +1,7 @@
package jace.lawless;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
@@ -11,6 +12,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -136,7 +139,12 @@ public class LawlessHacks extends Cheats {
System.out.println("Playing " + resourcePath);
}
// Log path
- return new Media(resourcePath);
+ try {
+ return new Media(resourcePath);
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Unable to load audio track " + resourcePath, e);
+ return null;
+ }
}
private void playMusic(int track, boolean switchScores) {
diff --git a/Platform/Apple/tools/jace/src/main/java/jace/lawless/Media.java b/Platform/Apple/tools/jace/src/main/java/jace/lawless/Media.java
index 8aa7e1e9..e3b21e0d 100644
--- a/Platform/Apple/tools/jace/src/main/java/jace/lawless/Media.java
+++ b/Platform/Apple/tools/jace/src/main/java/jace/lawless/Media.java
@@ -1,32 +1,83 @@
package jace.lawless;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import org.lwjgl.stb.STBVorbis;
+import org.lwjgl.stb.STBVorbisInfo;
+import org.lwjgl.system.MemoryStack;
+
import javafx.util.Duration;
public class Media {
+ long streamHandle = -1;
+ long totalSamples = 0;
+ float totalDuration = 0;
+ long sampleRate = 0;
+ boolean isStereo = true;
+ ShortBuffer sampleBuffer = ShortBuffer.allocate(2);
- public Media(String resourcePath) {
+ public Media(String resourcePath) throws IOException {
+ // Load resource into memory
+ try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ ByteBuffer byteBuffer = ByteBuffer.wrap(outputStream.toByteArray());
+
+ IntBuffer error = IntBuffer.allocate(1);
+ streamHandle = STBVorbis.stb_vorbis_open_memory(byteBuffer, error, null);
+
+ MemoryStack stack = MemoryStack.stackPush();
+ STBVorbisInfo info = STBVorbisInfo.malloc(stack);
+ STBVorbis.stb_vorbis_get_info(streamHandle, info);
+ totalSamples = STBVorbis.stb_vorbis_stream_length_in_samples(streamHandle);
+ totalDuration = STBVorbis.stb_vorbis_stream_length_in_seconds(streamHandle);
+ sampleRate = info.sample_rate();
+ isStereo = info.channels() == 2;
+ info.free();
+ }
+ }
+
+ public void close() {
+ STBVorbis.stb_vorbis_close(streamHandle);
}
public void seekToTime(Duration millis) {
+ int sampleNumber = (int) (millis.toMillis() * sampleRate / 1000);
+ STBVorbis.stb_vorbis_seek(streamHandle, sampleNumber);
}
public boolean isEnded() {
- return false;
+ return STBVorbis.stb_vorbis_get_sample_offset(streamHandle) >= totalSamples;
}
public void restart() {
+ STBVorbis.stb_vorbis_seek_start(streamHandle);
}
public short getNextLeftSample() {
- return 0;
+ // read next sample for left and right channels
+ int numSamples = STBVorbis.stb_vorbis_get_frame_short_interleaved(streamHandle, isStereo ? 2 : 1, sampleBuffer);
+ if (numSamples == 0) {
+ return 0;
+ }
+ return sampleBuffer.get(0);
}
public short getNextRightSample() {
- return 0;
+ return isStereo ? sampleBuffer.get(1) : sampleBuffer.get(0);
}
public java.time.Duration getCurrentTime() {
- return null;
- }
-
+ int sampleNumber = STBVorbis.stb_vorbis_get_sample_offset(streamHandle);
+ return java.time.Duration.ofMillis((long) (sampleNumber * 1000 / sampleRate));
+ }
}
\ No newline at end of file
diff --git a/Platform/Apple/tools/jace/src/main/java/jace/lawless/MediaPlayer.java b/Platform/Apple/tools/jace/src/main/java/jace/lawless/MediaPlayer.java
index 06de038b..14b268c7 100644
--- a/Platform/Apple/tools/jace/src/main/java/jace/lawless/MediaPlayer.java
+++ b/Platform/Apple/tools/jace/src/main/java/jace/lawless/MediaPlayer.java
@@ -19,7 +19,7 @@ public class MediaPlayer {
Executor executor = Executors.newSingleThreadExecutor();
public static enum Status {
- PLAYING, PAUSED, STOPPED
+ NOT_STARTED, PLAYING, PAUSED, STOPPED
}
public static final int INDEFINITE = -1;
@@ -40,6 +40,7 @@ public class MediaPlayer {
return vol;
}
+ // NOTE: Once a song is stopped, it cannot be restarted.
public void stop() {
status = Status.STOPPED;
try {
@@ -47,6 +48,7 @@ public class MediaPlayer {
} catch (InterruptedException | ExecutionException e) {
// Ignore exception on shutdown
}
+ song.close();
}
public void setCycleCount(int i) {
@@ -61,11 +63,19 @@ public class MediaPlayer {
song.seekToTime(millis);
}
+ public void pause() {
+ status = Status.PAUSED;
+ }
+
public void play() {
- repeats = 0;
- status = Status.PLAYING;
- if (playbackBuffer == null || !playbackBuffer.isAlive()) {
- playbackBuffer = SoundMixer.createBuffer(true);
+ if (status == Status.STOPPED) {
+ return;
+ } else if (status == Status.NOT_STARTED) {
+ repeats = 0;
+ status = Status.PLAYING;
+ if (playbackBuffer == null || !playbackBuffer.isAlive()) {
+ playbackBuffer = SoundMixer.createBuffer(true);
+ }
}
executor.execute(() -> {
while (status == Status.PLAYING && (maxRepetitions == INDEFINITE || repeats < maxRepetitions)) {
diff --git a/Platform/Apple/tools/jace/src/main/java/module-info.java b/Platform/Apple/tools/jace/src/main/java/module-info.java
index c102bbf8..25b7d2cb 100644
--- a/Platform/Apple/tools/jace/src/main/java/module-info.java
+++ b/Platform/Apple/tools/jace/src/main/java/module-info.java
@@ -38,6 +38,7 @@ module lawlesslegends {
requires javafx.media;
requires jdk.jsobject;
requires org.lwjgl.openal;
+ requires org.lwjgl.stb;
// requires org.reflections;