Play notification sounds from core
The sound player was a desktop class only because it decoded wave files through Java Sound. A small RIFF reader replaces that, so the player moves to core and every platform gets it from AudioBackend by default. The reader takes integer PCM up to 32 bits and 32-bit float, in plain or extensible headers. It decodes all 151 files of the TS3 sound packs to the same samples Java Sound does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package com.ts3client.sound;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class WaveFileTest {
|
||||
|
||||
@TempDir
|
||||
Path dir;
|
||||
|
||||
/** Builds a wave file around raw sample bytes. */
|
||||
private File wave(int format, int channels, int bits, boolean extensible, byte[] data) throws IOException {
|
||||
int fmtSize = extensible ? 40 : 16;
|
||||
ByteBuffer b = ByteBuffer.allocate(12 + 8 + fmtSize + 8 + data.length).order(ByteOrder.LITTLE_ENDIAN);
|
||||
b.put("RIFF".getBytes()).putInt(b.capacity() - 8).put("WAVE".getBytes());
|
||||
b.put("fmt ".getBytes()).putInt(fmtSize);
|
||||
b.putShort((short) (extensible ? 0xFFFE : format)).putShort((short) channels).putInt(44100)
|
||||
.putInt(44100 * channels * bits / 8).putShort((short) (channels * bits / 8)).putShort((short) bits);
|
||||
if (extensible) {
|
||||
b.putShort((short) 22).putShort((short) bits).putInt(0).putShort((short) format).put(new byte[14]);
|
||||
}
|
||||
b.put("data".getBytes()).putInt(data.length).put(data);
|
||||
File file = dir.resolve("test.wav").toFile();
|
||||
Files.write(file.toPath(), b.array());
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsSixteenBitMono() throws IOException {
|
||||
WaveFile.Audio audio = WaveFile.readMono(wave(1, 1, 16, false, new byte[]{0, 0x40, 0, (byte) 0xC0}));
|
||||
assertEquals(44100, audio.sampleRate());
|
||||
assertArrayEquals(new float[]{0.5f, -0.5f}, audio.samples());
|
||||
}
|
||||
|
||||
@Test
|
||||
void averagesStereoDown() throws IOException {
|
||||
WaveFile.Audio audio = WaveFile.readMono(wave(1, 2, 16, false, new byte[]{0, 0x40, 0, 0}));
|
||||
assertArrayEquals(new float[]{0.25f}, audio.samples());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsUnsignedEightBit() throws IOException {
|
||||
WaveFile.Audio audio = WaveFile.readMono(wave(1, 1, 8, false, new byte[]{(byte) 192, 64, (byte) 128}));
|
||||
assertArrayEquals(new float[]{0.5f, -0.5f, 0f}, audio.samples());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsTwentyFourBitThroughAnExtensibleHeader() throws IOException {
|
||||
WaveFile.Audio audio = WaveFile.readMono(wave(1, 1, 24, true, new byte[]{0, 0, 0x40, 0, 0, (byte) 0xC0}));
|
||||
assertArrayEquals(new float[]{0.5f, -0.5f}, audio.samples());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsFloat() throws IOException {
|
||||
byte[] data = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putFloat(0.25f).putFloat(-1f).array();
|
||||
assertArrayEquals(new float[]{0.25f, -1f}, WaveFile.readMono(wave(3, 1, 32, false, data)).samples());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCompressedAudio() {
|
||||
assertThrows(IOException.class, () -> WaveFile.readMono(wave(2, 1, 4, false, new byte[4])));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user