Add MP3 sound support via JLayer decoding
- build.gradle: add javazoom:jlayer:1.0.1 dependency (bundled via include()) - Mp3Decoder: new utility class that decodes MP3 to WAV using JLayer - SplashSound: detect .mp3 files in config dir, decode to .wav for the dynamic resource pack - Default .ogg still works; place a .mp3 file in config/justasplash/ for MP3 playback
This commit is contained in:
@@ -35,6 +35,9 @@ dependencies {
|
||||
|
||||
modImplementation("com.github.usefulness:webp-imageio:0.10.2")
|
||||
include("com.github.usefulness:webp-imageio:0.10.2")
|
||||
|
||||
modImplementation("javazoom:jlayer:1.0.1")
|
||||
include("javazoom:jlayer:1.0.1")
|
||||
}
|
||||
|
||||
loom {
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.anomaly.justasplash.splash;
|
||||
import net.anomaly.justasplash.JustASplash;
|
||||
import net.anomaly.justasplash.config.SplashConfig;
|
||||
import net.anomaly.justasplash.util.Compat;
|
||||
import net.anomaly.justasplash.util.Mp3Decoder;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
@@ -30,14 +31,17 @@ public class SplashSound {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasCustomOgg;
|
||||
boolean hasCustomSound;
|
||||
try (Stream<Path> files = Files.list(assetDir)) {
|
||||
hasCustomOgg = files.anyMatch(p -> p.toString().endsWith(".ogg"));
|
||||
hasCustomSound = files.anyMatch(p -> {
|
||||
String name = p.toString().toLowerCase();
|
||||
return name.endsWith(".ogg") || name.endsWith(".mp3");
|
||||
});
|
||||
} catch (IOException e) {
|
||||
hasCustomOgg = false;
|
||||
hasCustomSound = false;
|
||||
}
|
||||
|
||||
if (hasCustomOgg && !packRegistered) {
|
||||
if (hasCustomSound && !packRegistered) {
|
||||
ensurePackMeta(assetDir);
|
||||
ensureAssetStructure(assetDir);
|
||||
|
||||
@@ -71,12 +75,25 @@ public class SplashSound {
|
||||
Path soundsDir = assetDir.resolve("assets/justasplash/sounds");
|
||||
Files.createDirectories(soundsDir);
|
||||
|
||||
Path dst = soundsDir.resolve(soundFile);
|
||||
if (!Files.exists(dst)) {
|
||||
Files.copy(src, dst);
|
||||
String dstName = soundFile;
|
||||
if (soundFile.toLowerCase().endsWith(".mp3")) {
|
||||
dstName = soundFile.replaceAll("(?i)\\.mp3$", ".wav");
|
||||
}
|
||||
|
||||
String name = soundFile.replaceAll("\\.\\w+$", "");
|
||||
Path dst = soundsDir.resolve(dstName);
|
||||
if (!Files.exists(dst)) {
|
||||
if (soundFile.toLowerCase().endsWith(".mp3")) {
|
||||
try {
|
||||
Mp3Decoder.decodeToWav(src, dst);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Files.copy(src, dst);
|
||||
}
|
||||
}
|
||||
|
||||
String name = soundFile.replaceAll("(?i)\\.\\w+$", "");
|
||||
Path soundsJson = assetDir.resolve("assets/justasplash/sounds.json");
|
||||
if (!Files.exists(soundsJson)) {
|
||||
String json = "{\"splash\":{\"sounds\":[{\"name\":\"justasplash:"
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package net.anomaly.justasplash.util;
|
||||
|
||||
import javazoom.jl.decoder.Bitstream;
|
||||
import javazoom.jl.decoder.Decoder;
|
||||
import javazoom.jl.decoder.Header;
|
||||
import javazoom.jl.decoder.SampleBuffer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class Mp3Decoder {
|
||||
public static void decodeToWav(Path mp3Path, Path wavPath) throws Exception {
|
||||
byte[] pcmData;
|
||||
int sampleRate = 44100;
|
||||
int channels = 2;
|
||||
|
||||
try (InputStream input = Files.newInputStream(mp3Path)) {
|
||||
Bitstream bitstream = new Bitstream(input);
|
||||
Decoder decoder = new Decoder();
|
||||
ByteArrayOutputStream pcmOut = new ByteArrayOutputStream();
|
||||
|
||||
boolean first = true;
|
||||
while (true) {
|
||||
Header header = bitstream.readFrame();
|
||||
if (header == null) break;
|
||||
|
||||
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(header, bitstream);
|
||||
if (first) {
|
||||
sampleRate = decoder.getOutputFrequency();
|
||||
channels = decoder.getOutputChannels();
|
||||
first = false;
|
||||
}
|
||||
|
||||
short[] samples = output.getBuffer();
|
||||
int len = output.getBufferLength();
|
||||
byte[] bytes = new byte[len * 2];
|
||||
for (int i = 0; i < len; i++) {
|
||||
bytes[i * 2] = (byte) (samples[i] & 0xFF);
|
||||
bytes[i * 2 + 1] = (byte) ((samples[i] >> 8) & 0xFF);
|
||||
}
|
||||
pcmOut.write(bytes);
|
||||
|
||||
bitstream.closeFrame();
|
||||
}
|
||||
|
||||
pcmData = pcmOut.toByteArray();
|
||||
}
|
||||
|
||||
writeWav(pcmData, sampleRate, channels, 16, wavPath);
|
||||
}
|
||||
|
||||
private static void writeWav(byte[] pcmData, int sampleRate, int channels, int bitsPerSample, Path output) throws IOException {
|
||||
int byteRate = sampleRate * channels * bitsPerSample / 8;
|
||||
int blockAlign = channels * bitsPerSample / 8;
|
||||
int dataSize = pcmData.length;
|
||||
|
||||
try (DataOutputStream dos = new DataOutputStream(Files.newOutputStream(output))) {
|
||||
dos.writeBytes("RIFF");
|
||||
writeLEInt(dos, 36 + dataSize);
|
||||
dos.writeBytes("WAVE");
|
||||
|
||||
dos.writeBytes("fmt ");
|
||||
writeLEInt(dos, 16);
|
||||
writeLEShort(dos, (short) 1);
|
||||
writeLEShort(dos, (short) channels);
|
||||
writeLEInt(dos, sampleRate);
|
||||
writeLEInt(dos, byteRate);
|
||||
writeLEShort(dos, (short) blockAlign);
|
||||
writeLEShort(dos, (short) bitsPerSample);
|
||||
|
||||
dos.writeBytes("data");
|
||||
writeLEInt(dos, dataSize);
|
||||
dos.write(pcmData);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeLEInt(DataOutputStream dos, int value) throws IOException {
|
||||
dos.writeByte(value & 0xFF);
|
||||
dos.writeByte((value >> 8) & 0xFF);
|
||||
dos.writeByte((value >> 16) & 0xFF);
|
||||
dos.writeByte((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
private static void writeLEShort(DataOutputStream dos, short value) throws IOException {
|
||||
dos.writeByte(value & 0xFF);
|
||||
dos.writeByte((value >> 8) & 0xFF);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user