Implemented untested OGG playback for music

This commit is contained in:
Brendan Robert 2023-10-24 23:30:50 -05:00
parent 06c16733c2
commit 59ceac0b59
5 changed files with 88 additions and 14 deletions

View File

@ -201,6 +201,10 @@
<groupId>org.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId> <artifactId>lwjgl-openal</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId> <artifactId>lwjgl</artifactId>
@ -216,7 +220,7 @@
<artifactId>jlayer</artifactId> <artifactId>jlayer</artifactId>
<version>1.0.2-gdx</version> <version>1.0.2-gdx</version>
</dependency> </dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>
<id>lwjgl-natives-linux-amd64</id> <id>lwjgl-natives-linux-amd64</id>

View File

@ -1,6 +1,7 @@
package jace.lawless; package jace.lawless;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
@ -11,6 +12,8 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -136,7 +139,12 @@ public class LawlessHacks extends Cheats {
System.out.println("Playing " + resourcePath); System.out.println("Playing " + resourcePath);
} }
// Log path // 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) { private void playMusic(int track, boolean switchScores) {

View File

@ -1,32 +1,83 @@
package jace.lawless; 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; import javafx.util.Duration;
public class Media { 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) { public void seekToTime(Duration millis) {
int sampleNumber = (int) (millis.toMillis() * sampleRate / 1000);
STBVorbis.stb_vorbis_seek(streamHandle, sampleNumber);
} }
public boolean isEnded() { public boolean isEnded() {
return false; return STBVorbis.stb_vorbis_get_sample_offset(streamHandle) >= totalSamples;
} }
public void restart() { public void restart() {
STBVorbis.stb_vorbis_seek_start(streamHandle);
} }
public short getNextLeftSample() { 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() { public short getNextRightSample() {
return 0; return isStereo ? sampleBuffer.get(1) : sampleBuffer.get(0);
} }
public java.time.Duration getCurrentTime() { 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));
}
} }

View File

@ -19,7 +19,7 @@ public class MediaPlayer {
Executor executor = Executors.newSingleThreadExecutor(); Executor executor = Executors.newSingleThreadExecutor();
public static enum Status { public static enum Status {
PLAYING, PAUSED, STOPPED NOT_STARTED, PLAYING, PAUSED, STOPPED
} }
public static final int INDEFINITE = -1; public static final int INDEFINITE = -1;
@ -40,6 +40,7 @@ public class MediaPlayer {
return vol; return vol;
} }
// NOTE: Once a song is stopped, it cannot be restarted.
public void stop() { public void stop() {
status = Status.STOPPED; status = Status.STOPPED;
try { try {
@ -47,6 +48,7 @@ public class MediaPlayer {
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
// Ignore exception on shutdown // Ignore exception on shutdown
} }
song.close();
} }
public void setCycleCount(int i) { public void setCycleCount(int i) {
@ -61,11 +63,19 @@ public class MediaPlayer {
song.seekToTime(millis); song.seekToTime(millis);
} }
public void pause() {
status = Status.PAUSED;
}
public void play() { public void play() {
repeats = 0; if (status == Status.STOPPED) {
status = Status.PLAYING; return;
if (playbackBuffer == null || !playbackBuffer.isAlive()) { } else if (status == Status.NOT_STARTED) {
playbackBuffer = SoundMixer.createBuffer(true); repeats = 0;
status = Status.PLAYING;
if (playbackBuffer == null || !playbackBuffer.isAlive()) {
playbackBuffer = SoundMixer.createBuffer(true);
}
} }
executor.execute(() -> { executor.execute(() -> {
while (status == Status.PLAYING && (maxRepetitions == INDEFINITE || repeats < maxRepetitions)) { while (status == Status.PLAYING && (maxRepetitions == INDEFINITE || repeats < maxRepetitions)) {

View File

@ -38,6 +38,7 @@ module lawlesslegends {
requires javafx.media; requires javafx.media;
requires jdk.jsobject; requires jdk.jsobject;
requires org.lwjgl.openal; requires org.lwjgl.openal;
requires org.lwjgl.stb;
// requires org.reflections; // requires org.reflections;