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> <artifactId>logback-classic</artifactId>
<version>1.5.18</version> <!-- latest at the time --> <version>1.5.18</version> <!-- latest at the time -->
</dependency> </dependency>
<dependency>
<groupId>com.github.trilarion</groupId>
<artifactId>java-vorbis-support</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

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

View File

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

View File

@ -41,14 +41,20 @@ public class Sound {
var annotation = map.get(soundKey); 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);
var typeVolue = annotation.type().getVolume();
int totalVolume = (int) ((volume + typeVolue) * 100) / 200;
new Thread(() -> {
try {
SoundPlayer.playSound(resource);
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException | InterruptedException e) { } catch (LineUnavailableException | IOException | UnsupportedAudioFileException | InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
}).start();
} }
} }

View File

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

View File

@ -1,5 +1,15 @@
package cz.jzitnik.game.core.sound; package cz.jzitnik.game.core.sound;
import lombok.Getter;
@Getter
public enum SoundType { 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; import cz.jzitnik.game.core.sound.SoundType;
@SoundRegistry(key = SoundKey.GRASS, resourceLocation = { @SoundRegistry(key = SoundKey.GRASS, resourceLocation = {
"grass/grass1.wav", "grass/grass1.ogg",
"grass/grass2.wav", "grass/grass2.ogg",
"grass/grass3.wav", "grass/grass3.ogg",
"grass/grass4.wav" "grass/grass4.ogg"
}, type = SoundType.BLOCK) }, type = SoundType.BLOCK)
public class GrassSound { public class GrassSound {
} }

View File

@ -5,12 +5,12 @@ import cz.jzitnik.game.core.sound.SoundKey;
import cz.jzitnik.game.core.sound.SoundType; import cz.jzitnik.game.core.sound.SoundType;
@SoundRegistry(key = SoundKey.GRASS_WALKING, resourceLocation = { @SoundRegistry(key = SoundKey.GRASS_WALKING, resourceLocation = {
"grass/walk1.wav", "grass/walk1.ogg",
"grass/walk2.wav", "grass/walk2.ogg",
"grass/walk3.wav", "grass/walk3.ogg",
"grass/walk4.wav", "grass/walk4.ogg",
"grass/walk5.wav", "grass/walk5.ogg",
"grass/walk6.wav", "grass/walk6.ogg",
}, type = SoundType.BLOCK) }, type = SoundType.BLOCK)
public class GrassWalkingSound { 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.SpriteLoader;
import cz.jzitnik.game.annotations.BlockRegistry; import cz.jzitnik.game.annotations.BlockRegistry;
import cz.jzitnik.game.annotations.Flamable; 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.Block;
import cz.jzitnik.game.entities.items.ItemType; import cz.jzitnik.game.entities.items.ItemType;
import java.util.ArrayList; import java.util.ArrayList;
@MineSound(SoundKey.WOOD)
@PlaceSound(SoundKey.WOOD)
@Flamable @Flamable
@BlockRegistry("oak_log") @BlockRegistry("oak_log")
public class OakLogBlock extends Block { 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.Game;
import cz.jzitnik.game.sprites.Air; import cz.jzitnik.game.sprites.Air;
import cz.jzitnik.game.sprites.SimpleSprite; import cz.jzitnik.game.sprites.SimpleSprite;
import cz.jzitnik.game.sprites.Steve;
import cz.jzitnik.game.blocks.Chest; import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace; import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.Escape; import cz.jzitnik.game.ui.Escape;

View File

@ -1,32 +1,58 @@
package cz.jzitnik.tui; package cz.jzitnik.tui;
import javax.sound.sampled.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.sound.sampled.*;
import java.io.IOException; import java.io.IOException;
@Slf4j @Slf4j
public class SoundPlayer { public class SoundPlayer {
public static Clip playSound(String filePath, int volume) public static void playSound(String filePath)
throws LineUnavailableException, IOException, UnsupportedAudioFileException, InterruptedException { throws LineUnavailableException, IOException, UnsupportedAudioFileException, InterruptedException {
if (!filePath.endsWith(".ogg")) {
return;
}
log.info("Loading resource: {}", "sounds/" + filePath); 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); var file = SoundPlayer.class.getClassLoader().getResourceAsStream("sounds/" + filePath);
float min = volumeControl.getMinimum(); AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);
float max = volumeControl.getMaximum(); AudioFormat baseFormat = audioStream.getFormat();
float gain = min + (max - min) * (volume / 100.0f);
volumeControl.setValue(gain);
log.info("Starting to play {}", filePath); // Convert the audio format to PCM_SIGNED
clip.start(); 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.