refactor(sounds): Rewritten code for sound to ogg

Now SoundPlayer class supports only .ogg files instead of wav file which
can reduce the final .jar file size drastically. It uses library.
This commit is contained in:
Jakub Žitník 2025-03-27 21:39:12 +01:00
parent a84d3bec00
commit 433dbf6f96
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
33 changed files with 107 additions and 40 deletions

View File

@ -128,6 +128,11 @@
<artifactId>logback-classic</artifactId>
<version>1.5.18</version> <!-- latest at the time -->
</dependency>
<dependency>
<groupId>com.github.trilarion</groupId>
<artifactId>java-vorbis-support</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
</project>

View File

@ -282,7 +282,6 @@ public class Game extends AutoTransientSupport {
gameStates.dependencies.eventHandlerProvider.handleMine(screenRenderer, this, x, y);
screenRenderer.render(this);
for (Block block : blocksCopy) {
if (block.getClass().isAnnotationPresent(MineSound.class)) {
@ -294,6 +293,8 @@ public class Game extends AutoTransientSupport {
}
}
screenRenderer.render(this);
update(screenRenderer);
}

View File

@ -1,10 +1,12 @@
package cz.jzitnik.game.config;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Configuration {
public class Configuration implements Serializable {
private int soundVolume = 100; // 0-100
}

View File

@ -38,17 +38,23 @@ public class Sound {
public void playSound(Configuration configuration, SoundKey soundKey) {
var volume = configuration.getSoundVolume();
var annotation = map.get(soundKey);
try {
var resources = annotation.resourceLocation();
var resources = annotation.resourceLocation();
var resource = resources[random.nextInt(resources.length)];
var resource = resources[random.nextInt(resources.length)];
SoundPlayer.playSound(resource, volume);
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
var typeVolue = annotation.type().getVolume();
int totalVolume = (int) ((volume + typeVolue) * 100) / 200;
new Thread(() -> {
try {
SoundPlayer.playSound(resource);
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}).start();
}
}

View File

@ -5,5 +5,7 @@ public enum SoundKey {
GRASS_WALKING,
GRAVEL,
GRAVEL_WALKING
GRAVEL_WALKING,
WOOD,
}

View File

@ -1,5 +1,15 @@
package cz.jzitnik.game.core.sound;
import lombok.Getter;
@Getter
public enum SoundType {
BLOCK
BLOCK(100);
// TODO: Volume is not currently used but will be probably used in future to better normalize the sound volume
private final int volume;
SoundType(int volume) {
this.volume = volume;
}
}

View File

@ -5,10 +5,10 @@ import cz.jzitnik.game.core.sound.SoundKey;
import cz.jzitnik.game.core.sound.SoundType;
@SoundRegistry(key = SoundKey.GRASS, resourceLocation = {
"grass/grass1.wav",
"grass/grass2.wav",
"grass/grass3.wav",
"grass/grass4.wav"
"grass/grass1.ogg",
"grass/grass2.ogg",
"grass/grass3.ogg",
"grass/grass4.ogg"
}, type = SoundType.BLOCK)
public class GrassSound {
}

View File

@ -5,12 +5,12 @@ import cz.jzitnik.game.core.sound.SoundKey;
import cz.jzitnik.game.core.sound.SoundType;
@SoundRegistry(key = SoundKey.GRASS_WALKING, resourceLocation = {
"grass/walk1.wav",
"grass/walk2.wav",
"grass/walk3.wav",
"grass/walk4.wav",
"grass/walk5.wav",
"grass/walk6.wav",
"grass/walk1.ogg",
"grass/walk2.ogg",
"grass/walk3.ogg",
"grass/walk4.ogg",
"grass/walk5.ogg",
"grass/walk6.ogg",
}, type = SoundType.BLOCK)
public class GrassWalkingSound {
}

View File

@ -0,0 +1,11 @@
package cz.jzitnik.game.core.sound.registry;
import cz.jzitnik.game.annotations.SoundRegistry;
import cz.jzitnik.game.core.sound.SoundKey;
import cz.jzitnik.game.core.sound.SoundType;
@SoundRegistry(key = SoundKey.WOOD, resourceLocation = {
"wood/wood1.ogg",
}, type = SoundType.BLOCK)
public class WoodSound {
}

View File

@ -3,11 +3,16 @@ package cz.jzitnik.game.entities.items.registry.blocks.blocks;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.annotations.Flamable;
import cz.jzitnik.game.annotations.MineSound;
import cz.jzitnik.game.annotations.PlaceSound;
import cz.jzitnik.game.core.sound.SoundKey;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemType;
import java.util.ArrayList;
@MineSound(SoundKey.WOOD)
@PlaceSound(SoundKey.WOOD)
@Flamable
@BlockRegistry("oak_log")
public class OakLogBlock extends Block {

View File

@ -5,7 +5,6 @@ import cz.jzitnik.game.entities.SteveData;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.sprites.Air;
import cz.jzitnik.game.sprites.SimpleSprite;
import cz.jzitnik.game.sprites.Steve;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.Escape;

View File

@ -1,32 +1,58 @@
package cz.jzitnik.tui;
import javax.sound.sampled.*;
import lombok.extern.slf4j.Slf4j;
import javax.sound.sampled.*;
import java.io.IOException;
@Slf4j
public class SoundPlayer {
public static Clip playSound(String filePath, int volume)
public static void playSound(String filePath)
throws LineUnavailableException, IOException, UnsupportedAudioFileException, InterruptedException {
if (!filePath.endsWith(".ogg")) {
return;
}
log.info("Loading resource: {}", "sounds/" + filePath);
var soundFile = SoundPlayer.class.getClassLoader().getResourceAsStream("sounds/" + filePath);
AudioInputStream audioStream = AudioSystem.getAudioInputStream(soundFile);
Clip clip = AudioSystem.getClip();
clip.open(audioStream);
FloatControl volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum();
float gain = min + (max - min) * (volume / 100.0f);
volumeControl.setValue(gain);
var file = SoundPlayer.class.getClassLoader().getResourceAsStream("sounds/" + filePath);
AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);
AudioFormat baseFormat = audioStream.getFormat();
log.info("Starting to play {}", filePath);
clip.start();
// Convert the audio format to PCM_SIGNED
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false
);
// Thread.sleep(clip.getMicrosecondLength() / 1000);
// Apply the format change to the stream
AudioInputStream dataIn = AudioSystem.getAudioInputStream(targetFormat, audioStream);
return clip;
byte[] buffer = new byte[8192]; // Larger buffer to reduce read/write operations
DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) {
if (line != null) {
line.open(targetFormat);
line.start();
int bytesRead;
while ((bytesRead = dataIn.read(buffer, 0, buffer.length)) != -1) {
line.write(buffer, 0, bytesRead);
}
// Ensure the line is fully flushed before stopping
line.drain();
}
}
// Close streams after use
dataIn.close();
audioStream.close();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.